diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -14,12 +14,17 @@ 'AlmanacBindingDeletePropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingDeletePropertyTransaction.php', 'AlmanacBindingDisableController' => 'applications/almanac/controller/AlmanacBindingDisableController.php', 'AlmanacBindingDisableTransaction' => 'applications/almanac/xaction/AlmanacBindingDisableTransaction.php', + 'AlmanacBindingEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacBindingEditConduitAPIMethod.php', 'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php', + 'AlmanacBindingEditEngine' => 'applications/almanac/editor/AlmanacBindingEditEngine.php', 'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php', 'AlmanacBindingInterfaceTransaction' => 'applications/almanac/xaction/AlmanacBindingInterfaceTransaction.php', 'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php', 'AlmanacBindingPropertyEditEngine' => 'applications/almanac/editor/AlmanacBindingPropertyEditEngine.php', 'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php', + 'AlmanacBindingSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacBindingSearchConduitAPIMethod.php', + 'AlmanacBindingSearchEngine' => 'applications/almanac/query/AlmanacBindingSearchEngine.php', + 'AlmanacBindingServiceTransaction' => 'applications/almanac/xaction/AlmanacBindingServiceTransaction.php', 'AlmanacBindingSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacBindingSetPropertyTransaction.php', 'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php', 'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php', @@ -5206,16 +5211,22 @@ 'AlmanacPropertyInterface', 'PhabricatorDestructibleInterface', 'PhabricatorExtendedPolicyInterface', + 'PhabricatorConduitResultInterface', ), 'AlmanacBindingDeletePropertyTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingDisableController' => 'AlmanacServiceController', 'AlmanacBindingDisableTransaction' => 'AlmanacBindingTransactionType', + 'AlmanacBindingEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'AlmanacBindingEditController' => 'AlmanacServiceController', + 'AlmanacBindingEditEngine' => 'PhabricatorEditEngine', 'AlmanacBindingEditor' => 'AlmanacEditor', 'AlmanacBindingInterfaceTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingPHIDType' => 'PhabricatorPHIDType', 'AlmanacBindingPropertyEditEngine' => 'AlmanacPropertyEditEngine', 'AlmanacBindingQuery' => 'AlmanacQuery', + 'AlmanacBindingSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'AlmanacBindingSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'AlmanacBindingServiceTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingSetPropertyTransaction' => 'AlmanacBindingTransactionType', 'AlmanacBindingTableView' => 'AphrontView', 'AlmanacBindingTransaction' => 'AlmanacModularTransaction', diff --git a/src/applications/almanac/conduit/AlmanacBindingEditConduitAPIMethod.php b/src/applications/almanac/conduit/AlmanacBindingEditConduitAPIMethod.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/conduit/AlmanacBindingEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +service = $service; + return $this; + } + + public function getService() { + if (!$this->service) { + throw new PhutilInvalidStateException('setService'); + } + return $this->service; + } + + public function isEngineConfigurable() { + return false; + } + + public function getEngineName() { + return pht('Almanac Bindings'); + } + + public function getSummaryHeader() { + return pht('Edit Almanac Binding Configurations'); + } + + public function getSummaryText() { + return pht('This engine is used to edit Almanac bindings.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + + protected function newEditableObject() { + $service = $this->getService(); + return AlmanacBinding::initializeNewBinding($service); + } + + protected function newEditableObjectForDocumentation() { + $service_type = AlmanacCustomServiceType::SERVICETYPE; + $service = AlmanacService::initializeNewService($service_type); + $this->setService($service); + return $this->newEditableObject(); + } + + protected function newEditableObjectFromConduit(array $raw_xactions) { + $service_phid = null; + foreach ($raw_xactions as $raw_xaction) { + if ($raw_xaction['type'] !== 'service') { + continue; + } + + $service_phid = $raw_xaction['value']; + } + + if ($service_phid === null) { + throw new Exception( + pht( + 'When creating a new Almanac binding via the Conduit API, you '. + 'must provide a "service" transaction to select a service to bind.')); + } + + $service = id(new AlmanacServiceQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($service_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$service) { + throw new Exception( + pht( + 'Service "%s" is unrecognized, restricted, or you do not have '. + 'permission to edit it.', + $service_phid)); + } + + $this->setService($service); + + return $this->newEditableObject(); + } + + protected function newObjectQuery() { + return new AlmanacBindingQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Binding'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Binding'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Binding'); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Binding'); + } + + protected function getObjectCreateShortText() { + return pht('Create Binding'); + } + + protected function getObjectName() { + return pht('Binding'); + } + + protected function getEditorURI() { + return '/almanac/binding/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/almanac/binding/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('service') + ->setLabel(pht('Service')) + ->setIsConduitOnly(true) + ->setTransactionType( + AlmanacBindingServiceTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Service to create a binding for.')) + ->setConduitDescription(pht('Select the service to bind.')) + ->setConduitTypeDescription(pht('Service PHID.')) + ->setValue($object->getServicePHID()), + id(new PhabricatorTextEditField()) + ->setKey('interface') + ->setLabel(pht('Interface')) + ->setIsConduitOnly(true) + ->setTransactionType( + AlmanacBindingInterfaceTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Interface to bind the service to.')) + ->setConduitDescription(pht('Set the interface to bind.')) + ->setConduitTypeDescription(pht('Interface PHID.')) + ->setValue($object->getInterfacePHID()), + ); + } + +} diff --git a/src/applications/almanac/query/AlmanacBindingSearchEngine.php b/src/applications/almanac/query/AlmanacBindingSearchEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/query/AlmanacBindingSearchEngine.php @@ -0,0 +1,80 @@ +setLabel(pht('Services')) + ->setKey('servicePHIDs') + ->setAliases(array('service', 'servicePHID', 'services')) + ->setDescription(pht('Search for bindings on particular services.')), + id(new PhabricatorPHIDsSearchField()) + ->setLabel(pht('Devices')) + ->setKey('devicePHIDs') + ->setAliases(array('device', 'devicePHID', 'devices')) + ->setDescription(pht('Search for bindings on particular devices.')), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['servicePHIDs']) { + $query->withServicePHIDs($map['servicePHIDs']); + } + + if ($map['devicePHIDs']) { + $query->withDevicePHIDs($map['devicePHIDs']); + } + + return $query; + } + + protected function getURI($path) { + return '/almanac/binding/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Bindings'), + ); + + 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 renderResultList( + array $devices, + PhabricatorSavedQuery $query, + array $handles) { + + // For now, this SearchEngine just supports API access via Conduit. + throw new PhutilMethodNotImplementedException(); + } + +} diff --git a/src/applications/almanac/storage/AlmanacBinding.php b/src/applications/almanac/storage/AlmanacBinding.php --- a/src/applications/almanac/storage/AlmanacBinding.php +++ b/src/applications/almanac/storage/AlmanacBinding.php @@ -7,7 +7,8 @@ PhabricatorApplicationTransactionInterface, AlmanacPropertyInterface, PhabricatorDestructibleInterface, - PhabricatorExtendedPolicyInterface { + PhabricatorExtendedPolicyInterface, + PhabricatorConduitResultInterface { protected $servicePHID; protected $devicePHID; @@ -23,6 +24,7 @@ public static function initializeNewBinding(AlmanacService $service) { return id(new AlmanacBinding()) ->setServicePHID($service->getPHID()) + ->attachService($service) ->attachAlmanacProperties(array()) ->setIsDisabled(0); } @@ -225,4 +227,36 @@ } +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('servicePHID') + ->setType('phid') + ->setDescription(pht('The bound service.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('devicePHID') + ->setType('phid') + ->setDescription(pht('The device the service is bound to.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('interfacePHID') + ->setType('phid') + ->setDescription(pht('The interface the service is bound to.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'servicePHID' => $this->getServicePHID(), + 'devicePHID' => $this->getDevicePHID(), + 'interfacePHID' => $this->getInterfacePHID(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } diff --git a/src/applications/almanac/xaction/AlmanacBindingServiceTransaction.php b/src/applications/almanac/xaction/AlmanacBindingServiceTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/xaction/AlmanacBindingServiceTransaction.php @@ -0,0 +1,64 @@ +getServicePHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setServicePHID($value); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $service_phid = $object->getServicePHID(); + if ($this->isEmptyTextTransaction($service_phid, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Bindings must have a service.')); + } + + foreach ($xactions as $xaction) { + if (!$this->isNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'The service for a binding can not be changed once it has '. + 'been created.'), + $xaction); + continue; + } + + $service_phid = $xaction->getNewValue(); + $services = id(new AlmanacServiceQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($service_phid)) + ->execute(); + if (!$services) { + $errors[] = $this->newInvalidError( + pht('You can not bind a nonexistent or restricted service.'), + $xaction); + continue; + } + + $service = head($services); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $this->getActor(), + $service, + PhabricatorPolicyCapability::CAN_EDIT); + if (!$can_edit) { + $errors[] = $this->newInvalidError( + pht( + 'You can not bind a service which you do not have permission '. + 'to edit.')); + continue; + } + } + + return $errors; + } + +}