diff --git a/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php b/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php index 9a982f49b6..04e8b6d05b 100644 --- a/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php +++ b/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php @@ -1,38 +1,38 @@ '; } protected function execute(ConduitAPIRequest $request) { - $classes = id(new PhutilClassMapQuery()) - ->setAncestorClass('ConduitAPIMethod') + $methods = id(new PhabricatorConduitMethodQuery()) + ->setViewer($request->getUser()) ->execute(); - $names_to_params = array(); - foreach ($classes as $class) { - $names_to_params[$class->getAPIMethodName()] = array( - 'description' => $class->getMethodDescription(), - 'params' => $class->getParamTypes(), - 'return' => $class->getReturnType(), + $map = array(); + foreach ($methods as $method) { + $map[$method->getAPIMethodName()] = array( + 'description' => $method->getMethodDescription(), + 'params' => $method->getParamTypes(), + 'return' => $method->getReturnType(), ); } - return $names_to_params; + return $map; } } diff --git a/src/applications/conduit/query/PhabricatorConduitMethodQuery.php b/src/applications/conduit/query/PhabricatorConduitMethodQuery.php index e1e7e1914b..cb30bf5e55 100644 --- a/src/applications/conduit/query/PhabricatorConduitMethodQuery.php +++ b/src/applications/conduit/query/PhabricatorConduitMethodQuery.php @@ -1,122 +1,159 @@ methods = $methods; return $this; } public function withNameContains($name_contains) { $this->nameContains = $name_contains; return $this; } public function withApplicationNames(array $application_names) { $this->applicationNames = $application_names; return $this; } public function withIsStable($is_stable) { $this->isStable = $is_stable; return $this; } public function withIsUnstable($is_unstable) { $this->isUnstable = $is_unstable; return $this; } public function withIsDeprecated($is_deprecated) { $this->isDeprecated = $is_deprecated; return $this; } protected function loadPage() { $methods = $this->getAllMethods(); $methods = $this->filterMethods($methods); return $methods; } private function getAllMethods() { return id(new PhutilClassMapQuery()) ->setAncestorClass('ConduitAPIMethod') ->setSortMethod('getSortOrder') ->execute(); } private function filterMethods(array $methods) { foreach ($methods as $key => $method) { $application = $method->getApplication(); if (!$application) { continue; } if (!$application->isInstalled()) { unset($methods[$key]); } } $status = array( ConduitAPIMethod::METHOD_STATUS_STABLE => $this->isStable, ConduitAPIMethod::METHOD_STATUS_DEPRECATED => $this->isDeprecated, ConduitAPIMethod::METHOD_STATUS_UNSTABLE => $this->isUnstable, ); // Only apply status filters if any of them are set. if (array_filter($status)) { foreach ($methods as $key => $method) { $keep = idx($status, $method->getMethodStatus()); if (!$keep) { unset($methods[$key]); } } } if ($this->applicationNames) { $map = array_fuse($this->applicationNames); foreach ($methods as $key => $method) { $needle = $method->getApplicationName(); $needle = phutil_utf8_strtolower($needle); if (empty($map[$needle])) { unset($methods[$key]); } } } if ($this->nameContains) { $needle = phutil_utf8_strtolower($this->nameContains); foreach ($methods as $key => $method) { $haystack = $method->getAPIMethodName(); $haystack = phutil_utf8_strtolower($haystack); if (strpos($haystack, $needle) === false) { unset($methods[$key]); } } } if ($this->methods) { $map = array_fuse($this->methods); foreach ($methods as $key => $method) { $needle = $method->getAPIMethodName(); if (empty($map[$needle])) { unset($methods[$key]); } } } return $methods; } + protected function willFilterPage(array $methods) { + $application_phids = array(); + foreach ($methods as $method) { + $application = $method->getApplication(); + if ($application === null) { + continue; + } + $application_phids[] = $application->getPHID(); + } + + if ($application_phids) { + $applications = id(new PhabricatorApplicationQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($application_phids) + ->execute(); + $applications = mpull($applications, null, 'getPHID'); + } else { + $applications = array(); + } + + // Remove methods which belong to an application the viewer can not see. + foreach ($methods as $key => $method) { + $application = $method->getApplication(); + if ($application === null) { + continue; + } + + if (empty($applications[$application->getPHID()])) { + $this->didRejectResult($method); + unset($methods[$key]); + } + } + + return $methods; + } + public function getQueryApplicationClass() { return 'PhabricatorConduitApplication'; } } diff --git a/src/applications/paste/editor/PhabricatorPasteEditEngine.php b/src/applications/paste/editor/PhabricatorPasteEditEngine.php index 0198507fa0..ce03a7ff06 100644 --- a/src/applications/paste/editor/PhabricatorPasteEditEngine.php +++ b/src/applications/paste/editor/PhabricatorPasteEditEngine.php @@ -1,83 +1,87 @@ getViewer()); } protected function newObjectQuery() { return id(new PhabricatorPasteQuery()) ->needRawContent(true); } protected function getObjectCreateTitleText($object) { return pht('Create New Paste'); } protected function getObjectEditTitleText($object) { return pht('Edit %s %s', $object->getMonogram(), $object->getTitle()); } protected function getObjectEditShortText($object) { return $object->getMonogram(); } protected function getObjectCreateShortText() { return pht('Create Paste'); } protected function getObjectViewURI($object) { return '/P'.$object->getID(); } protected function buildCustomEditFields($object) { $langs = array( '' => pht('(Detect From Filename in Title)'), ) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices'); return array( id(new PhabricatorTextEditField()) ->setKey('title') ->setLabel(pht('Title')) ->setDescription(pht('Name of the paste.')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) ->setValue($object->getTitle()), id(new PhabricatorSelectEditField()) ->setKey('language') ->setLabel(pht('Language')) ->setDescription( pht( 'Programming language to interpret the paste as for syntax '. 'highlighting. By default, the language is inferred from the '. 'title.')) ->setAliases(array('lang')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) ->setValue($object->getLanguage()) ->setOptions($langs), id(new PhabricatorSelectEditField()) ->setKey('status') ->setLabel(pht('Status')) ->setDescription(pht('Archive the paste.')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS) ->setValue($object->getStatus()) ->setOptions(PhabricatorPaste::getStatusNameMap()), id(new PhabricatorTextAreaEditField()) ->setKey('text') ->setLabel(pht('Text')) ->setDescription(pht('The main body text of the paste.')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) ->setMonospaced(true) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setValue($object->getRawContent()), ); } } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 3137a7d8bc..13c2b3e017 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1,1016 +1,1017 @@ viewer = $viewer; return $this; } final public function getViewer() { return $this->viewer; } final public function setController(PhabricatorController $controller) { $this->controller = $controller; $this->setViewer($controller->getViewer()); return $this; } final public function getController() { return $this->controller; } final public function getEngineKey() { return $this->getPhobjectClassConstant('ENGINECONST', 64); } /* -( Managing Fields )---------------------------------------------------- */ + abstract public function getEngineApplicationClass(); abstract protected function buildCustomEditFields($object); final protected function buildEditFields($object) { $viewer = $this->getViewer(); $editor = $object->getApplicationTransactionEditor(); $types = $editor->getTransactionTypesForObject($object); $types = array_fuse($types); $fields = $this->buildCustomEditFields($object); if ($object instanceof PhabricatorPolicyInterface) { $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($object) ->execute(); $map = array( PhabricatorTransactions::TYPE_VIEW_POLICY => array( 'key' => 'policy.view', 'aliases' => array('view'), 'capability' => PhabricatorPolicyCapability::CAN_VIEW, 'label' => pht('View Policy'), 'description' => pht('Controls who can view the object.'), 'edit' => 'view', ), PhabricatorTransactions::TYPE_EDIT_POLICY => array( 'key' => 'policy.edit', 'aliases' => array('edit'), 'capability' => PhabricatorPolicyCapability::CAN_EDIT, 'label' => pht('Edit Policy'), 'description' => pht('Controls who can edit the object.'), 'edit' => 'edit', ), PhabricatorTransactions::TYPE_JOIN_POLICY => array( 'key' => 'policy.join', 'aliases' => array('join'), 'capability' => PhabricatorPolicyCapability::CAN_JOIN, 'label' => pht('Join Policy'), 'description' => pht('Controls who can join the object.'), 'edit' => 'join', ), ); foreach ($map as $type => $spec) { if (empty($types[$type])) { continue; } $capability = $spec['capability']; $key = $spec['key']; $aliases = $spec['aliases']; $label = $spec['label']; $description = $spec['description']; $edit = $spec['edit']; $policy_field = id(new PhabricatorPolicyEditField()) ->setKey($key) ->setLabel($label) ->setDescription($description) ->setAliases($aliases) ->setCapability($capability) ->setPolicies($policies) ->setTransactionType($type) ->setEditTypeKey($edit) ->setValue($object->getPolicy($capability)); $fields[] = $policy_field; if ($object instanceof PhabricatorSpacesInterface) { if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { $type_space = PhabricatorTransactions::TYPE_SPACE; if (isset($types[$type_space])) { $space_field = id(new PhabricatorSpaceEditField()) ->setKey('spacePHID') ->setLabel(pht('Space')) ->setEditTypeKey('space') ->setDescription( pht('Shifts the object in the Spaces application.')) ->setAliases(array('space', 'policy.space')) ->setTransactionType($type_space) ->setValue($object->getSpacePHID()); $fields[] = $space_field; $policy_field->setSpaceField($space_field); } } } } } $edge_type = PhabricatorTransactions::TYPE_EDGE; $object_phid = $object->getPHID(); $project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; if ($object instanceof PhabricatorProjectInterface) { if (isset($types[$edge_type])) { if ($object_phid) { $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $object_phid, $project_edge_type); $project_phids = array_reverse($project_phids); } else { $project_phids = array(); } $edge_field = id(new PhabricatorProjectsEditField()) ->setKey('projectPHIDs') ->setLabel(pht('Projects')) ->setEditTypeKey('projects') ->setDescription(pht('Add or remove associated projects.')) ->setAliases(array('project', 'projects')) ->setTransactionType($edge_type) ->setMetadataValue('edge:type', $project_edge_type) ->setValue($project_phids); $fields[] = $edge_field; } } $subscribers_type = PhabricatorTransactions::TYPE_SUBSCRIBERS; if ($object instanceof PhabricatorSubscribableInterface) { if (isset($types[$subscribers_type])) { if ($object_phid) { $sub_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( $object_phid); } else { // TODO: Allow applications to provide default subscribers; Maniphest // does this at a minimum. $sub_phids = array(); } $subscribers_field = id(new PhabricatorSubscribersEditField()) ->setKey('subscriberPHIDs') ->setLabel(pht('Subscribers')) ->setEditTypeKey('subscribers') ->setDescription(pht('Manage subscribers.')) ->setAliases(array('subscriber', 'subscribers')) ->setTransactionType($subscribers_type) ->setValue($sub_phids); $fields[] = $subscribers_field; } } $config = $this->getEditEngineConfiguration(); $fields = $config->applyConfigurationToFields($this, $fields); foreach ($fields as $field) { $field ->setViewer($viewer) ->setObject($object); } return $fields; } /* -( Display Text )------------------------------------------------------- */ /** * @task text */ abstract public function getEngineName(); /** * @task text */ abstract protected function getObjectCreateTitleText($object); /** * @task text */ abstract protected function getObjectEditTitleText($object); /** * @task text */ abstract protected function getObjectCreateShortText(); /** * @task text */ abstract protected function getObjectEditShortText($object); /** * @task text */ protected function getObjectCreateButtonText($object) { return $this->getObjectCreateTitleText($object); } /** * @task text */ protected function getObjectEditButtonText($object) { return pht('Save Changes'); } /* -( Edit Engine Configuration )------------------------------------------ */ protected function supportsEditEngineConfiguration() { return true; } final protected function getEditEngineConfiguration() { return $this->editEngineConfiguration; } private function loadEditEngineConfiguration($key) { if ($key === null) { $key = self::EDITENGINECONFIG_DEFAULT; } $config = id(new PhabricatorEditEngineConfigurationQuery()) ->setViewer($this->getViewer()) ->withEngineKeys(array($this->getEngineKey())) ->withIdentifiers(array($key)) ->executeOne(); if (!$config) { return null; } $this->editEngineConfiguration = $config; return $config; } final public function getBuiltinEngineConfigurations() { $configurations = $this->newBuiltinEngineConfigurations(); if (!$configurations) { throw new Exception( pht( 'EditEngine ("%s") returned no builtin engine configurations, but '. 'an edit engine must have at least one configuration.', get_class($this))); } assert_instances_of($configurations, 'PhabricatorEditEngineConfiguration'); $has_default = false; foreach ($configurations as $config) { if ($config->getBuiltinKey() == self::EDITENGINECONFIG_DEFAULT) { $has_default = true; } } if (!$has_default) { $first = head($configurations); if (!$first->getBuiltinKey()) { $first->setBuiltinKey(self::EDITENGINECONFIG_DEFAULT); if (!strlen($first->getName())) { $first->setName($this->getObjectCreateShortText()); } } else { throw new Exception( pht( 'EditEngine ("%s") returned builtin engine configurations, '. 'but none are marked as default and the first configuration has '. 'a different builtin key already. Mark a builtin as default or '. 'omit the key from the first configuration', get_class($this))); } } $builtins = array(); foreach ($configurations as $key => $config) { $builtin_key = $config->getBuiltinKey(); if ($builtin_key === null) { throw new Exception( pht( 'EditEngine ("%s") returned builtin engine configurations, '. 'but one (with key "%s") is missing a builtin key. Provide a '. 'builtin key for each configuration (you can omit it from the '. 'first configuration in the list to automatically assign the '. 'default key).', get_class($this), $key)); } if (isset($builtins[$builtin_key])) { throw new Exception( pht( 'EditEngine ("%s") returned builtin engine configurations, '. 'but at least two specify the same builtin key ("%s"). Engines '. 'must have unique builtin keys.', get_class($this), $builtin_key)); } $builtins[$builtin_key] = $config; } return $builtins; } protected function newBuiltinEngineConfigurations() { return array( $this->newConfiguration(), ); } final protected function newConfiguration() { return PhabricatorEditEngineConfiguration::initializeNewConfiguration( $this->getViewer(), $this); } /* -( Managing URIs )------------------------------------------------------ */ /** * @task uri */ abstract protected function getObjectViewURI($object); /** * @task uri */ protected function getObjectEditURI($object) { return $this->getController()->getApplicationURI('edit/'); } /** * @task uri */ protected function getObjectCreateCancelURI($object) { return $this->getController()->getApplicationURI(); } /** * @task uri */ protected function getObjectEditCancelURI($object) { return $this->getObjectViewURI($object); } /** * @task uri */ protected function getEditURI($object, $path = null) { $parts = array( $this->getObjectEditURI($object), ); if (!$this->getIsCreate()) { $parts[] = $object->getID().'/'; } if ($path !== null) { $parts[] = $path; } return implode('', $parts); } /* -( Creating and Loading Objects )--------------------------------------- */ /** * Initialize a new object for creation. * * @return object Newly initialized object. * @task load */ abstract protected function newEditableObject(); /** * Build an empty query for objects. * * @return PhabricatorPolicyAwareQuery Query. * @task load */ abstract protected function newObjectQuery(); /** * Test if this workflow is creating a new object or editing an existing one. * * @return bool True if a new object is being created. * @task load */ final public function getIsCreate() { return $this->isCreate; } /** * Flag this workflow as a create or edit. * * @param bool True if this is a create workflow. * @return this * @task load */ private function setIsCreate($is_create) { $this->isCreate = $is_create; return $this; } /** * Load an object by ID. * * @param int Object ID. * @return object|null Object, or null if no such object exists. * @task load */ private function newObjectFromID($id) { $query = $this->newObjectQuery() ->withIDs(array($id)); return $this->newObjectFromQuery($query); } /** * Load an object by PHID. * * @param phid Object PHID. * @return object|null Object, or null if no such object exists. * @task load */ private function newObjectFromPHID($phid) { $query = $this->newObjectQuery() ->withPHIDs(array($phid)); return $this->newObjectFromQuery($query); } /** * Load an object given a configured query. * * @param PhabricatorPolicyAwareQuery Configured query. * @return object|null Object, or null if no such object exists. * @task load */ private function newObjectFromQuery(PhabricatorPolicyAwareQuery $query) { $viewer = $this->getViewer(); $object = $query ->setViewer($viewer) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$object) { return null; } return $object; } /** * Verify that an object is appropriate for editing. * * @param wild Loaded value. * @return void * @task load */ private function validateObject($object) { if (!$object || !is_object($object)) { throw new Exception( pht( 'EditEngine "%s" created or loaded an invalid object: object must '. 'actually be an object, but is of some other type ("%s").', get_class($this), gettype($object))); } if (!($object instanceof PhabricatorApplicationTransactionInterface)) { throw new Exception( pht( 'EditEngine "%s" created or loaded an invalid object: object (of '. 'class "%s") must implement "%s", but does not.', get_class($this), get_class($object), 'PhabricatorApplicationTransactionInterface')); } } /* -( Responding to Web Requests )----------------------------------------- */ final public function buildResponse() { $viewer = $this->getViewer(); $controller = $this->getController(); $request = $controller->getRequest(); $config = $this->loadEditEngineConfiguration($request->getURIData('form')); if (!$config) { return new Aphront404Response(); } $id = $request->getURIData('id'); if ($id) { $this->setIsCreate(false); $object = $this->newObjectFromID($id); if (!$object) { return new Aphront404Response(); } } else { $this->setIsCreate(true); $object = $this->newEditableObject(); } $this->validateObject($object); $action = $request->getURIData('editAction'); switch ($action) { case 'parameters': return $this->buildParametersResponse($object); default: return $this->buildEditResponse($object); } } private function buildCrumbs($object, $final = false) { $controller = $this->getcontroller(); $crumbs = $controller->buildApplicationCrumbsForEditEngine(); if ($this->getIsCreate()) { $create_text = $this->getObjectCreateShortText(); if ($final) { $crumbs->addTextCrumb($create_text); } else { $edit_uri = $this->getEditURI($object); $crumbs->addTextCrumb($create_text, $edit_uri); } } else { $crumbs->addTextCrumb( $this->getObjectEditShortText($object), $this->getObjectViewURI($object)); $edit_text = pht('Edit'); if ($final) { $crumbs->addTextCrumb($edit_text); } else { $edit_uri = $this->getEditURI($object); $crumbs->addTextCrumb($edit_text, $edit_uri); } } return $crumbs; } private function buildEditResponse($object) { $viewer = $this->getViewer(); $controller = $this->getController(); $request = $controller->getRequest(); $fields = $this->buildEditFields($object); $template = $object->getApplicationTransactionTemplate(); $validation_exception = null; if ($request->isFormPost()) { foreach ($fields as $field) { $field->readValueFromSubmit($request); } $xactions = array(); foreach ($fields as $field) { $xactions[] = $field->generateTransaction(clone $template); } $editor = $object->getApplicationTransactionEditor() ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($object, $xactions); return id(new AphrontRedirectResponse()) ->setURI($this->getObjectViewURI($object)); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; } } else { if ($this->getIsCreate()) { foreach ($fields as $field) { $field->readValueFromRequest($request); } } else { foreach ($fields as $field) { $field->readValueFromObject($object); } } } $action_button = $this->buildEditFormActionButton($object); if ($this->getIsCreate()) { $header_text = $this->getObjectCreateTitleText($object); } else { $header_text = $this->getObjectEditTitleText($object); } $header = id(new PHUIHeaderView()) ->setHeader($header_text) ->addActionLink($action_button); $crumbs = $this->buildCrumbs($object, $final = true); $form = $this->buildEditForm($object, $fields); $box = id(new PHUIObjectBoxView()) ->setUser($viewer) ->setHeader($header) ->setValidationException($validation_exception) ->appendChild($form); return $controller->newPage() ->setTitle($header_text) ->setCrumbs($crumbs) ->appendChild($box); } private function buildEditForm($object, array $fields) { $viewer = $this->getViewer(); $form = id(new AphrontFormView()) ->setUser($viewer); foreach ($fields as $field) { $field->appendToForm($form); } if ($this->getIsCreate()) { $cancel_uri = $this->getObjectCreateCancelURI($object); $submit_button = $this->getObjectCreateButtonText($object); } else { $cancel_uri = $this->getObjectEditCancelURI($object); $submit_button = $this->getObjectEditButtonText($object); } $form->appendControl( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($submit_button)); return $form; } private function buildEditFormActionButton($object) { $viewer = $this->getViewer(); $action_view = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($this->buildEditFormActions($object) as $action) { $action_view->addAction($action); } $action_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Actions')) ->setHref('#') ->setIconFont('fa-bars') ->setDropdownMenu($action_view); return $action_button; } private function buildEditFormActions($object) { $actions = array(); if ($this->supportsEditEngineConfiguration()) { $engine_key = $this->getEngineKey(); $config = $this->getEditEngineConfiguration(); $actions[] = id(new PhabricatorActionView()) ->setName(pht('Manage Form Configurations')) ->setIcon('fa-list-ul') ->setHref("/transactions/editengine/{$engine_key}/"); $actions[] = id(new PhabricatorActionView()) ->setName(pht('Edit Form Configuration')) ->setIcon('fa-pencil') ->setHref($config->getURI()); } $actions[] = id(new PhabricatorActionView()) ->setName(pht('Show HTTP Parameters')) ->setIcon('fa-crosshairs') ->setHref($this->getEditURI($object, 'parameters/')); return $actions; } /* -( Responding to HTTP Parameter Requests )------------------------------ */ /** * Respond to a request for documentation on HTTP parameters. * * @param object Editable object. * @return AphrontResponse Response object. * @task http */ private function buildParametersResponse($object) { $controller = $this->getController(); $viewer = $this->getViewer(); $request = $controller->getRequest(); $fields = $this->buildEditFields($object); $crumbs = $this->buildCrumbs($object); $crumbs->addTextCrumb(pht('HTTP Parameters')); $crumbs->setBorder(true); $header_text = pht( 'HTTP Parameters: %s', $this->getObjectCreateShortText()); $header = id(new PHUIHeaderView()) ->setHeader($header_text); $help_view = id(new PhabricatorApplicationEditHTTPParameterHelpView()) ->setUser($viewer) ->setFields($fields); $document = id(new PHUIDocumentViewPro()) ->setUser($viewer) ->setHeader($header) ->appendChild($help_view); return $controller->newPage() ->setTitle(pht('HTTP Parameters')) ->setCrumbs($crumbs) ->addClass('pro-white-background') ->appendChild($document); } /* -( Conduit )------------------------------------------------------------ */ /** * Respond to a Conduit edit request. * * This method accepts a list of transactions to apply to an object, and * either edits an existing object or creates a new one. * * @task conduit */ final public function buildConduitResponse(ConduitAPIRequest $request) { $viewer = $this->getViewer(); $config = $this->loadEditEngineConfiguration(null); if (!$config) { throw new Exception( pht( 'Unable to load configuration for this EditEngine ("%s").', get_class($this))); } $phid = $request->getValue('objectPHID'); if ($phid) { $this->setIsCreate(false); $object = $this->newObjectFromPHID($phid); if (!$object) { throw new Exception(pht('No such object with PHID "%s".', $phid)); } } else { $this->setIsCreate(true); $object = $this->newEditableObject(); } $this->validateObject($object); $fields = $this->buildEditFields($object); $types = $this->getAllEditTypesFromFields($fields); $template = $object->getApplicationTransactionTemplate(); $xactions = $this->getConduitTransactions($request, $types, $template); $editor = $object->getApplicationTransactionEditor() ->setActor($viewer) ->setContentSourceFromConduitRequest($request) ->setContinueOnNoEffect(true); $xactions = $editor->applyTransactions($object, $xactions); $xactions_struct = array(); foreach ($xactions as $xaction) { $xactions_struct[] = array( 'phid' => $xaction->getPHID(), ); } return array( 'object' => array( 'id' => $object->getID(), 'phid' => $object->getPHID(), ), 'transactions' => $xactions_struct, ); } /** * Generate transactions which can be applied from edit actions in a Conduit * request. * * @param ConduitAPIRequest The request. * @param list Supported edit types. * @param PhabricatorApplicationTransaction Template transaction. * @return list Generated transactions. * @task conduit */ private function getConduitTransactions( ConduitAPIRequest $request, array $types, PhabricatorApplicationTransaction $template) { $transactions_key = 'transactions'; $xactions = $request->getValue($transactions_key); if (!is_array($xactions)) { throw new Exception( pht( 'Parameter "%s" is not a list of transactions.', $transactions_key)); } foreach ($xactions as $key => $xaction) { if (!is_array($xaction)) { throw new Exception( pht( 'Parameter "%s" must contain a list of transaction descriptions, '. 'but item with key "%s" is not a dictionary.', $transactions_key, $key)); } if (!array_key_exists('type', $xaction)) { throw new Exception( pht( 'Parameter "%s" must contain a list of transaction descriptions, '. 'but item with key "%s" is missing a "type" field. Each '. 'transaction must have a type field.', $transactions_key, $key)); } $type = $xaction['type']; if (empty($types[$type])) { throw new Exception( pht( 'Transaction with key "%s" has invalid type "%s". This type is '. 'not recognized. Valid types are: %s.', $key, $type, implode(', ', array_keys($types)))); } } $results = array(); foreach ($xactions as $xaction) { $type = $types[$xaction['type']]; $results[] = $type->generateTransaction( clone $template, $xaction); } return $results; } /** * @return map * @task conduit */ private function getAllEditTypesFromFields(array $fields) { $types = array(); foreach ($fields as $field) { $field_types = $field->getEditTransactionTypes(); foreach ($field_types as $field_type) { $field_type->setField($field); $types[$field_type->getEditType()] = $field_type; } } return $types; } public function getAllEditTypes() { $config = $this->loadEditEngineConfiguration(null); if (!$config) { return array(); } $object = $this->newEditableObject(); $fields = $this->buildEditFields($object); return $this->getAllEditTypesFromFields($fields); } final public static function getAllEditEngines() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->setUniqueMethod('getEngineKey') ->execute(); } final public static function getByKey(PhabricatorUser $viewer, $key) { return id(new PhabricatorEditEngineQuery()) ->setViewer($viewer) ->withEngineKeys(array($key)) ->executeOne(); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getPHID() { return get_class($this); } public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::getMostOpenPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } public function describeAutomaticCapability($capability) { return null; } } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php index dafc56c825..cb7662c7ba 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php @@ -1,188 +1,194 @@ newEditEngine(); + $class = $engine->getEngineApplicationClass(); + return PhabricatorApplication::getByClass($class); + } + public function getMethodStatus() { return self::METHOD_STATUS_UNSTABLE; } public function getMethodStatusDescription() { return pht('ApplicationEditor methods are highly unstable.'); } final protected function defineParamTypes() { return array( 'transactions' => 'list>', 'objectPHID' => 'optional phid', ); } final protected function defineReturnType() { return 'map'; } final protected function execute(ConduitAPIRequest $request) { $engine = $this->newEditEngine() ->setViewer($request->getUser()); return $engine->buildConduitResponse($request); } final public function getMethodDescription() { // TODO: We don't currently have a real viewer in this method. $viewer = new PhabricatorUser(); $engine = $this->newEditEngine() ->setViewer($viewer); $types = $engine->getAllEditTypes(); $out = array(); $out[] = pht(<<getEditType(); $edit_summary = $type->getSummary(); $table[] = "| `{$edit_type}` | {$edit_summary} |"; } $out[] = implode("\n", $table); foreach ($types as $type) { $section = array(); $section[] = pht('Edit Type: %s', $type->getEditType()); $section[] = '---------'; $section[] = null; $section[] = $type->getDescription(); $section[] = null; $section[] = pht( 'This edit generates transactions of type `%s` internally.', $type->getTransactionType()); $section[] = null; $type_description = pht( 'Use `%s` to select this edit type.', $type->getEditType()); $value_type = $type->getValueType(); $value_description = $type->getValueDescription(); $table = array(); $table[] = "| {$key} | {$head_type} | {$description} |"; $table[] = '|--------|--------------|----------------|'; $table[] = "| `type` | `const` | {$type_description} |"; $table[] = "| `value` | `{$value_type}` | {$value_description} |"; $section[] = implode("\n", $table); $out[] = implode("\n", $section); } $out = implode("\n\n", $out); return $out; } } diff --git a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php index 17b604b7b2..e47447c351 100644 --- a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php +++ b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php @@ -1,78 +1,82 @@ targetEngine = $target_engine; return $this; } public function getTargetEngine() { return $this->targetEngine; } public function getEngineName() { return pht('Edit Configurations'); } + public function getEngineApplicationClass() { + return 'PhabricatorTransactionsApplication'; + } + protected function newEditableObject() { return PhabricatorEditEngineConfiguration::initializeNewConfiguration( $this->getViewer(), $this->getTargetEngine()); } protected function newObjectQuery() { return id(new PhabricatorEditEngineConfigurationQuery()); } protected function getObjectCreateTitleText($object) { return pht('Create New Form'); } protected function getObjectEditTitleText($object) { return pht('Edit Form %d: %s', $object->getID(), $object->getDisplayName()); } protected function getObjectEditShortText($object) { return pht('Form %d', $object->getID()); } protected function getObjectCreateShortText() { return pht('Create Form'); } protected function getObjectViewURI($object) { $engine_key = $this->getTargetEngine()->getEngineKey(); $id = $object->getID(); return "/transactions/editengine/{$engine_key}/view/{$id}/"; } protected function getObjectEditURI($object) { $engine_key = $this->getTargetEngine()->getEngineKey(); $id = $object->getID(); return "/transactions/editengine/{$engine_key}/edit/{$id}/"; } protected function getObjectCreateCancelURI($object) { $engine_key = $this->getTargetEngine()->getEngineKey(); return "/transactions/editengine/{$engine_key}/"; } protected function buildCustomEditFields($object) { return array( id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the form.')) ->setTransactionType( PhabricatorEditEngineConfigurationTransaction::TYPE_NAME) ->setValue($object->getName()), ); } }