Page MenuHomePhabricator

D8930.id21386.diff
No OneTemporary

D8930.id21386.diff

diff --git a/bin/sms b/bin/sms
new file mode 120000
--- /dev/null
+++ b/bin/sms
@@ -0,0 +1 @@
+../scripts/sms/manage_sms.php
\ No newline at end of file
diff --git a/externals/twilio-php b/externals/twilio-php
new file mode 160000
--- /dev/null
+++ b/externals/twilio-php
@@ -0,0 +1 @@
+Subproject commit 389e07ee41eabc422ea88b805006bccac7de9eb3
diff --git a/resources/sql/autopatches/20140507.smstable.sql b/resources/sql/autopatches/20140507.smstable.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20140507.smstable.sql
@@ -0,0 +1,13 @@
+CREATE TABLE {$NAMESPACE}_sms.sms (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ providerShortName VARCHAR(16) NOT NULL COLLATE utf8_bin,
+ providerSMSID VARCHAR(40) NOT NULL COLLATE utf8_bin,
+ toNumber VARCHAR(20) NOT NULL COLLATE utf8_bin,
+ fromNumber VARCHAR(20) COLLATE utf8_bin,
+ body LONGTEXT NOT NULL COLLATE utf8_bin,
+ sendStatus VARCHAR(16) COLLATE utf8_bin,
+ sendCount TINYINT UNSIGNED NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_provider` (providerSMSID, providerShortName)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
diff --git a/scripts/sms/manage_sms.php b/scripts/sms/manage_sms.php
new file mode 100755
--- /dev/null
+++ b/scripts/sms/manage_sms.php
@@ -0,0 +1,21 @@
+#!/usr/bin/env php
+<?php
+
+$root = dirname(dirname(dirname(__FILE__)));
+require_once $root.'/scripts/__init_script__.php';
+
+$args = new PhutilArgumentParser($argv);
+$args->setTagline('manage SMS');
+$args->setSynopsis(<<<EOSYNOPSIS
+**sms** __command__ [__options__]
+ Manage Phabricator SMS stuff.
+
+EOSYNOPSIS
+ );
+$args->parseStandardArguments();
+
+$workflows = id(new PhutilSymbolLoader())
+ ->setAncestorClass('PhabricatorSMSManagementWorkflow')
+ ->loadObjects();
+$workflows[] = new PhutilHelpArgumentWorkflow();
+$args->parseWorkflows($workflows);
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2037,6 +2037,19 @@
'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php',
'PhabricatorRobotsController' => 'applications/system/controller/PhabricatorRobotsController.php',
'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php',
+ 'PhabricatorSMS' => 'infrastructure/sms/storage/PhabricatorSMS.php',
+ 'PhabricatorSMSConfigOptions' => 'applications/config/option/PhabricatorSMSConfigOptions.php',
+ 'PhabricatorSMSDAO' => 'infrastructure/sms/storage/PhabricatorSMSDAO.php',
+ 'PhabricatorSMSDemultiplexWorker' => 'infrastructure/sms/worker/PhabricatorSMSDemultiplexWorker.php',
+ 'PhabricatorSMSImplementationAdapter' => 'infrastructure/sms/adapter/PhabricatorSMSImplementationAdapter.php',
+ 'PhabricatorSMSImplementationTestBlackholeAdapter' => 'infrastructure/sms/adapter/PhabricatorSMSImplementationTestBlackholeAdapter.php',
+ 'PhabricatorSMSImplementationTwilioAdapter' => 'infrastructure/sms/adapter/PhabricatorSMSImplementationTwilioAdapter.php',
+ 'PhabricatorSMSManagementListOutboundWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementListOutboundWorkflow.php',
+ 'PhabricatorSMSManagementSendTestWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementSendTestWorkflow.php',
+ 'PhabricatorSMSManagementShowOutboundWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementShowOutboundWorkflow.php',
+ 'PhabricatorSMSManagementWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementWorkflow.php',
+ 'PhabricatorSMSSendWorker' => 'infrastructure/sms/worker/PhabricatorSMSSendWorker.php',
+ 'PhabricatorSMSWorker' => 'infrastructure/sms/worker/PhabricatorSMSWorker.php',
'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php',
'PhabricatorSSHKeyGenerator' => 'infrastructure/util/PhabricatorSSHKeyGenerator.php',
'PhabricatorSSHLog' => 'infrastructure/log/PhabricatorSSHLog.php',
@@ -4977,6 +4990,18 @@
'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
'PhabricatorRobotsController' => 'PhabricatorController',
'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
+ 'PhabricatorSMS' => 'PhabricatorSMSDAO',
+ 'PhabricatorSMSConfigOptions' => 'PhabricatorApplicationConfigOptions',
+ 'PhabricatorSMSDAO' => 'PhabricatorLiskDAO',
+ 'PhabricatorSMSDemultiplexWorker' => 'PhabricatorSMSWorker',
+ 'PhabricatorSMSImplementationTestBlackholeAdapter' => 'PhabricatorSMSImplementationAdapter',
+ 'PhabricatorSMSImplementationTwilioAdapter' => 'PhabricatorSMSImplementationAdapter',
+ 'PhabricatorSMSManagementListOutboundWorkflow' => 'PhabricatorSMSManagementWorkflow',
+ 'PhabricatorSMSManagementSendTestWorkflow' => 'PhabricatorSMSManagementWorkflow',
+ 'PhabricatorSMSManagementShowOutboundWorkflow' => 'PhabricatorSMSManagementWorkflow',
+ 'PhabricatorSMSManagementWorkflow' => 'PhabricatorManagementWorkflow',
+ 'PhabricatorSMSSendWorker' => 'PhabricatorSMSWorker',
+ 'PhabricatorSMSWorker' => 'PhabricatorWorker',
'PhabricatorSSHKeyGenerator' => 'Phobject',
'PhabricatorSSHLog' => 'Phobject',
'PhabricatorSSHPassthruCommand' => 'Phobject',
diff --git a/src/applications/config/option/PhabricatorSMSConfigOptions.php b/src/applications/config/option/PhabricatorSMSConfigOptions.php
new file mode 100644
--- /dev/null
+++ b/src/applications/config/option/PhabricatorSMSConfigOptions.php
@@ -0,0 +1,48 @@
+<?php
+
+final class PhabricatorSMSConfigOptions
+ extends PhabricatorApplicationConfigOptions {
+
+ public function getName() {
+ return pht('SMS');
+ }
+
+ public function getDescription() {
+ return pht('Configure SMS.');
+ }
+
+ public function getOptions() {
+ $adapter_description = $this->deformat(pht(<<<EODOC
+Adapter class to use to transmit SMS to an external provider. A given external
+provider will most likely need more configuration which will most likely
+require registration and payment for the service.
+EODOC
+ ));
+
+ return array(
+ $this->newOption(
+ 'sms.default-sender',
+ 'string',
+ '12345678901')
+ ->setDescription(pht('Default "from" number.')),
+ $this->newOption(
+ 'sms.default-adapter',
+ 'class',
+ 'PhabricatorSMSImplementationTwilioAdapter')
+ ->setBaseClass('PhabricatorSMSImplementationAdapter')
+ ->setSummary(pht('Control how sms is sent.'))
+ ->setDescription($adapter_description),
+ $this->newOption(
+ 'twilio.account-sid',
+ 'string',
+ 'ABC123ABC123ABC123ABC123ABC123')
+ ->setDescription(pht('Account ID on Twilio service.')),
+ $this->newOption(
+ 'twilio.auth-token',
+ 'string',
+ 'ABC123ABC123ABC123ABC123ABC123')
+ ->setDescription(pht('Authorization token from Twilio service.'))
+ );
+ }
+
+}
diff --git a/src/infrastructure/sms/adapter/PhabricatorSMSImplementationAdapter.php b/src/infrastructure/sms/adapter/PhabricatorSMSImplementationAdapter.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/adapter/PhabricatorSMSImplementationAdapter.php
@@ -0,0 +1,84 @@
+<?php
+
+abstract class PhabricatorSMSImplementationAdapter {
+
+ private $fromNumber;
+ private $toNumber;
+ private $body;
+
+ public function setFrom($number) {
+ $this->fromNumber = $number;
+ return $this;
+ }
+
+ public function getFrom() {
+ return $this->fromNumber;
+ }
+
+ public function setTo($number) {
+ $this->toNumber = $number;
+ return $this;
+ }
+
+ public function getTo() {
+ return $this->toNumber;
+ }
+
+ public function setBody($body) {
+ $this->body = $body;
+ return $this;
+ }
+
+ public function getBody() {
+ return $this->body;
+ }
+
+ /**
+ * 16 characters or less, to be used in database columns and exposed
+ * to administrators during configuration directly.
+ */
+ abstract public function getProviderShortName();
+
+ /**
+ * Send the message. Generally, this means connecting to some service and
+ * handing data to it. SMS APIs are generally asynchronous, so truly
+ * determining success or failure is probably impossible synchronously.
+ *
+ * That said, if the adapter determines that the SMS will never be
+ * deliverable, or there is some other known failure, it should throw
+ * an exception.
+ *
+ * @return null
+ */
+ abstract public function send();
+
+ /**
+ * Most (all?) SMS APIs are asynchronous, but some do send back some
+ * initial information. Use this hook to determine what the updated
+ * sentStatus should be and what the provider is using for an SMS ID,
+ * as well as throw exceptions if there are any failures.
+ *
+ * @return array Tuple of ($sms_id and $sent_status)
+ */
+ abstract public function getSMSDataFromResult($result);
+
+ /**
+ * Due to the asynchronous nature of sending SMS messages, it can be
+ * necessary to poll the provider regarding the sent status of a given
+ * sms.
+ *
+ * For now, this *MUST* be implemented and *MUST* work.
+ */
+ abstract public function pollSMSSentStatus(PhabricatorSMS $sms);
+
+ /**
+ * Convenience function to handle sending an SMS.
+ */
+ public static function sendSMS(array $to_numbers, $body) {
+ PhabricatorWorker::scheduleTask(
+ 'PhabricatorSMSDemultiplexWorker',
+ array(
+ 'toNumbers' => $to_numbers,
+ 'body' => $body));
+ }
+}
diff --git a/src/infrastructure/sms/adapter/PhabricatorSMSImplementationTestBlackholeAdapter.php b/src/infrastructure/sms/adapter/PhabricatorSMSImplementationTestBlackholeAdapter.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/adapter/PhabricatorSMSImplementationTestBlackholeAdapter.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * This is useful for testing, but otherwise your SMS ends up in a blackhole.
+ */
+final class PhabricatorSMSImplementationTestBlackholeAdapter
+ extends PhabricatorSMSImplementationAdapter {
+
+ public function getProviderShortName() {
+ return 'testtesttest';
+ }
+
+ public function send() {
+ // I guess this is what a blackhole looks like
+ }
+
+ public function getSMSDataFromResult($result) {
+ return array(
+ Filesystem::readRandomCharacters(40),
+ PhabricatorSMS::STATUS_SENT);
+ }
+
+ public function pollSMSSentStatus(PhabricatorSMS $sms) {
+ if ($sms->getID()) {
+ return PhabricatorSMS::STATUS_SENT;
+ }
+ return PhabricatorSMS::STATUS_SENT_UNCONFIRMED;
+ }
+
+}
diff --git a/src/infrastructure/sms/adapter/PhabricatorSMSImplementationTwilioAdapter.php b/src/infrastructure/sms/adapter/PhabricatorSMSImplementationTwilioAdapter.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/adapter/PhabricatorSMSImplementationTwilioAdapter.php
@@ -0,0 +1,75 @@
+<?php
+
+final class PhabricatorSMSImplementationTwilioAdapter
+ extends PhabricatorSMSImplementationAdapter {
+
+ public function getProviderShortName() {
+ return 'twilio';
+ }
+
+ /**
+ * @phutil-external-symbol class Services_Twilio
+ */
+ private function buildClient() {
+ $account_sid = PhabricatorEnv::getEnvConfig('twilio.account-sid');
+ $auth_token = PhabricatorEnv::getEnvConfig('twilio.auth-token');
+ return new Services_Twilio($account_sid, $auth_token);
+ }
+
+ /**
+ * @phutil-external-symbol class Services_Twilio_RestException
+ */
+ public function send() {
+ $client = $this->buildClient();
+
+ try {
+ $message = $client->account->sms_messages->create(array(
+ 'From' => $this->getFrom(),
+ 'To' => $this->getTo(),
+ 'Body' => $this->getBody()));
+ } catch (Services_Twilio_RestException $e) {
+ $message = sprintf(
+ 'HTTP Code %d: %s',
+ $e->getStatus(),
+ $e->getMessage());
+
+ // Twilio tries to provide a link to more specific details if they can.
+ if ($e->getInfo()) {
+ $message .= sprintf(' For more information, see %s.', $e->getInfo());
+ }
+ throw new PhabricatorWorkerPermanentFailureException($message);
+ }
+ }
+
+ public function getSMSDataFromResult($result) {
+ return array($result->sid, $this->getSMSStatus($result->status));
+ }
+
+ public function pollSMSSentStatus(PhabricatorSMS $sms) {
+ $client = $this->buildClient();
+ $message = $client->account->messages->get($sms->getProviderSMSID());
+
+ return $this->getSMSStatus($message->status);
+ }
+
+ /**
+ * See https://www.twilio.com/docs/api/rest/sms#sms-status-values.
+ */
+ private function getSMSStatus($twilio_status) {
+ switch ($twilio_status) {
+ case 'failed':
+ $status = PhabricatorSMS::STATUS_FAILED;
+ break;
+ case 'sent':
+ $status = PhabricatorSMS::STATUS_SENT;
+ break;
+ case 'sending':
+ case 'queued':
+ default:
+ $status = PhabricatorSMS::STATUS_SENT_UNCONFIRMED;
+ break;
+ }
+ return $status;
+ }
+
+}
diff --git a/src/infrastructure/sms/management/PhabricatorSMSManagementListOutboundWorkflow.php b/src/infrastructure/sms/management/PhabricatorSMSManagementListOutboundWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/management/PhabricatorSMSManagementListOutboundWorkflow.php
@@ -0,0 +1,50 @@
+<?php
+
+final class PhabricatorSMSManagementListOutboundWorkflow
+ extends PhabricatorSMSManagementWorkflow {
+
+ protected function didConstruct() {
+ $this
+ ->setName('list-outbound')
+ ->setSynopsis('List outbound sms messages sent by Phabricator.')
+ ->setExamples(
+ "**list-outbound**")
+ ->setArguments(
+ array(
+ array(
+ 'name' => 'limit',
+ 'param' => 'N',
+ 'default' => 100,
+ 'help' =>
+ 'Show a specific number of sms messages (default 100).',
+ ),
+ ));
+ }
+
+ public function execute(PhutilArgumentParser $args) {
+ $console = PhutilConsole::getConsole();
+ $viewer = $this->getViewer();
+
+ $sms_messages = id(new PhabricatorSMS())->loadAllWhere(
+ '1 = 1 ORDER BY id DESC LIMIT %d',
+ $args->getArg('limit'));
+
+ if (!$sms_messages) {
+ $console->writeErr("%s\n", pht("No sent sms."));
+ return 0;
+ }
+
+ foreach (array_reverse($sms_messages) as $sms) {
+ $console->writeOut(
+ "%s\n",
+ sprintf(
+ "% 8d %-8s To: %s",
+ $sms->getID(),
+ $sms->getSendStatus(),
+ $sms->getToNumber()));
+ }
+
+ return 0;
+ }
+
+}
diff --git a/src/infrastructure/sms/management/PhabricatorSMSManagementSendTestWorkflow.php b/src/infrastructure/sms/management/PhabricatorSMSManagementSendTestWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/management/PhabricatorSMSManagementSendTestWorkflow.php
@@ -0,0 +1,48 @@
+<?php
+
+final class PhabricatorSMSManagementSendTestWorkflow
+ extends PhabricatorSMSManagementWorkflow {
+
+ protected function didConstruct() {
+ $this
+ ->setName('send-test')
+ ->setSynopsis(
+ pht(
+ 'Simulate sending an sms. This may be useful to test your sms '.
+ 'configuration, or while developing new sms adapters.'))
+ ->setExamples(
+ "**send-test** --to 12345678 --body 'pizza time yet?'")
+ ->setArguments(
+ array(
+ array(
+ 'name' => 'to',
+ 'param' => 'number',
+ 'help' => 'Send sms "To:" the specified number.',
+ 'repeat' => true,
+ ),
+ array(
+ 'name' => 'body',
+ 'param' => 'text',
+ 'help' => 'Send sms with the specified body.',
+ ),
+ ));
+ }
+
+ public function execute(PhutilArgumentParser $args) {
+ $console = PhutilConsole::getConsole();
+ $viewer = $this->getViewer();
+
+ $tos = $args->getArg('to');
+ $body = $args->getArg('body');
+
+ PhabricatorWorker::setRunAllTasksInProcess(true);
+ PhabricatorSMSImplementationAdapter::sendSMS($tos, $body);
+
+ $console->writeErr(
+ "%s\n\n phabricator/ $ ./bin/sms list-outbound \n\n",
+ pht(
+ 'Send completed! You can view the list of SMS messages sent by '.
+ 'running this command:'));
+ }
+
+}
diff --git a/src/infrastructure/sms/management/PhabricatorSMSManagementShowOutboundWorkflow.php b/src/infrastructure/sms/management/PhabricatorSMSManagementShowOutboundWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/management/PhabricatorSMSManagementShowOutboundWorkflow.php
@@ -0,0 +1,68 @@
+<?php
+
+final class PhabricatorSMSManagementShowOutboundWorkflow
+ extends PhabricatorSMSManagementWorkflow {
+
+ protected function didConstruct() {
+ $this
+ ->setName('show-outbound')
+ ->setSynopsis('Show diagnostic details about outbound sms.')
+ ->setExamples(
+ "**show-outbound** --id 1 --id 2")
+ ->setArguments(
+ array(
+ array(
+ 'name' => 'id',
+ 'param' => 'id',
+ 'help' => 'Show details about outbound sms with given ID.',
+ 'repeat' => true,
+ ),
+ ));
+ }
+
+ public function execute(PhutilArgumentParser $args) {
+ $console = PhutilConsole::getConsole();
+
+ $ids = $args->getArg('id');
+ if (!$ids) {
+ throw new PhutilArgumentUsageException(
+ "Use the '--id' flag to specify one or more sms messages to show.");
+ }
+
+ $messages = id(new PhabricatorSMS())->loadAllWhere(
+ 'id IN (%Ld)',
+ $ids);
+
+ if ($ids) {
+ $ids = array_fuse($ids);
+ $missing = array_diff_key($ids, $messages);
+ if ($missing) {
+ throw new PhutilArgumentUsageException(
+ "Some specified sms messages do not exist: ".
+ implode(', ', array_keys($missing)));
+ }
+ }
+
+ $last_key = last_key($messages);
+ foreach ($messages as $message_key => $message) {
+ $info = array();
+
+ $info[] = pht('PROPERTIES');
+ $info[] = pht('ID: %d', $message->getID());
+ $info[] = pht('Status: %s', $message->getSentStatus());
+ $info[] = pht('To: %s', $message->getTo());
+ $info[] = pht('From: %s', $message->getFrom());
+
+ $info[] = null;
+ $info[] = pht('BODY');
+ $info[] = $message->getBody();
+
+ $console->writeOut('%s', implode("\n", $info));
+
+ if ($message_key != $last_key) {
+ $console->writeOut("\n%s\n\n", str_repeat('-', 80));
+ }
+ }
+ }
+
+}
diff --git a/src/infrastructure/sms/management/PhabricatorSMSManagementWorkflow.php b/src/infrastructure/sms/management/PhabricatorSMSManagementWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/management/PhabricatorSMSManagementWorkflow.php
@@ -0,0 +1,6 @@
+<?php
+
+abstract class PhabricatorSMSManagementWorkflow
+ extends PhabricatorManagementWorkflow {
+
+}
diff --git a/src/infrastructure/sms/storage/PhabricatorSMS.php b/src/infrastructure/sms/storage/PhabricatorSMS.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/storage/PhabricatorSMS.php
@@ -0,0 +1,45 @@
+<?php
+
+final class PhabricatorSMS
+ extends PhabricatorSMSDAO {
+
+ const MAXIMUM_SEND_TRIES = 5;
+
+ /**
+ * Status constants should be 16 characters or less. See status entries
+ * for details on what they indicate about the underlying SMS.
+ */
+
+ // in the beginning, all SMS are unsent
+ const STATUS_UNSENT = 'unsent';
+ // that nebulous time when we've sent it from Phabricator but haven't
+ // heard anything from the external API
+ const STATUS_SENT_UNCONFIRMED = 'sent-unconfirmed';
+ // "success"
+ const STATUS_SENT = 'sent';
+ // "fail" but we'll try again
+ const STATUS_FAILED = 'failed';
+ // we're giving up on our external API partner
+ const STATUS_FAILED_PERMANENTLY = 'permafailed';
+
+ protected $providerShortName;
+ protected $providerSMSID;
+ // numbers can be up to 20 digits long
+ protected $toNumber;
+ protected $fromNumber;
+ protected $body;
+ protected $sendStatus;
+ protected $sendCount;
+
+ public static function initializeNewSMS($body) {
+ // NOTE: these values will be updated to correct values when the
+ // SMS is sent for the first time. In particular, the ProviderShortName
+ // and ProviderSMSID are totally garbage data before a send it attempted.
+ return id(new PhabricatorSMS())
+ ->setBody($body)
+ ->setSendStatus(PhabricatorSMS::STATUS_UNSENT)
+ ->setSendCount(0)
+ ->setProviderShortName('phabricator')
+ ->setProviderSMSID(Filesystem::readRandomCharacters(40));
+ }
+}
diff --git a/src/infrastructure/sms/storage/PhabricatorSMSDAO.php b/src/infrastructure/sms/storage/PhabricatorSMSDAO.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/storage/PhabricatorSMSDAO.php
@@ -0,0 +1,11 @@
+<?php
+
+abstract class PhabricatorSMSDAO
+ extends PhabricatorLiskDAO {
+
+
+ public function getApplicationName() {
+ return 'sms';
+ }
+
+}
diff --git a/src/infrastructure/sms/worker/PhabricatorSMSDemultiplexWorker.php b/src/infrastructure/sms/worker/PhabricatorSMSDemultiplexWorker.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/worker/PhabricatorSMSDemultiplexWorker.php
@@ -0,0 +1,30 @@
+<?php
+
+final class PhabricatorSMSDemultiplexWorker
+ extends PhabricatorSMSWorker {
+
+ public function doWork() {
+ $viewer = PhabricatorUser::getOmnipotentUser();
+
+ $task_data = $this->getTaskData();
+
+ $to_numbers = idx($task_data, 'toNumbers');
+ if (!$to_numbers) {
+ // If we don't have any to numbers, don't send any sms.
+ return;
+ }
+
+ foreach ($to_numbers as $number) {
+ // NOTE: we will set the fromNumber and the proper provider data
+ // in the `PhabricatorSMSSendWorker`.
+ $sms = PhabricatorSMS::initializeNewSMS($task_data['body']);
+ $sms->setToNumber($number);
+ $sms->save();
+ $this->queueTask(
+ 'PhabricatorSMSSendWorker',
+ array(
+ 'smsID' => $sms->getID()));
+ }
+ }
+
+}
diff --git a/src/infrastructure/sms/worker/PhabricatorSMSSendWorker.php b/src/infrastructure/sms/worker/PhabricatorSMSSendWorker.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/worker/PhabricatorSMSSendWorker.php
@@ -0,0 +1,69 @@
+<?php
+
+final class PhabricatorSMSSendWorker
+ extends PhabricatorSMSWorker {
+
+ public function getMaximumRetryCount() {
+ return PhabricatorSMS::MAXIMUM_SEND_TRIES;
+ }
+
+ public function getWaitBeforeRetry() {
+ return phutil_units('1 minute in seconds');
+ }
+
+ public function doWork() {
+ $viewer = PhabricatorUser::getOmnipotentUser();
+
+ $task_data = $this->getTaskData();
+
+ $sms = id(new PhabricatorSMS())
+ ->loadOneWhere('id = %d', $task_data['smsID']);
+
+ if (!$sms) {
+ throw new PhabricatorWorkerPermanentFailureException(
+ pht('SMS object was not found.'));
+ }
+
+ // this has the potential to be updated asynchronously
+ if ($sms->getSendStatus() == PhabricatorSMS::STATUS_SENT) {
+ return;
+ }
+
+ $adapter = PhabricatorEnv::getEnvConfig('sms.default-adapter');
+ $adapter = newv($adapter, array());
+ $up_to_date_status = $adapter->pollSMSSentStatus($sms);
+ if ($up_to_date_status) {
+ $sms->setSendStatus($up_to_date_status);
+ if ($up_to_date_status == PhabricatorSMS::STATUS_SENT) {
+ $sms->save();
+ return;
+ }
+ }
+
+ $from_number = PhabricatorEnv::getEnvConfig('sms.default-sender');
+ // always set the from number if we get this far in case of configuration
+ // changes.
+ $sms->setFromNumber($from_number);
+
+ $adapter->setTo($sms->getToNumber());
+ $adapter->setFrom($sms->getFromNumber());
+ $adapter->setBody($sms->getBody());
+ // give the provider name the same treatment as phone number
+ $sms->setProviderShortName($adapter->getProviderShortName());
+
+ try {
+ $sms->setSendCount($sms->getSendCount() + 1);
+ $result = $adapter->send();
+ list($sms_id, $sent_status) = $adapter->getSMSDataFromResult($result);
+ } catch (Exception $e) {
+ $sms->setSendStatus(PhabricatorSMS::STATUS_FAILED_PERMANENTLY);
+ $sms->save();
+ throw new PhabricatorWorkerPermanentFailureException(
+ $e->getMessage());
+ }
+ $sms->setProviderSMSID($sms_id);
+ $sms->setSendStatus($sent_status);
+ $sms->save();
+ }
+
+}
diff --git a/src/infrastructure/sms/worker/PhabricatorSMSWorker.php b/src/infrastructure/sms/worker/PhabricatorSMSWorker.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/sms/worker/PhabricatorSMSWorker.php
@@ -0,0 +1,11 @@
+<?php
+
+abstract class PhabricatorSMSWorker
+ extends PhabricatorWorker {
+
+ public function renderForDisplay(PhabricatorUser $viewer) {
+ // This data has some sensitive stuff, so don't show it.
+ return null;
+ }
+
+}
diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
--- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -119,6 +119,7 @@
'db.phragment' => array(),
'db.dashboard' => array(),
'db.system' => array(),
+ 'db.sms' => array(),
'0000.legacy.sql' => array(
'legacy' => 0,
),

File Metadata

Mime Type
text/plain
Expires
Tue, Mar 18, 2:57 PM (4 d, 1 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7710029
Default Alt Text
D8930.id21386.diff (25 KB)

Event Timeline