diff --git a/src/applications/config/option/PhabricatorCoreConfigOptions.php b/src/applications/config/option/PhabricatorCoreConfigOptions.php --- a/src/applications/config/option/PhabricatorCoreConfigOptions.php +++ b/src/applications/config/option/PhabricatorCoreConfigOptions.php @@ -214,12 +214,35 @@ ->setDescription(pht('Cache namespace.')), $this->newOption('phabricator.allow-email-users', 'bool', false) ->setBoolOptions( - array( - pht('Allow'), - pht('Disallow'), - ))->setDescription( - pht( - 'Allow non-members to interact with tasks over email.')), + array( + pht('Allow'), + pht('Disallow'), + )) + ->setDescription( + pht('Allow non-members to interact with tasks over email.')), + $this->newOption('phabricator.silent', 'bool', false) + ->setLocked(true) + ->setBoolOptions( + array( + pht('Run Silently'), + pht('Run Normally'), + )) + ->setSummary(pht('Stop Phabricator from sending any email, etc.')) + ->setDescription( + pht( + 'This option allows you to stop Phabricator from sending '. + 'any data to external services. Among other things, it will '. + 'disable email, SMS, repository mirroring, and HTTP hooks.'. + "\n\n". + 'This option is intended to allow a Phabricator instance to '. + 'be exported, copied, imported, and run in a test environment '. + 'without impacting users. For example, if you are migrating '. + 'to new hardware, you could perform a test migration first, '. + 'make sure things work, and then do a production cutover '. + 'later with higher confidence and less disruption. Without '. + 'this flag, users would receive duplicate email during the '. + 'time the test instance and old production instance were '. + 'both in operation.')), ); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -126,7 +126,15 @@ $boxes[] = id(new PhabricatorAnchorView())->setAnchorName('mirrors'); + $mirror_info = array(); + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $mirror_info[] = pht( + 'Phabricator is running in silent mode, so changes will not '. + 'be pushed to mirrors.'); + } + $boxes[] = id(new PHUIObjectBoxView()) + ->setFormErrors($mirror_info) ->setHeaderText(pht('Mirrors')) ->addPropertyList($mirror_properties); diff --git a/src/applications/doorkeeper/worker/DoorkeeperFeedWorker.php b/src/applications/doorkeeper/worker/DoorkeeperFeedWorker.php --- a/src/applications/doorkeeper/worker/DoorkeeperFeedWorker.php +++ b/src/applications/doorkeeper/worker/DoorkeeperFeedWorker.php @@ -157,6 +157,11 @@ * @{method:publishFeedStory}. */ final protected function doWork() { + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $this->log(pht('Phabricator is running in silent mode.')); + return; + } + if (!$this->isEnabled()) { $this->log("Doorkeeper worker '%s' is not enabled.\n", get_class($this)); return; diff --git a/src/applications/feed/worker/FeedPublisherHTTPWorker.php b/src/applications/feed/worker/FeedPublisherHTTPWorker.php --- a/src/applications/feed/worker/FeedPublisherHTTPWorker.php +++ b/src/applications/feed/worker/FeedPublisherHTTPWorker.php @@ -3,6 +3,11 @@ final class FeedPublisherHTTPWorker extends FeedPushWorker { protected function doWork() { + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + // Don't invoke hooks in silent mode. + return; + } + $story = $this->loadFeedStory(); $data = $story->getStoryData(); diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php --- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php @@ -633,6 +633,15 @@ } } + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $this->setStatus(self::STATUS_VOID); + $this->setMessage( + pht( + 'Phabricator is running in silent mode. See `phabricator.silent` '. + 'in the configuration to change this setting.')); + return $this->save(); + } + $mailer->addHeader('X-Phabricator-Sent-This-Message', 'Yes'); $mailer->addHeader('X-Mail-Transport-Agent', 'MetaMTA'); diff --git a/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php b/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php --- a/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php @@ -13,6 +13,12 @@ return; } + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $this->log( + pht('Phabricator is running in silent mode; declining to mirror.')); + return; + } + $mirrors = id(new PhabricatorRepositoryMirrorQuery()) ->setViewer($this->getViewer()) ->withRepositoryPHIDs(array($repository->getPHID())) diff --git a/src/infrastructure/sms/worker/PhabricatorSMSSendWorker.php b/src/infrastructure/sms/worker/PhabricatorSMSSendWorker.php --- a/src/infrastructure/sms/worker/PhabricatorSMSSendWorker.php +++ b/src/infrastructure/sms/worker/PhabricatorSMSSendWorker.php @@ -55,6 +55,15 @@ // give the provider name the same treatment as phone number $sms->setProviderShortName($adapter->getProviderShortName()); + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $sms->setSendStatus(PhabricatorSMS::STATUS_FAILED_PERMANENTLY); + $sms->save(); + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'Phabricator is running in silent mode. See `phabricator.silent` '. + 'in the configuration to change this setting.')); + } + try { $result = $adapter->send(); list($sms_id, $sent_status) = $adapter->getSMSDataFromResult($result); diff --git a/src/infrastructure/testing/PhabricatorTestCase.php b/src/infrastructure/testing/PhabricatorTestCase.php --- a/src/infrastructure/testing/PhabricatorTestCase.php +++ b/src/infrastructure/testing/PhabricatorTestCase.php @@ -119,6 +119,9 @@ $this->env->overrideEnvConfig( 'phabricator.base-uri', 'http://phabricator.example.com'); + + // Tests do their own stubbing/voiding for events. + $this->env->overrideEnvConfig('phabricator.silent', false); } protected function didRunTests() {