Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15400958
D8930.id21386.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
25 KB
Referenced Files
None
Subscribers
None
D8930.id21386.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D8930: Add SMS support
Attached
Detach File
Event Timeline
Log In to Comment