1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 22:10:55 +01:00

Roughly generate transaction-oriented API methods from EditEngines

Summary:
Ref T5873. Ref T9132. This is really rough and feels pretty flimsy at the edges (missing validation, generality, modularity, clean error handling, etc) but gets us most of the way toward generating plausible "whatever.edit" Conduit API methods from EditEngines.

These methods are full-power methods which can do everything the edit form can, automatically support the same range of operations, and update when new fields are added.

Test Plan:
  - Used new `paste.edit` to create a new Paste.
  - Used new `paste.edit` to update an existing paste.
  - Applied a variety of different transactions.
  - Hit a reasonable set of errors.

{F941144}

{F941145}

{F941146}

{F941147}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T5873, T9132

Differential Revision: https://secure.phabricator.com/D14393
This commit is contained in:
epriestley 2015-11-03 08:45:00 -08:00
parent 3dec4c7dbd
commit 5030ba0401
10 changed files with 663 additions and 2 deletions

View file

@ -1530,6 +1530,7 @@ phutil_register_library_map(array(
'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php', 'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php',
'PasteDefaultEditCapability' => 'applications/paste/capability/PasteDefaultEditCapability.php', 'PasteDefaultEditCapability' => 'applications/paste/capability/PasteDefaultEditCapability.php',
'PasteDefaultViewCapability' => 'applications/paste/capability/PasteDefaultViewCapability.php', 'PasteDefaultViewCapability' => 'applications/paste/capability/PasteDefaultViewCapability.php',
'PasteEditConduitAPIMethod' => 'applications/paste/conduit/PasteEditConduitAPIMethod.php',
'PasteEmbedView' => 'applications/paste/view/PasteEmbedView.php', 'PasteEmbedView' => 'applications/paste/view/PasteEmbedView.php',
'PasteInfoConduitAPIMethod' => 'applications/paste/conduit/PasteInfoConduitAPIMethod.php', 'PasteInfoConduitAPIMethod' => 'applications/paste/conduit/PasteInfoConduitAPIMethod.php',
'PasteMailReceiver' => 'applications/paste/mail/PasteMailReceiver.php', 'PasteMailReceiver' => 'applications/paste/mail/PasteMailReceiver.php',
@ -1571,6 +1572,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php', 'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php',
'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php', 'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php',
'PhabricatorApplicationEditEngine' => 'applications/transactions/editengine/PhabricatorApplicationEditEngine.php', 'PhabricatorApplicationEditEngine' => 'applications/transactions/editengine/PhabricatorApplicationEditEngine.php',
'PhabricatorApplicationEditEngineAPIMethod' => 'applications/transactions/editengine/PhabricatorApplicationEditEngineAPIMethod.php',
'PhabricatorApplicationEditHTTPParameterHelpView' => 'applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php', 'PhabricatorApplicationEditHTTPParameterHelpView' => 'applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php',
'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php', 'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php',
'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php', 'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php',
@ -2094,6 +2096,7 @@ phutil_register_library_map(array(
'PhabricatorEdgeConfig' => 'infrastructure/edges/constants/PhabricatorEdgeConfig.php', 'PhabricatorEdgeConfig' => 'infrastructure/edges/constants/PhabricatorEdgeConfig.php',
'PhabricatorEdgeConstants' => 'infrastructure/edges/constants/PhabricatorEdgeConstants.php', 'PhabricatorEdgeConstants' => 'infrastructure/edges/constants/PhabricatorEdgeConstants.php',
'PhabricatorEdgeCycleException' => 'infrastructure/edges/exception/PhabricatorEdgeCycleException.php', 'PhabricatorEdgeCycleException' => 'infrastructure/edges/exception/PhabricatorEdgeCycleException.php',
'PhabricatorEdgeEditType' => 'applications/transactions/edittype/PhabricatorEdgeEditType.php',
'PhabricatorEdgeEditor' => 'infrastructure/edges/editor/PhabricatorEdgeEditor.php', 'PhabricatorEdgeEditor' => 'infrastructure/edges/editor/PhabricatorEdgeEditor.php',
'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php', 'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php',
'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php', 'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php',
@ -2101,6 +2104,7 @@ phutil_register_library_map(array(
'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php', 'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php',
'PhabricatorEdgeTypeTestCase' => 'infrastructure/edges/type/__tests__/PhabricatorEdgeTypeTestCase.php', 'PhabricatorEdgeTypeTestCase' => 'infrastructure/edges/type/__tests__/PhabricatorEdgeTypeTestCase.php',
'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php', 'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php',
'PhabricatorEditType' => 'applications/transactions/edittype/PhabricatorEditType.php',
'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php', 'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php',
'PhabricatorElasticSearchEngine' => 'applications/search/engine/PhabricatorElasticSearchEngine.php', 'PhabricatorElasticSearchEngine' => 'applications/search/engine/PhabricatorElasticSearchEngine.php',
'PhabricatorElasticSearchSetupCheck' => 'applications/config/check/PhabricatorElasticSearchSetupCheck.php', 'PhabricatorElasticSearchSetupCheck' => 'applications/config/check/PhabricatorElasticSearchSetupCheck.php',
@ -2936,6 +2940,7 @@ phutil_register_library_map(array(
'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php', 'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php',
'PhabricatorSetupIssueUIExample' => 'applications/uiexample/examples/PhabricatorSetupIssueUIExample.php', 'PhabricatorSetupIssueUIExample' => 'applications/uiexample/examples/PhabricatorSetupIssueUIExample.php',
'PhabricatorSetupIssueView' => 'applications/config/view/PhabricatorSetupIssueView.php', 'PhabricatorSetupIssueView' => 'applications/config/view/PhabricatorSetupIssueView.php',
'PhabricatorSimpleEditType' => 'applications/transactions/edittype/PhabricatorSimpleEditType.php',
'PhabricatorSite' => 'aphront/site/PhabricatorSite.php', 'PhabricatorSite' => 'aphront/site/PhabricatorSite.php',
'PhabricatorSlowvoteApplication' => 'applications/slowvote/application/PhabricatorSlowvoteApplication.php', 'PhabricatorSlowvoteApplication' => 'applications/slowvote/application/PhabricatorSlowvoteApplication.php',
'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/PhabricatorSlowvoteChoice.php', 'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/PhabricatorSlowvoteChoice.php',
@ -5457,6 +5462,7 @@ phutil_register_library_map(array(
'PasteCreateMailReceiver' => 'PhabricatorMailReceiver', 'PasteCreateMailReceiver' => 'PhabricatorMailReceiver',
'PasteDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PasteDefaultEditCapability' => 'PhabricatorPolicyCapability',
'PasteDefaultViewCapability' => 'PhabricatorPolicyCapability', 'PasteDefaultViewCapability' => 'PhabricatorPolicyCapability',
'PasteEditConduitAPIMethod' => 'PhabricatorApplicationEditEngineAPIMethod',
'PasteEmbedView' => 'AphrontView', 'PasteEmbedView' => 'AphrontView',
'PasteInfoConduitAPIMethod' => 'PasteConduitAPIMethod', 'PasteInfoConduitAPIMethod' => 'PasteConduitAPIMethod',
'PasteMailReceiver' => 'PhabricatorObjectMailReceiver', 'PasteMailReceiver' => 'PhabricatorObjectMailReceiver',
@ -5501,6 +5507,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationEditEngine' => 'Phobject', 'PhabricatorApplicationEditEngine' => 'Phobject',
'PhabricatorApplicationEditEngineAPIMethod' => 'ConduitAPIMethod',
'PhabricatorApplicationEditHTTPParameterHelpView' => 'AphrontView', 'PhabricatorApplicationEditHTTPParameterHelpView' => 'AphrontView',
'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationLaunchView' => 'AphrontTagView', 'PhabricatorApplicationLaunchView' => 'AphrontTagView',
@ -6121,6 +6128,7 @@ phutil_register_library_map(array(
'PhabricatorEdgeConfig' => 'PhabricatorEdgeConstants', 'PhabricatorEdgeConfig' => 'PhabricatorEdgeConstants',
'PhabricatorEdgeConstants' => 'Phobject', 'PhabricatorEdgeConstants' => 'Phobject',
'PhabricatorEdgeCycleException' => 'Exception', 'PhabricatorEdgeCycleException' => 'Exception',
'PhabricatorEdgeEditType' => 'PhabricatorEditType',
'PhabricatorEdgeEditor' => 'Phobject', 'PhabricatorEdgeEditor' => 'Phobject',
'PhabricatorEdgeGraph' => 'AbstractDirectedGraph', 'PhabricatorEdgeGraph' => 'AbstractDirectedGraph',
'PhabricatorEdgeQuery' => 'PhabricatorQuery', 'PhabricatorEdgeQuery' => 'PhabricatorQuery',
@ -6128,6 +6136,7 @@ phutil_register_library_map(array(
'PhabricatorEdgeType' => 'Phobject', 'PhabricatorEdgeType' => 'Phobject',
'PhabricatorEdgeTypeTestCase' => 'PhabricatorTestCase', 'PhabricatorEdgeTypeTestCase' => 'PhabricatorTestCase',
'PhabricatorEditField' => 'Phobject', 'PhabricatorEditField' => 'Phobject',
'PhabricatorEditType' => 'Phobject',
'PhabricatorEditor' => 'Phobject', 'PhabricatorEditor' => 'Phobject',
'PhabricatorElasticSearchEngine' => 'PhabricatorSearchEngine', 'PhabricatorElasticSearchEngine' => 'PhabricatorSearchEngine',
'PhabricatorElasticSearchSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorElasticSearchSetupCheck' => 'PhabricatorSetupCheck',
@ -7120,6 +7129,7 @@ phutil_register_library_map(array(
'PhabricatorSetupIssue' => 'Phobject', 'PhabricatorSetupIssue' => 'Phobject',
'PhabricatorSetupIssueUIExample' => 'PhabricatorUIExample', 'PhabricatorSetupIssueUIExample' => 'PhabricatorUIExample',
'PhabricatorSetupIssueView' => 'AphrontView', 'PhabricatorSetupIssueView' => 'AphrontView',
'PhabricatorSimpleEditType' => 'PhabricatorEditType',
'PhabricatorSite' => 'AphrontSite', 'PhabricatorSite' => 'AphrontSite',
'PhabricatorSlowvoteApplication' => 'PhabricatorApplication', 'PhabricatorSlowvoteApplication' => 'PhabricatorApplication',
'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO',

View file

@ -0,0 +1,19 @@
<?php
final class PasteEditConduitAPIMethod
extends PhabricatorApplicationEditEngineAPIMethod {
public function getAPIMethodName() {
return 'paste.edit';
}
public function newEditEngine() {
return new PhabricatorPasteEditEngine();
}
public function getMethodSummary() {
return pht(
'Apply transactions to create a new paste or edit an existing one.');
}
}

View file

@ -72,6 +72,30 @@ final class PhabricatorPasteEditor
$this->fileName = $name; $this->fileName = $name;
} }
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
if (!$object->getFilePHID() && !$xactions) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('You must provide content to create a paste.'),
null);
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
break;
}
return $errors;
}
protected function getCustomTransactionOldValue( protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) { PhabricatorApplicationTransaction $xaction) {

View file

@ -41,6 +41,7 @@ final class PhabricatorPaste extends PhabricatorPasteDAO
return id(new PhabricatorPaste()) return id(new PhabricatorPaste())
->setTitle('') ->setTitle('')
->setLanguage('')
->setStatus(self::STATUS_ACTIVE) ->setStatus(self::STATUS_ACTIVE)
->setAuthorPHID($actor->getPHID()) ->setAuthorPHID($actor->getPHID())
->setViewPolicy($view_policy) ->setViewPolicy($view_policy)

View file

@ -1,5 +1,10 @@
<?php <?php
/**
* @task web Responding to Web Requests
* @task conduit Responding to Conduit Requests
*/
abstract class PhabricatorApplicationEditEngine extends Phobject { abstract class PhabricatorApplicationEditEngine extends Phobject {
private $viewer; private $viewer;
@ -47,6 +52,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject {
'capability' => PhabricatorPolicyCapability::CAN_VIEW, 'capability' => PhabricatorPolicyCapability::CAN_VIEW,
'label' => pht('View Policy'), 'label' => pht('View Policy'),
'description' => pht('Controls who can view the object.'), 'description' => pht('Controls who can view the object.'),
'edit' => 'view',
), ),
PhabricatorTransactions::TYPE_EDIT_POLICY => array( PhabricatorTransactions::TYPE_EDIT_POLICY => array(
'key' => 'policy.edit', 'key' => 'policy.edit',
@ -54,6 +60,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject {
'capability' => PhabricatorPolicyCapability::CAN_EDIT, 'capability' => PhabricatorPolicyCapability::CAN_EDIT,
'label' => pht('Edit Policy'), 'label' => pht('Edit Policy'),
'description' => pht('Controls who can edit the object.'), 'description' => pht('Controls who can edit the object.'),
'edit' => 'edit',
), ),
PhabricatorTransactions::TYPE_JOIN_POLICY => array( PhabricatorTransactions::TYPE_JOIN_POLICY => array(
'key' => 'policy.join', 'key' => 'policy.join',
@ -61,6 +68,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject {
'capability' => PhabricatorPolicyCapability::CAN_JOIN, 'capability' => PhabricatorPolicyCapability::CAN_JOIN,
'label' => pht('Join Policy'), 'label' => pht('Join Policy'),
'description' => pht('Controls who can join the object.'), 'description' => pht('Controls who can join the object.'),
'edit' => 'join',
), ),
); );
@ -74,6 +82,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject {
$aliases = $spec['aliases']; $aliases = $spec['aliases'];
$label = $spec['label']; $label = $spec['label'];
$description = $spec['description']; $description = $spec['description'];
$edit = $spec['edit'];
$policy_field = id(new PhabricatorPolicyEditField()) $policy_field = id(new PhabricatorPolicyEditField())
->setKey($key) ->setKey($key)
@ -83,6 +92,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject {
->setCapability($capability) ->setCapability($capability)
->setPolicies($policies) ->setPolicies($policies)
->setTransactionType($type) ->setTransactionType($type)
->setEditTypeKey($edit)
->setValue($object->getPolicy($capability)); ->setValue($object->getPolicy($capability));
$fields[] = $policy_field; $fields[] = $policy_field;
@ -93,6 +103,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject {
$space_field = id(new PhabricatorSpaceEditField()) $space_field = id(new PhabricatorSpaceEditField())
->setKey('spacePHID') ->setKey('spacePHID')
->setLabel(pht('Space')) ->setLabel(pht('Space'))
->setEditTypeKey('space')
->setDescription( ->setDescription(
pht('Shifts the object in the Spaces application.')) pht('Shifts the object in the Spaces application.'))
->setAliases(array('space', 'policy.space')) ->setAliases(array('space', 'policy.space'))
@ -126,6 +137,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject {
$edge_field = id(new PhabricatorDatasourceEditField()) $edge_field = id(new PhabricatorDatasourceEditField())
->setKey('projectPHIDs') ->setKey('projectPHIDs')
->setLabel(pht('Projects')) ->setLabel(pht('Projects'))
->setEditTypeKey('projects')
->setDescription( ->setDescription(
pht( pht(
'Add or remove associated projects.')) 'Add or remove associated projects.'))
@ -154,6 +166,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject {
$subscribers_field = id(new PhabricatorDatasourceEditField()) $subscribers_field = id(new PhabricatorDatasourceEditField())
->setKey('subscriberPHIDs') ->setKey('subscriberPHIDs')
->setLabel(pht('Subscribers')) ->setLabel(pht('Subscribers'))
->setEditTypeKey('subscribers')
->setDescription(pht('Manage subscribers.')) ->setDescription(pht('Manage subscribers.'))
->setDatasource(new PhabricatorMetaMTAMailableDatasource()) ->setDatasource(new PhabricatorMetaMTAMailableDatasource())
->setAliases(array('subscriber', 'subscribers')) ->setAliases(array('subscriber', 'subscribers'))
@ -240,11 +253,9 @@ abstract class PhabricatorApplicationEditEngine extends Phobject {
if (!$object) { if (!$object) {
return new Aphront404Response(); return new Aphront404Response();
} }
$this->setIsCreate(false); $this->setIsCreate(false);
} else { } else {
$object = $this->newEditableObject(); $object = $this->newEditableObject();
$this->setIsCreate(true); $this->setIsCreate(true);
} }
@ -443,4 +454,171 @@ abstract class PhabricatorApplicationEditEngine extends Phobject {
return $actions; return $actions;
} }
/* -( 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();
$phid = $request->getValue('objectPHID');
if ($phid) {
$object = $this->newObjectQuery()
->setViewer($viewer)
->withPHIDs(array($phid))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$object) {
throw new Exception(pht('No such object with PHID "%s".', $phid));
}
$this->setIsCreate(false);
} else {
$object = $this->newEditableObject();
$this->setIsCreate(true);
}
$fields = $this->buildEditFields($object);
foreach ($fields as $field) {
$field
->setViewer($viewer)
->setObject($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<PhabricatorEditType> Supported edit types.
* @param PhabricatorApplicationTransaction Template transaction.
* @return list<PhabricatorApplicationTransaction> 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<string, PhabricatorEditType>
* @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() {
$object = $this->newEditableObject();
$fields = $this->buildEditFields($object);
return $this->getAllEditTypesFromFields($fields);
}
} }

View file

@ -0,0 +1,188 @@
<?php
abstract class PhabricatorApplicationEditEngineAPIMethod
extends ConduitAPIMethod {
abstract public function newEditEngine();
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<map<string, wild>>',
'objectPHID' => 'optional phid',
);
}
final protected function defineReturnType() {
return 'map<string, wild>';
}
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(<<<EOTEXT
This is a standard **ApplicationEditor** method which allows you to create and
modify objects by applying transactions.
Each transaction applies one change to the object. For example, to create an
object with a specific title or change the title of an existing object you might
start by building a transaction like this:
```lang=json, name=Example Single Transaction
{
"type": "title",
"value": "New Object Title"
}
```
By passing a list of transactions in the `transactions` parameter, you can
apply a sequence of edits. For example, you'll often pass a value like this to
create an object with several field values or apply changes to multiple fields:
```lang=json, name=Example Transaction List
[
{
"type": "title",
"value": "New Object Title"
},
{
"type": "body",
"value": "New body text for the object."
},
{
"type": "projects.add",
"value": ["PHID-PROJ-1111", "PHID-PROJ-2222"]
}
]
```
Exactly which types of edits are available depends on the object you're editing.
Creating Objects
----------------
To create an object, pass a list of `transactions` but leave `objectPHID`
empty. This will create a new object with the initial field values you
specify.
Editing Objects
---------------
To edit an object, pass a list of `transactions` and specify an object to
apply them to with `objectPHID`. This will apply the changes to the object.
Return Type
-----------
WARNING: The structure of the return value from these methods is likely to
change as ApplicationEditor evolves.
Return values look something like this for now:
```lang=json, name=Example Return Value
{
"object": {
"phid": "PHID-XXXX-1111"
},
"transactions": [
{
"phid": "PHID-YYYY-1111",
},
{
"phid": "PHID-YYYY-2222",
}
]
}
```
The `object` key contains information about the object which was created or
edited.
The `transactions` key contains information about the transactions which were
actually applied. For many reasons, the transactions which actually apply may
be greater or fewer in number than the transactions you provided, or may differ
in their nature in other ways.
Edit Types
==========
This API method supports these edit types:
EOTEXT
);
$key = pht('Key');
$summary = pht('Summary');
$description = pht('Description');
$head_type = pht('Type');
$table = array();
$table[] = "| {$key} | {$summary} |";
$table[] = '|--------|----------------|';
foreach ($types as $type) {
$edit_type = $type->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;
}
}

View file

@ -12,6 +12,8 @@ abstract class PhabricatorEditField extends Phobject {
private $transactionType; private $transactionType;
private $metadata = array(); private $metadata = array();
private $description; private $description;
private $editTypeKey;
public function setKey($key) { public function setKey($key) {
$this->key = $key; $this->key = $key;
@ -223,4 +225,75 @@ abstract class PhabricatorEditField extends Phobject {
return 'string'; return 'string';
} }
public function setEditTypeKey($edit_type_key) {
$this->editTypeKey = $edit_type_key;
return $this;
}
public function getEditTypeKey() {
if ($this->editTypeKey === null) {
return $this->getKey();
}
return $this->editTypeKey;
}
public function getEditTransactionTypes() {
$transaction_type = $this->getTransactionType();
$type_key = $this->getEditTypeKey();
// TODO: This is a pretty big pile of hard-coded hacks for now.
$edge_types = array(
PhabricatorTransactions::TYPE_EDGE => array(
'+' => pht('Add projects.'),
'-' => pht('Remove projects.'),
'=' => pht('Set associated projects, overwriting current value.'),
),
PhabricatorTransactions::TYPE_SUBSCRIBERS => array(
'+' => pht('Add subscribers.'),
'-' => pht('Remove subscribers.'),
'=' => pht('Set subscribers, overwriting current value.'),
),
);
if (isset($edge_types[$transaction_type])) {
$base = id(new PhabricatorEdgeEditType())
->setTransactionType($transaction_type)
->setMetadata($this->metadata);
$strings = $edge_types[$transaction_type];
$add = id(clone $base)
->setEditType($type_key.'.add')
->setEdgeOperation('+')
->setDescription($strings['+'])
->setValueDescription(pht('List of PHIDs to add.'));
$rem = id(clone $base)
->setEditType($type_key.'.remove')
->setEdgeOperation('-')
->setDescription($strings['-'])
->setValueDescription(pht('List of PHIDs to remove.'));
$set = id(clone $base)
->setEditType($type_key.'.set')
->setEdgeOperation('=')
->setDescription($strings['='])
->setValueDescription(pht('List of PHIDs to set.'));
return array(
$add,
$rem,
$set,
);
}
return array(
id(new PhabricatorSimpleEditType())
->setEditType($type_key)
->setTransactionType($transaction_type)
->setValueType($this->getHTTPParameterType())
->setDescription($this->getDescription())
->setMetadata($this->metadata),
);
}
} }

View file

@ -0,0 +1,51 @@
<?php
final class PhabricatorEdgeEditType extends PhabricatorEditType {
private $edgeOperation;
private $valueDescription;
public function setEdgeOperation($edge_operation) {
$this->edgeOperation = $edge_operation;
return $this;
}
public function getEdgeOperation() {
return $this->edgeOperation;
}
public function getValueType() {
return 'list<phid>';
}
public function generateTransaction(
PhabricatorApplicationTransaction $template,
array $spec) {
$value = idx($spec, 'value');
$value = array_fuse($value);
$value = array(
$this->getEdgeOperation() => $value,
);
$template
->setTransactionType($this->getTransactionType())
->setNewValue($value);
foreach ($this->getMetadata() as $key => $value) {
$template->setMetadataValue($key, $value);
}
return $template;
}
public function setValueDescription($value_description) {
$this->valueDescription = $value_description;
return $this;
}
public function getValueDescription() {
return $this->valueDescription;
}
}

View file

@ -0,0 +1,76 @@
<?php
abstract class PhabricatorEditType extends Phobject {
private $editType;
private $transactionType;
private $field;
private $description;
private $summary;
private $metadata = array();
public function setDescription($description) {
$this->description = $description;
return $this;
}
public function getDescription() {
return $this->description;
}
public function setSummary($summary) {
$this->summary = $summary;
return $this;
}
public function getSummary() {
if ($this->summary === null) {
return $this->getDescription();
}
return $this->summary;
}
public function setField(PhabricatorEditField $field) {
$this->field = $field;
return $this;
}
public function getField() {
return $this->field;
}
public function setEditType($edit_type) {
$this->editType = $edit_type;
return $this;
}
public function getEditType() {
return $this->editType;
}
public function setMetadata($metadata) {
$this->metadata = $metadata;
return $this;
}
public function getMetadata() {
return $this->metadata;
}
public function setTransactionType($transaction_type) {
$this->transactionType = $transaction_type;
return $this;
}
public function getTransactionType() {
return $this->transactionType;
}
abstract public function generateTransaction(
PhabricatorApplicationTransaction $template,
array $spec);
abstract public function getValueType();
abstract public function getValueDescription();
}

View file

@ -0,0 +1,41 @@
<?php
final class PhabricatorSimpleEditType extends PhabricatorEditType {
private $valueType;
private $valueDescription;
public function setValueType($value_type) {
$this->valueType = $value_type;
return $this;
}
public function getValueType() {
return $this->valueType;
}
public function generateTransaction(
PhabricatorApplicationTransaction $template,
array $spec) {
$template
->setTransactionType($this->getTransactionType())
->setNewValue(idx($spec, 'value'));
foreach ($this->getMetadata() as $key => $value) {
$template->setMetadataValue($key, $value);
}
return $template;
}
public function setValueDescription($value_description) {
$this->valueDescription = $value_description;
return $this;
}
public function getValueDescription() {
return $this->valueDescription;
}
}