@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

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

Modularize application extensions to EditEngine

Summary:
Ref T9132. Currently, EditEngine had some branchy-`instanceof` code like this:

```
if ($object instanceof Whatever) {
do_magic();
}

if ($object instanceof SomethingElse) {
do_other_magic();
}
```

...where `Whatever` and `SomethingElse` are first-party applications like ProjectsInterface and SubscribersInterface.

This kind of code is generally bad because third-parties can't add new stuff, and it suggest something is kind of hacky in its architecture. Ideally, we would eventually get rid of almost all of this.

T9789 is a similar discussion of this for the next layer down (`TransactionEditor`) and plans to get rid of branchy-instanceofs there too.

Since I'm about to add more stuff here (for Custom Fields), split it out first so I'm not digging us any deeper than I already dug us.

Broadly, this allows third-party extensions to add fields to every EditEngine UI if they want, like we do for Policies, Subscribers, Projects and Comments today (and CustomFields soon).

Test Plan:
{F1007575}

- Observed that all fields still appear on the form and seem to work correctly.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9132

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

+477 -146
+12
src/__phutil_library_map__.php
··· 1844 1844 'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php', 1845 1845 'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php', 1846 1846 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', 1847 + 'PhabricatorCommentEditEngineExtension' => 'applications/transactions/editengineextension/PhabricatorCommentEditEngineExtension.php', 1847 1848 'PhabricatorCommentEditField' => 'applications/transactions/editfield/PhabricatorCommentEditField.php', 1848 1849 'PhabricatorCommentEditType' => 'applications/transactions/edittype/PhabricatorCommentEditType.php', 1849 1850 'PhabricatorCommitBranchesField' => 'applications/repository/customfield/PhabricatorCommitBranchesField.php', ··· 2132 2133 'PhabricatorEditEngineConfigurationTransactionQuery' => 'applications/transactions/query/PhabricatorEditEngineConfigurationTransactionQuery.php', 2133 2134 'PhabricatorEditEngineConfigurationViewController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php', 2134 2135 'PhabricatorEditEngineController' => 'applications/transactions/controller/PhabricatorEditEngineController.php', 2136 + 'PhabricatorEditEngineExtension' => 'applications/transactions/editengineextension/PhabricatorEditEngineExtension.php', 2137 + 'PhabricatorEditEngineExtensionModule' => 'applications/transactions/editengineextension/PhabricatorEditEngineExtensionModule.php', 2135 2138 'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php', 2136 2139 'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php', 2137 2140 'PhabricatorEditEngineSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineSearchEngine.php', ··· 2706 2709 'PhabricatorPolicyDAO' => 'applications/policy/storage/PhabricatorPolicyDAO.php', 2707 2710 'PhabricatorPolicyDataTestCase' => 'applications/policy/__tests__/PhabricatorPolicyDataTestCase.php', 2708 2711 'PhabricatorPolicyEditController' => 'applications/policy/controller/PhabricatorPolicyEditController.php', 2712 + 'PhabricatorPolicyEditEngineExtension' => 'applications/policy/editor/PhabricatorPolicyEditEngineExtension.php', 2709 2713 'PhabricatorPolicyEditField' => 'applications/transactions/editfield/PhabricatorPolicyEditField.php', 2710 2714 'PhabricatorPolicyException' => 'applications/policy/exception/PhabricatorPolicyException.php', 2711 2715 'PhabricatorPolicyExplainController' => 'applications/policy/controller/PhabricatorPolicyExplainController.php', ··· 2797 2801 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', 2798 2802 'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php', 2799 2803 'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php', 2804 + 'PhabricatorProjectsEditEngineExtension' => 'applications/project/editor/PhabricatorProjectsEditEngineExtension.php', 2800 2805 'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php', 2801 2806 'PhabricatorProjectsPolicyRule' => 'applications/project/policyrule/PhabricatorProjectsPolicyRule.php', 2802 2807 'PhabricatorProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php', ··· 3087 3092 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php', 3088 3093 'PhabricatorSubscriptionsApplication' => 'applications/subscriptions/application/PhabricatorSubscriptionsApplication.php', 3089 3094 'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php', 3095 + 'PhabricatorSubscriptionsEditEngineExtension' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php', 3090 3096 'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php', 3091 3097 'PhabricatorSubscriptionsHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php', 3092 3098 'PhabricatorSubscriptionsListController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsListController.php', ··· 5896 5902 'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5897 5903 'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine', 5898 5904 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', 5905 + 'PhabricatorCommentEditEngineExtension' => 'PhabricatorEditEngineExtension', 5899 5906 'PhabricatorCommentEditField' => 'PhabricatorEditField', 5900 5907 'PhabricatorCommentEditType' => 'PhabricatorEditType', 5901 5908 'PhabricatorCommitBranchesField' => 'PhabricatorCommitCustomField', ··· 6232 6239 'PhabricatorEditEngineConfigurationTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 6233 6240 'PhabricatorEditEngineConfigurationViewController' => 'PhabricatorEditEngineController', 6234 6241 'PhabricatorEditEngineController' => 'PhabricatorApplicationTransactionController', 6242 + 'PhabricatorEditEngineExtension' => 'Phobject', 6243 + 'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule', 6235 6244 'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController', 6236 6245 'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 6237 6246 'PhabricatorEditEngineSearchEngine' => 'PhabricatorApplicationSearchEngine', ··· 6898 6907 'PhabricatorPolicyDAO' => 'PhabricatorLiskDAO', 6899 6908 'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase', 6900 6909 'PhabricatorPolicyEditController' => 'PhabricatorPolicyController', 6910 + 'PhabricatorPolicyEditEngineExtension' => 'PhabricatorEditEngineExtension', 6901 6911 'PhabricatorPolicyEditField' => 'PhabricatorEditField', 6902 6912 'PhabricatorPolicyException' => 'Exception', 6903 6913 'PhabricatorPolicyExplainController' => 'PhabricatorPolicyController', ··· 7014 7024 'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 7015 7025 'PhabricatorProjectViewController' => 'PhabricatorProjectController', 7016 7026 'PhabricatorProjectWatchController' => 'PhabricatorProjectController', 7027 + 'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension', 7017 7028 'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField', 7018 7029 'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule', 7019 7030 'PhabricatorProtocolAdapter' => 'Phobject', ··· 7360 7371 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 7361 7372 'PhabricatorSubscriptionsApplication' => 'PhabricatorApplication', 7362 7373 'PhabricatorSubscriptionsEditController' => 'PhabricatorController', 7374 + 'PhabricatorSubscriptionsEditEngineExtension' => 'PhabricatorEditEngineExtension', 7363 7375 'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor', 7364 7376 'PhabricatorSubscriptionsHeraldAction' => 'HeraldAction', 7365 7377 'PhabricatorSubscriptionsListController' => 'PhabricatorController',
+118
src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyEditEngineExtension 4 + extends PhabricatorEditEngineExtension { 5 + 6 + const EXTENSIONKEY = 'policy.policy'; 7 + 8 + public function getExtensionPriority() { 9 + return 250; 10 + } 11 + 12 + public function isExtensionEnabled() { 13 + return true; 14 + } 15 + 16 + public function getExtensionName() { 17 + return pht('Policies'); 18 + } 19 + 20 + public function supportsObject( 21 + PhabricatorEditEngine $engine, 22 + PhabricatorApplicationTransactionInterface $object) { 23 + return ($object instanceof PhabricatorPolicyInterface); 24 + } 25 + 26 + public function buildCustomEditFields( 27 + PhabricatorEditEngine $engine, 28 + PhabricatorApplicationTransactionInterface $object) { 29 + 30 + $viewer = $engine->getViewer(); 31 + 32 + $editor = $object->getApplicationTransactionEditor(); 33 + $types = $editor->getTransactionTypesForObject($object); 34 + $types = array_fuse($types); 35 + 36 + $policies = id(new PhabricatorPolicyQuery()) 37 + ->setViewer($viewer) 38 + ->setObject($object) 39 + ->execute(); 40 + 41 + $map = array( 42 + PhabricatorTransactions::TYPE_VIEW_POLICY => array( 43 + 'key' => 'policy.view', 44 + 'aliases' => array('view'), 45 + 'capability' => PhabricatorPolicyCapability::CAN_VIEW, 46 + 'label' => pht('View Policy'), 47 + 'description' => pht('Controls who can view the object.'), 48 + 'edit' => 'view', 49 + ), 50 + PhabricatorTransactions::TYPE_EDIT_POLICY => array( 51 + 'key' => 'policy.edit', 52 + 'aliases' => array('edit'), 53 + 'capability' => PhabricatorPolicyCapability::CAN_EDIT, 54 + 'label' => pht('Edit Policy'), 55 + 'description' => pht('Controls who can edit the object.'), 56 + 'edit' => 'edit', 57 + ), 58 + PhabricatorTransactions::TYPE_JOIN_POLICY => array( 59 + 'key' => 'policy.join', 60 + 'aliases' => array('join'), 61 + 'capability' => PhabricatorPolicyCapability::CAN_JOIN, 62 + 'label' => pht('Join Policy'), 63 + 'description' => pht('Controls who can join the object.'), 64 + 'edit' => 'join', 65 + ), 66 + ); 67 + 68 + $fields = array(); 69 + foreach ($map as $type => $spec) { 70 + if (empty($types[$type])) { 71 + continue; 72 + } 73 + 74 + $capability = $spec['capability']; 75 + $key = $spec['key']; 76 + $aliases = $spec['aliases']; 77 + $label = $spec['label']; 78 + $description = $spec['description']; 79 + $edit = $spec['edit']; 80 + 81 + $policy_field = id(new PhabricatorPolicyEditField()) 82 + ->setKey($key) 83 + ->setLabel($label) 84 + ->setDescription($description) 85 + ->setAliases($aliases) 86 + ->setCapability($capability) 87 + ->setPolicies($policies) 88 + ->setTransactionType($type) 89 + ->setEditTypeKey($edit) 90 + ->setValue($object->getPolicy($capability)); 91 + $fields[] = $policy_field; 92 + 93 + if (!($object instanceof PhabricatorSpacesInterface)) { 94 + if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { 95 + $type_space = PhabricatorTransactions::TYPE_SPACE; 96 + if (isset($types[$type_space])) { 97 + $space_field = id(new PhabricatorSpaceEditField()) 98 + ->setKey('spacePHID') 99 + ->setLabel(pht('Space')) 100 + ->setEditTypeKey('space') 101 + ->setDescription( 102 + pht('Shifts the object in the Spaces application.')) 103 + ->setIsReorderable(false) 104 + ->setAliases(array('space', 'policy.space')) 105 + ->setTransactionType($type_space) 106 + ->setValue($object->getSpacePHID()); 107 + $fields[] = $space_field; 108 + 109 + $policy_field->setSpaceField($space_field); 110 + } 111 + } 112 + } 113 + } 114 + 115 + return $fields; 116 + } 117 + 118 + }
+60
src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectsEditEngineExtension 4 + extends PhabricatorEditEngineExtension { 5 + 6 + const EXTENSIONKEY = 'projects.projects'; 7 + 8 + public function getExtensionPriority() { 9 + return 500; 10 + } 11 + 12 + public function isExtensionEnabled() { 13 + return PhabricatorApplication::isClassInstalled( 14 + 'PhabricatorProjectApplication'); 15 + } 16 + 17 + public function getExtensionName() { 18 + return pht('Projects'); 19 + } 20 + 21 + public function supportsObject( 22 + PhabricatorEditEngine $engine, 23 + PhabricatorApplicationTransactionInterface $object) { 24 + 25 + return ($object instanceof PhabricatorProjectInterface); 26 + } 27 + 28 + public function buildCustomEditFields( 29 + PhabricatorEditEngine $engine, 30 + PhabricatorApplicationTransactionInterface $object) { 31 + 32 + $edge_type = PhabricatorTransactions::TYPE_EDGE; 33 + $project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 34 + 35 + $object_phid = $object->getPHID(); 36 + if ($object_phid) { 37 + $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 38 + $object_phid, 39 + $project_edge_type); 40 + $project_phids = array_reverse($project_phids); 41 + } else { 42 + $project_phids = array(); 43 + } 44 + 45 + $projects_field = id(new PhabricatorProjectsEditField()) 46 + ->setKey('projectPHIDs') 47 + ->setLabel(pht('Projects')) 48 + ->setEditTypeKey('projects') 49 + ->setDescription(pht('Add or remove associated projects.')) 50 + ->setAliases(array('project', 'projects')) 51 + ->setTransactionType($edge_type) 52 + ->setMetadataValue('edge:type', $project_edge_type) 53 + ->setValue($project_phids); 54 + 55 + return array( 56 + $projects_field, 57 + ); 58 + } 59 + 60 + }
+56
src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php
··· 1 + <?php 2 + 3 + final class PhabricatorSubscriptionsEditEngineExtension 4 + extends PhabricatorEditEngineExtension { 5 + 6 + const EXTENSIONKEY = 'subscriptions.subscribers'; 7 + 8 + public function getExtensionPriority() { 9 + return 750; 10 + } 11 + 12 + public function isExtensionEnabled() { 13 + return true; 14 + } 15 + 16 + public function getExtensionName() { 17 + return pht('Subscriptions'); 18 + } 19 + 20 + public function supportsObject( 21 + PhabricatorEditEngine $engine, 22 + PhabricatorApplicationTransactionInterface $object) { 23 + return ($object instanceof PhabricatorSubscribableInterface); 24 + } 25 + 26 + public function buildCustomEditFields( 27 + PhabricatorEditEngine $engine, 28 + PhabricatorApplicationTransactionInterface $object) { 29 + 30 + $subscribers_type = PhabricatorTransactions::TYPE_SUBSCRIBERS; 31 + 32 + $object_phid = $object->getPHID(); 33 + if ($object_phid) { 34 + $sub_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( 35 + $object_phid); 36 + } else { 37 + // TODO: Allow applications to provide default subscribers? Maniphest 38 + // does this at a minimum. 39 + $sub_phids = array(); 40 + } 41 + 42 + $subscribers_field = id(new PhabricatorSubscribersEditField()) 43 + ->setKey('subscriberPHIDs') 44 + ->setLabel(pht('Subscribers')) 45 + ->setEditTypeKey('subscribers') 46 + ->setDescription(pht('Manage subscribers.')) 47 + ->setAliases(array('subscriber', 'subscribers')) 48 + ->setTransactionType($subscribers_type) 49 + ->setValue($sub_phids); 50 + 51 + return array( 52 + $subscribers_field, 53 + ); 54 + } 55 + 56 + }
+26 -146
src/applications/transactions/editengine/PhabricatorEditEngine.php
··· 73 73 74 74 final protected function buildEditFields($object) { 75 75 $viewer = $this->getViewer(); 76 - $editor = $object->getApplicationTransactionEditor(); 77 - 78 - $types = $editor->getTransactionTypesForObject($object); 79 - $types = array_fuse($types); 80 76 81 77 $fields = $this->buildCustomEditFields($object); 82 78 83 - if ($object instanceof PhabricatorPolicyInterface) { 84 - $policies = id(new PhabricatorPolicyQuery()) 85 - ->setViewer($viewer) 86 - ->setObject($object) 87 - ->execute(); 88 - 89 - $map = array( 90 - PhabricatorTransactions::TYPE_VIEW_POLICY => array( 91 - 'key' => 'policy.view', 92 - 'aliases' => array('view'), 93 - 'capability' => PhabricatorPolicyCapability::CAN_VIEW, 94 - 'label' => pht('View Policy'), 95 - 'description' => pht('Controls who can view the object.'), 96 - 'edit' => 'view', 97 - ), 98 - PhabricatorTransactions::TYPE_EDIT_POLICY => array( 99 - 'key' => 'policy.edit', 100 - 'aliases' => array('edit'), 101 - 'capability' => PhabricatorPolicyCapability::CAN_EDIT, 102 - 'label' => pht('Edit Policy'), 103 - 'description' => pht('Controls who can edit the object.'), 104 - 'edit' => 'edit', 105 - ), 106 - PhabricatorTransactions::TYPE_JOIN_POLICY => array( 107 - 'key' => 'policy.join', 108 - 'aliases' => array('join'), 109 - 'capability' => PhabricatorPolicyCapability::CAN_JOIN, 110 - 'label' => pht('Join Policy'), 111 - 'description' => pht('Controls who can join the object.'), 112 - 'edit' => 'join', 113 - ), 114 - ); 115 - 116 - foreach ($map as $type => $spec) { 117 - if (empty($types[$type])) { 118 - continue; 119 - } 120 - 121 - $capability = $spec['capability']; 122 - $key = $spec['key']; 123 - $aliases = $spec['aliases']; 124 - $label = $spec['label']; 125 - $description = $spec['description']; 126 - $edit = $spec['edit']; 127 - 128 - $policy_field = id(new PhabricatorPolicyEditField()) 129 - ->setKey($key) 130 - ->setLabel($label) 131 - ->setDescription($description) 132 - ->setAliases($aliases) 133 - ->setCapability($capability) 134 - ->setPolicies($policies) 135 - ->setTransactionType($type) 136 - ->setEditTypeKey($edit) 137 - ->setValue($object->getPolicy($capability)); 138 - $fields[] = $policy_field; 139 - 140 - if ($object instanceof PhabricatorSpacesInterface) { 141 - if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { 142 - $type_space = PhabricatorTransactions::TYPE_SPACE; 143 - if (isset($types[$type_space])) { 144 - $space_field = id(new PhabricatorSpaceEditField()) 145 - ->setKey('spacePHID') 146 - ->setLabel(pht('Space')) 147 - ->setEditTypeKey('space') 148 - ->setDescription( 149 - pht('Shifts the object in the Spaces application.')) 150 - ->setIsReorderable(false) 151 - ->setAliases(array('space', 'policy.space')) 152 - ->setTransactionType($type_space) 153 - ->setValue($object->getSpacePHID()); 154 - $fields[] = $space_field; 155 - 156 - $policy_field->setSpaceField($space_field); 157 - } 158 - } 159 - } 160 - } 161 - } 162 - 163 - $edge_type = PhabricatorTransactions::TYPE_EDGE; 164 - $object_phid = $object->getPHID(); 165 - 166 - $project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 79 + $extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions(); 80 + foreach ($extensions as $extension) { 81 + $extension->setViewer($viewer); 167 82 168 - if ($object instanceof PhabricatorProjectInterface) { 169 - if (isset($types[$edge_type])) { 170 - if ($object_phid) { 171 - $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 172 - $object_phid, 173 - $project_edge_type); 174 - $project_phids = array_reverse($project_phids); 175 - } else { 176 - $project_phids = array(); 177 - } 178 - 179 - $edge_field = id(new PhabricatorProjectsEditField()) 180 - ->setKey('projectPHIDs') 181 - ->setLabel(pht('Projects')) 182 - ->setEditTypeKey('projects') 183 - ->setDescription(pht('Add or remove associated projects.')) 184 - ->setAliases(array('project', 'projects')) 185 - ->setTransactionType($edge_type) 186 - ->setMetadataValue('edge:type', $project_edge_type) 187 - ->setValue($project_phids); 188 - $fields[] = $edge_field; 83 + if (!$extension->supportsObject($this, $object)) { 84 + continue; 189 85 } 190 - } 191 86 192 - $subscribers_type = PhabricatorTransactions::TYPE_SUBSCRIBERS; 87 + $extension_fields = $extension->buildCustomEditFields($this, $object); 193 88 194 - if ($object instanceof PhabricatorSubscribableInterface) { 195 - if (isset($types[$subscribers_type])) { 196 - if ($object_phid) { 197 - $sub_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( 198 - $object_phid); 199 - } else { 200 - // TODO: Allow applications to provide default subscribers; Maniphest 201 - // does this at a minimum. 202 - $sub_phids = array(); 203 - } 89 + // TODO: Validate this in more detail with a more tailored error. 90 + assert_instances_of($extension_fields, 'PhabricatorEditField'); 204 91 205 - $subscribers_field = id(new PhabricatorSubscribersEditField()) 206 - ->setKey('subscriberPHIDs') 207 - ->setLabel(pht('Subscribers')) 208 - ->setEditTypeKey('subscribers') 209 - ->setDescription(pht('Manage subscribers.')) 210 - ->setAliases(array('subscriber', 'subscribers')) 211 - ->setTransactionType($subscribers_type) 212 - ->setValue($sub_phids); 213 - $fields[] = $subscribers_field; 92 + foreach ($extension_fields as $field) { 93 + $fields[] = $field; 214 94 } 215 - } 216 - 217 - $xaction = $object->getApplicationTransactionTemplate(); 218 - $comment = $xaction->getApplicationTransactionCommentObject(); 219 - if ($comment) { 220 - $comment_type = PhabricatorTransactions::TYPE_COMMENT; 221 - 222 - $comment_field = id(new PhabricatorCommentEditField()) 223 - ->setKey('comment') 224 - ->setLabel(pht('Comments')) 225 - ->setDescription(pht('Add comments.')) 226 - ->setAliases(array('comments')) 227 - ->setIsHidden(true) 228 - ->setTransactionType($comment_type) 229 - ->setValue(null); 230 - $fields[] = $comment_field; 231 95 } 232 96 233 97 $config = $this->getEditEngineConfiguration(); ··· 678 542 $validation_exception = null; 679 543 if ($request->isFormPost()) { 680 544 foreach ($fields as $field) { 545 + $field->setIsSubmittedForm(true); 546 + 681 547 if ($field->getIsLocked() || $field->getIsHidden()) { 682 548 continue; 683 549 } ··· 709 575 ->setURI($this->getObjectViewURI($object)); 710 576 } catch (PhabricatorApplicationTransactionValidationException $ex) { 711 577 $validation_exception = $ex; 578 + 579 + foreach ($fields as $field) { 580 + $xaction_type = $field->getTransactionType(); 581 + if ($xaction_type === null) { 582 + continue; 583 + } 584 + 585 + $message = $ex->getShortMessage($xaction_type); 586 + if ($message === null) { 587 + continue; 588 + } 589 + 590 + $field->setControlError($message); 591 + } 712 592 } 713 593 } else { 714 594 if ($this->getIsCreate()) {
+55
src/applications/transactions/editengineextension/PhabricatorCommentEditEngineExtension.php
··· 1 + <?php 2 + 3 + final class PhabricatorCommentEditEngineExtension 4 + extends PhabricatorEditEngineExtension { 5 + 6 + const EXTENSIONKEY = 'transactions.comment'; 7 + 8 + public function getExtensionPriority() { 9 + return 9000; 10 + } 11 + 12 + public function isExtensionEnabled() { 13 + return true; 14 + } 15 + 16 + public function getExtensionName() { 17 + return pht('Comments'); 18 + } 19 + 20 + public function supportsObject( 21 + PhabricatorEditEngine $engine, 22 + PhabricatorApplicationTransactionInterface $object) { 23 + 24 + $xaction = $object->getApplicationTransactionTemplate(); 25 + 26 + try { 27 + $comment = $xaction->getApplicationTransactionCommentObject(); 28 + } catch (PhutilMethodNotImplementedException $ex) { 29 + $comment = null; 30 + } 31 + 32 + return (bool)$comment; 33 + } 34 + 35 + public function buildCustomEditFields( 36 + PhabricatorEditEngine $engine, 37 + PhabricatorApplicationTransactionInterface $object) { 38 + 39 + $comment_type = PhabricatorTransactions::TYPE_COMMENT; 40 + 41 + $comment_field = id(new PhabricatorCommentEditField()) 42 + ->setKey('comment') 43 + ->setLabel(pht('Comments')) 44 + ->setDescription(pht('Add comments.')) 45 + ->setAliases(array('comments')) 46 + ->setIsHidden(true) 47 + ->setTransactionType($comment_type) 48 + ->setValue(null); 49 + 50 + return array( 51 + $comment_field, 52 + ); 53 + } 54 + 55 + }
+55
src/applications/transactions/editengineextension/PhabricatorEditEngineExtension.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorEditEngineExtension extends Phobject { 4 + 5 + private $viewer; 6 + 7 + final public function getExtensionKey() { 8 + return $this->getPhobjectClassConstant('EXTENSIONKEY'); 9 + } 10 + 11 + final public function setViewer($viewer) { 12 + $this->viewer = $viewer; 13 + return $this; 14 + } 15 + 16 + final public function getViewer() { 17 + return $this->viewer; 18 + } 19 + 20 + public function getExtensionPriority() { 21 + return 1000; 22 + } 23 + 24 + abstract public function isExtensionEnabled(); 25 + abstract public function getExtensionName(); 26 + 27 + abstract public function supportsObject( 28 + PhabricatorEditEngine $engine, 29 + PhabricatorApplicationTransactionInterface $object); 30 + 31 + abstract public function buildCustomEditFields( 32 + PhabricatorEditEngine $engine, 33 + PhabricatorApplicationTransactionInterface $object); 34 + 35 + final public static function getAllExtensions() { 36 + return id(new PhutilClassMapQuery()) 37 + ->setAncestorClass(__CLASS__) 38 + ->setUniqueMethod('getExtensionKey') 39 + ->setSortMethod('getExtensionPriority') 40 + ->execute(); 41 + } 42 + 43 + final public static function getAllEnabledExtensions() { 44 + $extensions = self::getAllExtensions(); 45 + 46 + foreach ($extensions as $key => $extension) { 47 + if (!$extension->isExtensionEnabled()) { 48 + unset($extensions[$key]); 49 + } 50 + } 51 + 52 + return $extensions; 53 + } 54 + 55 + }
+52
src/applications/transactions/editengineextension/PhabricatorEditEngineExtensionModule.php
··· 1 + <?php 2 + 3 + final class PhabricatorEditEngineExtensionModule 4 + extends PhabricatorConfigModule { 5 + 6 + public function getModuleKey() { 7 + return 'editengine'; 8 + } 9 + 10 + public function getModuleName() { 11 + return pht('EditEngine Extensions'); 12 + } 13 + 14 + public function renderModuleStatus(AphrontRequest $request) { 15 + $viewer = $request->getViewer(); 16 + 17 + $extensions = PhabricatorEditEngineExtension::getAllExtensions(); 18 + 19 + $rows = array(); 20 + foreach ($extensions as $extension) { 21 + $rows[] = array( 22 + $extension->getExtensionPriority(), 23 + get_class($extension), 24 + $extension->getExtensionName(), 25 + $extension->isExtensionEnabled() 26 + ? pht('Yes') 27 + : pht('No'), 28 + ); 29 + } 30 + 31 + $table = id(new AphrontTableView($rows)) 32 + ->setHeaders( 33 + array( 34 + pht('Priority'), 35 + pht('Class'), 36 + pht('Name'), 37 + pht('Enabled'), 38 + )) 39 + ->setColumnClasses( 40 + array( 41 + null, 42 + null, 43 + 'wide pri', 44 + null, 45 + )); 46 + 47 + return id(new PHUIObjectBoxView()) 48 + ->setHeaderText(pht('EditEngine Extensions')) 49 + ->setTable($table); 50 + } 51 + 52 + }
+3
src/applications/transactions/editfield/PhabricatorDatasourceEditField.php
··· 11 11 } 12 12 13 13 public function getDatasource() { 14 + if (!$this->datasource) { 15 + throw new PhutilInvalidStateException('setDatasource'); 16 + } 14 17 return $this->datasource; 15 18 } 16 19
+40
src/applications/transactions/editfield/PhabricatorEditField.php
··· 13 13 private $metadata = array(); 14 14 private $description; 15 15 private $editTypeKey; 16 + private $isRequired; 16 17 17 18 private $isLocked; 18 19 private $isHidden; 19 20 20 21 private $isPreview; 21 22 private $isEditDefaults; 23 + private $isSubmittedForm; 24 + private $controlError; 22 25 23 26 private $isReorderable = true; 24 27 private $isDefaultable = true; ··· 145 148 throw new PhutilMethodNotImplementedException(); 146 149 } 147 150 151 + public function setIsSubmittedForm($is_submitted) { 152 + $this->isSubmittedForm = $is_submitted; 153 + return $this; 154 + } 155 + 156 + public function getIsSubmittedForm() { 157 + return $this->isSubmittedForm; 158 + } 159 + 160 + public function setIsRequired($is_required) { 161 + $this->isRequired = $is_required; 162 + return $this; 163 + } 164 + 165 + public function getIsRequired() { 166 + return $this->isRequired; 167 + } 168 + 169 + public function setControlError($control_error) { 170 + $this->controlError = $control_error; 171 + return $this; 172 + } 173 + 174 + public function getControlError() { 175 + return $this->controlError; 176 + } 177 + 148 178 protected function renderControl() { 149 179 $control = $this->newControl(); 150 180 if ($control === null) { ··· 175 205 } 176 206 177 207 $control->setDisabled($disabled); 208 + 209 + 210 + if ($this->getIsSubmittedForm()) { 211 + $error = $this->getControlError(); 212 + if ($error !== null) { 213 + $control->setError($error); 214 + } 215 + } else if ($this->getIsRequired()) { 216 + $control->setError(true); 217 + } 178 218 179 219 return $control; 180 220 }