Page MenuHomePhabricator

D19988.diff
No OneTemporary

D19988.diff

diff --git a/resources/sql/autopatches/20190116.contact.01.number.sql b/resources/sql/autopatches/20190116.contact.01.number.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20190116.contact.01.number.sql
@@ -0,0 +1,11 @@
+CREATE TABLE {$NAMESPACE}_auth.auth_contactnumber (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ objectPHID VARBINARY(64) NOT NULL,
+ contactNumber VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT},
+ status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
+ properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
+ uniqueKey BINARY(12),
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20190116.contact.02.xaction.sql b/resources/sql/autopatches/20190116.contact.02.xaction.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20190116.contact.02.xaction.sql
@@ -0,0 +1,19 @@
+CREATE TABLE {$NAMESPACE}_auth.auth_contactnumbertransaction (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ authorPHID VARBINARY(64) NOT NULL,
+ objectPHID VARBINARY(64) NOT NULL,
+ viewPolicy VARBINARY(64) NOT NULL,
+ editPolicy VARBINARY(64) NOT NULL,
+ commentPHID VARBINARY(64) DEFAULT NULL,
+ commentVersion INT UNSIGNED NOT NULL,
+ transactionType VARCHAR(32) NOT NULL,
+ oldValue LONGTEXT NOT NULL,
+ newValue LONGTEXT NOT NULL,
+ contentSource LONGTEXT NOT NULL,
+ metadata LONGTEXT NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (`phid`),
+ KEY `key_object` (`objectPHID`)
+) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};
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
@@ -2200,6 +2200,18 @@
'PhabricatorAuthConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthConduitAPIMethod.php',
'PhabricatorAuthConduitTokenRevoker' => 'applications/auth/revoker/PhabricatorAuthConduitTokenRevoker.php',
'PhabricatorAuthConfirmLinkController' => 'applications/auth/controller/PhabricatorAuthConfirmLinkController.php',
+ 'PhabricatorAuthContactNumber' => 'applications/auth/storage/PhabricatorAuthContactNumber.php',
+ 'PhabricatorAuthContactNumberController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberController.php',
+ 'PhabricatorAuthContactNumberEditController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberEditController.php',
+ 'PhabricatorAuthContactNumberEditEngine' => 'applications/auth/editor/PhabricatorAuthContactNumberEditEngine.php',
+ 'PhabricatorAuthContactNumberEditor' => 'applications/auth/editor/PhabricatorAuthContactNumberEditor.php',
+ 'PhabricatorAuthContactNumberNumberTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.php',
+ 'PhabricatorAuthContactNumberPHIDType' => 'applications/auth/phid/PhabricatorAuthContactNumberPHIDType.php',
+ 'PhabricatorAuthContactNumberQuery' => 'applications/auth/query/PhabricatorAuthContactNumberQuery.php',
+ 'PhabricatorAuthContactNumberTransaction' => 'applications/auth/storage/PhabricatorAuthContactNumberTransaction.php',
+ 'PhabricatorAuthContactNumberTransactionQuery' => 'applications/auth/query/PhabricatorAuthContactNumberTransactionQuery.php',
+ 'PhabricatorAuthContactNumberTransactionType' => 'applications/auth/xaction/PhabricatorAuthContactNumberTransactionType.php',
+ 'PhabricatorAuthContactNumberViewController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberViewController.php',
'PhabricatorAuthController' => 'applications/auth/controller/PhabricatorAuthController.php',
'PhabricatorAuthDAO' => 'applications/auth/storage/PhabricatorAuthDAO.php',
'PhabricatorAuthDisableController' => 'applications/auth/controller/config/PhabricatorAuthDisableController.php',
@@ -2739,6 +2751,7 @@
'PhabricatorConpherenceWidgetVisibleSetting' => 'applications/settings/setting/PhabricatorConpherenceWidgetVisibleSetting.php',
'PhabricatorConsoleApplication' => 'applications/console/application/PhabricatorConsoleApplication.php',
'PhabricatorConsoleContentSource' => 'infrastructure/contentsource/PhabricatorConsoleContentSource.php',
+ 'PhabricatorContactNumbersSettingsPanel' => 'applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php',
'PhabricatorContentSource' => 'infrastructure/contentsource/PhabricatorContentSource.php',
'PhabricatorContentSourceModule' => 'infrastructure/contentsource/PhabricatorContentSourceModule.php',
'PhabricatorContentSourceView' => 'infrastructure/contentsource/PhabricatorContentSourceView.php',
@@ -3870,6 +3883,7 @@
'PhabricatorPholioApplication' => 'applications/pholio/application/PhabricatorPholioApplication.php',
'PhabricatorPholioMockTestDataGenerator' => 'applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php',
'PhabricatorPhoneNumber' => 'applications/metamta/message/PhabricatorPhoneNumber.php',
+ 'PhabricatorPhoneNumberTestCase' => 'applications/metamta/message/__tests__/PhabricatorPhoneNumberTestCase.php',
'PhabricatorPhortuneApplication' => 'applications/phortune/application/PhabricatorPhortuneApplication.php',
'PhabricatorPhortuneContentSource' => 'applications/phortune/contentsource/PhabricatorPhortuneContentSource.php',
'PhabricatorPhortuneManagementInvoiceWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php',
@@ -7884,6 +7898,23 @@
'PhabricatorAuthConduitAPIMethod' => 'ConduitAPIMethod',
'PhabricatorAuthConduitTokenRevoker' => 'PhabricatorAuthRevoker',
'PhabricatorAuthConfirmLinkController' => 'PhabricatorAuthController',
+ 'PhabricatorAuthContactNumber' => array(
+ 'PhabricatorAuthDAO',
+ 'PhabricatorApplicationTransactionInterface',
+ 'PhabricatorPolicyInterface',
+ 'PhabricatorDestructibleInterface',
+ ),
+ 'PhabricatorAuthContactNumberController' => 'PhabricatorAuthController',
+ 'PhabricatorAuthContactNumberEditController' => 'PhabricatorAuthContactNumberController',
+ 'PhabricatorAuthContactNumberEditEngine' => 'PhabricatorEditEngine',
+ 'PhabricatorAuthContactNumberEditor' => 'PhabricatorApplicationTransactionEditor',
+ 'PhabricatorAuthContactNumberNumberTransaction' => 'PhabricatorAuthContactNumberTransactionType',
+ 'PhabricatorAuthContactNumberPHIDType' => 'PhabricatorPHIDType',
+ 'PhabricatorAuthContactNumberQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorAuthContactNumberTransaction' => 'PhabricatorModularTransaction',
+ 'PhabricatorAuthContactNumberTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'PhabricatorAuthContactNumberTransactionType' => 'PhabricatorModularTransactionType',
+ 'PhabricatorAuthContactNumberViewController' => 'PhabricatorAuthContactNumberController',
'PhabricatorAuthController' => 'PhabricatorController',
'PhabricatorAuthDAO' => 'PhabricatorLiskDAO',
'PhabricatorAuthDisableController' => 'PhabricatorAuthProviderConfigController',
@@ -8524,6 +8555,7 @@
'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting',
'PhabricatorConsoleApplication' => 'PhabricatorApplication',
'PhabricatorConsoleContentSource' => 'PhabricatorContentSource',
+ 'PhabricatorContactNumbersSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorContentSource' => 'Phobject',
'PhabricatorContentSourceModule' => 'PhabricatorConfigModule',
'PhabricatorContentSourceView' => 'AphrontView',
@@ -9816,6 +9848,7 @@
'PhabricatorPholioApplication' => 'PhabricatorApplication',
'PhabricatorPholioMockTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorPhoneNumber' => 'Phobject',
+ 'PhabricatorPhoneNumberTestCase' => 'PhabricatorTestCase',
'PhabricatorPhortuneApplication' => 'PhabricatorApplication',
'PhabricatorPhortuneContentSource' => 'PhabricatorContentSource',
'PhabricatorPhortuneManagementInvoiceWorkflow' => 'PhabricatorPhortuneManagementWorkflow',
diff --git a/src/applications/auth/application/PhabricatorAuthApplication.php b/src/applications/auth/application/PhabricatorAuthApplication.php
--- a/src/applications/auth/application/PhabricatorAuthApplication.php
+++ b/src/applications/auth/application/PhabricatorAuthApplication.php
@@ -104,6 +104,12 @@
'PhabricatorAuthMessageViewController',
),
+ 'contact/' => array(
+ $this->getEditRoutePattern('edit/') =>
+ 'PhabricatorAuthContactNumberEditController',
+ '(?P<id>[1-9]\d*)/' =>
+ 'PhabricatorAuthContactNumberViewController',
+ ),
),
'/oauth/(?P<provider>\w+)/login/'
diff --git a/src/applications/auth/controller/contact/PhabricatorAuthContactNumberController.php b/src/applications/auth/controller/contact/PhabricatorAuthContactNumberController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/controller/contact/PhabricatorAuthContactNumberController.php
@@ -0,0 +1,16 @@
+<?php
+
+abstract class PhabricatorAuthContactNumberController
+ extends PhabricatorAuthController {
+
+ protected function buildApplicationCrumbs() {
+ $crumbs = parent::buildApplicationCrumbs();
+
+ $crumbs->addTextCrumb(
+ pht('Contact Numbers'),
+ pht('/settings/panel/contact/'));
+
+ return $crumbs;
+ }
+
+}
diff --git a/src/applications/auth/controller/contact/PhabricatorAuthContactNumberEditController.php b/src/applications/auth/controller/contact/PhabricatorAuthContactNumberEditController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/controller/contact/PhabricatorAuthContactNumberEditController.php
@@ -0,0 +1,12 @@
+<?php
+
+final class PhabricatorAuthContactNumberEditController
+ extends PhabricatorAuthContactNumberController {
+
+ public function handleRequest(AphrontRequest $request) {
+ return id(new PhabricatorAuthContactNumberEditEngine())
+ ->setController($this)
+ ->buildResponse();
+ }
+
+}
diff --git a/src/applications/auth/controller/contact/PhabricatorAuthContactNumberViewController.php b/src/applications/auth/controller/contact/PhabricatorAuthContactNumberViewController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/controller/contact/PhabricatorAuthContactNumberViewController.php
@@ -0,0 +1,98 @@
+<?php
+
+final class PhabricatorAuthContactNumberViewController
+ extends PhabricatorAuthContactNumberController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+
+ $number = id(new PhabricatorAuthContactNumberQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($request->getURIData('id')))
+ ->executeOne();
+ if (!$number) {
+ return new Aphront404Response();
+ }
+
+ $crumbs = $this->buildApplicationCrumbs()
+ ->addTextCrumb($number->getObjectName())
+ ->setBorder(true);
+
+ $header = $this->buildHeaderView($number);
+ $properties = $this->buildPropertiesView($number);
+ $curtain = $this->buildCurtain($number);
+
+ $timeline = $this->buildTransactionTimeline(
+ $number,
+ new PhabricatorAuthContactNumberTransactionQuery());
+ $timeline->setShouldTerminate(true);
+
+ $view = id(new PHUITwoColumnView())
+ ->setHeader($header)
+ ->setCurtain($curtain)
+ ->setMainColumn(
+ array(
+ $timeline,
+ ))
+ ->addPropertySection(pht('Details'), $properties);
+
+ return $this->newPage()
+ ->setTitle($number->getDisplayName())
+ ->setCrumbs($crumbs)
+ ->setPageObjectPHIDs(
+ array(
+ $number->getPHID(),
+ ))
+ ->appendChild($view);
+ }
+
+ private function buildHeaderView(PhabricatorAuthContactNumber $number) {
+ $viewer = $this->getViewer();
+
+ $view = id(new PHUIHeaderView())
+ ->setViewer($viewer)
+ ->setHeader($number->getObjectName())
+ ->setPolicyObject($number);
+
+ return $view;
+ }
+
+ private function buildPropertiesView(
+ PhabricatorAuthContactNumber $number) {
+ $viewer = $this->getViewer();
+
+ $view = id(new PHUIPropertyListView())
+ ->setViewer($viewer);
+
+ $view->addProperty(
+ pht('Owner'),
+ $viewer->renderHandle($number->getObjectPHID()));
+
+ $view->addProperty(pht('Contact Number'), $number->getDisplayName());
+
+ return $view;
+ }
+
+ private function buildCurtain(PhabricatorAuthContactNumber $number) {
+ $viewer = $this->getViewer();
+ $id = $number->getID();
+
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $number,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $curtain = $this->newCurtainView($number);
+
+ $curtain->addAction(
+ id(new PhabricatorActionView())
+ ->setName(pht('Edit Contact Number'))
+ ->setIcon('fa-pencil')
+ ->setHref($this->getApplicationURI("contact/edit/{$id}/"))
+ ->setDisabled(!$can_edit)
+ ->setWorkflow(!$can_edit));
+
+ return $curtain;
+ }
+
+}
diff --git a/src/applications/auth/editor/PhabricatorAuthContactNumberEditEngine.php b/src/applications/auth/editor/PhabricatorAuthContactNumberEditEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/editor/PhabricatorAuthContactNumberEditEngine.php
@@ -0,0 +1,86 @@
+<?php
+
+final class PhabricatorAuthContactNumberEditEngine
+ extends PhabricatorEditEngine {
+
+ const ENGINECONST = 'auth.contact';
+
+ public function isEngineConfigurable() {
+ return false;
+ }
+
+ public function getEngineName() {
+ return pht('Contact Numbers');
+ }
+
+ public function getSummaryHeader() {
+ return pht('Edit Contact Numbers');
+ }
+
+ public function getSummaryText() {
+ return pht('This engine is used to edit contact numbers.');
+ }
+
+ public function getEngineApplicationClass() {
+ return 'PhabricatorAuthApplication';
+ }
+
+ protected function newEditableObject() {
+ $viewer = $this->getViewer();
+ return PhabricatorAuthContactNumber::initializeNewContactNumber($viewer);
+ }
+
+ protected function newObjectQuery() {
+ return new PhabricatorAuthContactNumberQuery();
+ }
+
+ protected function getObjectCreateTitleText($object) {
+ return pht('Create Contact Number');
+ }
+
+ protected function getObjectCreateButtonText($object) {
+ return pht('Create Contact Number');
+ }
+
+ protected function getObjectEditTitleText($object) {
+ return pht('Edit Contact Number');
+ }
+
+ protected function getObjectEditShortText($object) {
+ return $object->getObjectName();
+ }
+
+ protected function getObjectCreateShortText() {
+ return pht('Create Contact Number');
+ }
+
+ protected function getObjectName() {
+ return pht('Contact Number');
+ }
+
+ protected function getEditorURI() {
+ return '/auth/contact/edit/';
+ }
+
+ protected function getObjectCreateCancelURI($object) {
+ return '/settings/panel/contact/';
+ }
+
+ protected function getObjectViewURI($object) {
+ return $object->getURI();
+ }
+
+ protected function buildCustomEditFields($object) {
+ return array(
+ id(new PhabricatorTextEditField())
+ ->setKey('contactNumber')
+ ->setTransactionType(
+ PhabricatorAuthContactNumberNumberTransaction::TRANSACTIONTYPE)
+ ->setLabel(pht('Contact Number'))
+ ->setDescription(pht('The contact number.'))
+ ->setValue($object->getContactNumber())
+ ->setIsRequired(true),
+ );
+ }
+
+}
diff --git a/src/applications/auth/editor/PhabricatorAuthContactNumberEditor.php b/src/applications/auth/editor/PhabricatorAuthContactNumberEditor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/editor/PhabricatorAuthContactNumberEditor.php
@@ -0,0 +1,38 @@
+<?php
+
+final class PhabricatorAuthContactNumberEditor
+ extends PhabricatorApplicationTransactionEditor {
+
+ public function getEditorApplicationClass() {
+ return 'PhabricatorAuthApplication';
+ }
+
+ public function getEditorObjectsDescription() {
+ return pht('Contact Numbers');
+ }
+
+ public function getCreateObjectTitle($author, $object) {
+ return pht('%s created this contact number.', $author);
+ }
+
+ public function getCreateObjectTitleForFeed($author, $object) {
+ return pht('%s created %s.', $author, $object);
+ }
+
+ protected function didCatchDuplicateKeyException(
+ PhabricatorLiskDAO $object,
+ array $xactions,
+ Exception $ex) {
+
+ $errors = array();
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ PhabricatorAuthContactNumberNumberTransaction::TRANSACTIONTYPE,
+ pht('Duplicate'),
+ pht('This contact number is already in use.'),
+ null);
+
+ throw new PhabricatorApplicationTransactionValidationException($errors);
+ }
+
+
+}
diff --git a/src/applications/auth/phid/PhabricatorAuthContactNumberPHIDType.php b/src/applications/auth/phid/PhabricatorAuthContactNumberPHIDType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/phid/PhabricatorAuthContactNumberPHIDType.php
@@ -0,0 +1,38 @@
+<?php
+
+final class PhabricatorAuthContactNumberPHIDType
+ extends PhabricatorPHIDType {
+
+ const TYPECONST = 'CTNM';
+
+ public function getTypeName() {
+ return pht('Contact Number');
+ }
+
+ public function newObject() {
+ return new PhabricatorAuthContactNumber();
+ }
+
+ public function getPHIDTypeApplicationClass() {
+ return 'PhabricatorAuthApplication';
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new PhabricatorAuthContactNumberQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $contact_number = $objects[$phid];
+ }
+ }
+
+}
diff --git a/src/applications/auth/query/PhabricatorAuthContactNumberQuery.php b/src/applications/auth/query/PhabricatorAuthContactNumberQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/query/PhabricatorAuthContactNumberQuery.php
@@ -0,0 +1,90 @@
+<?php
+
+final class PhabricatorAuthContactNumberQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $objectPHIDs;
+ private $statuses;
+ private $uniqueKeys;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ public function withObjectPHIDs(array $object_phids) {
+ $this->objectPHIDs = $object_phids;
+ return $this;
+ }
+
+ public function withStatuses(array $statuses) {
+ $this->statuses = $statuses;
+ return $this;
+ }
+
+ public function withUniqueKeys(array $unique_keys) {
+ $this->uniqueKeys = $unique_keys;
+ return $this;
+ }
+
+ public function newResultObject() {
+ return new PhabricatorAuthContactNumber();
+ }
+
+ protected function loadPage() {
+ return $this->loadStandardPage($this->newResultObject());
+ }
+
+ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
+ $where = parent::buildWhereClauseParts($conn);
+
+ if ($this->ids !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'phid IN (%Ls)',
+ $this->phids);
+ }
+
+ if ($this->objectPHIDs !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'objectPHID IN (%Ls)',
+ $this->objectPHIDs);
+ }
+
+ if ($this->statuses !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'status IN (%Ls)',
+ $this->statuses);
+ }
+
+ if ($this->uniqueKeys !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'uniqueKey IN (%Ls)',
+ $this->uniqueKeys);
+ }
+
+ return $where;
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorAuthApplication';
+ }
+
+}
diff --git a/src/applications/auth/query/PhabricatorAuthContactNumberTransactionQuery.php b/src/applications/auth/query/PhabricatorAuthContactNumberTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/query/PhabricatorAuthContactNumberTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorAuthContactNumberTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new PhabricatorAuthContactNumberTransaction();
+ }
+
+}
diff --git a/src/applications/auth/storage/PhabricatorAuthContactNumber.php b/src/applications/auth/storage/PhabricatorAuthContactNumber.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/storage/PhabricatorAuthContactNumber.php
@@ -0,0 +1,141 @@
+<?php
+
+
+final class PhabricatorAuthContactNumber
+ extends PhabricatorAuthDAO
+ implements
+ PhabricatorApplicationTransactionInterface,
+ PhabricatorPolicyInterface,
+ PhabricatorDestructibleInterface {
+
+ protected $objectPHID;
+ protected $contactNumber;
+ protected $uniqueKey;
+ protected $status;
+ protected $properties = array();
+
+ const STATUS_ACTIVE = 'active';
+ const STATUS_DISABLED = 'disabled';
+
+ protected function getConfiguration() {
+ return array(
+ self::CONFIG_SERIALIZATION => array(
+ 'properties' => self::SERIALIZATION_JSON,
+ ),
+ self::CONFIG_AUX_PHID => true,
+ self::CONFIG_COLUMN_SCHEMA => array(
+ 'contactNumber' => 'text255',
+ 'status' => 'text32',
+ 'uniqueKey' => 'bytes12?',
+ ),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_object' => array(
+ 'columns' => array('objectPHID'),
+ ),
+ 'key_unique' => array(
+ 'columns' => array('uniqueKey'),
+ 'unique' => true,
+ ),
+ ),
+ ) + parent::getConfiguration();
+ }
+
+ public static function initializeNewContactNumber($object) {
+ return id(new self())
+ ->setStatus(self::STATUS_ACTIVE)
+ ->setObjectPHID($object->getPHID());
+ }
+
+ public function getPHIDType() {
+ return PhabricatorAuthContactNumberPHIDType::TYPECONST;
+ }
+
+ public function getURI() {
+ return urisprintf('/auth/contact/%s/', $this->getID());
+ }
+
+ public function getObjectName() {
+ return pht('Contact Number %d', $this->getID());
+ }
+
+ public function getDisplayName() {
+ return $this->getContactNumber();
+ }
+
+ public function isDisabled() {
+ return ($this->getStatus() === self::STATUS_DISABLED);
+ }
+
+ public function newIconView() {
+ if ($this->isDisabled()) {
+ return id(new PHUIIconView())
+ ->setIcon('fa-ban', 'grey')
+ ->setTooltip(pht('Disabled'));
+ }
+
+ return id(new PHUIIconView())
+ ->setIcon('fa-mobile', 'green')
+ ->setTooltip(pht('Active Phone Number'));
+ }
+
+ public function newUniqueKey() {
+ $parts = array(
+ // This is future-proofing for a world where we have multiple types
+ // of contact numbers, so we might be able to avoid re-hashing
+ // everything.
+ 'phone',
+ $this->getContactNumber(),
+ );
+
+ $parts = implode("\0", $parts);
+
+ return PhabricatorHash::digestForIndex($parts);
+ }
+
+ public function save() {
+ $this->uniqueKey = $this->newUniqueKey();
+ return parent::save();
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ );
+ }
+
+ public function getPolicy($capability) {
+ return $this->getObjectPHID();
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return false;
+ }
+
+
+/* -( PhabricatorDestructibleInterface )----------------------------------- */
+
+
+ public function destroyObjectPermanently(
+ PhabricatorDestructionEngine $engine) {
+ $this->delete();
+ }
+
+
+/* -( PhabricatorApplicationTransactionInterface )------------------------- */
+
+
+ public function getApplicationTransactionEditor() {
+ return new PhabricatorAuthContactNumberEditor();
+ }
+
+ public function getApplicationTransactionTemplate() {
+ return new PhabricatorAuthContactNumberTransaction();
+ }
+
+
+}
diff --git a/src/applications/auth/storage/PhabricatorAuthContactNumberTransaction.php b/src/applications/auth/storage/PhabricatorAuthContactNumberTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/storage/PhabricatorAuthContactNumberTransaction.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhabricatorAuthContactNumberTransaction
+ extends PhabricatorModularTransaction {
+
+ public function getApplicationName() {
+ return 'auth';
+ }
+
+ public function getApplicationTransactionType() {
+ return PhabricatorAuthContactNumberPHIDType::TYPECONST;
+ }
+
+ public function getBaseTransactionClass() {
+ return 'PhabricatorAuthContactNumberTransactionType';
+ }
+
+}
diff --git a/src/applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.php b/src/applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.php
@@ -0,0 +1,91 @@
+<?php
+
+final class PhabricatorAuthContactNumberNumberTransaction
+ extends PhabricatorAuthContactNumberTransactionType {
+
+ const TRANSACTIONTYPE = 'number';
+
+ public function generateOldValue($object) {
+ return $object->getContactNumber();
+ }
+
+ public function generateNewValue($object, $value) {
+ $number = new PhabricatorPhoneNumber($value);
+ return $number->toE164();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setContactNumber($value);
+ }
+
+ public function getTitle() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ return pht(
+ '%s changed this contact number from %s to %s.',
+ $this->renderAuthor(),
+ $this->renderOldValue(),
+ $this->renderNewValue());
+ }
+
+ public function validateTransactions($object, array $xactions) {
+ $errors = array();
+
+ $current_value = $object->getContactNumber();
+ if ($this->isEmptyTextTransaction($current_value, $xactions)) {
+ $errors[] = $this->newRequiredError(
+ pht('Contact numbers must have a contact number.'));
+ return $errors;
+ }
+
+ $max_length = $object->getColumnMaximumByteLength('contactNumber');
+ foreach ($xactions as $xaction) {
+ $new_value = $xaction->getNewValue();
+ $new_length = strlen($new_value);
+ if ($new_length > $max_length) {
+ $errors[] = $this->newInvalidError(
+ pht(
+ 'Contact numbers can not be longer than %s characters.',
+ new PhutilNumber($max_length)),
+ $xaction);
+ continue;
+ }
+
+ try {
+ new PhabricatorPhoneNumber($new_value);
+ } catch (Exception $ex) {
+ $errors[] = $this->newInvalidError(
+ pht(
+ 'Contact number is invalid: %s',
+ $ex->getMessage()),
+ $xaction);
+ continue;
+ }
+
+ $new_value = $this->generateNewValue($object, $new_value);
+
+ $unique_key = id(clone $object)
+ ->setContactNumber($new_value)
+ ->newUniqueKey();
+
+ $other = id(new PhabricatorAuthContactNumberQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withUniqueKeys(array($unique_key))
+ ->executeOne();
+
+ if ($other) {
+ if ($other->getID() !== $object->getID()) {
+ $errors[] = $this->newInvalidError(
+ pht('Contact number is already in use.'),
+ $xaction);
+ continue;
+ }
+ }
+
+ }
+
+ return $errors;
+ }
+
+}
diff --git a/src/applications/auth/xaction/PhabricatorAuthContactNumberTransactionType.php b/src/applications/auth/xaction/PhabricatorAuthContactNumberTransactionType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/auth/xaction/PhabricatorAuthContactNumberTransactionType.php
@@ -0,0 +1,4 @@
+<?php
+
+abstract class PhabricatorAuthContactNumberTransactionType
+ extends PhabricatorModularTransactionType {}
diff --git a/src/applications/metamta/message/PhabricatorPhoneNumber.php b/src/applications/metamta/message/PhabricatorPhoneNumber.php
--- a/src/applications/metamta/message/PhabricatorPhoneNumber.php
+++ b/src/applications/metamta/message/PhabricatorPhoneNumber.php
@@ -8,13 +8,23 @@
public function __construct($raw_number) {
$number = preg_replace('/[^\d]+/', '', $raw_number);
- if (!preg_match('/^[1-9]\d{1,14}\z/', $number)) {
+ if (!preg_match('/^[1-9]\d{9,14}\z/', $number)) {
throw new Exception(
pht(
- 'Phone number ("%s") is not in a recognized format.',
+ 'Phone number ("%s") is not in a recognized format: expected a '.
+ 'US number like "(555) 555-5555", or an international number '.
+ 'like "+55 5555 555555".',
$raw_number));
}
+ // If the number didn't start with "+" and has has 10 digits, assume it is
+ // a US number with no country code prefix, like "(555) 555-5555".
+ if (!preg_match('/^[+]/', $raw_number)) {
+ if (strlen($number) === 10) {
+ $number = '1'.$number;
+ }
+ }
+
$this->number = $number;
}
diff --git a/src/applications/metamta/message/__tests__/PhabricatorPhoneNumberTestCase.php b/src/applications/metamta/message/__tests__/PhabricatorPhoneNumberTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/applications/metamta/message/__tests__/PhabricatorPhoneNumberTestCase.php
@@ -0,0 +1,37 @@
+<?php
+
+final class PhabricatorPhoneNumberTestCase
+ extends PhabricatorTestCase {
+
+ public function testNumberNormalization() {
+ $map = array(
+ '+15555555555' => '+15555555555',
+ '+1 (555) 555-5555' => '+15555555555',
+ '(555) 555-5555' => '+15555555555',
+
+ '' => false,
+ '1-800-CALL-SAUL' => false,
+ );
+
+ foreach ($map as $input => $expect) {
+ $caught = null;
+ try {
+ $actual = id(new PhabricatorPhoneNumber($input))
+ ->toE164();
+ } catch (Exception $ex) {
+ $caught = $ex;
+ }
+
+ $this->assertEqual(
+ (bool)$caught,
+ ($expect === false),
+ pht('Exception raised by: %s', $input));
+
+ if ($expect !== false) {
+ $this->assertEqual($expect, $actual, pht('E164 of: %s', $input));
+ }
+ }
+
+ }
+
+}
diff --git a/src/applications/search/view/PhabricatorSearchResultView.php b/src/applications/search/view/PhabricatorSearchResultView.php
--- a/src/applications/search/view/PhabricatorSearchResultView.php
+++ b/src/applications/search/view/PhabricatorSearchResultView.php
@@ -126,7 +126,7 @@
}
// Go through the string one display glyph at a time. If a glyph starts
- // on a highlighted byte position, turn on highlighting for the nubmer
+ // on a highlighted byte position, turn on highlighting for the number
// of matching bytes. If a query searches for "e" and the document contains
// an "e" followed by a bunch of combining marks, this will correctly
// highlight the entire glyph.
diff --git a/src/applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php b/src/applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php
new file mode 100644
--- /dev/null
+++ b/src/applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php
@@ -0,0 +1,69 @@
+<?php
+
+final class PhabricatorContactNumbersSettingsPanel
+ extends PhabricatorSettingsPanel {
+
+ public function getPanelKey() {
+ return 'contact';
+ }
+
+ public function getPanelName() {
+ return pht('Contact Numbers');
+ }
+
+ public function getPanelGroupKey() {
+ return PhabricatorSettingsAuthenticationPanelGroup::PANELGROUPKEY;
+ }
+
+ public function processRequest(AphrontRequest $request) {
+ $user = $this->getUser();
+ $viewer = $request->getUser();
+
+ $numbers = id(new PhabricatorAuthContactNumberQuery())
+ ->setViewer($viewer)
+ ->withObjectPHIDs(array($user->getPHID()))
+ ->execute();
+
+ $rows = array();
+ foreach ($numbers as $number) {
+ $rows[] = array(
+ $number->newIconView(),
+ phutil_tag(
+ 'a',
+ array(
+ 'href' => $number->getURI(),
+ ),
+ $number->getDisplayName()),
+ phabricator_datetime($number->getDateCreated(), $viewer),
+ );
+ }
+
+ $table = id(new AphrontTableView($rows))
+ ->setNoDataString(
+ pht("You haven't added any contact numbers to your account."))
+ ->setHeaders(
+ array(
+ null,
+ pht('Number'),
+ pht('Created'),
+ ))
+ ->setColumnClasses(
+ array(
+ null,
+ 'wide pri',
+ 'right',
+ ));
+
+ $buttons = array();
+
+ $buttons[] = id(new PHUIButtonView())
+ ->setTag('a')
+ ->setIcon('fa-plus')
+ ->setText(pht('Add Contact Number'))
+ ->setHref('/auth/contact/edit/')
+ ->setColor(PHUIButtonView::GREY);
+
+ return $this->newBox(pht('Contact Numbers'), $table, $buttons);
+ }
+
+}

File Metadata

Mime Type
text/plain
Expires
Mon, May 13, 11:52 PM (2 w, 5 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/v7/yg/77hibljitsnqluyp
Default Alt Text
D19988.diff (33 KB)

Event Timeline