diff --git a/resources/sql/autopatches/20141103.almanac.1.delprop.sql b/resources/sql/autopatches/20141103.almanac.1.delprop.sql new file mode 100644 index 0000000000..55fa77942d --- /dev/null +++ b/resources/sql/autopatches/20141103.almanac.1.delprop.sql @@ -0,0 +1 @@ +DROP TABLE {$NAMESPACE}_almanac.almanac_deviceproperty; diff --git a/resources/sql/autopatches/20141103.almanac.2.addprop.sql b/resources/sql/autopatches/20141103.almanac.2.addprop.sql new file mode 100644 index 0000000000..a7a6ec9272 --- /dev/null +++ b/resources/sql/autopatches/20141103.almanac.2.addprop.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_property ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + fieldIndex BINARY(12) NOT NULL, + fieldName VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, + fieldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + UNIQUE KEY `objectPHID` (objectPHID, fieldIndex) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 639a2690be..6eb2bc9873 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -22,9 +22,11 @@ phutil_register_library_map(array( 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', + 'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php', 'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php', 'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php', 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', + 'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php', 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php', @@ -32,7 +34,6 @@ phutil_register_library_map(array( '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', @@ -59,6 +60,11 @@ phutil_register_library_map(array( 'AlmanacNetworkTransaction' => 'applications/almanac/storage/AlmanacNetworkTransaction.php', 'AlmanacNetworkTransactionQuery' => 'applications/almanac/query/AlmanacNetworkTransactionQuery.php', 'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php', + 'AlmanacProperty' => 'applications/almanac/storage/AlmanacProperty.php', + 'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php', + 'AlmanacPropertyEditController' => 'applications/almanac/controller/AlmanacPropertyEditController.php', + 'AlmanacPropertyInterface' => 'applications/almanac/property/AlmanacPropertyInterface.php', + 'AlmanacPropertyQuery' => 'applications/almanac/query/AlmanacPropertyQuery.php', 'AlmanacService' => 'applications/almanac/storage/AlmanacService.php', 'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php', 'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php', @@ -2978,9 +2984,14 @@ phutil_register_library_map(array( 'AlmanacConduitUtil' => 'Phobject', 'AlmanacConsoleController' => 'AlmanacController', 'AlmanacController' => 'PhabricatorController', + 'AlmanacCoreCustomField' => array( + 'AlmanacCustomField', + 'PhabricatorStandardCustomFieldInterface', + ), 'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability', 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', + 'AlmanacCustomField' => 'PhabricatorCustomField', 'AlmanacDAO' => 'PhabricatorLiskDAO', 'AlmanacDevice' => array( 'AlmanacDAO', @@ -2991,7 +3002,6 @@ phutil_register_library_map(array( 'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor', 'AlmanacDeviceListController' => 'AlmanacDeviceController', 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', - 'AlmanacDeviceProperty' => 'AlmanacDAO', 'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction', @@ -3024,9 +3034,19 @@ phutil_register_library_map(array( 'AlmanacNetworkTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacNetworkViewController' => 'AlmanacNetworkController', + 'AlmanacProperty' => array( + 'PhabricatorCustomFieldStorage', + 'PhabricatorPolicyInterface', + ), + 'AlmanacPropertyController' => 'AlmanacController', + 'AlmanacPropertyEditController' => 'AlmanacDeviceController', + 'AlmanacPropertyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'AlmanacService' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', + 'PhabricatorCustomFieldInterface', + 'PhabricatorApplicationTransactionInterface', + 'AlmanacPropertyInterface', ), 'AlmanacServiceController' => 'AlmanacController', 'AlmanacServiceEditController' => 'AlmanacServiceController', diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php index 625850b37f..696359b3e1 100644 --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -56,6 +56,9 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication { 'edit/(?:(?P\d+)/)?' => 'AlmanacNetworkEditController', '(?P\d+)/' => 'AlmanacNetworkViewController', ), + 'property/' => array( + 'edit/(?:(?P\d+)/)?' => 'AlmanacPropertyEditController', + ), ), ); } diff --git a/src/applications/almanac/controller/AlmanacController.php b/src/applications/almanac/controller/AlmanacController.php index 5a95566d75..bc7d2ee8f6 100644 --- a/src/applications/almanac/controller/AlmanacController.php +++ b/src/applications/almanac/controller/AlmanacController.php @@ -1,4 +1,65 @@ getViewer(); + + $properties = id(new AlmanacPropertyQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($object->getPHID())) + ->execute(); + + $rows = array(); + foreach ($properties as $property) { + $value = $property->getFieldValue(); + + $rows[] = array( + $property->getFieldName(), + PhabricatorConfigJSON::prettyPrintJSON($value), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('No properties.')) + ->setHeaders( + array( + pht('Name'), + pht('Value'), + )) + ->setColumnClasses( + array( + null, + 'wide', + )); + + $phid = $object->getPHID(); + $add_uri = $this->getApplicationURI("property/edit/?objectPHID={$phid}"); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $object, + PhabricatorPolicyCapability::CAN_EDIT); + + $add_button = id(new PHUIButtonView()) + ->setTag('a') + ->setHref($add_uri) + ->setWorkflow(true) + ->setDisabled(!$can_edit) + ->setText(pht('Add Property')) + ->setIcon( + id(new PHUIIconView()) + ->setIconFont('fa-plus')); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Properties')) + ->addActionLink($add_button); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($table); + } + +} diff --git a/src/applications/almanac/controller/AlmanacPropertyController.php b/src/applications/almanac/controller/AlmanacPropertyController.php new file mode 100644 index 0000000000..73d47431fd --- /dev/null +++ b/src/applications/almanac/controller/AlmanacPropertyController.php @@ -0,0 +1,3 @@ +getViewer(); + + $id = $request->getURIData('id'); + if ($id) { + $property = id(new AlmanacPropertyQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$property) { + return new Aphront404Response(); + } + + $object = $property->getObject(); + + $is_new = false; + $title = pht('Edit Property'); + $save_button = pht('Save Changes'); + } else { + $object = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($request->getStr('objectPHID'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$object) { + return new Aphront404Response(); + } + + $is_new = true; + $title = pht('Add Property'); + $save_button = pht('Add Property'); + } + + if (!($object instanceof AlmanacPropertyInterface)) { + return new Aphront404Response(); + } + + $cancel_uri = $object->getURI(); + + if ($is_new) { + $errors = array(); + $property = null; + + $v_name = null; + $e_name = true; + + if ($request->isFormPost()) { + $name = $request->getStr('name'); + if (!strlen($name)) { + $e_name = pht('Required'); + $errors[] = pht('You must provide a property name.'); + } else { + $caught = null; + try { + AlmanacNames::validateServiceOrDeviceName($name); + } catch (Exception $ex) { + $caught = $ex; + } + if ($caught) { + $e_name = pht('Invalid'); + $errors[] = $caught->getMessage(); + } + } + + if (!$errors) { + $property = id(new AlmanacPropertyQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($object->getPHID())) + ->withNames(array($name)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$property) { + $property = id(new AlmanacProperty()) + ->setObjectPHID($object->getPHID()) + ->setFieldName($name); + } + } + } + + if (!$property) { + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTextControl()) + ->setName('name') + ->setLabel(pht('Name')) + ->setValue($v_name) + ->setError($e_name)); + + return $this->newDialog() + ->setTitle($title) + ->setErrors($errors) + ->addHiddenInput('objectPHID', $request->getStr('objectPHID')) + ->appendForm($form) + ->addSubmitButton(pht('Continue')) + ->addCancelButton($cancel_uri); + } + } + + $v_name = $property->getFieldName(); + $e_name = true; + + $v_value = $property->getFieldValue(); + $e_value = null; + + $object->attachAlmanacProperties(array($property)); + + $field_list = PhabricatorCustomField::getObjectFields( + $object, + PhabricatorCustomField::ROLE_EDIT); + $field_list + ->setViewer($viewer) + ->readFieldsFromStorage($object); + + $validation_exception = null; + if ($request->isFormPost() && $request->getStr('isValueEdit')) { + $xactions = $field_list->buildFieldTransactionsFromRequest( + $object->getApplicationTransactionTemplate(), + $request); + + $editor = $object->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + try { + $editor->applyTransactions($object, $xactions); + return id(new AphrontRedirectResponse())->setURI($cancel_uri); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + } + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->addHiddenInput('objectPHID', $request->getStr('objectPHID')) + ->addHiddenInput('name', $request->getStr('name')) + ->addHiddenInput('isValueEdit', true); + + $field_list->appendFieldsToForm($form); + + return $this->newDialog() + ->setTitle($title) + ->setValidationException($validation_exception) + ->appendForm($form) + ->addSubmitButton($save_button) + ->addCancelButton($cancel_uri); + } + +} diff --git a/src/applications/almanac/controller/AlmanacServiceViewController.php b/src/applications/almanac/controller/AlmanacServiceViewController.php index 8218e3eafe..fb7675551f 100644 --- a/src/applications/almanac/controller/AlmanacServiceViewController.php +++ b/src/applications/almanac/controller/AlmanacServiceViewController.php @@ -15,6 +15,7 @@ final class AlmanacServiceViewController $service = id(new AlmanacServiceQuery()) ->setViewer($viewer) ->withNames(array($name)) + ->needProperties(true) ->executeOne(); if (!$service) { return new Aphront404Response(); @@ -56,6 +57,7 @@ final class AlmanacServiceViewController $crumbs, $box, $bindings, + $this->buildAlmanacPropertiesTable($service), $xaction_view, ), array( diff --git a/src/applications/almanac/customfield/AlmanacCoreCustomField.php b/src/applications/almanac/customfield/AlmanacCoreCustomField.php new file mode 100644 index 0000000000..6e3fb6b6ff --- /dev/null +++ b/src/applications/almanac/customfield/AlmanacCoreCustomField.php @@ -0,0 +1,62 @@ +getAlmanacProperties() as $property) { + $specs[$property->getFieldName()] = array( + 'name' => $property->getFieldName(), + 'type' => 'text', + ); + } + + return PhabricatorStandardCustomField::buildStandardFields($this, $specs); + } + + public function shouldUseStorage() { + return false; + } + + public function readValueFromObject(PhabricatorCustomFieldInterface $object) { + $key = $this->getProxy()->getRawStandardFieldKey(); + $this->setValueFromStorage($object->getAlmanacPropertyValue($key)); + } + + public function applyApplicationTransactionInternalEffects( + PhabricatorApplicationTransaction $xaction) { + return; + } + + public function applyApplicationTransactionExternalEffects( + PhabricatorApplicationTransaction $xaction) { + + $object = $this->getObject(); + $phid = $object->getPHID(); + $key = $this->getProxy()->getRawStandardFieldKey(); + + $property = id(new AlmanacPropertyQuery()) + ->setViewer($this->getViewer()) + ->withObjectPHIDs(array($phid)) + ->withNames(array($key)) + ->executeOne(); + if (!$property) { + $property = id(new AlmanacProperty()) + ->setObjectPHID($phid) + ->setFieldIndex(PhabricatorHash::digestForIndex($key)) + ->setFieldName($key); + } + + $property + ->setFieldValue($xaction->getNewValue()) + ->save(); + } + +} diff --git a/src/applications/almanac/customfield/AlmanacCustomField.php b/src/applications/almanac/customfield/AlmanacCustomField.php new file mode 100644 index 0000000000..6aefc35d2d --- /dev/null +++ b/src/applications/almanac/customfield/AlmanacCustomField.php @@ -0,0 +1,4 @@ +setName(php_uname('n')) ->save(); - id(new AlmanacDeviceProperty()) - ->setDevicePHID($host->getPHID()) - ->setKey('conduitPublicOpenSSHKey') + id(new AlmanacProperty()) + ->setObjectPHID($host->getPHID()) + ->setName('conduitPublicOpenSSHKey') ->setValue($public_key) ->save(); - id(new AlmanacDeviceProperty()) - ->setDevicePHID($host->getPHID()) - ->setKey('conduitPublicOpenSSLKey') + id(new AlmanacProperty()) + ->setObjectPHID($host->getPHID()) + ->setName('conduitPublicOpenSSLKey') ->setValue($this->convertToOpenSSLPublicKey($public_key)) ->save(); diff --git a/src/applications/almanac/property/AlmanacPropertyInterface.php b/src/applications/almanac/property/AlmanacPropertyInterface.php new file mode 100644 index 0000000000..89ec271af6 --- /dev/null +++ b/src/applications/almanac/property/AlmanacPropertyInterface.php @@ -0,0 +1,11 @@ +phids = $phids; + return $this; + } + + public function withNames(array $names) { + $this->names = $names; + return $this; + } + + protected function loadPage() { + $table = new AlmanacProperty(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + protected function willFilterPage(array $properties) { + $object_phids = mpull($properties, 'getObjectPHID'); + + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withPHIDs($object_phids) + ->execute(); + $objects = mpull($objects, null, 'getPHID'); + + foreach ($properties as $key => $property) { + $object = idx($objects, $property->getObjectPHID()); + if (!$object) { + unset($properties[$key]); + continue; + } + $property->attachObject($object); + } + + return $properties; + } + + protected function buildWhereClause($conn_r) { + $where = array(); + + if ($this->objectPHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'objectPHID IN (%Ls)', + $this->objectPHIDs); + } + + if ($this->names !== null) { + $hashes = array(); + foreach ($this->names as $name) { + $hashes[] = PhabricatorHash::digestForIndex($name); + } + $where[] = qsprintf( + $conn_r, + 'fieldIndex IN (%Ls)', + $hashes); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + +} diff --git a/src/applications/almanac/query/AlmanacServiceQuery.php b/src/applications/almanac/query/AlmanacServiceQuery.php index 090f4ed67f..61ee741e50 100644 --- a/src/applications/almanac/query/AlmanacServiceQuery.php +++ b/src/applications/almanac/query/AlmanacServiceQuery.php @@ -6,6 +6,7 @@ final class AlmanacServiceQuery private $ids; private $phids; private $names; + private $needProperties; public function withIDs(array $ids) { $this->ids = $ids; @@ -22,6 +23,11 @@ final class AlmanacServiceQuery return $this; } + public function needProperties($need) { + $this->needProperties = $need; + return $this; + } + protected function loadPage() { $table = new AlmanacService(); $conn_r = $table->establishConnection('r'); @@ -71,6 +77,25 @@ final class AlmanacServiceQuery return $this->formatWhereClause($where); } + protected function didFilterPage(array $services) { + // NOTE: We load properties unconditionally because CustomField assumes + // it can always generate a list of fields on an object. It may make + // sense to re-examine that assumption eventually. + + $properties = id(new AlmanacPropertyQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withObjectPHIDs(mpull($services, null, 'getPHID')) + ->execute(); + $properties = mgroup($properties, 'getObjectPHID'); + foreach ($services as $service) { + $service_properties = idx($properties, $service->getPHID(), array()); + $service->attachAlmanacProperties($service_properties); + } + + return $services; + } + public function getQueryApplicationClass() { return 'PhabricatorAlmanacApplication'; } diff --git a/src/applications/almanac/storage/AlmanacDeviceProperty.php b/src/applications/almanac/storage/AlmanacDeviceProperty.php deleted file mode 100644 index c388b68f4e..0000000000 --- a/src/applications/almanac/storage/AlmanacDeviceProperty.php +++ /dev/null @@ -1,25 +0,0 @@ - array( - 'value' => self::SERIALIZATION_JSON, - ), - self::CONFIG_COLUMN_SCHEMA => array( - 'key' => 'text128', - ), - self::CONFIG_KEY_SCHEMA => array( - 'key_device' => array( - 'columns' => array('devicePHID', 'key'), - ), - ), - ) + parent::getConfiguration(); - } - -} diff --git a/src/applications/almanac/storage/AlmanacProperty.php b/src/applications/almanac/storage/AlmanacProperty.php new file mode 100644 index 0000000000..9ce53f4362 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacProperty.php @@ -0,0 +1,56 @@ + 'text128', + ); + + return $config; + } + + public function getObject() { + return $this->assertAttached($this->object); + } + + public function attachObject(PhabricatorLiskDAO $object) { + $this->object = $object; + return $this; + } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + return $this->getObject()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getObject()->hasAutomaticCapability($capability, $viewer); + } + + public function describeAutomaticCapability($capability) { + return pht('Properties inherit the policies of their object.'); + } + +} diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacService.php index 4ea48a5729..cdc1a75fa6 100644 --- a/src/applications/almanac/storage/AlmanacService.php +++ b/src/applications/almanac/storage/AlmanacService.php @@ -2,7 +2,11 @@ final class AlmanacService extends AlmanacDAO - implements PhabricatorPolicyInterface { + implements + PhabricatorPolicyInterface, + PhabricatorCustomFieldInterface, + PhabricatorApplicationTransactionInterface, + AlmanacPropertyInterface { protected $name; protected $nameIndex; @@ -10,6 +14,9 @@ final class AlmanacService protected $viewPolicy; protected $editPolicy; + private $customFields = self::ATTACHABLE; + private $almanacProperties = self::ATTACHABLE; + public static function initializeNewService() { return id(new AlmanacService()) ->setViewPolicy(PhabricatorPolicies::POLICY_USER) @@ -56,6 +63,38 @@ final class AlmanacService return '/almanac/service/view/'.$this->getName().'/'; } + +/* -( AlmanacPropertyInterface )------------------------------------------- */ + + + public function attachAlmanacProperties(array $properties) { + assert_instances_of($properties, 'AlmanacProperty'); + $this->almanacProperties = mpull($properties, null, 'getFieldName'); + return $this; + } + + public function getAlmanacProperties() { + return $this->assertAttached($this->almanacProperties); + } + + public function hasAlmanacProperty($key) { + $this->assertAttached($this->almanacProperties); + return isset($this->almanacProperties[$key]); + } + + public function getAlmanacProperty($key) { + return $this->assertAttachedKey($this->almanacProperties, $key); + } + + public function getAlmanacPropertyValue($key, $default = null) { + if ($this->hasAlmanacProperty($key)) { + return $this->getAlmanacProperty($key)->getFieldValue(); + } else { + return $default; + } + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -83,4 +122,41 @@ final class AlmanacService return null; } + +/* -( PhabricatorCustomFieldInterface )------------------------------------ */ + + + public function getCustomFieldSpecificationForRole($role) { + return array(); + } + + public function getCustomFieldBaseClass() { + return 'AlmanacCustomField'; + } + + public function getCustomFields() { + return $this->assertAttached($this->customFields); + } + + public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { + $this->customFields = $fields; + return $this; + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new AlmanacServiceEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new AlmanacServiceTransaction(); + } + } diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php index 5609148be2..59bfb542ef 100644 --- a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php +++ b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php @@ -119,7 +119,7 @@ abstract class PhabricatorApplicationTransactionQuery // NOTE: We have to do this after loading objects, because the objects // may help determine which handles are required (for example, in the case - // of custom fields. + // of custom fields). if ($this->needHandles) { $phids = array();