mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-26 07:20:57 +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',
|
||||
'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',
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
),
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
$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)
|
||||
|
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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() {
|
||||
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',
|
||||
));
|
||||
|
||||
|
|
Loading…
Reference in a new issue