@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Support designating a contact number as "primary"

Summary:
Depends on D20010. Ref T920. Allow users to designate which contact number is "primary": the number we'll actually send stuff to.

Since this interacts in weird ways with "disable", just do a "when any number is touched, put all of the user's rows into the right state" sort of thing.

Test Plan:
- Added numbers, made numbers primary, disabled a primary number, un-disabled a number with no primaries. Got sensible behavior in all cases.

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T920

Differential Revision: https://secure.phabricator.com/D20011

+237 -5
+2
resources/sql/autopatches/20190121.contact.01.primary.sql
··· 1 + ALTER TABLE {$NAMESPACE}_auth.auth_contactnumber 2 + ADD isPrimary BOOL NOT NULL;
+4
src/__phutil_library_map__.php
··· 2207 2207 'PhabricatorAuthContactNumberEditor' => 'applications/auth/editor/PhabricatorAuthContactNumberEditor.php', 2208 2208 'PhabricatorAuthContactNumberNumberTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.php', 2209 2209 'PhabricatorAuthContactNumberPHIDType' => 'applications/auth/phid/PhabricatorAuthContactNumberPHIDType.php', 2210 + 'PhabricatorAuthContactNumberPrimaryController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberPrimaryController.php', 2211 + 'PhabricatorAuthContactNumberPrimaryTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberPrimaryTransaction.php', 2210 2212 'PhabricatorAuthContactNumberQuery' => 'applications/auth/query/PhabricatorAuthContactNumberQuery.php', 2211 2213 'PhabricatorAuthContactNumberStatusTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberStatusTransaction.php', 2212 2214 'PhabricatorAuthContactNumberTransaction' => 'applications/auth/storage/PhabricatorAuthContactNumberTransaction.php', ··· 7912 7914 'PhabricatorAuthContactNumberEditor' => 'PhabricatorApplicationTransactionEditor', 7913 7915 'PhabricatorAuthContactNumberNumberTransaction' => 'PhabricatorAuthContactNumberTransactionType', 7914 7916 'PhabricatorAuthContactNumberPHIDType' => 'PhabricatorPHIDType', 7917 + 'PhabricatorAuthContactNumberPrimaryController' => 'PhabricatorAuthContactNumberController', 7918 + 'PhabricatorAuthContactNumberPrimaryTransaction' => 'PhabricatorAuthContactNumberTransactionType', 7915 7919 'PhabricatorAuthContactNumberQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 7916 7920 'PhabricatorAuthContactNumberStatusTransaction' => 'PhabricatorAuthContactNumberTransactionType', 7917 7921 'PhabricatorAuthContactNumberTransaction' => 'PhabricatorModularTransaction',
+2
src/applications/auth/application/PhabricatorAuthApplication.php
··· 113 113 'PhabricatorAuthContactNumberViewController', 114 114 '(?P<action>disable|enable)/(?P<id>[1-9]\d*)/' => 115 115 'PhabricatorAuthContactNumberDisableController', 116 + 'primary/(?P<id>[1-9]\d*)/' => 117 + 'PhabricatorAuthContactNumberPrimaryController', 116 118 ), 117 119 ), 118 120
+78
src/applications/auth/controller/contact/PhabricatorAuthContactNumberPrimaryController.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthContactNumberPrimaryController 4 + extends PhabricatorAuthContactNumberController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + $id = $request->getURIData('id'); 9 + 10 + $number = id(new PhabricatorAuthContactNumberQuery()) 11 + ->setViewer($viewer) 12 + ->withIDs(array($id)) 13 + ->requireCapabilities( 14 + array( 15 + PhabricatorPolicyCapability::CAN_VIEW, 16 + PhabricatorPolicyCapability::CAN_EDIT, 17 + )) 18 + ->executeOne(); 19 + if (!$number) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + $id = $number->getID(); 24 + $cancel_uri = $number->getURI(); 25 + 26 + if ($number->isDisabled()) { 27 + return $this->newDialog() 28 + ->setTitle(pht('Number Disabled')) 29 + ->appendParagraph( 30 + pht( 31 + 'You can not make a disabled number your primary contact number.')) 32 + ->addCancelButton($cancel_uri); 33 + } 34 + 35 + if ($number->getIsPrimary()) { 36 + return $this->newDialog() 37 + ->setTitle(pht('Number Already Primary')) 38 + ->appendParagraph( 39 + pht( 40 + 'This contact number is already your primary contact number.')) 41 + ->addCancelButton($cancel_uri); 42 + } 43 + 44 + if ($request->isFormPost()) { 45 + $xactions = array(); 46 + 47 + $xactions[] = id(new PhabricatorAuthContactNumberTransaction()) 48 + ->setTransactionType( 49 + PhabricatorAuthContactNumberPrimaryTransaction::TRANSACTIONTYPE) 50 + ->setNewValue(true); 51 + 52 + $editor = id(new PhabricatorAuthContactNumberEditor()) 53 + ->setActor($viewer) 54 + ->setContentSourceFromRequest($request) 55 + ->setContinueOnNoEffect(true) 56 + ->setContinueOnMissingFields(true); 57 + 58 + $editor->applyTransactions($number, $xactions); 59 + 60 + return id(new AphrontRedirectResponse())->setURI($cancel_uri); 61 + } 62 + 63 + $number_display = phutil_tag( 64 + 'strong', 65 + array(), 66 + $number->getDisplayName()); 67 + 68 + return $this->newDialog() 69 + ->setTitle(pht('Set Primary Contact Number')) 70 + ->appendParagraph( 71 + pht( 72 + 'Designate %s as your primary contact number?', 73 + $number_display)) 74 + ->addSubmitButton(pht('Make Primary')) 75 + ->addCancelButton($cancel_uri); 76 + } 77 + 78 + }
+12 -1
src/applications/auth/controller/contact/PhabricatorAuthContactNumberViewController.php
··· 56 56 57 57 if ($number->isDisabled()) { 58 58 $view->setStatus('fa-ban', 'red', pht('Disabled')); 59 + } else if ($number->getIsPrimary()) { 60 + $view->setStatus('fa-certificate', 'blue', pht('Primary')); 59 61 } 60 62 61 63 return $view; ··· 96 98 ->setDisabled(!$can_edit) 97 99 ->setWorkflow(!$can_edit)); 98 100 99 - 100 101 if ($number->isDisabled()) { 101 102 $disable_uri = $this->getApplicationURI("contact/enable/{$id}/"); 102 103 $disable_name = pht('Enable Contact Number'); 103 104 $disable_icon = 'fa-check'; 105 + $can_primary = false; 104 106 } else { 105 107 $disable_uri = $this->getApplicationURI("contact/disable/{$id}/"); 106 108 $disable_name = pht('Disable Contact Number'); 107 109 $disable_icon = 'fa-ban'; 110 + $can_primary = !$number->getIsPrimary(); 108 111 } 112 + 113 + $curtain->addAction( 114 + id(new PhabricatorActionView()) 115 + ->setName(pht('Make Primary Number')) 116 + ->setIcon('fa-certificate') 117 + ->setHref($this->getApplicationURI("contact/primary/{$id}/")) 118 + ->setDisabled(!$can_primary) 119 + ->setWorkflow(true)); 109 120 110 121 $curtain->addAction( 111 122 id(new PhabricatorActionView())
+75 -3
src/applications/auth/storage/PhabricatorAuthContactNumber.php
··· 12 12 protected $contactNumber; 13 13 protected $uniqueKey; 14 14 protected $status; 15 + protected $isPrimary; 15 16 protected $properties = array(); 16 17 17 18 const STATUS_ACTIVE = 'active'; ··· 27 28 'contactNumber' => 'text255', 28 29 'status' => 'text32', 29 30 'uniqueKey' => 'bytes12?', 31 + 'isPrimary' => 'bool', 30 32 ), 31 33 self::CONFIG_KEY_SCHEMA => array( 32 34 'key_object' => array( ··· 43 45 public static function initializeNewContactNumber($object) { 44 46 return id(new self()) 45 47 ->setStatus(self::STATUS_ACTIVE) 46 - ->setObjectPHID($object->getPHID()); 48 + ->setObjectPHID($object->getPHID()) 49 + ->setIsPrimary(0); 47 50 } 48 51 49 52 public function getPHIDType() { ··· 73 76 ->setTooltip(pht('Disabled')); 74 77 } 75 78 79 + if ($this->getIsPrimary()) { 80 + return id(new PHUIIconView()) 81 + ->setIcon('fa-certificate', 'blue') 82 + ->setTooltip(pht('Primary Number')); 83 + } 84 + 76 85 return id(new PHUIIconView()) 77 - ->setIcon('fa-mobile', 'green') 86 + ->setIcon('fa-hashtag', 'bluegrey') 78 87 ->setTooltip(pht('Active Phone Number')); 79 88 } 80 89 ··· 101 110 $this->uniqueKey = $this->newUniqueKey(); 102 111 } 103 112 104 - return parent::save(); 113 + parent::save(); 114 + 115 + return $this->updatePrimaryContactNumber(); 116 + } 117 + 118 + private function updatePrimaryContactNumber() { 119 + // Update the "isPrimary" column so that at most one number is primary for 120 + // each user, and no disabled number is primary. 121 + 122 + $conn = $this->establishConnection('w'); 123 + $this_id = (int)$this->getID(); 124 + 125 + if ($this->getIsPrimary() && !$this->isDisabled()) { 126 + // If we're trying to make this number primary and it's active, great: 127 + // make this number the primary number. 128 + $primary_id = $this_id; 129 + } else { 130 + // If we aren't trying to make this number primary or it is disabled, 131 + // pick another number to make primary if we can. A number must be active 132 + // to become primary. 133 + 134 + // If there are multiple active numbers, pick the oldest one currently 135 + // marked primary (usually, this should mean that we just keep the 136 + // current primary number as primary). 137 + 138 + // If none are marked primary, just pick the oldest one. 139 + $primary_row = queryfx_one( 140 + $conn, 141 + 'SELECT id FROM %R 142 + WHERE objectPHID = %s AND status = %s 143 + ORDER BY isPrimary DESC, id ASC 144 + LIMIT 1', 145 + $this, 146 + $this->getObjectPHID(), 147 + self::STATUS_ACTIVE); 148 + if ($primary_row) { 149 + $primary_id = (int)$primary_row['id']; 150 + } else { 151 + $primary_id = -1; 152 + } 153 + } 154 + 155 + // Set the chosen number to primary, and all other numbers to nonprimary. 156 + 157 + queryfx( 158 + $conn, 159 + 'UPDATE %R SET isPrimary = IF(id = %d, 1, 0) 160 + WHERE objectPHID = %s', 161 + $this, 162 + $primary_id, 163 + $this->getObjectPHID()); 164 + 165 + $this->setIsPrimary((int)($primary_id === $this_id)); 166 + 167 + return $this; 105 168 } 106 169 107 170 public static function getStatusNameMap() { ··· 117 180 'name' => pht('Disabled'), 118 181 ), 119 182 ); 183 + } 184 + 185 + public function getSortVector() { 186 + // Sort the primary number first, then active numbers, then disabled 187 + // numbers. In each group, sort from oldest to newest. 188 + return id(new PhutilSortVector()) 189 + ->addInt($this->getIsPrimary() ? 0 : 1) 190 + ->addInt($this->isDisabled() ? 1 : 0) 191 + ->addInt($this->getID()); 120 192 } 121 193 122 194
+49
src/applications/auth/xaction/PhabricatorAuthContactNumberPrimaryTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthContactNumberPrimaryTransaction 4 + extends PhabricatorAuthContactNumberTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'primary'; 7 + 8 + public function generateOldValue($object) { 9 + return (bool)$object->getIsPrimary(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setIsPrimary((int)$value); 14 + } 15 + 16 + public function getTitle() { 17 + return pht( 18 + '%s made this the primary contact number.', 19 + $this->renderAuthor()); 20 + } 21 + 22 + public function validateTransactions($object, array $xactions) { 23 + $errors = array(); 24 + 25 + foreach ($xactions as $xaction) { 26 + $new_value = $xaction->getNewValue(); 27 + 28 + if (!$new_value) { 29 + $errors[] = $this->newInvalidError( 30 + pht( 31 + 'To choose a different primary contact number, make that '. 32 + 'number primary (instead of trying to demote this one).'), 33 + $xaction); 34 + continue; 35 + } 36 + 37 + if ($object->isDisabled()) { 38 + $errors[] = $this->newInvalidError( 39 + pht( 40 + 'You can not make a disabled number a primary contact number.'), 41 + $xaction); 42 + continue; 43 + } 44 + } 45 + 46 + return $errors; 47 + } 48 + 49 + }
+15 -1
src/applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php
··· 12 12 } 13 13 14 14 public function getPanelMenuIcon() { 15 - return 'fa-mobile'; 15 + return 'fa-hashtag'; 16 16 } 17 17 18 18 public function getPanelGroupKey() { ··· 31 31 ->setViewer($viewer) 32 32 ->withObjectPHIDs(array($user->getPHID())) 33 33 ->execute(); 34 + $numbers = msortv($numbers, 'getSortVector'); 34 35 35 36 $rows = array(); 37 + $row_classes = array(); 36 38 foreach ($numbers as $number) { 39 + if ($number->getIsPrimary()) { 40 + $primary_display = pht('Primary'); 41 + $row_classes[] = 'highlighted'; 42 + } else { 43 + $primary_display = null; 44 + $row_classes[] = null; 45 + } 46 + 37 47 $rows[] = array( 38 48 $number->newIconView(), 39 49 phutil_tag( ··· 42 52 'href' => $number->getURI(), 43 53 ), 44 54 $number->getDisplayName()), 55 + $primary_display, 45 56 phabricator_datetime($number->getDateCreated(), $viewer), 46 57 ); 47 58 } ··· 49 60 $table = id(new AphrontTableView($rows)) 50 61 ->setNoDataString( 51 62 pht("You haven't added any contact numbers to your account.")) 63 + ->setRowClasses($row_classes) 52 64 ->setHeaders( 53 65 array( 54 66 null, 55 67 pht('Number'), 68 + pht('Status'), 56 69 pht('Created'), 57 70 )) 58 71 ->setColumnClasses( 59 72 array( 60 73 null, 61 74 'wide pri', 75 + null, 62 76 'right', 63 77 )); 64 78