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

Allow custom fields to have validation logic

Summary:
Ref T418. This is fairly messy, but basically:

- Add a validation phase to TransactionEditor.
- Add a validation phase to CustomField.
- Bring it to StandardField.
- Add validation logic for the int field.
- Provide support in related classes.

Test Plan: See screenshot.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T418

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

+265 -8
+4
src/__phutil_library_map__.php
··· 868 868 'PhabricatorApplicationTransactionQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionQuery.php', 869 869 'PhabricatorApplicationTransactionResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionResponse.php', 870 870 'PhabricatorApplicationTransactionTextDiffDetailView' => 'applications/transactions/view/PhabricatorApplicationTransactionTextDiffDetailView.php', 871 + 'PhabricatorApplicationTransactionValidationError' => 'applications/transactions/error/PhabricatorApplicationTransactionValidationError.php', 872 + 'PhabricatorApplicationTransactionValidationException' => 'applications/transactions/exception/PhabricatorApplicationTransactionValidationException.php', 871 873 'PhabricatorApplicationTransactionView' => 'applications/transactions/view/PhabricatorApplicationTransactionView.php', 872 874 'PhabricatorApplicationTransactions' => 'applications/transactions/application/PhabricatorApplicationTransactions.php', 873 875 'PhabricatorApplicationUIExamples' => 'applications/uiexample/application/PhabricatorApplicationUIExamples.php', ··· 2960 2962 'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2961 2963 'PhabricatorApplicationTransactionResponse' => 'AphrontProxyResponse', 2962 2964 'PhabricatorApplicationTransactionTextDiffDetailView' => 'AphrontView', 2965 + 'PhabricatorApplicationTransactionValidationError' => 'Phobject', 2966 + 'PhabricatorApplicationTransactionValidationException' => 'Exception', 2963 2967 'PhabricatorApplicationTransactionView' => 'AphrontView', 2964 2968 'PhabricatorApplicationTransactions' => 'PhabricatorApplication', 2965 2969 'PhabricatorApplicationUIExamples' => 'PhabricatorApplication',
+8 -3
src/applications/people/controller/PhabricatorPeopleProfileEditController.php
··· 39 39 ->setViewer($user) 40 40 ->readFieldsFromStorage($user); 41 41 42 + $validation_exception = null; 42 43 if ($request->isFormPost()) { 43 44 $xactions = $field_list->buildFieldTransactionsFromRequest( 44 45 new PhabricatorUserTransaction(), ··· 50 51 PhabricatorContentSource::newFromRequest($request)) 51 52 ->setContinueOnNoEffect(true); 52 53 53 - $editor->applyTransactions($user, $xactions); 54 - 55 - return id(new AphrontRedirectResponse())->setURI($profile_uri); 54 + try { 55 + $editor->applyTransactions($user, $xactions); 56 + return id(new AphrontRedirectResponse())->setURI($profile_uri); 57 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 58 + $validation_exception = $ex; 59 + } 56 60 } 57 61 58 62 $title = pht('Edit Profile'); ··· 78 82 79 83 $form_box = id(new PHUIFormBoxView()) 80 84 ->setHeaderText(pht('Edit Your Profile')) 85 + ->setValidationException($validation_exception) 81 86 ->setForm($form); 82 87 83 88 return $this->buildApplicationPage(
+61 -1
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 185 185 PhabricatorLiskDAO $object, 186 186 array $xactions) { 187 187 return false; 188 - 189 188 } 190 189 191 190 protected function applyInitialEffects( ··· 356 355 $transaction_open = false; 357 356 358 357 if (!$is_preview) { 358 + $errors = array(); 359 + $type_map = mgroup($xactions, 'getTransactionType'); 360 + foreach ($this->getTransactionTypes() as $type) { 361 + $type_xactions = idx($type_map, $type, array()); 362 + $errors[] = $this->validateTransaction($object, $type, $type_xactions); 363 + } 364 + 365 + $errors = array_mergev($errors); 366 + if ($errors) { 367 + throw new PhabricatorApplicationTransactionValidationException($errors); 368 + } 369 + 359 370 if ($object->getID()) { 360 371 foreach ($xactions as $xaction) { 361 372 ··· 1000 1011 } 1001 1012 1002 1013 return $xactions; 1014 + } 1015 + 1016 + 1017 + /** 1018 + * Hook for validating transactions. This callback will be invoked for each 1019 + * available transaction type, even if an edit does not apply any transactions 1020 + * of that type. This allows you to raise exceptions when required fields are 1021 + * missing, by detecting that the object has no field value and there is no 1022 + * transaction which sets one. 1023 + * 1024 + * @param PhabricatorLiskDAO Object being edited. 1025 + * @param string Transaction type to validate. 1026 + * @param list<PhabricatorApplicationTransaction> Transactions of given type, 1027 + * which may be empty if the edit does not apply any transactions of the 1028 + * given type. 1029 + * @return list<PhabricatorApplicationTransactionValidationError> List of 1030 + * validation errors. 1031 + */ 1032 + protected function validateTransaction( 1033 + PhabricatorLiskDAO $object, 1034 + $type, 1035 + array $xactions) { 1036 + 1037 + $errors = array(); 1038 + switch ($type) { 1039 + case PhabricatorTransactions::TYPE_CUSTOMFIELD: 1040 + $groups = array(); 1041 + foreach ($xactions as $xaction) { 1042 + $groups[$xaction->getMetadataValue('customfield:key')][] = $xaction; 1043 + } 1044 + 1045 + $field_list = PhabricatorCustomField::getObjectFields( 1046 + $object, 1047 + PhabricatorCustomField::ROLE_EDIT); 1048 + 1049 + $role_xactions = PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS; 1050 + foreach ($field_list->getFields() as $field) { 1051 + if (!$field->shouldEnableForRole($role_xactions)) { 1052 + continue; 1053 + } 1054 + $errors[] = $field->validateApplicationTransactions( 1055 + $this, 1056 + $type, 1057 + idx($groups, $field->getFieldKey(), array())); 1058 + } 1059 + break; 1060 + } 1061 + 1062 + return array_mergev($errors); 1003 1063 } 1004 1064 1005 1065
+39
src/applications/transactions/error/PhabricatorApplicationTransactionValidationError.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationTransactionValidationError 4 + extends Phobject { 5 + 6 + private $type; 7 + private $transaction; 8 + private $shortMessage; 9 + private $message; 10 + 11 + public function __construct( 12 + $type, 13 + $short_message, 14 + $message, 15 + PhabricatorApplicationTransaction $xaction = null) { 16 + 17 + $this->type = $type; 18 + $this->shortMessage = $short_message; 19 + $this->message = $message; 20 + $this->transaction = $xaction; 21 + } 22 + 23 + public function getType() { 24 + return $this->type; 25 + } 26 + 27 + public function getTransaction() { 28 + return $this->tranaction; 29 + } 30 + 31 + public function getShortMessage() { 32 + return $this->shortMessage; 33 + } 34 + 35 + public function getMessage() { 36 + return $this->message; 37 + } 38 + 39 + }
+43
src/applications/transactions/exception/PhabricatorApplicationTransactionValidationException.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationTransactionValidationException 4 + extends Exception { 5 + 6 + private $errors; 7 + 8 + public function __construct(array $errors) { 9 + assert_instances_of( 10 + $errors, 11 + 'PhabricatorApplicationTransactionValidationError'); 12 + 13 + $this->errors = $errors; 14 + 15 + $message = array(); 16 + $message[] = 'Validation errors:'; 17 + foreach ($this->errors as $error) { 18 + $message[] = ' - '.$error->getMessage(); 19 + } 20 + 21 + parent::__construct(implode("\n", $message)); 22 + } 23 + 24 + public function getErrors() { 25 + return $this->errors; 26 + } 27 + 28 + public function getErrorMessages() { 29 + return mpull($this->errors, 'getMessage'); 30 + } 31 + 32 + public function getShortMessage($type) { 33 + foreach ($this->errors as $error) { 34 + if ($error->getType() === $type) { 35 + if ($error->getShortMessage() !== null) { 36 + return $error->getShortMessage(); 37 + } 38 + } 39 + } 40 + return null; 41 + } 42 + 43 + }
+30
src/infrastructure/customfield/field/PhabricatorCustomField.php
··· 844 844 } 845 845 846 846 847 + /** 848 + * Validate transactions for an object. This allows you to raise an error 849 + * when a transaction would set a field to an invalid value, or when a field 850 + * is required but no transactions provide value. 851 + * 852 + * @param PhabricatorLiskDAO Editor applying the transactions. 853 + * @param string Transaction type. This type is always 854 + * `PhabricatorTransactions::TYPE_CUSTOMFIELD`, it is provided for 855 + * convenience when constructing exceptions. 856 + * @param list<PhabricatorApplicationTransaction> Transactions being applied, 857 + * which may be empty if this field is not being edited. 858 + * @return list<PhabricatorApplicationTransactionValidationError> Validation 859 + * errors. 860 + * 861 + * @task appxaction 862 + */ 863 + public function validateApplicationTransactions( 864 + PhabricatorApplicationTransactionEditor $editor, 865 + $type, 866 + array $xactions) { 867 + if ($this->proxy) { 868 + return $this->proxy->validateApplicationTransactions( 869 + $editor, 870 + $type, 871 + $xactions); 872 + } 873 + return array(); 874 + } 875 + 876 + 847 877 /* -( Edit View )---------------------------------------------------------- */ 848 878 849 879
+25
src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
··· 11 11 private $applicationField; 12 12 private $strings; 13 13 private $caption; 14 + private $fieldError; 14 15 15 16 abstract public function getFieldType(); 16 17 ··· 114 115 return idx($this->fieldConfig, $key, $default); 115 116 } 116 117 118 + public function setFieldError($field_error) { 119 + $this->fieldError = $field_error; 120 + return $this; 121 + } 122 + 123 + public function getFieldError() { 124 + return $this->fieldError; 125 + } 117 126 118 127 119 128 /* -( PhabricatorCustomField )--------------------------------------------- */ ··· 178 187 ->setName($this->getFieldKey()) 179 188 ->setCaption($this->getCaption()) 180 189 ->setValue($this->getFieldValue()) 190 + ->setError($this->getFieldError()) 181 191 ->setLabel($this->getFieldName()); 182 192 } 183 193 ··· 228 238 $value, 229 239 array $handles) { 230 240 return; 241 + } 242 + 243 + public function validateApplicationTransactions( 244 + PhabricatorApplicationTransactionEditor $editor, 245 + $type, 246 + array $xactions) { 247 + 248 + $this->setFieldError(null); 249 + 250 + $errors = parent::validateApplicationTransactions( 251 + $editor, 252 + $type, 253 + $xactions); 254 + 255 + return $errors; 231 256 } 232 257 233 258 }
+28 -1
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php
··· 21 21 public function getValueForStorage() { 22 22 $value = $this->getFieldValue(); 23 23 if (strlen($value)) { 24 - return (int)$value; 24 + return $value; 25 25 } else { 26 26 return null; 27 27 } ··· 66 66 ->setLabel($this->getFieldName()) 67 67 ->setName($this->getFieldKey()) 68 68 ->setValue($value)); 69 + } 70 + 71 + public function validateApplicationTransactions( 72 + PhabricatorApplicationTransactionEditor $editor, 73 + $type, 74 + array $xactions) { 75 + 76 + $errors = parent::validateApplicationTransactions( 77 + $editor, 78 + $type, 79 + $xactions); 80 + 81 + foreach ($xactions as $xaction) { 82 + $value = $xaction->getNewValue(); 83 + if (strlen($value)) { 84 + if (!preg_match('/^-?\d+/', $value)) { 85 + $errors[] = new PhabricatorApplicationTransactionValidationError( 86 + $type, 87 + pht('Invalid'), 88 + pht('%s must be an integer.', $this->getFieldName()), 89 + $xaction); 90 + $this->setFieldError(pht('Invalid')); 91 + } 92 + } 93 + } 94 + 95 + return $errors; 69 96 } 70 97 71 98 }
+27 -3
src/view/form/PHUIFormBoxView.php
··· 5 5 private $headerText; 6 6 private $formError = null; 7 7 private $form; 8 + private $validationException; 8 9 9 10 public function setHeaderText($text) { 10 11 $this->headerText = $text; ··· 21 22 return $this; 22 23 } 23 24 25 + public function setValidationException( 26 + PhabricatorApplicationTransactionValidationException $ex = null) { 27 + $this->validationException = $ex; 28 + return $this; 29 + } 30 + 24 31 public function render() { 25 32 26 - $error = $this->formError ? $this->formError : null; 27 - 28 33 $header = id(new PhabricatorActionHeaderView()) 29 34 ->setHeaderTitle($this->headerText) 30 35 ->setHeaderColor(PhabricatorActionHeaderView::HEADER_LIGHTBLUE); 31 36 37 + $ex = $this->validationException; 38 + $exception_errors = null; 39 + if ($ex) { 40 + $messages = array(); 41 + foreach ($ex->getErrors() as $error) { 42 + $messages[] = $error->getMessage(); 43 + } 44 + if ($messages) { 45 + $exception_errors = id(new AphrontErrorView()) 46 + ->setErrors($messages); 47 + } 48 + } 49 + 32 50 $content = id(new PHUIBoxView()) 33 - ->appendChild(array($header, $error, $this->form)) 51 + ->appendChild( 52 + array( 53 + $header, 54 + $this->formError, 55 + $exception_errors, 56 + $this->form, 57 + )) 34 58 ->setBorder(true) 35 59 ->addMargin(PHUI::MARGIN_LARGE_TOP) 36 60 ->addMargin(PHUI::MARGIN_LARGE_LEFT)