From 247cb94d5ae03049dbbadcbf468855629f97eba3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 17 Oct 2014 05:02:14 -0700 Subject: [PATCH] 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 --- .../autopatches/20141016.almanac.device.sql | 22 +++ .../autopatches/20141016.almanac.dxaction.sql | 19 +++ src/__phutil_library_map__.php | 24 ++- .../PhabricatorAlmanacApplication.php | 8 + .../AlmanacCreateDevicesCapability.php | 16 ++ .../controller/AlmanacConsoleController.php | 8 + .../controller/AlmanacDeviceController.php | 14 ++ .../AlmanacDeviceEditController.php | 142 +++++++++++++++++ .../AlmanacDeviceListController.php | 52 +++++++ .../AlmanacDeviceViewController.php | 95 ++++++++++++ .../almanac/editor/AlmanacDeviceEditor.php | 144 ++++++++++++++++++ .../almanac/editor/AlmanacServiceEditor.php | 4 +- .../almanac/query/AlmanacDeviceQuery.php | 17 +++ .../query/AlmanacDeviceSearchEngine.php | 79 ++++++++++ .../query/AlmanacDeviceTransactionQuery.php | 10 ++ .../almanac/storage/AlmanacDevice.php | 46 +++++- .../storage/AlmanacDeviceTransaction.php | 45 ++++++ .../almanac/storage/AlmanacService.php | 50 +----- .../almanac/util/AlmanacNames.php | 56 +++++++ .../__tests__/AlmanacNamesTestCase.php} | 6 +- 20 files changed, 798 insertions(+), 59 deletions(-) create mode 100644 resources/sql/autopatches/20141016.almanac.device.sql create mode 100644 resources/sql/autopatches/20141016.almanac.dxaction.sql create mode 100644 src/applications/almanac/capability/AlmanacCreateDevicesCapability.php create mode 100644 src/applications/almanac/controller/AlmanacDeviceController.php create mode 100644 src/applications/almanac/controller/AlmanacDeviceEditController.php create mode 100644 src/applications/almanac/controller/AlmanacDeviceListController.php create mode 100644 src/applications/almanac/controller/AlmanacDeviceViewController.php create mode 100644 src/applications/almanac/editor/AlmanacDeviceEditor.php create mode 100644 src/applications/almanac/query/AlmanacDeviceSearchEngine.php create mode 100644 src/applications/almanac/query/AlmanacDeviceTransactionQuery.php create mode 100644 src/applications/almanac/storage/AlmanacDeviceTransaction.php create mode 100644 src/applications/almanac/util/AlmanacNames.php rename src/applications/almanac/{storage/__tests__/AlmanacServiceTestCase.php => util/__tests__/AlmanacNamesTestCase.php} (85%) diff --git a/resources/sql/autopatches/20141016.almanac.device.sql b/resources/sql/autopatches/20141016.almanac.device.sql new file mode 100644 index 0000000000..d309524fa0 --- /dev/null +++ b/resources/sql/autopatches/20141016.almanac.device.sql @@ -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; diff --git a/resources/sql/autopatches/20141016.almanac.dxaction.sql b/resources/sql/autopatches/20141016.almanac.dxaction.sql new file mode 100644 index 0000000000..5600ec6276 --- /dev/null +++ b/resources/sql/autopatches/20141016.almanac.dxaction.sql @@ -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; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 600a0014a1..32d79eac09 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -12,14 +12,25 @@ phutil_register_library_map(array( 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', + 'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php', 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.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', 'AlmanacDeviceProperty' => 'applications/almanac/storage/AlmanacDeviceProperty.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', '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', 'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php', 'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php', @@ -28,7 +39,6 @@ phutil_register_library_map(array( 'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php', 'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php', 'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php', - 'AlmanacServiceTestCase' => 'applications/almanac/storage/__tests__/AlmanacServiceTestCase.php', 'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php', 'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php', 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', @@ -2921,17 +2931,28 @@ phutil_register_library_map(array( 'AlmanacConduitUtil' => 'Phobject', 'AlmanacConsoleController' => 'AlmanacController', 'AlmanacController' => 'PhabricatorController', + 'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacDAO' => 'PhabricatorLiskDAO', 'AlmanacDevice' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', ), + 'AlmanacDeviceController' => 'AlmanacController', + 'AlmanacDeviceEditController' => 'AlmanacDeviceController', + 'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor', + 'AlmanacDeviceListController' => 'AlmanacDeviceController', 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', 'AlmanacDeviceProperty' => 'AlmanacDAO', 'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction', + 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'AlmanacDeviceViewController' => 'AlmanacDeviceController', 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'AlmanacNames' => 'Phobject', + 'AlmanacNamesTestCase' => 'PhabricatorTestCase', 'AlmanacService' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', @@ -2943,7 +2964,6 @@ phutil_register_library_map(array( 'AlmanacServicePHIDType' => 'PhabricatorPHIDType', 'AlmanacServiceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'AlmanacServiceTestCase' => 'PhabricatorTestCase', 'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacServiceViewController' => 'AlmanacServiceController', diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php index 515f008771..35483312f3 100644 --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -39,6 +39,11 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication { 'edit/(?:(?P\d+)/)?' => 'AlmanacServiceEditController', 'view/(?P[^/]+)/' => 'AlmanacServiceViewController', ), + 'device/' => array( + '(?:query/(?P[^/]+)/)?' => 'AlmanacDeviceListController', + 'edit/(?:(?P\d+)/)?' => 'AlmanacDeviceEditController', + 'view/(?P[^/]+)/' => 'AlmanacDeviceViewController', + ), ), ); } @@ -48,6 +53,9 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication { AlmanacCreateServicesCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), + AlmanacCreateDevicesCapability::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), ); } diff --git a/src/applications/almanac/capability/AlmanacCreateDevicesCapability.php b/src/applications/almanac/capability/AlmanacCreateDevicesCapability.php new file mode 100644 index 0000000000..f5f279fc89 --- /dev/null +++ b/src/applications/almanac/capability/AlmanacCreateDevicesCapability.php @@ -0,0 +1,16 @@ +addItem( + id(new PHUIObjectItemView()) + ->setHeader(pht('Devices')) + ->setHref($this->getApplicationURI('device/')) + ->addAttribute( + pht( + 'Manage Almanac devices.'))); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Console')); diff --git a/src/applications/almanac/controller/AlmanacDeviceController.php b/src/applications/almanac/controller/AlmanacDeviceController.php new file mode 100644 index 0000000000..11c8855a5d --- /dev/null +++ b/src/applications/almanac/controller/AlmanacDeviceController.php @@ -0,0 +1,14 @@ +getApplicationURI('device/'); + $crumbs->addTextCrumb(pht('Devices'), $list_uri); + + return $crumbs; + } + +} diff --git a/src/applications/almanac/controller/AlmanacDeviceEditController.php b/src/applications/almanac/controller/AlmanacDeviceEditController.php new file mode 100644 index 0000000000..b8bd075d23 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacDeviceEditController.php @@ -0,0 +1,142 @@ +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, + )); + } + +} diff --git a/src/applications/almanac/controller/AlmanacDeviceListController.php b/src/applications/almanac/controller/AlmanacDeviceListController.php new file mode 100644 index 0000000000..3487676aba --- /dev/null +++ b/src/applications/almanac/controller/AlmanacDeviceListController.php @@ -0,0 +1,52 @@ +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; + } + + +} diff --git a/src/applications/almanac/controller/AlmanacDeviceViewController.php b/src/applications/almanac/controller/AlmanacDeviceViewController.php new file mode 100644 index 0000000000..3b9c581059 --- /dev/null +++ b/src/applications/almanac/controller/AlmanacDeviceViewController.php @@ -0,0 +1,95 @@ +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; + } + +} diff --git a/src/applications/almanac/editor/AlmanacDeviceEditor.php b/src/applications/almanac/editor/AlmanacDeviceEditor.php new file mode 100644 index 0000000000..04c4abf810 --- /dev/null +++ b/src/applications/almanac/editor/AlmanacDeviceEditor.php @@ -0,0 +1,144 @@ +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; + } + + + +} diff --git a/src/applications/almanac/editor/AlmanacServiceEditor.php b/src/applications/almanac/editor/AlmanacServiceEditor.php index dce5a9a178..1a3b273751 100644 --- a/src/applications/almanac/editor/AlmanacServiceEditor.php +++ b/src/applications/almanac/editor/AlmanacServiceEditor.php @@ -100,8 +100,10 @@ final class AlmanacServiceEditor foreach ($xactions as $xaction) { $message = null; + $name = $xaction->getNewValue(); + try { - AlmanacService::validateServiceName($xaction->getNewValue()); + AlmanacNames::validateServiceOrDeviceName($name); } catch (Exception $ex) { $message = $ex->getMessage(); } diff --git a/src/applications/almanac/query/AlmanacDeviceQuery.php b/src/applications/almanac/query/AlmanacDeviceQuery.php index 1b0d630797..76c7a752e7 100644 --- a/src/applications/almanac/query/AlmanacDeviceQuery.php +++ b/src/applications/almanac/query/AlmanacDeviceQuery.php @@ -5,6 +5,7 @@ final class AlmanacDeviceQuery private $ids; private $phids; + private $names; public function withIDs(array $ids) { $this->ids = $ids; @@ -16,6 +17,11 @@ final class AlmanacDeviceQuery return $this; } + public function withNames(array $names) { + $this->names = $names; + return $this; + } + protected function loadPage() { $table = new AlmanacDevice(); $conn_r = $table->establishConnection('r'); @@ -48,6 +54,17 @@ final class AlmanacDeviceQuery $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); return $this->formatWhereClause($where); diff --git a/src/applications/almanac/query/AlmanacDeviceSearchEngine.php b/src/applications/almanac/query/AlmanacDeviceSearchEngine.php new file mode 100644 index 0000000000..a2847aae01 --- /dev/null +++ b/src/applications/almanac/query/AlmanacDeviceSearchEngine.php @@ -0,0 +1,79 @@ + 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; + } +} diff --git a/src/applications/almanac/query/AlmanacDeviceTransactionQuery.php b/src/applications/almanac/query/AlmanacDeviceTransactionQuery.php new file mode 100644 index 0000000000..8b50fa85e5 --- /dev/null +++ b/src/applications/almanac/query/AlmanacDeviceTransactionQuery.php @@ -0,0 +1,10 @@ +setViewPolicy(PhabricatorPolicies::POLICY_USER) + ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN); + } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, 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(); } @@ -19,6 +39,22 @@ final class AlmanacDevice 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 )----------------------------------------- */ @@ -26,16 +62,16 @@ final class AlmanacDevice public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: - // Until we get a clearer idea on what's going to be stored in this - // table, don't allow anyone (other than the omnipotent user) to find - // these objects. - return PhabricatorPolicies::POLICY_NOONE; + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); } } diff --git a/src/applications/almanac/storage/AlmanacDeviceTransaction.php b/src/applications/almanac/storage/AlmanacDeviceTransaction.php new file mode 100644 index 0000000000..0068ed6548 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacDeviceTransaction.php @@ -0,0 +1,45 @@ +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(); + } + +} diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacService.php index 2cc6057f73..4ea48a5729 100644 --- a/src/applications/almanac/storage/AlmanacService.php +++ b/src/applications/almanac/storage/AlmanacService.php @@ -41,7 +41,8 @@ final class AlmanacService } public function save() { - self::validateServiceName($this->getName()); + AlmanacNames::validateServiceOrDeviceName($this->getName()); + $this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); if (!$this->mailKey) { @@ -55,53 +56,6 @@ final class AlmanacService 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 )----------------------------------------- */ diff --git a/src/applications/almanac/util/AlmanacNames.php b/src/applications/almanac/util/AlmanacNames.php new file mode 100644 index 0000000000..d65779264d --- /dev/null +++ b/src/applications/almanac/util/AlmanacNames.php @@ -0,0 +1,56 @@ + false, 'a' => false, @@ -38,7 +38,7 @@ final class AlmanacServiceTestCase extends PhabricatorTestCase { foreach ($map as $input => $expect) { $caught = null; try { - AlmanacService::validateServiceName($input); + AlmanacNames::validateServiceOrDeviceName($input); } catch (Exception $ex) { $caught = $ex; }