Skip to content

Commit 596435b

Browse files
author
epriestley
committedJan 23, 2019
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
1 parent 1220376 commit 596435b

8 files changed

+237
-5
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE {$NAMESPACE}_auth.auth_contactnumber
2+
ADD isPrimary BOOL NOT NULL;

‎src/__phutil_library_map__.php

+4
Original file line numberDiff line numberDiff line change
@@ -2207,6 +2207,8 @@
22072207
'PhabricatorAuthContactNumberEditor' => 'applications/auth/editor/PhabricatorAuthContactNumberEditor.php',
22082208
'PhabricatorAuthContactNumberNumberTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.php',
22092209
'PhabricatorAuthContactNumberPHIDType' => 'applications/auth/phid/PhabricatorAuthContactNumberPHIDType.php',
2210+
'PhabricatorAuthContactNumberPrimaryController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberPrimaryController.php',
2211+
'PhabricatorAuthContactNumberPrimaryTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberPrimaryTransaction.php',
22102212
'PhabricatorAuthContactNumberQuery' => 'applications/auth/query/PhabricatorAuthContactNumberQuery.php',
22112213
'PhabricatorAuthContactNumberStatusTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberStatusTransaction.php',
22122214
'PhabricatorAuthContactNumberTransaction' => 'applications/auth/storage/PhabricatorAuthContactNumberTransaction.php',
@@ -7912,6 +7914,8 @@
79127914
'PhabricatorAuthContactNumberEditor' => 'PhabricatorApplicationTransactionEditor',
79137915
'PhabricatorAuthContactNumberNumberTransaction' => 'PhabricatorAuthContactNumberTransactionType',
79147916
'PhabricatorAuthContactNumberPHIDType' => 'PhabricatorPHIDType',
7917+
'PhabricatorAuthContactNumberPrimaryController' => 'PhabricatorAuthContactNumberController',
7918+
'PhabricatorAuthContactNumberPrimaryTransaction' => 'PhabricatorAuthContactNumberTransactionType',
79157919
'PhabricatorAuthContactNumberQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
79167920
'PhabricatorAuthContactNumberStatusTransaction' => 'PhabricatorAuthContactNumberTransactionType',
79177921
'PhabricatorAuthContactNumberTransaction' => 'PhabricatorModularTransaction',

‎src/applications/auth/application/PhabricatorAuthApplication.php

+2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ public function getRoutes() {
113113
'PhabricatorAuthContactNumberViewController',
114114
'(?P<action>disable|enable)/(?P<id>[1-9]\d*)/' =>
115115
'PhabricatorAuthContactNumberDisableController',
116+
'primary/(?P<id>[1-9]\d*)/' =>
117+
'PhabricatorAuthContactNumberPrimaryController',
116118
),
117119
),
118120

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
}

‎src/applications/auth/controller/contact/PhabricatorAuthContactNumberViewController.php

+12-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ private function buildHeaderView(PhabricatorAuthContactNumber $number) {
5656

5757
if ($number->isDisabled()) {
5858
$view->setStatus('fa-ban', 'red', pht('Disabled'));
59+
} else if ($number->getIsPrimary()) {
60+
$view->setStatus('fa-certificate', 'blue', pht('Primary'));
5961
}
6062

6163
return $view;
@@ -96,17 +98,26 @@ private function buildCurtain(PhabricatorAuthContactNumber $number) {
9698
->setDisabled(!$can_edit)
9799
->setWorkflow(!$can_edit));
98100

99-
100101
if ($number->isDisabled()) {
101102
$disable_uri = $this->getApplicationURI("contact/enable/{$id}/");
102103
$disable_name = pht('Enable Contact Number');
103104
$disable_icon = 'fa-check';
105+
$can_primary = false;
104106
} else {
105107
$disable_uri = $this->getApplicationURI("contact/disable/{$id}/");
106108
$disable_name = pht('Disable Contact Number');
107109
$disable_icon = 'fa-ban';
110+
$can_primary = !$number->getIsPrimary();
108111
}
109112

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));
120+
110121
$curtain->addAction(
111122
id(new PhabricatorActionView())
112123
->setName($disable_name)

‎src/applications/auth/storage/PhabricatorAuthContactNumber.php

+75-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ final class PhabricatorAuthContactNumber
1212
protected $contactNumber;
1313
protected $uniqueKey;
1414
protected $status;
15+
protected $isPrimary;
1516
protected $properties = array();
1617

1718
const STATUS_ACTIVE = 'active';
@@ -27,6 +28,7 @@ protected function getConfiguration() {
2728
'contactNumber' => 'text255',
2829
'status' => 'text32',
2930
'uniqueKey' => 'bytes12?',
31+
'isPrimary' => 'bool',
3032
),
3133
self::CONFIG_KEY_SCHEMA => array(
3234
'key_object' => array(
@@ -43,7 +45,8 @@ protected function getConfiguration() {
4345
public static function initializeNewContactNumber($object) {
4446
return id(new self())
4547
->setStatus(self::STATUS_ACTIVE)
46-
->setObjectPHID($object->getPHID());
48+
->setObjectPHID($object->getPHID())
49+
->setIsPrimary(0);
4750
}
4851

4952
public function getPHIDType() {
@@ -73,8 +76,14 @@ public function newIconView() {
7376
->setTooltip(pht('Disabled'));
7477
}
7578

79+
if ($this->getIsPrimary()) {
80+
return id(new PHUIIconView())
81+
->setIcon('fa-certificate', 'blue')
82+
->setTooltip(pht('Primary Number'));
83+
}
84+
7685
return id(new PHUIIconView())
77-
->setIcon('fa-mobile', 'green')
86+
->setIcon('fa-hashtag', 'bluegrey')
7887
->setTooltip(pht('Active Phone Number'));
7988
}
8089

@@ -101,7 +110,61 @@ public function save() {
101110
$this->uniqueKey = $this->newUniqueKey();
102111
}
103112

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;
105168
}
106169

107170
public static function getStatusNameMap() {
@@ -119,6 +182,15 @@ private static function getStatusPropertyMap() {
119182
);
120183
}
121184

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());
192+
}
193+
122194

123195
/* -( PhabricatorPolicyInterface )----------------------------------------- */
124196

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
}

‎src/applications/settings/panel/PhabricatorContactNumbersSettingsPanel.php

+15-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public function getPanelName() {
1212
}
1313

1414
public function getPanelMenuIcon() {
15-
return 'fa-mobile';
15+
return 'fa-hashtag';
1616
}
1717

1818
public function getPanelGroupKey() {
@@ -31,9 +31,19 @@ public function processRequest(AphrontRequest $request) {
3131
->setViewer($viewer)
3232
->withObjectPHIDs(array($user->getPHID()))
3333
->execute();
34+
$numbers = msortv($numbers, 'getSortVector');
3435

3536
$rows = array();
37+
$row_classes = array();
3638
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+
3747
$rows[] = array(
3848
$number->newIconView(),
3949
phutil_tag(
@@ -42,23 +52,27 @@ public function processRequest(AphrontRequest $request) {
4252
'href' => $number->getURI(),
4353
),
4454
$number->getDisplayName()),
55+
$primary_display,
4556
phabricator_datetime($number->getDateCreated(), $viewer),
4657
);
4758
}
4859

4960
$table = id(new AphrontTableView($rows))
5061
->setNoDataString(
5162
pht("You haven't added any contact numbers to your account."))
63+
->setRowClasses($row_classes)
5264
->setHeaders(
5365
array(
5466
null,
5567
pht('Number'),
68+
pht('Status'),
5669
pht('Created'),
5770
))
5871
->setColumnClasses(
5972
array(
6073
null,
6174
'wide pri',
75+
null,
6276
'right',
6377
));
6478

0 commit comments

Comments
 (0)
Failed to load comments.