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,10 @@ +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}, + 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 @@ -2201,6 +2201,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', @@ -2723,6 +2735,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', @@ -3855,6 +3868,7 @@ 'PhabricatorPholioConfigOptions' => 'applications/pholio/config/PhabricatorPholioConfigOptions.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', @@ -7869,6 +7883,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', @@ -8487,6 +8518,7 @@ 'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting', 'PhabricatorConsoleApplication' => 'PhabricatorApplication', 'PhabricatorConsoleContentSource' => 'PhabricatorContentSource', + 'PhabricatorContactNumbersSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorContentSource' => 'Phobject', 'PhabricatorContentSourceModule' => 'PhabricatorConfigModule', 'PhabricatorContentSourceView' => 'AphrontView', @@ -9780,6 +9812,7 @@ 'PhabricatorPholioConfigOptions' => 'PhabricatorApplicationConfigOptions', '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 @@ -94,6 +94,13 @@ '(?P[1-9]\d*)/' => 'PhabricatorAuthFactorProviderViewController', ), + + 'contact/' => array( + $this->getEditRoutePattern('edit/') => + 'PhabricatorAuthContactNumberEditController', + '(?P[1-9]\d*)/' => + 'PhabricatorAuthContactNumberViewController', + ), ), '/oauth/(?P\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 @@ +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 @@ +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 @@ +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 @@ +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 @@ +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 @@ +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 withContactNumbers(array $contact_numbers) { + $this->contactNumbers = $contact_numbers; + 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->contactNumbers !== null) { + $where[] = qsprintf( + $conn, + 'contactNumber IN (%Ls)', + $this->contactNumbers); + } + + 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 @@ + array( + 'properties' => self::SERIALIZATION_JSON, + ), + self::CONFIG_AUX_PHID => true, + self::CONFIG_COLUMN_SCHEMA => array( + 'contactNumber' => 'text255', + 'status' => 'text32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_number' => array( + 'columns' => array('contactNumber'), + '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')); + } + + +/* -( 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 @@ +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; + } + + $normal_value = $this->generateNewValue($object, $new_value); + + $other = id(new PhabricatorAuthContactNumberQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withContactNumbers(array($normal_value)) + ->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 @@ +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 @@ + '+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 @@ +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); + } + +}