mirror of
https://we.phorge.it/source/phorge.git
synced 2025-02-20 10:48:40 +01:00
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
This commit is contained in:
parent
12203762b7
commit
596435b35e
8 changed files with 237 additions and 5 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_auth.auth_contactnumber
|
||||||
|
ADD isPrimary BOOL NOT NULL;
|
|
@ -2207,6 +2207,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthContactNumberEditor' => 'applications/auth/editor/PhabricatorAuthContactNumberEditor.php',
|
'PhabricatorAuthContactNumberEditor' => 'applications/auth/editor/PhabricatorAuthContactNumberEditor.php',
|
||||||
'PhabricatorAuthContactNumberNumberTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.php',
|
'PhabricatorAuthContactNumberNumberTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.php',
|
||||||
'PhabricatorAuthContactNumberPHIDType' => 'applications/auth/phid/PhabricatorAuthContactNumberPHIDType.php',
|
'PhabricatorAuthContactNumberPHIDType' => 'applications/auth/phid/PhabricatorAuthContactNumberPHIDType.php',
|
||||||
|
'PhabricatorAuthContactNumberPrimaryController' => 'applications/auth/controller/contact/PhabricatorAuthContactNumberPrimaryController.php',
|
||||||
|
'PhabricatorAuthContactNumberPrimaryTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberPrimaryTransaction.php',
|
||||||
'PhabricatorAuthContactNumberQuery' => 'applications/auth/query/PhabricatorAuthContactNumberQuery.php',
|
'PhabricatorAuthContactNumberQuery' => 'applications/auth/query/PhabricatorAuthContactNumberQuery.php',
|
||||||
'PhabricatorAuthContactNumberStatusTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberStatusTransaction.php',
|
'PhabricatorAuthContactNumberStatusTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberStatusTransaction.php',
|
||||||
'PhabricatorAuthContactNumberTransaction' => 'applications/auth/storage/PhabricatorAuthContactNumberTransaction.php',
|
'PhabricatorAuthContactNumberTransaction' => 'applications/auth/storage/PhabricatorAuthContactNumberTransaction.php',
|
||||||
|
@ -7912,6 +7914,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorAuthContactNumberEditor' => 'PhabricatorApplicationTransactionEditor',
|
'PhabricatorAuthContactNumberEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
'PhabricatorAuthContactNumberNumberTransaction' => 'PhabricatorAuthContactNumberTransactionType',
|
'PhabricatorAuthContactNumberNumberTransaction' => 'PhabricatorAuthContactNumberTransactionType',
|
||||||
'PhabricatorAuthContactNumberPHIDType' => 'PhabricatorPHIDType',
|
'PhabricatorAuthContactNumberPHIDType' => 'PhabricatorPHIDType',
|
||||||
|
'PhabricatorAuthContactNumberPrimaryController' => 'PhabricatorAuthContactNumberController',
|
||||||
|
'PhabricatorAuthContactNumberPrimaryTransaction' => 'PhabricatorAuthContactNumberTransactionType',
|
||||||
'PhabricatorAuthContactNumberQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorAuthContactNumberQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorAuthContactNumberStatusTransaction' => 'PhabricatorAuthContactNumberTransactionType',
|
'PhabricatorAuthContactNumberStatusTransaction' => 'PhabricatorAuthContactNumberTransactionType',
|
||||||
'PhabricatorAuthContactNumberTransaction' => 'PhabricatorModularTransaction',
|
'PhabricatorAuthContactNumberTransaction' => 'PhabricatorModularTransaction',
|
||||||
|
|
|
@ -113,6 +113,8 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
|
||||||
'PhabricatorAuthContactNumberViewController',
|
'PhabricatorAuthContactNumberViewController',
|
||||||
'(?P<action>disable|enable)/(?P<id>[1-9]\d*)/' =>
|
'(?P<action>disable|enable)/(?P<id>[1-9]\d*)/' =>
|
||||||
'PhabricatorAuthContactNumberDisableController',
|
'PhabricatorAuthContactNumberDisableController',
|
||||||
|
'primary/(?P<id>[1-9]\d*)/' =>
|
||||||
|
'PhabricatorAuthContactNumberPrimaryController',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthContactNumberPrimaryController
|
||||||
|
extends PhabricatorAuthContactNumberController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getViewer();
|
||||||
|
$id = $request->getURIData('id');
|
||||||
|
|
||||||
|
$number = id(new PhabricatorAuthContactNumberQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($id))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$number) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $number->getID();
|
||||||
|
$cancel_uri = $number->getURI();
|
||||||
|
|
||||||
|
if ($number->isDisabled()) {
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Number Disabled'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'You can not make a disabled number your primary contact number.'))
|
||||||
|
->addCancelButton($cancel_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($number->getIsPrimary()) {
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Number Already Primary'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'This contact number is already your primary contact number.'))
|
||||||
|
->addCancelButton($cancel_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(new PhabricatorAuthContactNumberTransaction())
|
||||||
|
->setTransactionType(
|
||||||
|
PhabricatorAuthContactNumberPrimaryTransaction::TRANSACTIONTYPE)
|
||||||
|
->setNewValue(true);
|
||||||
|
|
||||||
|
$editor = id(new PhabricatorAuthContactNumberEditor())
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContentSourceFromRequest($request)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->setContinueOnMissingFields(true);
|
||||||
|
|
||||||
|
$editor->applyTransactions($number, $xactions);
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$number_display = phutil_tag(
|
||||||
|
'strong',
|
||||||
|
array(),
|
||||||
|
$number->getDisplayName());
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setTitle(pht('Set Primary Contact Number'))
|
||||||
|
->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'Designate %s as your primary contact number?',
|
||||||
|
$number_display))
|
||||||
|
->addSubmitButton(pht('Make Primary'))
|
||||||
|
->addCancelButton($cancel_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -56,6 +56,8 @@ final class PhabricatorAuthContactNumberViewController
|
||||||
|
|
||||||
if ($number->isDisabled()) {
|
if ($number->isDisabled()) {
|
||||||
$view->setStatus('fa-ban', 'red', pht('Disabled'));
|
$view->setStatus('fa-ban', 'red', pht('Disabled'));
|
||||||
|
} else if ($number->getIsPrimary()) {
|
||||||
|
$view->setStatus('fa-certificate', 'blue', pht('Primary'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
|
@ -96,17 +98,26 @@ final class PhabricatorAuthContactNumberViewController
|
||||||
->setDisabled(!$can_edit)
|
->setDisabled(!$can_edit)
|
||||||
->setWorkflow(!$can_edit));
|
->setWorkflow(!$can_edit));
|
||||||
|
|
||||||
|
|
||||||
if ($number->isDisabled()) {
|
if ($number->isDisabled()) {
|
||||||
$disable_uri = $this->getApplicationURI("contact/enable/{$id}/");
|
$disable_uri = $this->getApplicationURI("contact/enable/{$id}/");
|
||||||
$disable_name = pht('Enable Contact Number');
|
$disable_name = pht('Enable Contact Number');
|
||||||
$disable_icon = 'fa-check';
|
$disable_icon = 'fa-check';
|
||||||
|
$can_primary = false;
|
||||||
} else {
|
} else {
|
||||||
$disable_uri = $this->getApplicationURI("contact/disable/{$id}/");
|
$disable_uri = $this->getApplicationURI("contact/disable/{$id}/");
|
||||||
$disable_name = pht('Disable Contact Number');
|
$disable_name = pht('Disable Contact Number');
|
||||||
$disable_icon = 'fa-ban';
|
$disable_icon = 'fa-ban';
|
||||||
|
$can_primary = !$number->getIsPrimary();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$curtain->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setName(pht('Make Primary Number'))
|
||||||
|
->setIcon('fa-certificate')
|
||||||
|
->setHref($this->getApplicationURI("contact/primary/{$id}/"))
|
||||||
|
->setDisabled(!$can_primary)
|
||||||
|
->setWorkflow(true));
|
||||||
|
|
||||||
$curtain->addAction(
|
$curtain->addAction(
|
||||||
id(new PhabricatorActionView())
|
id(new PhabricatorActionView())
|
||||||
->setName($disable_name)
|
->setName($disable_name)
|
||||||
|
|
|
@ -12,6 +12,7 @@ final class PhabricatorAuthContactNumber
|
||||||
protected $contactNumber;
|
protected $contactNumber;
|
||||||
protected $uniqueKey;
|
protected $uniqueKey;
|
||||||
protected $status;
|
protected $status;
|
||||||
|
protected $isPrimary;
|
||||||
protected $properties = array();
|
protected $properties = array();
|
||||||
|
|
||||||
const STATUS_ACTIVE = 'active';
|
const STATUS_ACTIVE = 'active';
|
||||||
|
@ -27,6 +28,7 @@ final class PhabricatorAuthContactNumber
|
||||||
'contactNumber' => 'text255',
|
'contactNumber' => 'text255',
|
||||||
'status' => 'text32',
|
'status' => 'text32',
|
||||||
'uniqueKey' => 'bytes12?',
|
'uniqueKey' => 'bytes12?',
|
||||||
|
'isPrimary' => 'bool',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
'key_object' => array(
|
'key_object' => array(
|
||||||
|
@ -43,7 +45,8 @@ final class PhabricatorAuthContactNumber
|
||||||
public static function initializeNewContactNumber($object) {
|
public static function initializeNewContactNumber($object) {
|
||||||
return id(new self())
|
return id(new self())
|
||||||
->setStatus(self::STATUS_ACTIVE)
|
->setStatus(self::STATUS_ACTIVE)
|
||||||
->setObjectPHID($object->getPHID());
|
->setObjectPHID($object->getPHID())
|
||||||
|
->setIsPrimary(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPHIDType() {
|
public function getPHIDType() {
|
||||||
|
@ -73,8 +76,14 @@ final class PhabricatorAuthContactNumber
|
||||||
->setTooltip(pht('Disabled'));
|
->setTooltip(pht('Disabled'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->getIsPrimary()) {
|
||||||
|
return id(new PHUIIconView())
|
||||||
|
->setIcon('fa-certificate', 'blue')
|
||||||
|
->setTooltip(pht('Primary Number'));
|
||||||
|
}
|
||||||
|
|
||||||
return id(new PHUIIconView())
|
return id(new PHUIIconView())
|
||||||
->setIcon('fa-mobile', 'green')
|
->setIcon('fa-hashtag', 'bluegrey')
|
||||||
->setTooltip(pht('Active Phone Number'));
|
->setTooltip(pht('Active Phone Number'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +110,61 @@ final class PhabricatorAuthContactNumber
|
||||||
$this->uniqueKey = $this->newUniqueKey();
|
$this->uniqueKey = $this->newUniqueKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::save();
|
parent::save();
|
||||||
|
|
||||||
|
return $this->updatePrimaryContactNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updatePrimaryContactNumber() {
|
||||||
|
// Update the "isPrimary" column so that at most one number is primary for
|
||||||
|
// each user, and no disabled number is primary.
|
||||||
|
|
||||||
|
$conn = $this->establishConnection('w');
|
||||||
|
$this_id = (int)$this->getID();
|
||||||
|
|
||||||
|
if ($this->getIsPrimary() && !$this->isDisabled()) {
|
||||||
|
// If we're trying to make this number primary and it's active, great:
|
||||||
|
// make this number the primary number.
|
||||||
|
$primary_id = $this_id;
|
||||||
|
} else {
|
||||||
|
// If we aren't trying to make this number primary or it is disabled,
|
||||||
|
// pick another number to make primary if we can. A number must be active
|
||||||
|
// to become primary.
|
||||||
|
|
||||||
|
// If there are multiple active numbers, pick the oldest one currently
|
||||||
|
// marked primary (usually, this should mean that we just keep the
|
||||||
|
// current primary number as primary).
|
||||||
|
|
||||||
|
// If none are marked primary, just pick the oldest one.
|
||||||
|
$primary_row = queryfx_one(
|
||||||
|
$conn,
|
||||||
|
'SELECT id FROM %R
|
||||||
|
WHERE objectPHID = %s AND status = %s
|
||||||
|
ORDER BY isPrimary DESC, id ASC
|
||||||
|
LIMIT 1',
|
||||||
|
$this,
|
||||||
|
$this->getObjectPHID(),
|
||||||
|
self::STATUS_ACTIVE);
|
||||||
|
if ($primary_row) {
|
||||||
|
$primary_id = (int)$primary_row['id'];
|
||||||
|
} else {
|
||||||
|
$primary_id = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the chosen number to primary, and all other numbers to nonprimary.
|
||||||
|
|
||||||
|
queryfx(
|
||||||
|
$conn,
|
||||||
|
'UPDATE %R SET isPrimary = IF(id = %d, 1, 0)
|
||||||
|
WHERE objectPHID = %s',
|
||||||
|
$this,
|
||||||
|
$primary_id,
|
||||||
|
$this->getObjectPHID());
|
||||||
|
|
||||||
|
$this->setIsPrimary((int)($primary_id === $this_id));
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getStatusNameMap() {
|
public static function getStatusNameMap() {
|
||||||
|
@ -119,6 +182,15 @@ final class PhabricatorAuthContactNumber
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSortVector() {
|
||||||
|
// Sort the primary number first, then active numbers, then disabled
|
||||||
|
// numbers. In each group, sort from oldest to newest.
|
||||||
|
return id(new PhutilSortVector())
|
||||||
|
->addInt($this->getIsPrimary() ? 0 : 1)
|
||||||
|
->addInt($this->isDisabled() ? 1 : 0)
|
||||||
|
->addInt($this->getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorAuthContactNumberPrimaryTransaction
|
||||||
|
extends PhabricatorAuthContactNumberTransactionType {
|
||||||
|
|
||||||
|
const TRANSACTIONTYPE = 'primary';
|
||||||
|
|
||||||
|
public function generateOldValue($object) {
|
||||||
|
return (bool)$object->getIsPrimary();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyInternalEffects($object, $value) {
|
||||||
|
$object->setIsPrimary((int)$value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle() {
|
||||||
|
return pht(
|
||||||
|
'%s made this the primary contact number.',
|
||||||
|
$this->renderAuthor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateTransactions($object, array $xactions) {
|
||||||
|
$errors = array();
|
||||||
|
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$new_value = $xaction->getNewValue();
|
||||||
|
|
||||||
|
if (!$new_value) {
|
||||||
|
$errors[] = $this->newInvalidError(
|
||||||
|
pht(
|
||||||
|
'To choose a different primary contact number, make that '.
|
||||||
|
'number primary (instead of trying to demote this one).'),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($object->isDisabled()) {
|
||||||
|
$errors[] = $this->newInvalidError(
|
||||||
|
pht(
|
||||||
|
'You can not make a disabled number a primary contact number.'),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ final class PhabricatorContactNumbersSettingsPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPanelMenuIcon() {
|
public function getPanelMenuIcon() {
|
||||||
return 'fa-mobile';
|
return 'fa-hashtag';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPanelGroupKey() {
|
public function getPanelGroupKey() {
|
||||||
|
@ -31,9 +31,19 @@ final class PhabricatorContactNumbersSettingsPanel
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withObjectPHIDs(array($user->getPHID()))
|
->withObjectPHIDs(array($user->getPHID()))
|
||||||
->execute();
|
->execute();
|
||||||
|
$numbers = msortv($numbers, 'getSortVector');
|
||||||
|
|
||||||
$rows = array();
|
$rows = array();
|
||||||
|
$row_classes = array();
|
||||||
foreach ($numbers as $number) {
|
foreach ($numbers as $number) {
|
||||||
|
if ($number->getIsPrimary()) {
|
||||||
|
$primary_display = pht('Primary');
|
||||||
|
$row_classes[] = 'highlighted';
|
||||||
|
} else {
|
||||||
|
$primary_display = null;
|
||||||
|
$row_classes[] = null;
|
||||||
|
}
|
||||||
|
|
||||||
$rows[] = array(
|
$rows[] = array(
|
||||||
$number->newIconView(),
|
$number->newIconView(),
|
||||||
phutil_tag(
|
phutil_tag(
|
||||||
|
@ -42,6 +52,7 @@ final class PhabricatorContactNumbersSettingsPanel
|
||||||
'href' => $number->getURI(),
|
'href' => $number->getURI(),
|
||||||
),
|
),
|
||||||
$number->getDisplayName()),
|
$number->getDisplayName()),
|
||||||
|
$primary_display,
|
||||||
phabricator_datetime($number->getDateCreated(), $viewer),
|
phabricator_datetime($number->getDateCreated(), $viewer),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -49,16 +60,19 @@ final class PhabricatorContactNumbersSettingsPanel
|
||||||
$table = id(new AphrontTableView($rows))
|
$table = id(new AphrontTableView($rows))
|
||||||
->setNoDataString(
|
->setNoDataString(
|
||||||
pht("You haven't added any contact numbers to your account."))
|
pht("You haven't added any contact numbers to your account."))
|
||||||
|
->setRowClasses($row_classes)
|
||||||
->setHeaders(
|
->setHeaders(
|
||||||
array(
|
array(
|
||||||
null,
|
null,
|
||||||
pht('Number'),
|
pht('Number'),
|
||||||
|
pht('Status'),
|
||||||
pht('Created'),
|
pht('Created'),
|
||||||
))
|
))
|
||||||
->setColumnClasses(
|
->setColumnClasses(
|
||||||
array(
|
array(
|
||||||
null,
|
null,
|
||||||
'wide pri',
|
'wide pri',
|
||||||
|
null,
|
||||||
'right',
|
'right',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue