@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Give Almanac generic, custom-field-based properties

Summary:
Ref T5833. Currently, we have an `AlmanacDeviceProperty`, but it doesn't use CustomFields and is specific to devices. Make this more generic:

- Reuse most of the CustomField infrastructure (so we can eventually get easy support for nice editor UIs, etc).
- Make properties more generic so Services, Bindings and Devices can all have them.

The major difference between this implementation and existing CustomField implementations is that all other implementations are application-authoritative: the application code determines what the available list of fields is.

I want Almanac to be a bit more freeform (basically: you can write whatever properties you want, and we'll put nice UIs on them if we have a nice UI available). For example, we might have some sort of "ServiceTemplate" that says "a database binding should usually have the fields 'writable', 'active', 'credential'", which would do things like offer these as options and put a nice UI on them, but you should also be able to write whatever other properties you want and add services without building a specific service template for them.

This involves a little bit of rule bending, but ends up pretty clean. We can adjust CustomField to accommodate this a bit more gracefully later on if it makes sense.

Test Plan: {F229172}

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T5833

Differential Revision: https://secure.phabricator.com/D10777

+598 -36
+1
resources/sql/autopatches/20141103.almanac.1.delprop.sql
··· 1 + DROP TABLE {$NAMESPACE}_almanac.almanac_deviceproperty;
+8
resources/sql/autopatches/20141103.almanac.2.addprop.sql
··· 1 + CREATE TABLE {$NAMESPACE}_almanac.almanac_property ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + objectPHID VARBINARY(64) NOT NULL, 4 + fieldIndex BINARY(12) NOT NULL, 5 + fieldName VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, 6 + fieldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, 7 + UNIQUE KEY `objectPHID` (objectPHID, fieldIndex) 8 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+22 -2
src/__phutil_library_map__.php
··· 22 22 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', 23 23 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', 24 24 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', 25 + 'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php', 25 26 'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php', 26 27 'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php', 27 28 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', 29 + 'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php', 28 30 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 29 31 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 30 32 'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php', ··· 32 34 'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php', 33 35 'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php', 34 36 'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php', 35 - 'AlmanacDeviceProperty' => 'applications/almanac/storage/AlmanacDeviceProperty.php', 36 37 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', 37 38 'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php', 38 39 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', ··· 59 60 'AlmanacNetworkTransaction' => 'applications/almanac/storage/AlmanacNetworkTransaction.php', 60 61 'AlmanacNetworkTransactionQuery' => 'applications/almanac/query/AlmanacNetworkTransactionQuery.php', 61 62 'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php', 63 + 'AlmanacProperty' => 'applications/almanac/storage/AlmanacProperty.php', 64 + 'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php', 65 + 'AlmanacPropertyEditController' => 'applications/almanac/controller/AlmanacPropertyEditController.php', 66 + 'AlmanacPropertyInterface' => 'applications/almanac/property/AlmanacPropertyInterface.php', 67 + 'AlmanacPropertyQuery' => 'applications/almanac/query/AlmanacPropertyQuery.php', 62 68 'AlmanacService' => 'applications/almanac/storage/AlmanacService.php', 63 69 'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php', 64 70 'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php', ··· 2978 2984 'AlmanacConduitUtil' => 'Phobject', 2979 2985 'AlmanacConsoleController' => 'AlmanacController', 2980 2986 'AlmanacController' => 'PhabricatorController', 2987 + 'AlmanacCoreCustomField' => array( 2988 + 'AlmanacCustomField', 2989 + 'PhabricatorStandardCustomFieldInterface', 2990 + ), 2981 2991 'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability', 2982 2992 'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability', 2983 2993 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', 2994 + 'AlmanacCustomField' => 'PhabricatorCustomField', 2984 2995 'AlmanacDAO' => 'PhabricatorLiskDAO', 2985 2996 'AlmanacDevice' => array( 2986 2997 'AlmanacDAO', ··· 2991 3002 'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor', 2992 3003 'AlmanacDeviceListController' => 'AlmanacDeviceController', 2993 3004 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', 2994 - 'AlmanacDeviceProperty' => 'AlmanacDAO', 2995 3005 'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2996 3006 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', 2997 3007 'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction', ··· 3024 3034 'AlmanacNetworkTransaction' => 'PhabricatorApplicationTransaction', 3025 3035 'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3026 3036 'AlmanacNetworkViewController' => 'AlmanacNetworkController', 3037 + 'AlmanacProperty' => array( 3038 + 'PhabricatorCustomFieldStorage', 3039 + 'PhabricatorPolicyInterface', 3040 + ), 3041 + 'AlmanacPropertyController' => 'AlmanacController', 3042 + 'AlmanacPropertyEditController' => 'AlmanacDeviceController', 3043 + 'AlmanacPropertyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3027 3044 'AlmanacService' => array( 3028 3045 'AlmanacDAO', 3029 3046 'PhabricatorPolicyInterface', 3047 + 'PhabricatorCustomFieldInterface', 3048 + 'PhabricatorApplicationTransactionInterface', 3049 + 'AlmanacPropertyInterface', 3030 3050 ), 3031 3051 'AlmanacServiceController' => 'AlmanacController', 3032 3052 'AlmanacServiceEditController' => 'AlmanacServiceController',
+3
src/applications/almanac/application/PhabricatorAlmanacApplication.php
··· 56 56 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacNetworkEditController', 57 57 '(?P<id>\d+)/' => 'AlmanacNetworkViewController', 58 58 ), 59 + 'property/' => array( 60 + 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacPropertyEditController', 61 + ), 59 62 ), 60 63 ); 61 64 }
+62 -1
src/applications/almanac/controller/AlmanacController.php
··· 1 1 <?php 2 2 3 3 abstract class AlmanacController 4 - extends PhabricatorController {} 4 + extends PhabricatorController { 5 + 6 + 7 + protected function buildAlmanacPropertiesTable($object) { 8 + $viewer = $this->getViewer(); 9 + 10 + $properties = id(new AlmanacPropertyQuery()) 11 + ->setViewer($viewer) 12 + ->withObjectPHIDs(array($object->getPHID())) 13 + ->execute(); 14 + 15 + $rows = array(); 16 + foreach ($properties as $property) { 17 + $value = $property->getFieldValue(); 18 + 19 + $rows[] = array( 20 + $property->getFieldName(), 21 + PhabricatorConfigJSON::prettyPrintJSON($value), 22 + ); 23 + } 24 + 25 + $table = id(new AphrontTableView($rows)) 26 + ->setNoDataString(pht('No properties.')) 27 + ->setHeaders( 28 + array( 29 + pht('Name'), 30 + pht('Value'), 31 + )) 32 + ->setColumnClasses( 33 + array( 34 + null, 35 + 'wide', 36 + )); 37 + 38 + $phid = $object->getPHID(); 39 + $add_uri = $this->getApplicationURI("property/edit/?objectPHID={$phid}"); 40 + 41 + $can_edit = PhabricatorPolicyFilter::hasCapability( 42 + $viewer, 43 + $object, 44 + PhabricatorPolicyCapability::CAN_EDIT); 45 + 46 + $add_button = id(new PHUIButtonView()) 47 + ->setTag('a') 48 + ->setHref($add_uri) 49 + ->setWorkflow(true) 50 + ->setDisabled(!$can_edit) 51 + ->setText(pht('Add Property')) 52 + ->setIcon( 53 + id(new PHUIIconView()) 54 + ->setIconFont('fa-plus')); 55 + 56 + $header = id(new PHUIHeaderView()) 57 + ->setHeader(pht('Properties')) 58 + ->addActionLink($add_button); 59 + 60 + return id(new PHUIObjectBoxView()) 61 + ->setHeader($header) 62 + ->appendChild($table); 63 + } 64 + 65 + }
+3
src/applications/almanac/controller/AlmanacPropertyController.php
··· 1 + <?php 2 + 3 + abstract class AlmanacPropertyController extends AlmanacController {}
+169
src/applications/almanac/controller/AlmanacPropertyEditController.php
··· 1 + <?php 2 + 3 + final class AlmanacPropertyEditController 4 + extends AlmanacDeviceController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + 9 + $id = $request->getURIData('id'); 10 + if ($id) { 11 + $property = id(new AlmanacPropertyQuery()) 12 + ->setViewer($viewer) 13 + ->withIDs(array($id)) 14 + ->requireCapabilities( 15 + array( 16 + PhabricatorPolicyCapability::CAN_VIEW, 17 + PhabricatorPolicyCapability::CAN_EDIT, 18 + )) 19 + ->executeOne(); 20 + if (!$property) { 21 + return new Aphront404Response(); 22 + } 23 + 24 + $object = $property->getObject(); 25 + 26 + $is_new = false; 27 + $title = pht('Edit Property'); 28 + $save_button = pht('Save Changes'); 29 + } else { 30 + $object = id(new PhabricatorObjectQuery()) 31 + ->setViewer($viewer) 32 + ->withPHIDs(array($request->getStr('objectPHID'))) 33 + ->requireCapabilities( 34 + array( 35 + PhabricatorPolicyCapability::CAN_VIEW, 36 + PhabricatorPolicyCapability::CAN_EDIT, 37 + )) 38 + ->executeOne(); 39 + if (!$object) { 40 + return new Aphront404Response(); 41 + } 42 + 43 + $is_new = true; 44 + $title = pht('Add Property'); 45 + $save_button = pht('Add Property'); 46 + } 47 + 48 + if (!($object instanceof AlmanacPropertyInterface)) { 49 + return new Aphront404Response(); 50 + } 51 + 52 + $cancel_uri = $object->getURI(); 53 + 54 + if ($is_new) { 55 + $errors = array(); 56 + $property = null; 57 + 58 + $v_name = null; 59 + $e_name = true; 60 + 61 + if ($request->isFormPost()) { 62 + $name = $request->getStr('name'); 63 + if (!strlen($name)) { 64 + $e_name = pht('Required'); 65 + $errors[] = pht('You must provide a property name.'); 66 + } else { 67 + $caught = null; 68 + try { 69 + AlmanacNames::validateServiceOrDeviceName($name); 70 + } catch (Exception $ex) { 71 + $caught = $ex; 72 + } 73 + if ($caught) { 74 + $e_name = pht('Invalid'); 75 + $errors[] = $caught->getMessage(); 76 + } 77 + } 78 + 79 + if (!$errors) { 80 + $property = id(new AlmanacPropertyQuery()) 81 + ->setViewer($viewer) 82 + ->withObjectPHIDs(array($object->getPHID())) 83 + ->withNames(array($name)) 84 + ->requireCapabilities( 85 + array( 86 + PhabricatorPolicyCapability::CAN_VIEW, 87 + PhabricatorPolicyCapability::CAN_EDIT, 88 + )) 89 + ->executeOne(); 90 + if (!$property) { 91 + $property = id(new AlmanacProperty()) 92 + ->setObjectPHID($object->getPHID()) 93 + ->setFieldName($name); 94 + } 95 + } 96 + } 97 + 98 + if (!$property) { 99 + $form = id(new AphrontFormView()) 100 + ->setUser($viewer) 101 + ->appendChild( 102 + id(new AphrontFormTextControl()) 103 + ->setName('name') 104 + ->setLabel(pht('Name')) 105 + ->setValue($v_name) 106 + ->setError($e_name)); 107 + 108 + return $this->newDialog() 109 + ->setTitle($title) 110 + ->setErrors($errors) 111 + ->addHiddenInput('objectPHID', $request->getStr('objectPHID')) 112 + ->appendForm($form) 113 + ->addSubmitButton(pht('Continue')) 114 + ->addCancelButton($cancel_uri); 115 + } 116 + } 117 + 118 + $v_name = $property->getFieldName(); 119 + $e_name = true; 120 + 121 + $v_value = $property->getFieldValue(); 122 + $e_value = null; 123 + 124 + $object->attachAlmanacProperties(array($property)); 125 + 126 + $field_list = PhabricatorCustomField::getObjectFields( 127 + $object, 128 + PhabricatorCustomField::ROLE_EDIT); 129 + $field_list 130 + ->setViewer($viewer) 131 + ->readFieldsFromStorage($object); 132 + 133 + $validation_exception = null; 134 + if ($request->isFormPost() && $request->getStr('isValueEdit')) { 135 + $xactions = $field_list->buildFieldTransactionsFromRequest( 136 + $object->getApplicationTransactionTemplate(), 137 + $request); 138 + 139 + $editor = $object->getApplicationTransactionEditor() 140 + ->setActor($viewer) 141 + ->setContentSourceFromRequest($request) 142 + ->setContinueOnNoEffect(true) 143 + ->setContinueOnMissingFields(true); 144 + 145 + try { 146 + $editor->applyTransactions($object, $xactions); 147 + return id(new AphrontRedirectResponse())->setURI($cancel_uri); 148 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 149 + $validation_exception = $ex; 150 + } 151 + } 152 + 153 + $form = id(new AphrontFormView()) 154 + ->setUser($viewer) 155 + ->addHiddenInput('objectPHID', $request->getStr('objectPHID')) 156 + ->addHiddenInput('name', $request->getStr('name')) 157 + ->addHiddenInput('isValueEdit', true); 158 + 159 + $field_list->appendFieldsToForm($form); 160 + 161 + return $this->newDialog() 162 + ->setTitle($title) 163 + ->setValidationException($validation_exception) 164 + ->appendForm($form) 165 + ->addSubmitButton($save_button) 166 + ->addCancelButton($cancel_uri); 167 + } 168 + 169 + }
+2
src/applications/almanac/controller/AlmanacServiceViewController.php
··· 15 15 $service = id(new AlmanacServiceQuery()) 16 16 ->setViewer($viewer) 17 17 ->withNames(array($name)) 18 + ->needProperties(true) 18 19 ->executeOne(); 19 20 if (!$service) { 20 21 return new Aphront404Response(); ··· 56 57 $crumbs, 57 58 $box, 58 59 $bindings, 60 + $this->buildAlmanacPropertiesTable($service), 59 61 $xaction_view, 60 62 ), 61 63 array(
+62
src/applications/almanac/customfield/AlmanacCoreCustomField.php
··· 1 + <?php 2 + 3 + final class AlmanacCoreCustomField 4 + extends AlmanacCustomField 5 + implements PhabricatorStandardCustomFieldInterface { 6 + 7 + public function getStandardCustomFieldNamespace() { 8 + return 'almanac:core'; 9 + } 10 + 11 + public function createFields($object) { 12 + $specs = array(); 13 + 14 + foreach ($object->getAlmanacProperties() as $property) { 15 + $specs[$property->getFieldName()] = array( 16 + 'name' => $property->getFieldName(), 17 + 'type' => 'text', 18 + ); 19 + } 20 + 21 + return PhabricatorStandardCustomField::buildStandardFields($this, $specs); 22 + } 23 + 24 + public function shouldUseStorage() { 25 + return false; 26 + } 27 + 28 + public function readValueFromObject(PhabricatorCustomFieldInterface $object) { 29 + $key = $this->getProxy()->getRawStandardFieldKey(); 30 + $this->setValueFromStorage($object->getAlmanacPropertyValue($key)); 31 + } 32 + 33 + public function applyApplicationTransactionInternalEffects( 34 + PhabricatorApplicationTransaction $xaction) { 35 + return; 36 + } 37 + 38 + public function applyApplicationTransactionExternalEffects( 39 + PhabricatorApplicationTransaction $xaction) { 40 + 41 + $object = $this->getObject(); 42 + $phid = $object->getPHID(); 43 + $key = $this->getProxy()->getRawStandardFieldKey(); 44 + 45 + $property = id(new AlmanacPropertyQuery()) 46 + ->setViewer($this->getViewer()) 47 + ->withObjectPHIDs(array($phid)) 48 + ->withNames(array($key)) 49 + ->executeOne(); 50 + if (!$property) { 51 + $property = id(new AlmanacProperty()) 52 + ->setObjectPHID($phid) 53 + ->setFieldIndex(PhabricatorHash::digestForIndex($key)) 54 + ->setFieldName($key); 55 + } 56 + 57 + $property 58 + ->setFieldValue($xaction->getNewValue()) 59 + ->save(); 60 + } 61 + 62 + }
+4
src/applications/almanac/customfield/AlmanacCustomField.php
··· 1 + <?php 2 + 3 + abstract class AlmanacCustomField 4 + extends PhabricatorCustomField {}
+6 -6
src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php
··· 25 25 ->setName(php_uname('n')) 26 26 ->save(); 27 27 28 - id(new AlmanacDeviceProperty()) 29 - ->setDevicePHID($host->getPHID()) 30 - ->setKey('conduitPublicOpenSSHKey') 28 + id(new AlmanacProperty()) 29 + ->setObjectPHID($host->getPHID()) 30 + ->setName('conduitPublicOpenSSHKey') 31 31 ->setValue($public_key) 32 32 ->save(); 33 33 34 - id(new AlmanacDeviceProperty()) 35 - ->setDevicePHID($host->getPHID()) 36 - ->setKey('conduitPublicOpenSSLKey') 34 + id(new AlmanacProperty()) 35 + ->setObjectPHID($host->getPHID()) 36 + ->setName('conduitPublicOpenSSLKey') 37 37 ->setValue($this->convertToOpenSSLPublicKey($public_key)) 38 38 ->save(); 39 39
+11
src/applications/almanac/property/AlmanacPropertyInterface.php
··· 1 + <?php 2 + 3 + interface AlmanacPropertyInterface { 4 + 5 + public function attachAlmanacProperties(array $properties); 6 + public function getAlmanacProperties(); 7 + public function hasAlmanacProperty($key); 8 + public function getAlmanacProperty($key); 9 + public function getAlmanacPropertyValue($key, $default = null); 10 + 11 + }
+86
src/applications/almanac/query/AlmanacPropertyQuery.php
··· 1 + <?php 2 + 3 + final class AlmanacPropertyQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $objectPHIDs; 7 + private $names; 8 + 9 + public function withObjectPHIDs(array $phids) { 10 + $this->phids = $phids; 11 + return $this; 12 + } 13 + 14 + public function withNames(array $names) { 15 + $this->names = $names; 16 + return $this; 17 + } 18 + 19 + protected function loadPage() { 20 + $table = new AlmanacProperty(); 21 + $conn_r = $table->establishConnection('r'); 22 + 23 + $data = queryfx_all( 24 + $conn_r, 25 + 'SELECT * FROM %T %Q %Q %Q', 26 + $table->getTableName(), 27 + $this->buildWhereClause($conn_r), 28 + $this->buildOrderClause($conn_r), 29 + $this->buildLimitClause($conn_r)); 30 + 31 + return $table->loadAllFromArray($data); 32 + } 33 + 34 + protected function willFilterPage(array $properties) { 35 + $object_phids = mpull($properties, 'getObjectPHID'); 36 + 37 + $objects = id(new PhabricatorObjectQuery()) 38 + ->setViewer($this->getViewer()) 39 + ->setParentQuery($this) 40 + ->withPHIDs($object_phids) 41 + ->execute(); 42 + $objects = mpull($objects, null, 'getPHID'); 43 + 44 + foreach ($properties as $key => $property) { 45 + $object = idx($objects, $property->getObjectPHID()); 46 + if (!$object) { 47 + unset($properties[$key]); 48 + continue; 49 + } 50 + $property->attachObject($object); 51 + } 52 + 53 + return $properties; 54 + } 55 + 56 + protected function buildWhereClause($conn_r) { 57 + $where = array(); 58 + 59 + if ($this->objectPHIDs !== null) { 60 + $where[] = qsprintf( 61 + $conn_r, 62 + 'objectPHID IN (%Ls)', 63 + $this->objectPHIDs); 64 + } 65 + 66 + if ($this->names !== null) { 67 + $hashes = array(); 68 + foreach ($this->names as $name) { 69 + $hashes[] = PhabricatorHash::digestForIndex($name); 70 + } 71 + $where[] = qsprintf( 72 + $conn_r, 73 + 'fieldIndex IN (%Ls)', 74 + $hashes); 75 + } 76 + 77 + $where[] = $this->buildPagingClause($conn_r); 78 + 79 + return $this->formatWhereClause($where); 80 + } 81 + 82 + public function getQueryApplicationClass() { 83 + return 'PhabricatorAlmanacApplication'; 84 + } 85 + 86 + }
+25
src/applications/almanac/query/AlmanacServiceQuery.php
··· 6 6 private $ids; 7 7 private $phids; 8 8 private $names; 9 + private $needProperties; 9 10 10 11 public function withIDs(array $ids) { 11 12 $this->ids = $ids; ··· 19 20 20 21 public function withNames(array $names) { 21 22 $this->names = $names; 23 + return $this; 24 + } 25 + 26 + public function needProperties($need) { 27 + $this->needProperties = $need; 22 28 return $this; 23 29 } 24 30 ··· 69 75 $where[] = $this->buildPagingClause($conn_r); 70 76 71 77 return $this->formatWhereClause($where); 78 + } 79 + 80 + protected function didFilterPage(array $services) { 81 + // NOTE: We load properties unconditionally because CustomField assumes 82 + // it can always generate a list of fields on an object. It may make 83 + // sense to re-examine that assumption eventually. 84 + 85 + $properties = id(new AlmanacPropertyQuery()) 86 + ->setViewer($this->getViewer()) 87 + ->setParentQuery($this) 88 + ->withObjectPHIDs(mpull($services, null, 'getPHID')) 89 + ->execute(); 90 + $properties = mgroup($properties, 'getObjectPHID'); 91 + foreach ($services as $service) { 92 + $service_properties = idx($properties, $service->getPHID(), array()); 93 + $service->attachAlmanacProperties($service_properties); 94 + } 95 + 96 + return $services; 72 97 } 73 98 74 99 public function getQueryApplicationClass() {
-25
src/applications/almanac/storage/AlmanacDeviceProperty.php
··· 1 - <?php 2 - 3 - final class AlmanacDeviceProperty extends AlmanacDAO { 4 - 5 - protected $devicePHID; 6 - protected $key; 7 - protected $value; 8 - 9 - public function getConfiguration() { 10 - return array( 11 - self::CONFIG_SERIALIZATION => array( 12 - 'value' => self::SERIALIZATION_JSON, 13 - ), 14 - self::CONFIG_COLUMN_SCHEMA => array( 15 - 'key' => 'text128', 16 - ), 17 - self::CONFIG_KEY_SCHEMA => array( 18 - 'key_device' => array( 19 - 'columns' => array('devicePHID', 'key'), 20 - ), 21 - ), 22 - ) + parent::getConfiguration(); 23 - } 24 - 25 - }
+56
src/applications/almanac/storage/AlmanacProperty.php
··· 1 + <?php 2 + 3 + final class AlmanacProperty 4 + extends PhabricatorCustomFieldStorage 5 + implements PhabricatorPolicyInterface { 6 + 7 + protected $fieldName; 8 + 9 + private $object = self::ATTACHABLE; 10 + 11 + public function getApplicationName() { 12 + return 'almanac'; 13 + } 14 + 15 + public function getConfiguration() { 16 + $config = parent::getConfiguration(); 17 + 18 + $config[self::CONFIG_COLUMN_SCHEMA] += array( 19 + 'fieldName' => 'text128', 20 + ); 21 + 22 + return $config; 23 + } 24 + 25 + public function getObject() { 26 + return $this->assertAttached($this->object); 27 + } 28 + 29 + public function attachObject(PhabricatorLiskDAO $object) { 30 + $this->object = $object; 31 + return $this; 32 + } 33 + 34 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 35 + 36 + 37 + public function getCapabilities() { 38 + return array( 39 + PhabricatorPolicyCapability::CAN_VIEW, 40 + PhabricatorPolicyCapability::CAN_EDIT, 41 + ); 42 + } 43 + 44 + public function getPolicy($capability) { 45 + return $this->getObject()->getPolicy($capability); 46 + } 47 + 48 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 49 + return $this->getObject()->hasAutomaticCapability($capability, $viewer); 50 + } 51 + 52 + public function describeAutomaticCapability($capability) { 53 + return pht('Properties inherit the policies of their object.'); 54 + } 55 + 56 + }
+77 -1
src/applications/almanac/storage/AlmanacService.php
··· 2 2 3 3 final class AlmanacService 4 4 extends AlmanacDAO 5 - implements PhabricatorPolicyInterface { 5 + implements 6 + PhabricatorPolicyInterface, 7 + PhabricatorCustomFieldInterface, 8 + PhabricatorApplicationTransactionInterface, 9 + AlmanacPropertyInterface { 6 10 7 11 protected $name; 8 12 protected $nameIndex; 9 13 protected $mailKey; 10 14 protected $viewPolicy; 11 15 protected $editPolicy; 16 + 17 + private $customFields = self::ATTACHABLE; 18 + private $almanacProperties = self::ATTACHABLE; 12 19 13 20 public static function initializeNewService() { 14 21 return id(new AlmanacService()) ··· 56 63 return '/almanac/service/view/'.$this->getName().'/'; 57 64 } 58 65 66 + 67 + /* -( AlmanacPropertyInterface )------------------------------------------- */ 68 + 69 + 70 + public function attachAlmanacProperties(array $properties) { 71 + assert_instances_of($properties, 'AlmanacProperty'); 72 + $this->almanacProperties = mpull($properties, null, 'getFieldName'); 73 + return $this; 74 + } 75 + 76 + public function getAlmanacProperties() { 77 + return $this->assertAttached($this->almanacProperties); 78 + } 79 + 80 + public function hasAlmanacProperty($key) { 81 + $this->assertAttached($this->almanacProperties); 82 + return isset($this->almanacProperties[$key]); 83 + } 84 + 85 + public function getAlmanacProperty($key) { 86 + return $this->assertAttachedKey($this->almanacProperties, $key); 87 + } 88 + 89 + public function getAlmanacPropertyValue($key, $default = null) { 90 + if ($this->hasAlmanacProperty($key)) { 91 + return $this->getAlmanacProperty($key)->getFieldValue(); 92 + } else { 93 + return $default; 94 + } 95 + } 96 + 97 + 59 98 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 60 99 61 100 ··· 81 120 82 121 public function describeAutomaticCapability($capability) { 83 122 return null; 123 + } 124 + 125 + 126 + /* -( PhabricatorCustomFieldInterface )------------------------------------ */ 127 + 128 + 129 + public function getCustomFieldSpecificationForRole($role) { 130 + return array(); 131 + } 132 + 133 + public function getCustomFieldBaseClass() { 134 + return 'AlmanacCustomField'; 135 + } 136 + 137 + public function getCustomFields() { 138 + return $this->assertAttached($this->customFields); 139 + } 140 + 141 + public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { 142 + $this->customFields = $fields; 143 + return $this; 144 + } 145 + 146 + 147 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 148 + 149 + 150 + public function getApplicationTransactionEditor() { 151 + return new AlmanacServiceEditor(); 152 + } 153 + 154 + public function getApplicationTransactionObject() { 155 + return $this; 156 + } 157 + 158 + public function getApplicationTransactionTemplate() { 159 + return new AlmanacServiceTransaction(); 84 160 } 85 161 86 162 }
+1 -1
src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php
··· 119 119 120 120 // NOTE: We have to do this after loading objects, because the objects 121 121 // may help determine which handles are required (for example, in the case 122 - // of custom fields. 122 + // of custom fields). 123 123 124 124 if ($this->needHandles) { 125 125 $phids = array();