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

Provide an Editor extension point for transaction validation

Summary:
Depends on D20040. Ref T13242. See PHI1039. See PHI873. Two reasonable cases have arisen recently where extending validation rules would help solve problems.

We can do this in a pretty straightforward way with a standard extension pattern.

Test Plan:
Used this extension to keep ducks away from projects:

```lang=php
<?php

final class NoDucksEditorExtension
extends PhabricatorEditorExtension {

const EXTENSIONKEY = 'no.ducks';

public function getExtensionName() {
return pht('No Ducks!');
}

public function supportsObject(
PhabricatorApplicationTransactionEditor $editor,
PhabricatorApplicationTransactionInterface $object) {
return ($object instanceof PhabricatorProject);
}

public function validateTransactions($object, array $xactions) {
$errors = array();

$name_type = PhabricatorProjectNameTransaction::TRANSACTIONTYPE;

$old_value = $object->getName();
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() !== $name_type) {
continue;
}

$new_value = $xaction->getNewValue();
if ($old_value === $new_value) {
continue;
}

if (preg_match('/duck/i', $new_value)) {
$errors[] = $this->newInvalidTransactionError(
$xaction,
pht('Project names must not contain the substring "duck".'));
}
}

return $errors;
}

}
```

{F6162585}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13242

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

+185
+4
src/__phutil_library_map__.php
··· 3061 3061 'PhabricatorEditPage' => 'applications/transactions/editengine/PhabricatorEditPage.php', 3062 3062 'PhabricatorEditType' => 'applications/transactions/edittype/PhabricatorEditType.php', 3063 3063 'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php', 3064 + 'PhabricatorEditorExtension' => 'applications/transactions/engineextension/PhabricatorEditorExtension.php', 3065 + 'PhabricatorEditorExtensionModule' => 'applications/transactions/engineextension/PhabricatorEditorExtensionModule.php', 3064 3066 'PhabricatorEditorMailEngineExtension' => 'applications/transactions/engineextension/PhabricatorEditorMailEngineExtension.php', 3065 3067 'PhabricatorEditorMultipleSetting' => 'applications/settings/setting/PhabricatorEditorMultipleSetting.php', 3066 3068 'PhabricatorEditorSetting' => 'applications/settings/setting/PhabricatorEditorSetting.php', ··· 8927 8929 'PhabricatorEditPage' => 'Phobject', 8928 8930 'PhabricatorEditType' => 'Phobject', 8929 8931 'PhabricatorEditor' => 'Phobject', 8932 + 'PhabricatorEditorExtension' => 'Phobject', 8933 + 'PhabricatorEditorExtensionModule' => 'PhabricatorConfigModule', 8930 8934 'PhabricatorEditorMailEngineExtension' => 'PhabricatorMailEngineExtension', 8931 8935 'PhabricatorEditorMultipleSetting' => 'PhabricatorSelectSetting', 8932 8936 'PhabricatorEditorSetting' => 'PhabricatorStringSetting',
+58
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 88 88 private $hasRequiredMFA = false; 89 89 private $request; 90 90 private $cancelURI; 91 + private $extensions; 91 92 92 93 const STORAGE_ENCODING_BINARY = 'binary'; 93 94 ··· 1013 1014 } 1014 1015 1015 1016 $errors[] = $this->validateAllTransactions($object, $xactions); 1017 + $errors[] = $this->validateTransactionsWithExtensions($object, $xactions); 1016 1018 $errors = array_mergev($errors); 1017 1019 1018 1020 $continue_on_missing = $this->getContinueOnMissingFields(); ··· 4974 4976 4975 4977 return $xactions; 4976 4978 } 4979 + 4980 + 4981 + /* -( Extensions )--------------------------------------------------------- */ 4982 + 4983 + 4984 + private function validateTransactionsWithExtensions( 4985 + PhabricatorLiskDAO $object, 4986 + array $xactions) { 4987 + $errors = array(); 4988 + 4989 + $extensions = $this->getEditorExtensions(); 4990 + foreach ($extensions as $extension) { 4991 + $extension_errors = $extension 4992 + ->setObject($object) 4993 + ->validateTransactions($object, $xactions); 4994 + 4995 + assert_instances_of( 4996 + $extension_errors, 4997 + 'PhabricatorApplicationTransactionValidationError'); 4998 + 4999 + $errors[] = $extension_errors; 5000 + } 5001 + 5002 + return array_mergev($errors); 5003 + } 5004 + 5005 + private function getEditorExtensions() { 5006 + if ($this->extensions === null) { 5007 + $this->extensions = $this->newEditorExtensions(); 5008 + } 5009 + return $this->extensions; 5010 + } 5011 + 5012 + private function newEditorExtensions() { 5013 + $extensions = PhabricatorEditorExtension::getAllExtensions(); 5014 + 5015 + $actor = $this->getActor(); 5016 + $object = $this->object; 5017 + foreach ($extensions as $key => $extension) { 5018 + 5019 + $extension = id(clone $extension) 5020 + ->setViewer($actor) 5021 + ->setEditor($this) 5022 + ->setObject($object); 5023 + 5024 + if (!$extension->supportsObject($this, $object)) { 5025 + unset($extensions[$key]); 5026 + continue; 5027 + } 5028 + 5029 + $extensions[$key] = $extension; 5030 + } 5031 + 5032 + return $extensions; 5033 + } 5034 + 4977 5035 4978 5036 }
+83
src/applications/transactions/engineextension/PhabricatorEditorExtension.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorEditorExtension 4 + extends Phobject { 5 + 6 + private $viewer; 7 + private $editor; 8 + private $object; 9 + 10 + final public function getExtensionKey() { 11 + return $this->getPhobjectClassConstant('EXTENSIONKEY'); 12 + } 13 + 14 + final public function setEditor( 15 + PhabricatorApplicationTransactionEditor $editor) { 16 + $this->editor = $editor; 17 + return $this; 18 + } 19 + 20 + final public function getEditor() { 21 + return $this->editor; 22 + } 23 + 24 + final public function setViewer(PhabricatorUser $viewer) { 25 + $this->viewer = $viewer; 26 + return $this; 27 + } 28 + 29 + final public function getViewer() { 30 + return $this->viewer; 31 + } 32 + 33 + final public function setObject( 34 + PhabricatorApplicationTransactionInterface $object) { 35 + $this->object = $object; 36 + return $this; 37 + } 38 + 39 + final public static function getAllExtensions() { 40 + return id(new PhutilClassMapQuery()) 41 + ->setAncestorClass(__CLASS__) 42 + ->setUniqueMethod('getExtensionKey') 43 + ->execute(); 44 + } 45 + 46 + abstract public function getExtensionName(); 47 + 48 + public function supportsObject( 49 + PhabricatorApplicationTransactionEditor $editor, 50 + PhabricatorApplicationTransactionInterface $object) { 51 + return true; 52 + } 53 + 54 + public function validateTransactions($object, array $xactions) { 55 + return array(); 56 + } 57 + 58 + final protected function newTransactionError( 59 + PhabricatorApplicationTransaction $xaction, 60 + $title, 61 + $message) { 62 + return new PhabricatorApplicationTransactionValidationError( 63 + $xaction->getTransactionType(), 64 + $title, 65 + $message, 66 + $xaction); 67 + } 68 + 69 + final protected function newRequiredTransasctionError( 70 + PhabricatorApplicationTransaction $xaction, 71 + $message) { 72 + return $this->newError($xaction, pht('Required'), $message) 73 + ->setIsMissingFieldError(true); 74 + } 75 + 76 + final protected function newInvalidTransactionError( 77 + PhabricatorApplicationTransaction $xaction, 78 + $message) { 79 + return $this->newTransactionError($xaction, pht('Invalid'), $message); 80 + } 81 + 82 + 83 + }
+40
src/applications/transactions/engineextension/PhabricatorEditorExtensionModule.php
··· 1 + <?php 2 + 3 + final class PhabricatorEditorExtensionModule 4 + extends PhabricatorConfigModule { 5 + 6 + public function getModuleKey() { 7 + return 'editor'; 8 + } 9 + 10 + public function getModuleName() { 11 + return pht('Engine: Editor'); 12 + } 13 + 14 + public function renderModuleStatus(AphrontRequest $request) { 15 + $viewer = $request->getViewer(); 16 + 17 + $extensions = PhabricatorEditorExtension::getAllExtensions(); 18 + 19 + $rows = array(); 20 + foreach ($extensions as $extension) { 21 + $rows[] = array( 22 + get_class($extension), 23 + $extension->getExtensionName(), 24 + ); 25 + } 26 + 27 + return id(new AphrontTableView($rows)) 28 + ->setHeaders( 29 + array( 30 + pht('Class'), 31 + pht('Name'), 32 + )) 33 + ->setColumnClasses( 34 + array( 35 + null, 36 + 'wide pri', 37 + )); 38 + } 39 + 40 + }