1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-25 16:22:43 +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:
epriestley 2019-01-21 13:02:37 -08:00
parent 12203762b7
commit 596435b35e
8 changed files with 237 additions and 5 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_auth.auth_contactnumber
ADD isPrimary BOOL NOT NULL;

View file

@ -2207,6 +2207,8 @@ phutil_register_library_map(array(
'PhabricatorAuthContactNumberEditor' => 'applications/auth/editor/PhabricatorAuthContactNumberEditor.php',
'PhabricatorAuthContactNumberNumberTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.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',
'PhabricatorAuthContactNumberStatusTransaction' => 'applications/auth/xaction/PhabricatorAuthContactNumberStatusTransaction.php',
'PhabricatorAuthContactNumberTransaction' => 'applications/auth/storage/PhabricatorAuthContactNumberTransaction.php',
@ -7912,6 +7914,8 @@ phutil_register_library_map(array(
'PhabricatorAuthContactNumberEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorAuthContactNumberNumberTransaction' => 'PhabricatorAuthContactNumberTransactionType',
'PhabricatorAuthContactNumberPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthContactNumberPrimaryController' => 'PhabricatorAuthContactNumberController',
'PhabricatorAuthContactNumberPrimaryTransaction' => 'PhabricatorAuthContactNumberTransactionType',
'PhabricatorAuthContactNumberQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthContactNumberStatusTransaction' => 'PhabricatorAuthContactNumberTransactionType',
'PhabricatorAuthContactNumberTransaction' => 'PhabricatorModularTransaction',

View file

@ -113,6 +113,8 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
'PhabricatorAuthContactNumberViewController',
'(?P<action>disable|enable)/(?P<id>[1-9]\d*)/' =>
'PhabricatorAuthContactNumberDisableController',
'primary/(?P<id>[1-9]\d*)/' =>
'PhabricatorAuthContactNumberPrimaryController',
),
),

View file

@ -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);
}
}

View file

@ -56,6 +56,8 @@ final class PhabricatorAuthContactNumberViewController
if ($number->isDisabled()) {
$view->setStatus('fa-ban', 'red', pht('Disabled'));
} else if ($number->getIsPrimary()) {
$view->setStatus('fa-certificate', 'blue', pht('Primary'));
}
return $view;
@ -96,17 +98,26 @@ final class PhabricatorAuthContactNumberViewController
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
if ($number->isDisabled()) {
$disable_uri = $this->getApplicationURI("contact/enable/{$id}/");
$disable_name = pht('Enable Contact Number');
$disable_icon = 'fa-check';
$can_primary = false;
} else {
$disable_uri = $this->getApplicationURI("contact/disable/{$id}/");
$disable_name = pht('Disable Contact Number');
$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(
id(new PhabricatorActionView())
->setName($disable_name)

View file

@ -12,6 +12,7 @@ final class PhabricatorAuthContactNumber
protected $contactNumber;
protected $uniqueKey;
protected $status;
protected $isPrimary;
protected $properties = array();
const STATUS_ACTIVE = 'active';
@ -27,6 +28,7 @@ final class PhabricatorAuthContactNumber
'contactNumber' => 'text255',
'status' => 'text32',
'uniqueKey' => 'bytes12?',
'isPrimary' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_object' => array(
@ -43,7 +45,8 @@ final class PhabricatorAuthContactNumber
public static function initializeNewContactNumber($object) {
return id(new self())
->setStatus(self::STATUS_ACTIVE)
->setObjectPHID($object->getPHID());
->setObjectPHID($object->getPHID())
->setIsPrimary(0);
}
public function getPHIDType() {
@ -73,8 +76,14 @@ final class PhabricatorAuthContactNumber
->setTooltip(pht('Disabled'));
}
if ($this->getIsPrimary()) {
return id(new PHUIIconView())
->setIcon('fa-certificate', 'blue')
->setTooltip(pht('Primary Number'));
}
return id(new PHUIIconView())
->setIcon('fa-mobile', 'green')
->setIcon('fa-hashtag', 'bluegrey')
->setTooltip(pht('Active Phone Number'));
}
@ -101,7 +110,61 @@ final class PhabricatorAuthContactNumber
$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() {
@ -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 )----------------------------------------- */

View file

@ -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;
}
}

View file

@ -12,7 +12,7 @@ final class PhabricatorContactNumbersSettingsPanel
}
public function getPanelMenuIcon() {
return 'fa-mobile';
return 'fa-hashtag';
}
public function getPanelGroupKey() {
@ -31,9 +31,19 @@ final class PhabricatorContactNumbersSettingsPanel
->setViewer($viewer)
->withObjectPHIDs(array($user->getPHID()))
->execute();
$numbers = msortv($numbers, 'getSortVector');
$rows = array();
$row_classes = array();
foreach ($numbers as $number) {
if ($number->getIsPrimary()) {
$primary_display = pht('Primary');
$row_classes[] = 'highlighted';
} else {
$primary_display = null;
$row_classes[] = null;
}
$rows[] = array(
$number->newIconView(),
phutil_tag(
@ -42,6 +52,7 @@ final class PhabricatorContactNumbersSettingsPanel
'href' => $number->getURI(),
),
$number->getDisplayName()),
$primary_display,
phabricator_datetime($number->getDateCreated(), $viewer),
);
}
@ -49,16 +60,19 @@ final class PhabricatorContactNumbersSettingsPanel
$table = id(new AphrontTableView($rows))
->setNoDataString(
pht("You haven't added any contact numbers to your account."))
->setRowClasses($row_classes)
->setHeaders(
array(
null,
pht('Number'),
pht('Status'),
pht('Created'),
))
->setColumnClasses(
array(
null,
'wide pri',
null,
'right',
));