1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-30 17:30:59 +01:00

Build AlmanacDevice UI

Summary: Ref T5833. The "uninteresting" part of this object is virtually identical to AlmanacService.

Test Plan: See screenshots.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T5833

Differential Revision: https://secure.phabricator.com/D10714
This commit is contained in:
epriestley 2014-10-17 05:02:14 -07:00
parent 796921021b
commit 247cb94d5a
20 changed files with 798 additions and 59 deletions

View file

@ -0,0 +1,22 @@
TRUNCATE TABLE {$NAMESPACE}_almanac.almanac_device;
ALTER TABLE {$NAMESPACE}_almanac.almanac_device
CHANGE name name VARCHAR(128) NOT NULL COLLATE utf8_bin;
ALTER TABLE {$NAMESPACE}_almanac.almanac_device
ADD nameIndex BINARY(12) NOT NULL;
ALTER TABLE {$NAMESPACE}_almanac.almanac_device
ADD mailKey BINARY(20) NOT NULL;
ALTER TABLE {$NAMESPACE}_almanac.almanac_device
ADD UNIQUE KEY `key_name` (nameIndex);
ALTER TABLE {$NAMESPACE}_almanac.almanac_device
ADD KEY `key_nametext` (name);
ALTER TABLE {$NAMESPACE}_almanac.almanac_device
ADD viewPolicy VARBINARY(64) NOT NULL;
ALTER TABLE {$NAMESPACE}_almanac.almanac_device
ADD editPolicy VARBINARY(64) NOT NULL;

View file

@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_almanac.almanac_devicetransaction (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) COLLATE utf8_bin NOT NULL,
authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL,
objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL,
viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL,
editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL,
commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL,
oldValue LONGTEXT COLLATE utf8_bin NOT NULL,
newValue LONGTEXT COLLATE utf8_bin NOT NULL,
contentSource LONGTEXT COLLATE utf8_bin NOT NULL,
metadata LONGTEXT COLLATE utf8_bin NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB, COLLATE utf8_general_ci;

View file

@ -12,14 +12,25 @@ phutil_register_library_map(array(
'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php',
'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php',
'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php',
'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php',
'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php',
'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php',
'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php',
'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php',
'AlmanacDeviceEditController' => 'applications/almanac/controller/AlmanacDeviceEditController.php',
'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php',
'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php',
'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php', 'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php',
'AlmanacDeviceProperty' => 'applications/almanac/storage/AlmanacDeviceProperty.php', 'AlmanacDeviceProperty' => 'applications/almanac/storage/AlmanacDeviceProperty.php',
'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php',
'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php',
'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php',
'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php',
'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php',
'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php',
'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php',
'AlmanacNames' => 'applications/almanac/util/AlmanacNames.php',
'AlmanacNamesTestCase' => 'applications/almanac/util/__tests__/AlmanacNamesTestCase.php',
'AlmanacService' => 'applications/almanac/storage/AlmanacService.php', 'AlmanacService' => 'applications/almanac/storage/AlmanacService.php',
'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php', 'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php',
'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php', 'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php',
@ -28,7 +39,6 @@ phutil_register_library_map(array(
'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php', 'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php',
'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php', 'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php',
'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php', 'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php',
'AlmanacServiceTestCase' => 'applications/almanac/storage/__tests__/AlmanacServiceTestCase.php',
'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php', 'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php',
'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php', 'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php',
'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php',
@ -2921,17 +2931,28 @@ phutil_register_library_map(array(
'AlmanacConduitUtil' => 'Phobject', 'AlmanacConduitUtil' => 'Phobject',
'AlmanacConsoleController' => 'AlmanacController', 'AlmanacConsoleController' => 'AlmanacController',
'AlmanacController' => 'PhabricatorController', 'AlmanacController' => 'PhabricatorController',
'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability',
'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability',
'AlmanacDAO' => 'PhabricatorLiskDAO', 'AlmanacDAO' => 'PhabricatorLiskDAO',
'AlmanacDevice' => array( 'AlmanacDevice' => array(
'AlmanacDAO', 'AlmanacDAO',
'PhabricatorPolicyInterface', 'PhabricatorPolicyInterface',
), ),
'AlmanacDeviceController' => 'AlmanacController',
'AlmanacDeviceEditController' => 'AlmanacDeviceController',
'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor',
'AlmanacDeviceListController' => 'AlmanacDeviceController',
'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType',
'AlmanacDeviceProperty' => 'AlmanacDAO', 'AlmanacDeviceProperty' => 'AlmanacDAO',
'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction',
'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacDeviceViewController' => 'AlmanacDeviceController',
'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow',
'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow',
'AlmanacNames' => 'Phobject',
'AlmanacNamesTestCase' => 'PhabricatorTestCase',
'AlmanacService' => array( 'AlmanacService' => array(
'AlmanacDAO', 'AlmanacDAO',
'PhabricatorPolicyInterface', 'PhabricatorPolicyInterface',
@ -2943,7 +2964,6 @@ phutil_register_library_map(array(
'AlmanacServicePHIDType' => 'PhabricatorPHIDType', 'AlmanacServicePHIDType' => 'PhabricatorPHIDType',
'AlmanacServiceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'AlmanacServiceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacServiceTestCase' => 'PhabricatorTestCase',
'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction',
'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacServiceViewController' => 'AlmanacServiceController', 'AlmanacServiceViewController' => 'AlmanacServiceController',

View file

@ -39,6 +39,11 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacServiceEditController', 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacServiceEditController',
'view/(?P<name>[^/]+)/' => 'AlmanacServiceViewController', 'view/(?P<name>[^/]+)/' => 'AlmanacServiceViewController',
), ),
'device/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'AlmanacDeviceListController',
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacDeviceEditController',
'view/(?P<name>[^/]+)/' => 'AlmanacDeviceViewController',
),
), ),
); );
} }
@ -48,6 +53,9 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
AlmanacCreateServicesCapability::CAPABILITY => array( AlmanacCreateServicesCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN, 'default' => PhabricatorPolicies::POLICY_ADMIN,
), ),
AlmanacCreateDevicesCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
); );
} }

View file

@ -0,0 +1,16 @@
<?php
final class AlmanacCreateDevicesCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'almanac.devices';
public function getCapabilityName() {
return pht('Can Create Devices');
}
public function describeCapabilityRejection() {
return pht('You do not have permission to create Almanac devices.');
}
}

View file

@ -20,6 +20,14 @@ final class AlmanacConsoleController extends AlmanacController {
pht( pht(
'Manage Almanac services.'))); 'Manage Almanac services.')));
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Devices'))
->setHref($this->getApplicationURI('device/'))
->addAttribute(
pht(
'Manage Almanac devices.')));
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Console')); $crumbs->addTextCrumb(pht('Console'));

View file

@ -0,0 +1,14 @@
<?php
abstract class AlmanacDeviceController extends AlmanacController {
public function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$list_uri = $this->getApplicationURI('device/');
$crumbs->addTextCrumb(pht('Devices'), $list_uri);
return $crumbs;
}
}

View file

@ -0,0 +1,142 @@
<?php
final class AlmanacDeviceEditController
extends AlmanacDeviceController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$list_uri = $this->getApplicationURI('device/');
$id = $request->getURIData('id');
if ($id) {
$device = id(new AlmanacDeviceQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$device) {
return new Aphront404Response();
}
$is_new = false;
$device_uri = $device->getURI();
$cancel_uri = $device_uri;
$title = pht('Edit Device');
$save_button = pht('Save Changes');
} else {
$this->requireApplicationCapability(
AlmanacCreateDevicesCapability::CAPABILITY);
$device = AlmanacDevice::initializeNewDevice();
$is_new = true;
$cancel_uri = $list_uri;
$title = pht('Create Device');
$save_button = pht('Create Device');
}
$v_name = $device->getName();
$e_name = true;
$validation_exception = null;
if ($request->isFormPost()) {
$v_name = $request->getStr('name');
$v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy');
$type_name = AlmanacDeviceTransaction::TYPE_NAME;
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
$xactions = array();
$xactions[] = id(new AlmanacDeviceTransaction())
->setTransactionType($type_name)
->setNewValue($v_name);
$xactions[] = id(new AlmanacDeviceTransaction())
->setTransactionType($type_view)
->setNewValue($v_view);
$xactions[] = id(new AlmanacDeviceTransaction())
->setTransactionType($type_edit)
->setNewValue($v_edit);
$editor = id(new AlmanacDeviceEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$editor->applyTransactions($device, $xactions);
$device_uri = $device->getURI();
return id(new AphrontRedirectResponse())->setURI($device_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
$e_name = $ex->getShortMessage($type_name);
$device->setViewPolicy($v_view);
$device->setEditPolicy($v_edit);
}
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($device)
->execute();
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($v_name)
->setError($e_name))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('viewPolicy')
->setPolicyObject($device)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicies($policies))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('editPolicy')
->setPolicyObject($device)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicies($policies))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($save_button));
$box = id(new PHUIObjectBoxView())
->setValidationException($validation_exception)
->setHeaderText($title)
->appendChild($form);
$crumbs = $this->buildApplicationCrumbs();
if ($is_new) {
$crumbs->addTextCrumb(pht('Create Device'));
} else {
$crumbs->addTextCrumb($device->getName(), $device_uri);
$crumbs->addTextCrumb(pht('Edit'));
}
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
));
}
}

View file

@ -0,0 +1,52 @@
<?php
final class AlmanacDeviceListController
extends AlmanacDeviceController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($request->getURIData('queryKey'))
->setSearchEngine(new AlmanacDeviceSearchEngine())
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
}
public function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$can_create = $this->hasApplicationCapability(
AlmanacCreateDevicesCapability::CAPABILITY);
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Device'))
->setHref($this->getApplicationURI('device/edit/'))
->setIcon('fa-plus-square')
->setDisabled(!$can_create)
->setWorkflow(!$can_create));
return $crumbs;
}
public function buildSideNavView() {
$viewer = $this->getViewer();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new AlmanacDeviceSearchEngine())
->setViewer($viewer)
->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
}
}

View file

@ -0,0 +1,95 @@
<?php
final class AlmanacDeviceViewController
extends AlmanacDeviceController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$name = $request->getURIData('name');
$device = id(new AlmanacDeviceQuery())
->setViewer($viewer)
->withNames(array($name))
->executeOne();
if (!$device) {
return new Aphront404Response();
}
$title = pht('Device %s', $device->getName());
$property_list = $this->buildPropertyList($device);
$action_list = $this->buildActionList($device);
$property_list->setActionList($action_list);
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($device->getName())
->setPolicyObject($device);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($property_list);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($device->getName());
$xactions = id(new AlmanacDeviceTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($device->getPHID()))
->execute();
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setObjectPHID($device->getPHID())
->setTransactions($xactions)
->setShouldTerminate(true);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
$xaction_view,
),
array(
'title' => $title,
));
}
private function buildPropertyList(AlmanacDevice $device) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
return $properties;
}
private function buildActionList(AlmanacDevice $device) {
$viewer = $this->getViewer();
$id = $device->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$device,
PhabricatorPolicyCapability::CAN_EDIT);
$actions = id(new PhabricatorActionListView())
->setUser($viewer);
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Device'))
->setHref($this->getApplicationURI("device/edit/{$id}/"))
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit));
return $actions;
}
}

View file

@ -0,0 +1,144 @@
<?php
final class AlmanacDeviceEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
public function getEditorObjectsDescription() {
return pht('Almanac Device');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = AlmanacDeviceTransaction::TYPE_NAME;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case AlmanacDeviceTransaction::TYPE_NAME:
return $object->getName();
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case AlmanacDeviceTransaction::TYPE_NAME:
return $xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case AlmanacDeviceTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case AlmanacDeviceTransaction::TYPE_NAME:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case AlmanacDeviceTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('Device name is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
} else {
foreach ($xactions as $xaction) {
$message = null;
$name = $xaction->getNewValue();
try {
AlmanacNames::validateServiceOrDeviceName($name);
} catch (Exception $ex) {
$message = $ex->getMessage();
}
if ($message !== null) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
$message,
$xaction);
$errors[] = $error;
}
}
}
if ($xactions) {
$duplicate = id(new AlmanacDeviceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withNames(array(last($xactions)->getNewValue()))
->executeOne();
if ($duplicate && ($duplicate->getID() != $object->getID())) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Not Unique'),
pht('Almanac devices must have unique names.'),
last($xactions));
$errors[] = $error;
}
}
break;
}
return $errors;
}
}

View file

@ -100,8 +100,10 @@ final class AlmanacServiceEditor
foreach ($xactions as $xaction) { foreach ($xactions as $xaction) {
$message = null; $message = null;
$name = $xaction->getNewValue();
try { try {
AlmanacService::validateServiceName($xaction->getNewValue()); AlmanacNames::validateServiceOrDeviceName($name);
} catch (Exception $ex) { } catch (Exception $ex) {
$message = $ex->getMessage(); $message = $ex->getMessage();
} }

View file

@ -5,6 +5,7 @@ final class AlmanacDeviceQuery
private $ids; private $ids;
private $phids; private $phids;
private $names;
public function withIDs(array $ids) { public function withIDs(array $ids) {
$this->ids = $ids; $this->ids = $ids;
@ -16,6 +17,11 @@ final class AlmanacDeviceQuery
return $this; return $this;
} }
public function withNames(array $names) {
$this->names = $names;
return $this;
}
protected function loadPage() { protected function loadPage() {
$table = new AlmanacDevice(); $table = new AlmanacDevice();
$conn_r = $table->establishConnection('r'); $conn_r = $table->establishConnection('r');
@ -48,6 +54,17 @@ final class AlmanacDeviceQuery
$this->phids); $this->phids);
} }
if ($this->names !== null) {
$hashes = array();
foreach ($this->names as $name) {
$hashes[] = PhabricatorHash::digestForIndex($name);
}
$where[] = qsprintf(
$conn_r,
'nameIndex IN (%Ls)',
$hashes);
}
$where[] = $this->buildPagingClause($conn_r); $where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where); return $this->formatWhereClause($where);

View file

@ -0,0 +1,79 @@
<?php
final class AlmanacDeviceSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Almanac Devices');
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
return $saved;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new AlmanacDeviceQuery());
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved_query) {}
protected function getURI($path) {
return '/almanac/device/'.$path;
}
public function getBuiltinQueryNames() {
$names = array(
'all' => pht('All Devices'),
);
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'all':
return $query;
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function getRequiredHandlePHIDsForResultList(
array $devices,
PhabricatorSavedQuery $query) {
return array();
}
protected function renderResultList(
array $devices,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($devices, 'AlmanacDevice');
$viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
$list->setUser($viewer);
foreach ($devices as $device) {
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Device %d', $device->getID()))
->setHeader($device->getName())
->setHref($device->getURI())
->setObject($device);
$list->addItem($item);
}
return $list;
}
}

View file

@ -0,0 +1,10 @@
<?php
final class AlmanacDeviceTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new AlmanacDeviceTransaction();
}
}

View file

@ -5,12 +5,32 @@ final class AlmanacDevice
implements PhabricatorPolicyInterface { implements PhabricatorPolicyInterface {
protected $name; protected $name;
protected $nameIndex;
protected $mailKey;
protected $viewPolicy;
protected $editPolicy;
public static function initializeNewDevice() {
return id(new AlmanacDevice())
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN);
}
public function getConfiguration() { public function getConfiguration() {
return array( return array(
self::CONFIG_AUX_PHID => true, self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255', 'name' => 'text128',
'nameIndex' => 'bytes12',
),
self::CONFIG_KEY_SCHEMA => array(
'key_name' => array(
'columns' => array('nameIndex'),
'unique' => true,
),
'key_nametext' => array(
'columns' => array('name'),
),
), ),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
@ -19,6 +39,22 @@ final class AlmanacDevice
return PhabricatorPHID::generateNewPHID(AlmanacDevicePHIDType::TYPECONST); return PhabricatorPHID::generateNewPHID(AlmanacDevicePHIDType::TYPECONST);
} }
public function save() {
AlmanacNames::validateServiceOrDeviceName($this->getName());
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
return parent::save();
}
public function getURI() {
return '/almanac/device/view/'.$this->getName().'/';
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */ /* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -26,16 +62,16 @@ final class AlmanacDevice
public function getCapabilities() { public function getCapabilities() {
return array( return array(
PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
); );
} }
public function getPolicy($capability) { public function getPolicy($capability) {
switch ($capability) { switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW: case PhabricatorPolicyCapability::CAN_VIEW:
// Until we get a clearer idea on what's going to be stored in this return $this->getViewPolicy();
// table, don't allow anyone (other than the omnipotent user) to find case PhabricatorPolicyCapability::CAN_EDIT:
// these objects. return $this->getEditPolicy();
return PhabricatorPolicies::POLICY_NOONE;
} }
} }

View file

@ -0,0 +1,45 @@
<?php
final class AlmanacDeviceTransaction
extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'almanac:device:name';
public function getApplicationName() {
return 'almanac';
}
public function getApplicationTransactionType() {
return AlmanacDevicePHIDType::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return null;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created this device.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s renamed this device from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
break;
}
return parent::getTitle();
}
}

View file

@ -41,7 +41,8 @@ final class AlmanacService
} }
public function save() { public function save() {
self::validateServiceName($this->getName()); AlmanacNames::validateServiceOrDeviceName($this->getName());
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); $this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
if (!$this->mailKey) { if (!$this->mailKey) {
@ -55,53 +56,6 @@ final class AlmanacService
return '/almanac/service/view/'.$this->getName().'/'; return '/almanac/service/view/'.$this->getName().'/';
} }
public static function validateServiceName($name) {
if (strlen($name) < 3) {
throw new Exception(
pht('Almanac service names must be at least 3 characters long.'));
}
if (!preg_match('/^[a-z0-9.-]+\z/', $name)) {
throw new Exception(
pht(
'Almanac service names may only contain lowercase letters, numbers, '.
'hyphens, and periods.'));
}
if (preg_match('/(^|\\.)\d+(\z|\\.)/', $name)) {
throw new Exception(
pht(
'Almanac service names may not have any segments containing only '.
'digits.'));
}
if (preg_match('/\.\./', $name)) {
throw new Exception(
pht(
'Almanac service names may not contain multiple consecutive '.
'periods.'));
}
if (preg_match('/\\.-|-\\./', $name)) {
throw new Exception(
pht(
'Amanac service names may not contain hyphens adjacent to periods.'));
}
if (preg_match('/--/', $name)) {
throw new Exception(
pht(
'Almanac service names may not contain multiple consecutive '.
'hyphens.'));
}
if (!preg_match('/^[a-z0-9].*[a-z0-9]\z/', $name)) {
throw new Exception(
pht(
'Almanac service names must begin and end with a letter or number.'));
}
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */ /* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -0,0 +1,56 @@
<?php
final class AlmanacNames extends Phobject {
public static function validateServiceOrDeviceName($name) {
if (strlen($name) < 3) {
throw new Exception(
pht(
'Almanac service and device names must be at least 3 '.
'characters long.'));
}
if (!preg_match('/^[a-z0-9.-]+\z/', $name)) {
throw new Exception(
pht(
'Almanac service and device names may only contain lowercase '.
'letters, numbers, hyphens, and periods.'));
}
if (preg_match('/(^|\\.)\d+(\z|\\.)/', $name)) {
throw new Exception(
pht(
'Almanac service and device names may not have any segments '.
'containing only digits.'));
}
if (preg_match('/\.\./', $name)) {
throw new Exception(
pht(
'Almanac service and device names may not contain multiple '.
'consecutive periods.'));
}
if (preg_match('/\\.-|-\\./', $name)) {
throw new Exception(
pht(
'Amanac service and device names may not contain hyphens adjacent '.
'to periods.'));
}
if (preg_match('/--/', $name)) {
throw new Exception(
pht(
'Almanac service and device names may not contain multiple '.
'consecutive hyphens.'));
}
if (!preg_match('/^[a-z0-9].*[a-z0-9]\z/', $name)) {
throw new Exception(
pht(
'Almanac service and device names must begin and end with a letter '.
'or number.'));
}
}
}

View file

@ -1,8 +1,8 @@
<?php <?php
final class AlmanacServiceTestCase extends PhabricatorTestCase { final class AlmanacNamesTestCase extends PhabricatorTestCase {
public function testServiceNames() { public function testServiceOrDeviceNames() {
$map = array( $map = array(
'' => false, '' => false,
'a' => false, 'a' => false,
@ -38,7 +38,7 @@ final class AlmanacServiceTestCase extends PhabricatorTestCase {
foreach ($map as $input => $expect) { foreach ($map as $input => $expect) {
$caught = null; $caught = null;
try { try {
AlmanacService::validateServiceName($input); AlmanacNames::validateServiceOrDeviceName($input);
} catch (Exception $ex) { } catch (Exception $ex) {
$caught = $ex; $caught = $ex;
} }