@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

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

+798 -59
+22
resources/sql/autopatches/20141016.almanac.device.sql
··· 1 + TRUNCATE TABLE {$NAMESPACE}_almanac.almanac_device; 2 + 3 + ALTER TABLE {$NAMESPACE}_almanac.almanac_device 4 + CHANGE name name VARCHAR(128) NOT NULL COLLATE utf8_bin; 5 + 6 + ALTER TABLE {$NAMESPACE}_almanac.almanac_device 7 + ADD nameIndex BINARY(12) NOT NULL; 8 + 9 + ALTER TABLE {$NAMESPACE}_almanac.almanac_device 10 + ADD mailKey BINARY(20) NOT NULL; 11 + 12 + ALTER TABLE {$NAMESPACE}_almanac.almanac_device 13 + ADD UNIQUE KEY `key_name` (nameIndex); 14 + 15 + ALTER TABLE {$NAMESPACE}_almanac.almanac_device 16 + ADD KEY `key_nametext` (name); 17 + 18 + ALTER TABLE {$NAMESPACE}_almanac.almanac_device 19 + ADD viewPolicy VARBINARY(64) NOT NULL; 20 + 21 + ALTER TABLE {$NAMESPACE}_almanac.almanac_device 22 + ADD editPolicy VARBINARY(64) NOT NULL;
+19
resources/sql/autopatches/20141016.almanac.dxaction.sql
··· 1 + CREATE TABLE {$NAMESPACE}_almanac.almanac_devicetransaction ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARCHAR(64) COLLATE utf8_bin NOT NULL, 4 + authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, 5 + objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, 6 + viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, 7 + editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, 8 + commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL, 11 + oldValue LONGTEXT COLLATE utf8_bin NOT NULL, 12 + newValue LONGTEXT COLLATE utf8_bin NOT NULL, 13 + contentSource LONGTEXT COLLATE utf8_bin NOT NULL, 14 + metadata LONGTEXT COLLATE utf8_bin NOT NULL, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY `key_phid` (`phid`), 18 + KEY `key_object` (`objectPHID`) 19 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+22 -2
src/__phutil_library_map__.php
··· 12 12 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', 13 13 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', 14 14 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', 15 + 'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php', 15 16 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', 16 17 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 17 18 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 19 + 'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php', 20 + 'AlmanacDeviceEditController' => 'applications/almanac/controller/AlmanacDeviceEditController.php', 21 + 'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php', 22 + 'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php', 18 23 'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php', 19 24 'AlmanacDeviceProperty' => 'applications/almanac/storage/AlmanacDeviceProperty.php', 20 25 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', 26 + 'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php', 27 + 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', 28 + 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', 29 + 'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php', 21 30 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', 22 31 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', 32 + 'AlmanacNames' => 'applications/almanac/util/AlmanacNames.php', 33 + 'AlmanacNamesTestCase' => 'applications/almanac/util/__tests__/AlmanacNamesTestCase.php', 23 34 'AlmanacService' => 'applications/almanac/storage/AlmanacService.php', 24 35 'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php', 25 36 'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php', ··· 28 39 'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php', 29 40 'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php', 30 41 'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php', 31 - 'AlmanacServiceTestCase' => 'applications/almanac/storage/__tests__/AlmanacServiceTestCase.php', 32 42 'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php', 33 43 'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php', 34 44 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', ··· 2921 2931 'AlmanacConduitUtil' => 'Phobject', 2922 2932 'AlmanacConsoleController' => 'AlmanacController', 2923 2933 'AlmanacController' => 'PhabricatorController', 2934 + 'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability', 2924 2935 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', 2925 2936 'AlmanacDAO' => 'PhabricatorLiskDAO', 2926 2937 'AlmanacDevice' => array( 2927 2938 'AlmanacDAO', 2928 2939 'PhabricatorPolicyInterface', 2929 2940 ), 2941 + 'AlmanacDeviceController' => 'AlmanacController', 2942 + 'AlmanacDeviceEditController' => 'AlmanacDeviceController', 2943 + 'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor', 2944 + 'AlmanacDeviceListController' => 'AlmanacDeviceController', 2930 2945 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', 2931 2946 'AlmanacDeviceProperty' => 'AlmanacDAO', 2932 2947 'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2948 + 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', 2949 + 'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction', 2950 + 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 2951 + 'AlmanacDeviceViewController' => 'AlmanacDeviceController', 2933 2952 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', 2934 2953 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', 2954 + 'AlmanacNames' => 'Phobject', 2955 + 'AlmanacNamesTestCase' => 'PhabricatorTestCase', 2935 2956 'AlmanacService' => array( 2936 2957 'AlmanacDAO', 2937 2958 'PhabricatorPolicyInterface', ··· 2943 2964 'AlmanacServicePHIDType' => 'PhabricatorPHIDType', 2944 2965 'AlmanacServiceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2945 2966 'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine', 2946 - 'AlmanacServiceTestCase' => 'PhabricatorTestCase', 2947 2967 'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction', 2948 2968 'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 2949 2969 'AlmanacServiceViewController' => 'AlmanacServiceController',
+8
src/applications/almanac/application/PhabricatorAlmanacApplication.php
··· 39 39 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacServiceEditController', 40 40 'view/(?P<name>[^/]+)/' => 'AlmanacServiceViewController', 41 41 ), 42 + 'device/' => array( 43 + '(?:query/(?P<queryKey>[^/]+)/)?' => 'AlmanacDeviceListController', 44 + 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacDeviceEditController', 45 + 'view/(?P<name>[^/]+)/' => 'AlmanacDeviceViewController', 46 + ), 42 47 ), 43 48 ); 44 49 } ··· 46 51 protected function getCustomCapabilities() { 47 52 return array( 48 53 AlmanacCreateServicesCapability::CAPABILITY => array( 54 + 'default' => PhabricatorPolicies::POLICY_ADMIN, 55 + ), 56 + AlmanacCreateDevicesCapability::CAPABILITY => array( 49 57 'default' => PhabricatorPolicies::POLICY_ADMIN, 50 58 ), 51 59 );
+16
src/applications/almanac/capability/AlmanacCreateDevicesCapability.php
··· 1 + <?php 2 + 3 + final class AlmanacCreateDevicesCapability 4 + extends PhabricatorPolicyCapability { 5 + 6 + const CAPABILITY = 'almanac.devices'; 7 + 8 + public function getCapabilityName() { 9 + return pht('Can Create Devices'); 10 + } 11 + 12 + public function describeCapabilityRejection() { 13 + return pht('You do not have permission to create Almanac devices.'); 14 + } 15 + 16 + }
+8
src/applications/almanac/controller/AlmanacConsoleController.php
··· 20 20 pht( 21 21 'Manage Almanac services.'))); 22 22 23 + $menu->addItem( 24 + id(new PHUIObjectItemView()) 25 + ->setHeader(pht('Devices')) 26 + ->setHref($this->getApplicationURI('device/')) 27 + ->addAttribute( 28 + pht( 29 + 'Manage Almanac devices.'))); 30 + 23 31 $crumbs = $this->buildApplicationCrumbs(); 24 32 $crumbs->addTextCrumb(pht('Console')); 25 33
+14
src/applications/almanac/controller/AlmanacDeviceController.php
··· 1 + <?php 2 + 3 + abstract class AlmanacDeviceController extends AlmanacController { 4 + 5 + public function buildApplicationCrumbs() { 6 + $crumbs = parent::buildApplicationCrumbs(); 7 + 8 + $list_uri = $this->getApplicationURI('device/'); 9 + $crumbs->addTextCrumb(pht('Devices'), $list_uri); 10 + 11 + return $crumbs; 12 + } 13 + 14 + }
+142
src/applications/almanac/controller/AlmanacDeviceEditController.php
··· 1 + <?php 2 + 3 + final class AlmanacDeviceEditController 4 + extends AlmanacDeviceController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + 9 + $list_uri = $this->getApplicationURI('device/'); 10 + 11 + $id = $request->getURIData('id'); 12 + if ($id) { 13 + $device = id(new AlmanacDeviceQuery()) 14 + ->setViewer($viewer) 15 + ->withIDs(array($id)) 16 + ->requireCapabilities( 17 + array( 18 + PhabricatorPolicyCapability::CAN_VIEW, 19 + PhabricatorPolicyCapability::CAN_EDIT, 20 + )) 21 + ->executeOne(); 22 + if (!$device) { 23 + return new Aphront404Response(); 24 + } 25 + 26 + $is_new = false; 27 + $device_uri = $device->getURI(); 28 + $cancel_uri = $device_uri; 29 + $title = pht('Edit Device'); 30 + $save_button = pht('Save Changes'); 31 + } else { 32 + $this->requireApplicationCapability( 33 + AlmanacCreateDevicesCapability::CAPABILITY); 34 + 35 + $device = AlmanacDevice::initializeNewDevice(); 36 + $is_new = true; 37 + 38 + $cancel_uri = $list_uri; 39 + $title = pht('Create Device'); 40 + $save_button = pht('Create Device'); 41 + } 42 + 43 + $v_name = $device->getName(); 44 + $e_name = true; 45 + $validation_exception = null; 46 + 47 + if ($request->isFormPost()) { 48 + $v_name = $request->getStr('name'); 49 + $v_view = $request->getStr('viewPolicy'); 50 + $v_edit = $request->getStr('editPolicy'); 51 + 52 + $type_name = AlmanacDeviceTransaction::TYPE_NAME; 53 + $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; 54 + $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; 55 + 56 + $xactions = array(); 57 + 58 + $xactions[] = id(new AlmanacDeviceTransaction()) 59 + ->setTransactionType($type_name) 60 + ->setNewValue($v_name); 61 + 62 + $xactions[] = id(new AlmanacDeviceTransaction()) 63 + ->setTransactionType($type_view) 64 + ->setNewValue($v_view); 65 + 66 + $xactions[] = id(new AlmanacDeviceTransaction()) 67 + ->setTransactionType($type_edit) 68 + ->setNewValue($v_edit); 69 + 70 + $editor = id(new AlmanacDeviceEditor()) 71 + ->setActor($viewer) 72 + ->setContentSourceFromRequest($request) 73 + ->setContinueOnNoEffect(true); 74 + 75 + try { 76 + $editor->applyTransactions($device, $xactions); 77 + 78 + $device_uri = $device->getURI(); 79 + return id(new AphrontRedirectResponse())->setURI($device_uri); 80 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 81 + $validation_exception = $ex; 82 + $e_name = $ex->getShortMessage($type_name); 83 + 84 + $device->setViewPolicy($v_view); 85 + $device->setEditPolicy($v_edit); 86 + } 87 + } 88 + 89 + $policies = id(new PhabricatorPolicyQuery()) 90 + ->setViewer($viewer) 91 + ->setObject($device) 92 + ->execute(); 93 + 94 + $form = id(new AphrontFormView()) 95 + ->setUser($viewer) 96 + ->appendChild( 97 + id(new AphrontFormTextControl()) 98 + ->setLabel(pht('Name')) 99 + ->setName('name') 100 + ->setValue($v_name) 101 + ->setError($e_name)) 102 + ->appendChild( 103 + id(new AphrontFormPolicyControl()) 104 + ->setName('viewPolicy') 105 + ->setPolicyObject($device) 106 + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) 107 + ->setPolicies($policies)) 108 + ->appendChild( 109 + id(new AphrontFormPolicyControl()) 110 + ->setName('editPolicy') 111 + ->setPolicyObject($device) 112 + ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) 113 + ->setPolicies($policies)) 114 + ->appendChild( 115 + id(new AphrontFormSubmitControl()) 116 + ->addCancelButton($cancel_uri) 117 + ->setValue($save_button)); 118 + 119 + $box = id(new PHUIObjectBoxView()) 120 + ->setValidationException($validation_exception) 121 + ->setHeaderText($title) 122 + ->appendChild($form); 123 + 124 + $crumbs = $this->buildApplicationCrumbs(); 125 + if ($is_new) { 126 + $crumbs->addTextCrumb(pht('Create Device')); 127 + } else { 128 + $crumbs->addTextCrumb($device->getName(), $device_uri); 129 + $crumbs->addTextCrumb(pht('Edit')); 130 + } 131 + 132 + return $this->buildApplicationPage( 133 + array( 134 + $crumbs, 135 + $box, 136 + ), 137 + array( 138 + 'title' => $title, 139 + )); 140 + } 141 + 142 + }
+52
src/applications/almanac/controller/AlmanacDeviceListController.php
··· 1 + <?php 2 + 3 + final class AlmanacDeviceListController 4 + extends AlmanacDeviceController { 5 + 6 + public function shouldAllowPublic() { 7 + return true; 8 + } 9 + 10 + public function handleRequest(AphrontRequest $request) { 11 + $controller = id(new PhabricatorApplicationSearchController()) 12 + ->setQueryKey($request->getURIData('queryKey')) 13 + ->setSearchEngine(new AlmanacDeviceSearchEngine()) 14 + ->setNavigation($this->buildSideNavView()); 15 + 16 + return $this->delegateToController($controller); 17 + } 18 + 19 + public function buildApplicationCrumbs() { 20 + $crumbs = parent::buildApplicationCrumbs(); 21 + 22 + $can_create = $this->hasApplicationCapability( 23 + AlmanacCreateDevicesCapability::CAPABILITY); 24 + 25 + $crumbs->addAction( 26 + id(new PHUIListItemView()) 27 + ->setName(pht('Create Device')) 28 + ->setHref($this->getApplicationURI('device/edit/')) 29 + ->setIcon('fa-plus-square') 30 + ->setDisabled(!$can_create) 31 + ->setWorkflow(!$can_create)); 32 + 33 + return $crumbs; 34 + } 35 + 36 + public function buildSideNavView() { 37 + $viewer = $this->getViewer(); 38 + 39 + $nav = new AphrontSideNavFilterView(); 40 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 41 + 42 + id(new AlmanacDeviceSearchEngine()) 43 + ->setViewer($viewer) 44 + ->addNavigationItems($nav->getMenu()); 45 + 46 + $nav->selectFilter(null); 47 + 48 + return $nav; 49 + } 50 + 51 + 52 + }
+95
src/applications/almanac/controller/AlmanacDeviceViewController.php
··· 1 + <?php 2 + 3 + final class AlmanacDeviceViewController 4 + extends AlmanacDeviceController { 5 + 6 + public function shouldAllowPublic() { 7 + return true; 8 + } 9 + 10 + public function handleRequest(AphrontRequest $request) { 11 + $viewer = $request->getViewer(); 12 + 13 + $name = $request->getURIData('name'); 14 + 15 + $device = id(new AlmanacDeviceQuery()) 16 + ->setViewer($viewer) 17 + ->withNames(array($name)) 18 + ->executeOne(); 19 + if (!$device) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + $title = pht('Device %s', $device->getName()); 24 + 25 + $property_list = $this->buildPropertyList($device); 26 + $action_list = $this->buildActionList($device); 27 + $property_list->setActionList($action_list); 28 + 29 + $header = id(new PHUIHeaderView()) 30 + ->setUser($viewer) 31 + ->setHeader($device->getName()) 32 + ->setPolicyObject($device); 33 + 34 + $box = id(new PHUIObjectBoxView()) 35 + ->setHeader($header) 36 + ->addPropertyList($property_list); 37 + 38 + $crumbs = $this->buildApplicationCrumbs(); 39 + $crumbs->addTextCrumb($device->getName()); 40 + 41 + $xactions = id(new AlmanacDeviceTransactionQuery()) 42 + ->setViewer($viewer) 43 + ->withObjectPHIDs(array($device->getPHID())) 44 + ->execute(); 45 + 46 + $xaction_view = id(new PhabricatorApplicationTransactionView()) 47 + ->setUser($viewer) 48 + ->setObjectPHID($device->getPHID()) 49 + ->setTransactions($xactions) 50 + ->setShouldTerminate(true); 51 + 52 + return $this->buildApplicationPage( 53 + array( 54 + $crumbs, 55 + $box, 56 + $xaction_view, 57 + ), 58 + array( 59 + 'title' => $title, 60 + )); 61 + } 62 + 63 + private function buildPropertyList(AlmanacDevice $device) { 64 + $viewer = $this->getViewer(); 65 + 66 + $properties = id(new PHUIPropertyListView()) 67 + ->setUser($viewer); 68 + 69 + return $properties; 70 + } 71 + 72 + private function buildActionList(AlmanacDevice $device) { 73 + $viewer = $this->getViewer(); 74 + $id = $device->getID(); 75 + 76 + $can_edit = PhabricatorPolicyFilter::hasCapability( 77 + $viewer, 78 + $device, 79 + PhabricatorPolicyCapability::CAN_EDIT); 80 + 81 + $actions = id(new PhabricatorActionListView()) 82 + ->setUser($viewer); 83 + 84 + $actions->addAction( 85 + id(new PhabricatorActionView()) 86 + ->setIcon('fa-pencil') 87 + ->setName(pht('Edit Device')) 88 + ->setHref($this->getApplicationURI("device/edit/{$id}/")) 89 + ->setWorkflow(!$can_edit) 90 + ->setDisabled(!$can_edit)); 91 + 92 + return $actions; 93 + } 94 + 95 + }
+144
src/applications/almanac/editor/AlmanacDeviceEditor.php
··· 1 + <?php 2 + 3 + final class AlmanacDeviceEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorAlmanacApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Almanac Device'); 12 + } 13 + 14 + public function getTransactionTypes() { 15 + $types = parent::getTransactionTypes(); 16 + 17 + $types[] = AlmanacDeviceTransaction::TYPE_NAME; 18 + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 19 + $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 20 + 21 + return $types; 22 + } 23 + 24 + protected function getCustomTransactionOldValue( 25 + PhabricatorLiskDAO $object, 26 + PhabricatorApplicationTransaction $xaction) { 27 + switch ($xaction->getTransactionType()) { 28 + case AlmanacDeviceTransaction::TYPE_NAME: 29 + return $object->getName(); 30 + } 31 + 32 + return parent::getCustomTransactionOldValue($object, $xaction); 33 + } 34 + 35 + protected function getCustomTransactionNewValue( 36 + PhabricatorLiskDAO $object, 37 + PhabricatorApplicationTransaction $xaction) { 38 + 39 + switch ($xaction->getTransactionType()) { 40 + case AlmanacDeviceTransaction::TYPE_NAME: 41 + return $xaction->getNewValue(); 42 + } 43 + 44 + return parent::getCustomTransactionNewValue($object, $xaction); 45 + } 46 + 47 + protected function applyCustomInternalTransaction( 48 + PhabricatorLiskDAO $object, 49 + PhabricatorApplicationTransaction $xaction) { 50 + 51 + switch ($xaction->getTransactionType()) { 52 + case AlmanacDeviceTransaction::TYPE_NAME: 53 + $object->setName($xaction->getNewValue()); 54 + return; 55 + case PhabricatorTransactions::TYPE_VIEW_POLICY: 56 + case PhabricatorTransactions::TYPE_EDIT_POLICY: 57 + return; 58 + } 59 + 60 + return parent::applyCustomInternalTransaction($object, $xaction); 61 + } 62 + 63 + protected function applyCustomExternalTransaction( 64 + PhabricatorLiskDAO $object, 65 + PhabricatorApplicationTransaction $xaction) { 66 + 67 + switch ($xaction->getTransactionType()) { 68 + case AlmanacDeviceTransaction::TYPE_NAME: 69 + case PhabricatorTransactions::TYPE_VIEW_POLICY: 70 + case PhabricatorTransactions::TYPE_EDIT_POLICY: 71 + return; 72 + } 73 + 74 + return parent::applyCustomExternalTransaction($object, $xaction); 75 + } 76 + 77 + protected function validateTransaction( 78 + PhabricatorLiskDAO $object, 79 + $type, 80 + array $xactions) { 81 + 82 + $errors = parent::validateTransaction($object, $type, $xactions); 83 + 84 + switch ($type) { 85 + case AlmanacDeviceTransaction::TYPE_NAME: 86 + $missing = $this->validateIsEmptyTextField( 87 + $object->getName(), 88 + $xactions); 89 + 90 + if ($missing) { 91 + $error = new PhabricatorApplicationTransactionValidationError( 92 + $type, 93 + pht('Required'), 94 + pht('Device name is required.'), 95 + nonempty(last($xactions), null)); 96 + 97 + $error->setIsMissingFieldError(true); 98 + $errors[] = $error; 99 + } else { 100 + foreach ($xactions as $xaction) { 101 + $message = null; 102 + $name = $xaction->getNewValue(); 103 + 104 + try { 105 + AlmanacNames::validateServiceOrDeviceName($name); 106 + } catch (Exception $ex) { 107 + $message = $ex->getMessage(); 108 + } 109 + 110 + if ($message !== null) { 111 + $error = new PhabricatorApplicationTransactionValidationError( 112 + $type, 113 + pht('Invalid'), 114 + $message, 115 + $xaction); 116 + $errors[] = $error; 117 + } 118 + } 119 + } 120 + 121 + if ($xactions) { 122 + $duplicate = id(new AlmanacDeviceQuery()) 123 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 124 + ->withNames(array(last($xactions)->getNewValue())) 125 + ->executeOne(); 126 + if ($duplicate && ($duplicate->getID() != $object->getID())) { 127 + $error = new PhabricatorApplicationTransactionValidationError( 128 + $type, 129 + pht('Not Unique'), 130 + pht('Almanac devices must have unique names.'), 131 + last($xactions)); 132 + $errors[] = $error; 133 + } 134 + } 135 + 136 + break; 137 + } 138 + 139 + return $errors; 140 + } 141 + 142 + 143 + 144 + }
+3 -1
src/applications/almanac/editor/AlmanacServiceEditor.php
··· 100 100 foreach ($xactions as $xaction) { 101 101 $message = null; 102 102 103 + $name = $xaction->getNewValue(); 104 + 103 105 try { 104 - AlmanacService::validateServiceName($xaction->getNewValue()); 106 + AlmanacNames::validateServiceOrDeviceName($name); 105 107 } catch (Exception $ex) { 106 108 $message = $ex->getMessage(); 107 109 }
+17
src/applications/almanac/query/AlmanacDeviceQuery.php
··· 5 5 6 6 private $ids; 7 7 private $phids; 8 + private $names; 8 9 9 10 public function withIDs(array $ids) { 10 11 $this->ids = $ids; ··· 13 14 14 15 public function withPHIDs(array $phids) { 15 16 $this->phids = $phids; 17 + return $this; 18 + } 19 + 20 + public function withNames(array $names) { 21 + $this->names = $names; 16 22 return $this; 17 23 } 18 24 ··· 46 52 $conn_r, 47 53 'phid IN (%Ls)', 48 54 $this->phids); 55 + } 56 + 57 + if ($this->names !== null) { 58 + $hashes = array(); 59 + foreach ($this->names as $name) { 60 + $hashes[] = PhabricatorHash::digestForIndex($name); 61 + } 62 + $where[] = qsprintf( 63 + $conn_r, 64 + 'nameIndex IN (%Ls)', 65 + $hashes); 49 66 } 50 67 51 68 $where[] = $this->buildPagingClause($conn_r);
+79
src/applications/almanac/query/AlmanacDeviceSearchEngine.php
··· 1 + <?php 2 + 3 + final class AlmanacDeviceSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + public function getResultTypeDescription() { 7 + return pht('Almanac Devices'); 8 + } 9 + 10 + public function buildSavedQueryFromRequest(AphrontRequest $request) { 11 + $saved = new PhabricatorSavedQuery(); 12 + 13 + return $saved; 14 + } 15 + 16 + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 17 + $query = id(new AlmanacDeviceQuery()); 18 + 19 + return $query; 20 + } 21 + 22 + public function buildSearchForm( 23 + AphrontFormView $form, 24 + PhabricatorSavedQuery $saved_query) {} 25 + 26 + protected function getURI($path) { 27 + return '/almanac/device/'.$path; 28 + } 29 + 30 + public function getBuiltinQueryNames() { 31 + $names = array( 32 + 'all' => pht('All Devices'), 33 + ); 34 + 35 + return $names; 36 + } 37 + 38 + public function buildSavedQueryFromBuiltin($query_key) { 39 + 40 + $query = $this->newSavedQuery(); 41 + $query->setQueryKey($query_key); 42 + 43 + switch ($query_key) { 44 + case 'all': 45 + return $query; 46 + } 47 + 48 + return parent::buildSavedQueryFromBuiltin($query_key); 49 + } 50 + 51 + protected function getRequiredHandlePHIDsForResultList( 52 + array $devices, 53 + PhabricatorSavedQuery $query) { 54 + return array(); 55 + } 56 + 57 + protected function renderResultList( 58 + array $devices, 59 + PhabricatorSavedQuery $query, 60 + array $handles) { 61 + assert_instances_of($devices, 'AlmanacDevice'); 62 + 63 + $viewer = $this->requireViewer(); 64 + 65 + $list = new PHUIObjectItemListView(); 66 + $list->setUser($viewer); 67 + foreach ($devices as $device) { 68 + $item = id(new PHUIObjectItemView()) 69 + ->setObjectName(pht('Device %d', $device->getID())) 70 + ->setHeader($device->getName()) 71 + ->setHref($device->getURI()) 72 + ->setObject($device); 73 + 74 + $list->addItem($item); 75 + } 76 + 77 + return $list; 78 + } 79 + }
+10
src/applications/almanac/query/AlmanacDeviceTransactionQuery.php
··· 1 + <?php 2 + 3 + final class AlmanacDeviceTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new AlmanacDeviceTransaction(); 8 + } 9 + 10 + }
+41 -5
src/applications/almanac/storage/AlmanacDevice.php
··· 5 5 implements PhabricatorPolicyInterface { 6 6 7 7 protected $name; 8 + protected $nameIndex; 9 + protected $mailKey; 10 + protected $viewPolicy; 11 + protected $editPolicy; 12 + 13 + public static function initializeNewDevice() { 14 + return id(new AlmanacDevice()) 15 + ->setViewPolicy(PhabricatorPolicies::POLICY_USER) 16 + ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN); 17 + } 8 18 9 19 public function getConfiguration() { 10 20 return array( 11 21 self::CONFIG_AUX_PHID => true, 12 22 self::CONFIG_COLUMN_SCHEMA => array( 13 - 'name' => 'text255', 23 + 'name' => 'text128', 24 + 'nameIndex' => 'bytes12', 25 + ), 26 + self::CONFIG_KEY_SCHEMA => array( 27 + 'key_name' => array( 28 + 'columns' => array('nameIndex'), 29 + 'unique' => true, 30 + ), 31 + 'key_nametext' => array( 32 + 'columns' => array('name'), 33 + ), 14 34 ), 15 35 ) + parent::getConfiguration(); 16 36 } ··· 19 39 return PhabricatorPHID::generateNewPHID(AlmanacDevicePHIDType::TYPECONST); 20 40 } 21 41 42 + public function save() { 43 + AlmanacNames::validateServiceOrDeviceName($this->getName()); 44 + 45 + $this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); 46 + 47 + if (!$this->mailKey) { 48 + $this->mailKey = Filesystem::readRandomCharacters(20); 49 + } 50 + 51 + return parent::save(); 52 + } 53 + 54 + public function getURI() { 55 + return '/almanac/device/view/'.$this->getName().'/'; 56 + } 57 + 22 58 23 59 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 24 60 ··· 26 62 public function getCapabilities() { 27 63 return array( 28 64 PhabricatorPolicyCapability::CAN_VIEW, 65 + PhabricatorPolicyCapability::CAN_EDIT, 29 66 ); 30 67 } 31 68 32 69 public function getPolicy($capability) { 33 70 switch ($capability) { 34 71 case PhabricatorPolicyCapability::CAN_VIEW: 35 - // Until we get a clearer idea on what's going to be stored in this 36 - // table, don't allow anyone (other than the omnipotent user) to find 37 - // these objects. 38 - return PhabricatorPolicies::POLICY_NOONE; 72 + return $this->getViewPolicy(); 73 + case PhabricatorPolicyCapability::CAN_EDIT: 74 + return $this->getEditPolicy(); 39 75 } 40 76 } 41 77
+45
src/applications/almanac/storage/AlmanacDeviceTransaction.php
··· 1 + <?php 2 + 3 + final class AlmanacDeviceTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + const TYPE_NAME = 'almanac:device:name'; 7 + 8 + public function getApplicationName() { 9 + return 'almanac'; 10 + } 11 + 12 + public function getApplicationTransactionType() { 13 + return AlmanacDevicePHIDType::TYPECONST; 14 + } 15 + 16 + public function getApplicationTransactionCommentObject() { 17 + return null; 18 + } 19 + 20 + public function getTitle() { 21 + $author_phid = $this->getAuthorPHID(); 22 + 23 + $old = $this->getOldValue(); 24 + $new = $this->getNewValue(); 25 + 26 + switch ($this->getTransactionType()) { 27 + case self::TYPE_NAME: 28 + if ($old === null) { 29 + return pht( 30 + '%s created this device.', 31 + $this->renderHandleLink($author_phid)); 32 + } else { 33 + return pht( 34 + '%s renamed this device from "%s" to "%s".', 35 + $this->renderHandleLink($author_phid), 36 + $old, 37 + $new); 38 + } 39 + break; 40 + } 41 + 42 + return parent::getTitle(); 43 + } 44 + 45 + }
+2 -48
src/applications/almanac/storage/AlmanacService.php
··· 41 41 } 42 42 43 43 public function save() { 44 - self::validateServiceName($this->getName()); 44 + AlmanacNames::validateServiceOrDeviceName($this->getName()); 45 + 45 46 $this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); 46 47 47 48 if (!$this->mailKey) { ··· 53 54 54 55 public function getURI() { 55 56 return '/almanac/service/view/'.$this->getName().'/'; 56 - } 57 - 58 - public static function validateServiceName($name) { 59 - if (strlen($name) < 3) { 60 - throw new Exception( 61 - pht('Almanac service names must be at least 3 characters long.')); 62 - } 63 - 64 - if (!preg_match('/^[a-z0-9.-]+\z/', $name)) { 65 - throw new Exception( 66 - pht( 67 - 'Almanac service names may only contain lowercase letters, numbers, '. 68 - 'hyphens, and periods.')); 69 - } 70 - 71 - if (preg_match('/(^|\\.)\d+(\z|\\.)/', $name)) { 72 - throw new Exception( 73 - pht( 74 - 'Almanac service names may not have any segments containing only '. 75 - 'digits.')); 76 - } 77 - 78 - if (preg_match('/\.\./', $name)) { 79 - throw new Exception( 80 - pht( 81 - 'Almanac service names may not contain multiple consecutive '. 82 - 'periods.')); 83 - } 84 - 85 - if (preg_match('/\\.-|-\\./', $name)) { 86 - throw new Exception( 87 - pht( 88 - 'Amanac service names may not contain hyphens adjacent to periods.')); 89 - } 90 - 91 - if (preg_match('/--/', $name)) { 92 - throw new Exception( 93 - pht( 94 - 'Almanac service names may not contain multiple consecutive '. 95 - 'hyphens.')); 96 - } 97 - 98 - if (!preg_match('/^[a-z0-9].*[a-z0-9]\z/', $name)) { 99 - throw new Exception( 100 - pht( 101 - 'Almanac service names must begin and end with a letter or number.')); 102 - } 103 57 } 104 58 105 59 /* -( PhabricatorPolicyInterface )----------------------------------------- */
+3 -3
src/applications/almanac/storage/__tests__/AlmanacServiceTestCase.php src/applications/almanac/util/__tests__/AlmanacNamesTestCase.php
··· 1 1 <?php 2 2 3 - final class AlmanacServiceTestCase extends PhabricatorTestCase { 3 + final class AlmanacNamesTestCase extends PhabricatorTestCase { 4 4 5 - public function testServiceNames() { 5 + public function testServiceOrDeviceNames() { 6 6 $map = array( 7 7 '' => false, 8 8 'a' => false, ··· 38 38 foreach ($map as $input => $expect) { 39 39 $caught = null; 40 40 try { 41 - AlmanacService::validateServiceName($input); 41 + AlmanacNames::validateServiceOrDeviceName($input); 42 42 } catch (Exception $ex) { 43 43 $caught = $ex; 44 44 }
+56
src/applications/almanac/util/AlmanacNames.php
··· 1 + <?php 2 + 3 + final class AlmanacNames extends Phobject { 4 + 5 + public static function validateServiceOrDeviceName($name) { 6 + if (strlen($name) < 3) { 7 + throw new Exception( 8 + pht( 9 + 'Almanac service and device names must be at least 3 '. 10 + 'characters long.')); 11 + } 12 + 13 + if (!preg_match('/^[a-z0-9.-]+\z/', $name)) { 14 + throw new Exception( 15 + pht( 16 + 'Almanac service and device names may only contain lowercase '. 17 + 'letters, numbers, hyphens, and periods.')); 18 + } 19 + 20 + if (preg_match('/(^|\\.)\d+(\z|\\.)/', $name)) { 21 + throw new Exception( 22 + pht( 23 + 'Almanac service and device names may not have any segments '. 24 + 'containing only digits.')); 25 + } 26 + 27 + if (preg_match('/\.\./', $name)) { 28 + throw new Exception( 29 + pht( 30 + 'Almanac service and device names may not contain multiple '. 31 + 'consecutive periods.')); 32 + } 33 + 34 + if (preg_match('/\\.-|-\\./', $name)) { 35 + throw new Exception( 36 + pht( 37 + 'Amanac service and device names may not contain hyphens adjacent '. 38 + 'to periods.')); 39 + } 40 + 41 + if (preg_match('/--/', $name)) { 42 + throw new Exception( 43 + pht( 44 + 'Almanac service and device names may not contain multiple '. 45 + 'consecutive hyphens.')); 46 + } 47 + 48 + if (!preg_match('/^[a-z0-9].*[a-z0-9]\z/', $name)) { 49 + throw new Exception( 50 + pht( 51 + 'Almanac service and device names must begin and end with a letter '. 52 + 'or number.')); 53 + } 54 + } 55 + 56 + }