@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.

Implement modular transactions for application policy changes

Summary: Still needs some cleanup, but ready for review in broad outline form.

Test Plan:
Made lots of policy changes to the Badges application and confirmed expected rows in `application_xactions`, confirmed expected changes to `phabricator.application-settings`.

See example output (not quite working for custom policy objects) here:

{F4922240}

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin, chad, epriestley

Maniphest Tasks: T11476

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

+371 -61
+6
src/__phutil_library_map__.php
··· 1849 1849 'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php', 1850 1850 'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php', 1851 1851 'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php', 1852 + 'PhabricatorApplicationEditEngine' => 'applications/meta/editor/PhabricatorApplicationEditEngine.php', 1852 1853 'PhabricatorApplicationEditHTTPParameterHelpView' => 'applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php', 1854 + 'PhabricatorApplicationEditor' => 'applications/meta/editor/PhabricatorApplicationEditor.php', 1853 1855 'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php', 1854 1856 'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php', 1857 + 'PhabricatorApplicationPolicyChangeTransaction' => 'applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php', 1855 1858 'PhabricatorApplicationProfileMenuItem' => 'applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php', 1856 1859 'PhabricatorApplicationQuery' => 'applications/meta/query/PhabricatorApplicationQuery.php', 1857 1860 'PhabricatorApplicationSchemaSpec' => 'applications/meta/storage/PhabricatorApplicationSchemaSpec.php', ··· 6889 6892 'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource', 6890 6893 'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController', 6891 6894 'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController', 6895 + 'PhabricatorApplicationEditEngine' => 'PhabricatorEditEngine', 6892 6896 'PhabricatorApplicationEditHTTPParameterHelpView' => 'AphrontView', 6897 + 'PhabricatorApplicationEditor' => 'PhabricatorApplicationTransactionEditor', 6893 6898 'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController', 6894 6899 'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController', 6900 + 'PhabricatorApplicationPolicyChangeTransaction' => 'PhabricatorApplicationTransactionType', 6895 6901 'PhabricatorApplicationProfileMenuItem' => 'PhabricatorProfileMenuItem', 6896 6902 'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 6897 6903 'PhabricatorApplicationSchemaSpec' => 'PhabricatorConfigSchemaSpec',
-1
src/applications/base/PhabricatorApplication.php
··· 284 284 throw new PhutilMethodNotImplementedException(); 285 285 } 286 286 287 - 288 287 /* -( Fact Integration )--------------------------------------------------- */ 289 288 290 289
+6
src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
··· 38 38 $header->setStatus('fa-ban', 'dark', pht('Uninstalled')); 39 39 } 40 40 41 + $timeline = $this->buildTransactionTimeline( 42 + $selected, 43 + new PhabricatorApplicationApplicationTransactionQuery()); 44 + $timeline->setShouldTerminate(true); 45 + 41 46 $curtain = $this->buildCurtain($selected); 42 47 $details = $this->buildPropertySectionView($selected); 43 48 $policies = $this->buildPolicyView($selected); ··· 61 66 ->setMainColumn(array( 62 67 $policies, 63 68 $panels, 69 + $timeline, 64 70 )) 65 71 ->addPropertySection(pht('Details'), $details); 66 72
+29 -53
src/applications/meta/controller/PhabricatorApplicationEditController.php
··· 30 30 ->execute(); 31 31 32 32 if ($request->isFormPost()) { 33 + $xactions = array(); 34 + 33 35 $result = array(); 36 + $template = $application->getApplicationTransactionTemplate(); 34 37 foreach ($application->getCapabilities() as $capability) { 38 + if (!$application->isCapabilityEditable($capability)) { 39 + continue; 40 + } 41 + 35 42 $old = $application->getPolicy($capability); 36 43 $new = $request->getStr('policy:'.$capability); 37 44 ··· 40 47 continue; 41 48 } 42 49 43 - if (empty($policies[$new])) { 44 - // Not a standard policy, check for a custom policy. 45 - $policy = id(new PhabricatorPolicyQuery()) 46 - ->setViewer($user) 47 - ->withPHIDs(array($new)) 48 - ->executeOne(); 49 - if (!$policy) { 50 - // Not a custom policy either. Can't set the policy to something 51 - // invalid, so skip this. 52 - continue; 53 - } 54 - } 50 + $result[$capability] = $new; 55 51 56 - if ($new == PhabricatorPolicies::POLICY_PUBLIC) { 57 - $capobj = PhabricatorPolicyCapability::getCapabilityByKey( 58 - $capability); 59 - if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) { 60 - // Can't set non-public policies to public. 61 - continue; 62 - } 63 - } 64 - 65 - $result[$capability] = $new; 52 + $xactions[] = id(clone $template) 53 + ->setTransactionType( 54 + PhabricatorApplicationPolicyChangeTransaction::TRANSACTIONTYPE) 55 + ->setMetadataValue( 56 + PhabricatorApplicationPolicyChangeTransaction::METADATA_ATTRIBUTE, 57 + $capability) 58 + ->setNewValue($new); 66 59 } 67 60 68 61 if ($result) { 69 - $key = 'phabricator.application-settings'; 70 - $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); 71 - $value = $config_entry->getValue(); 62 + $editor = id(new PhabricatorApplicationEditor()) 63 + ->setActor($user) 64 + ->setContentSourceFromRequest($request) 65 + ->setContinueOnNoEffect(true) 66 + ->setContinueOnMissingFields(true); 72 67 73 - $phid = $application->getPHID(); 74 - if (empty($value[$phid])) { 75 - $value[$application->getPHID()] = array(); 76 - } 77 - if (empty($value[$phid]['policy'])) { 78 - $value[$phid]['policy'] = array(); 68 + try { 69 + $editor->applyTransactions($application, $xactions); 70 + return id(new AphrontRedirectResponse())->setURI($view_uri); 71 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 72 + $validation_exception = $ex; 79 73 } 80 74 81 - $value[$phid]['policy'] = $result + $value[$phid]['policy']; 82 - 83 - // Don't allow users to make policy edits which would lock them out of 84 - // applications, since they would be unable to undo those actions. 85 - PhabricatorEnv::overrideConfig($key, $value); 86 - PhabricatorPolicyFilter::mustRetainCapability( 87 - $user, 88 - $application, 89 - PhabricatorPolicyCapability::CAN_VIEW); 90 - 91 - PhabricatorPolicyFilter::mustRetainCapability( 92 - $user, 93 - $application, 94 - PhabricatorPolicyCapability::CAN_EDIT); 95 - 96 - PhabricatorConfigEditor::storeNewValue( 97 - $user, 98 - $config_entry, 99 - $value, 100 - PhabricatorContentSource::newFromRequest($request)); 75 + return $this->newDialog() 76 + ->setTitle('Validation Failed') 77 + ->setValidationException($validation_exception) 78 + ->addCancelButton($view_uri); 101 79 } 102 - 103 - return id(new AphrontRedirectResponse())->setURI($view_uri); 104 80 } 105 81 106 82 $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
+64
src/applications/meta/editor/PhabricatorApplicationEditEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationEditEngine 4 + extends PhabricatorEditEngine { 5 + 6 + const ENGINECONST = 'application.application'; 7 + 8 + public function getEngineApplicationClass() { 9 + return 'PhabricatorApplicationsApplication'; 10 + } 11 + 12 + public function getEngineName() { 13 + return pht('Applications'); 14 + } 15 + 16 + public function getSummaryHeader() { 17 + return pht('Configure Application Forms'); 18 + } 19 + 20 + public function getSummaryText() { 21 + return pht('Configure creation and editing forms in Applications.'); 22 + } 23 + 24 + public function isEngineConfigurable() { 25 + return false; 26 + } 27 + 28 + protected function newEditableObject() { 29 + throw new PhutilMethodNotImplementedException(); 30 + } 31 + 32 + protected function newObjectQuery() { 33 + return new PhabricatorApplicationQuery(); 34 + } 35 + 36 + protected function getObjectCreateTitleText($object) { 37 + return pht('Create New Application'); 38 + } 39 + 40 + protected function getObjectEditTitleText($object) { 41 + return pht('Edit Application: %s', $object->getName()); 42 + } 43 + 44 + protected function getObjectEditShortText($object) { 45 + return $object->getName(); 46 + } 47 + 48 + protected function getObjectCreateShortText() { 49 + return pht('Create Application'); 50 + } 51 + 52 + protected function getObjectName() { 53 + return pht('Application'); 54 + } 55 + 56 + protected function getObjectViewURI($object) { 57 + return $object->getViewURI(); 58 + } 59 + 60 + protected function buildCustomEditFields($object) { 61 + return array(); 62 + } 63 + 64 + }
+46
src/applications/meta/editor/PhabricatorApplicationEditor.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorApplicationsApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Application'); 12 + } 13 + 14 + protected function supportsSearch() { 15 + return true; 16 + } 17 + 18 + public function getTransactionTypes() { 19 + $types = parent::getTransactionTypes(); 20 + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 21 + $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 22 + 23 + return $types; 24 + } 25 + 26 + protected function shouldSendMail( 27 + PhabricatorLiskDAO $object, 28 + array $xactions) { 29 + return false; 30 + } 31 + 32 + protected function shouldPublishFeedStory( 33 + PhabricatorLiskDAO $object, 34 + array $xactions) { 35 + return true; 36 + } 37 + 38 + protected function getMailTo(PhabricatorLiskDAO $object) { 39 + return array(); 40 + } 41 + 42 + protected function getMailCC(PhabricatorLiskDAO $object) { 43 + return array(); 44 + } 45 + 46 + }
-4
src/applications/meta/storage/PhabricatorApplicationApplicationTransaction.php
··· 11 11 return PhabricatorApplicationApplicationPHIDType::TYPECONST; 12 12 } 13 13 14 - public function getApplicationTransactionCommentObject() { 15 - return new PhabricatorApplicationTransactionComment(); 16 - } 17 - 18 14 public function getBaseTransactionClass() { 19 15 return 'PhabricatorApplicationTransactionType'; 20 16 }
+197
src/applications/meta/xactions/PhabricatorApplicationPolicyChangeTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationPolicyChangeTransaction 4 + extends PhabricatorApplicationTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'application.policy'; 7 + const METADATA_ATTRIBUTE = 'capability.name'; 8 + 9 + private $policies; 10 + 11 + public function generateOldValue($object) { 12 + $application = $object; 13 + $capability = $this->getCapabilityName(); 14 + return $application->getPolicy($capability); 15 + } 16 + 17 + public function applyInternalEffects($object, $value) { 18 + $application = $object; 19 + $user = $this->getActor(); 20 + 21 + $key = 'phabricator.application-settings'; 22 + $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); 23 + $current_value = $config_entry->getValue(); 24 + 25 + $phid = $application->getPHID(); 26 + if (empty($current_value[$phid])) { 27 + $current_value[$application->getPHID()] = array(); 28 + } 29 + if (empty($current_value[$phid]['policy'])) { 30 + $current_value[$phid]['policy'] = array(); 31 + } 32 + 33 + $new = array($this->getCapabilityName() => $value); 34 + $current_value[$phid]['policy'] = $new + $current_value[$phid]['policy']; 35 + 36 + $editor = $this->getEditor(); 37 + $content_source = $editor->getContentSource(); 38 + PhabricatorConfigEditor::storeNewValue( 39 + $user, 40 + $config_entry, 41 + $current_value, 42 + $content_source); 43 + } 44 + 45 + public function getTitle() { 46 + $old = $this->renderPolicy($this->getOldValue()); 47 + $new = $this->renderPolicy($this->getNewValue()); 48 + 49 + return pht( 50 + '%s changed the "%s" policy from "%s" to "%s".', 51 + $this->renderAuthor(), 52 + $this->renderCapability(), 53 + $old, 54 + $new); 55 + } 56 + 57 + public function getTitleForFeed() { 58 + $old = $this->renderPolicy($this->getOldValue()); 59 + $new = $this->renderPolicy($this->getNewValue()); 60 + 61 + return pht( 62 + '%s changed the "%s" policy for application %s from "%s" to "%s".', 63 + $this->renderAuthor(), 64 + $this->renderCapability(), 65 + $this->renderObject(), 66 + $old, 67 + $new); 68 + } 69 + 70 + public function validateTransactions($object, array $xactions) { 71 + $user = $this->getActor(); 72 + $application = $object; 73 + $policies = id(new PhabricatorPolicyQuery()) 74 + ->setViewer($user) 75 + ->setObject($application) 76 + ->execute(); 77 + 78 + $errors = array(); 79 + foreach ($xactions as $xaction) { 80 + $new = $xaction->getNewValue(); 81 + $capability = $xaction->getMetadataValue(self::METADATA_ATTRIBUTE); 82 + 83 + if (empty($policies[$new])) { 84 + // Not a standard policy, check for a custom policy. 85 + $policy = id(new PhabricatorPolicyQuery()) 86 + ->setViewer($user) 87 + ->withPHIDs(array($new)) 88 + ->executeOne(); 89 + if (!$policy) { 90 + $errors[] = $this->newInvalidError( 91 + pht('Policy does not exist.')); 92 + continue; 93 + } 94 + } else { 95 + $policy = idx($policies, $new); 96 + } 97 + 98 + if (!$policy->isValidPolicyForEdit()) { 99 + $errors[] = $this->newInvalidError( 100 + pht('Can\'t set the policy to a policy you can\'t view!')); 101 + continue; 102 + } 103 + 104 + if ($new == PhabricatorPolicies::POLICY_PUBLIC) { 105 + $capobj = PhabricatorPolicyCapability::getCapabilityByKey( 106 + $capability); 107 + if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) { 108 + $errors[] = $this->newInvalidError( 109 + pht('Can\'t set non-public policies to public.')); 110 + continue; 111 + } 112 + } 113 + 114 + if (!$application->isCapabilityEditable($capability)) { 115 + $errors[] = $this->newInvalidError( 116 + pht('Capability "%s" is not editable for this application.', 117 + $capability)); 118 + continue; 119 + } 120 + } 121 + 122 + // If we're changing these policies, the viewer needs to still be able to 123 + // view or edit the application under the new policy. 124 + $validate_map = array( 125 + PhabricatorPolicyCapability::CAN_VIEW, 126 + PhabricatorPolicyCapability::CAN_EDIT, 127 + ); 128 + $validate_map = array_fill_keys($validate_map, array()); 129 + 130 + foreach ($xactions as $xaction) { 131 + $capability = $xaction->getMetadataValue(self::METADATA_ATTRIBUTE); 132 + if (!isset($validate_map[$capability])) { 133 + continue; 134 + } 135 + 136 + $validate_map[$capability][] = $xaction; 137 + } 138 + 139 + foreach ($validate_map as $capability => $cap_xactions) { 140 + if (!$cap_xactions) { 141 + continue; 142 + } 143 + 144 + $editor = $this->getEditor(); 145 + $policy_errors = $editor->validatePolicyTransaction( 146 + $object, 147 + $cap_xactions, 148 + self::TRANSACTIONTYPE, 149 + $capability); 150 + 151 + foreach ($policy_errors as $error) { 152 + $errors[] = $error; 153 + } 154 + } 155 + 156 + return $errors; 157 + } 158 + 159 + private function renderPolicy($name) { 160 + $policies = $this->getAllPolicies(); 161 + if (empty($policies[$name])) { 162 + // Not a standard policy, check for a custom policy. 163 + $policy = id(new PhabricatorPolicyQuery()) 164 + ->setViewer($this->getViewer()) 165 + ->withPHIDs(array($name)) 166 + ->executeOne(); 167 + $policies[$name] = $policy; 168 + } 169 + 170 + $policy = idx($policies, $name); 171 + return $this->renderValue($policy->getFullName()); 172 + } 173 + 174 + private function getAllPolicies() { 175 + if (!$this->policies) { 176 + $viewer = $this->getViewer(); 177 + $application = $this->getObject(); 178 + $this->policies = id(new PhabricatorPolicyQuery()) 179 + ->setViewer($viewer) 180 + ->setObject($application) 181 + ->execute(); 182 + } 183 + 184 + return $this->policies; 185 + } 186 + 187 + private function renderCapability() { 188 + $application = $this->getObject(); 189 + $capability = $this->getCapabilityName(); 190 + return $application->getCapabilityLabel($capability); 191 + } 192 + 193 + private function getCapabilityName() { 194 + return $this->getMetadataValue(self::METADATA_ATTRIBUTE); 195 + } 196 + 197 + }
+7 -1
src/applications/policy/storage/PhabricatorPolicy.php
··· 264 264 public function getFullName() { 265 265 switch ($this->getType()) { 266 266 case PhabricatorPolicyType::TYPE_PROJECT: 267 - return pht('Project: %s', $this->getName()); 267 + return pht('Members of Project: %s', $this->getName()); 268 268 case PhabricatorPolicyType::TYPE_MASKED: 269 269 return pht('Other: %s', $this->getName()); 270 + case PhabricatorPolicyType::TYPE_USER: 271 + return pht('Only User: %s', $this->getName()); 270 272 default: 271 273 return $this->getName(); 272 274 } ··· 420 422 $other_strength = idx($strengths, $other->getPHID(), 0); 421 423 422 424 return ($this_strength > $other_strength); 425 + } 426 + 427 + public function isValidPolicyForEdit() { 428 + return $this->getType() !== PhabricatorPolicyType::TYPE_MASKED; 423 429 } 424 430 425 431 public static function getSpecialRules(
+12 -2
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 334 334 335 335 $xtype = $this->getModularTransactionType($type); 336 336 if ($xtype) { 337 + $xtype = clone $xtype; 338 + $xtype->setStorage($xaction); 337 339 return $xtype->generateOldValue($object); 338 340 } 339 341 ··· 414 416 415 417 $xtype = $this->getModularTransactionType($type); 416 418 if ($xtype) { 419 + $xtype = clone $xtype; 420 + $xtype->setStorage($xaction); 417 421 return $xtype->generateNewValue($object, $xaction->getNewValue()); 418 422 } 419 423 ··· 553 557 554 558 $xtype = $this->getModularTransactionType($type); 555 559 if ($xtype) { 560 + $xtype = clone $xtype; 561 + $xtype->setStorage($xaction); 556 562 return $xtype->applyInternalEffects($object, $xaction->getNewValue()); 557 563 } 558 564 ··· 2163 2169 return array_mergev($errors); 2164 2170 } 2165 2171 2166 - private function validatePolicyTransaction( 2172 + public function validatePolicyTransaction( 2167 2173 PhabricatorLiskDAO $object, 2168 2174 array $xactions, 2169 2175 $transaction_type, ··· 2772 2778 } 2773 2779 2774 2780 if (!$has_support) { 2775 - throw new Exception(pht('Capability not supported.')); 2781 + throw new Exception( 2782 + pht('The object being edited does not implement any standard '. 2783 + 'interfaces (like PhabricatorSubscribableInterface) which allow '. 2784 + 'CCs to be generated automatically. Override the "getMailCC()" '. 2785 + 'method and generate CCs explicitly.')); 2776 2786 } 2777 2787 2778 2788 return array_mergev($phids);
+4
src/applications/transactions/storage/PhabricatorModularTransactionType.php
··· 315 315 return $editor->getPHIDList($old, $new); 316 316 } 317 317 318 + public function getMetadataValue($key, $default = null) { 319 + return $this->getStorage()->getMetadataValue($key, $default); 320 + } 321 + 318 322 }