@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

Add "does not match regexp" to Herald

Summary:
Fixes T10330.

- Anywhere we support "matches regexp", also allow "does not match regexp". Although you can sometimes write a clever negative regexp, these rules are better expressed with "does not match <simple regexp>" anyway, and sometimes no regexp will work.
- Always allow "does not contain" when we support "contains".
- Fix some JS issues with certain rules affecting custom fields.

Test Plan:
- Wrote an "Affected files do not match regexp" rule that required every diff to touch "MANUALCHANGELOG.md".
- Tried to diff without the file; rejected.
- Tried to diff with the file; accepted.
- Wrote a bunch of "contains" and "does not contain" rules against text fields and custom fields, then edited tasks to trigger/observe them.
- Swapped the editor into custom text, user, remarkup, etc fields, no more JS errors.

{F1105172}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10330

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

+78 -8
+16 -8
src/applications/herald/adapter/HeraldAdapter.php
··· 14 14 const CONDITION_IS_ME = 'me'; 15 15 const CONDITION_IS_NOT_ME = '!me'; 16 16 const CONDITION_REGEXP = 'regexp'; 17 + const CONDITION_NOT_REGEXP = '!regexp'; 17 18 const CONDITION_RULE = 'conditions'; 18 19 const CONDITION_NOT_RULE = '!conditions'; 19 20 const CONDITION_EXISTS = 'exists'; ··· 322 323 self::CONDITION_IS_ME => pht('is myself'), 323 324 self::CONDITION_IS_NOT_ME => pht('is not myself'), 324 325 self::CONDITION_REGEXP => pht('matches regexp'), 326 + self::CONDITION_NOT_REGEXP => pht('does not match regexp'), 325 327 self::CONDITION_RULE => pht('matches:'), 326 328 self::CONDITION_NOT_RULE => pht('does not match:'), 327 329 self::CONDITION_EXISTS => pht('exists'), ··· 364 366 365 367 switch ($condition_type) { 366 368 case self::CONDITION_CONTAINS: 367 - // "Contains" can take an array of strings, as in "Any changed 368 - // filename" for diffs. 369 + case self::CONDITION_NOT_CONTAINS: 370 + // "Contains and "does not contain" can take an array of strings, as in 371 + // "Any changed filename" for diffs. 372 + 373 + $result_if_match = ($condition_type == self::CONDITION_CONTAINS); 374 + 369 375 foreach ((array)$field_value as $value) { 370 376 if (stripos($value, $condition_value) !== false) { 371 - return true; 377 + return $result_if_match; 372 378 } 373 379 } 374 - return false; 375 - case self::CONDITION_NOT_CONTAINS: 376 - return (stripos($field_value, $condition_value) === false); 380 + return !$result_if_match; 377 381 case self::CONDITION_IS: 378 382 return ($field_value == $condition_value); 379 383 case self::CONDITION_IS_NOT: ··· 427 431 case self::CONDITION_NEVER: 428 432 return false; 429 433 case self::CONDITION_REGEXP: 434 + case self::CONDITION_NOT_REGEXP: 435 + $result_if_match = ($condition_type == self::CONDITION_REGEXP); 436 + 430 437 foreach ((array)$field_value as $value) { 431 438 // We add the 'S' flag because we use the regexp multiple times. 432 439 // It shouldn't cause any troubles if the flag is already there ··· 437 444 pht('Regular expression is not valid!')); 438 445 } 439 446 if ($result) { 440 - return true; 447 + return $result_if_match; 441 448 } 442 449 } 443 - return false; 450 + return !$result_if_match; 444 451 case self::CONDITION_REGEXP_PAIR: 445 452 // Match a JSON-encoded pair of regular expressions against a 446 453 // dictionary. The first regexp must match the dictionary key, and the ··· 509 516 510 517 switch ($condition_type) { 511 518 case self::CONDITION_REGEXP: 519 + case self::CONDITION_NOT_REGEXP: 512 520 $ok = @preg_match($condition_value, ''); 513 521 if ($ok === false) { 514 522 throw new HeraldInvalidConditionException(
+5
src/applications/herald/field/HeraldField.php
··· 47 47 HeraldAdapter::CONDITION_IS, 48 48 HeraldAdapter::CONDITION_IS_NOT, 49 49 HeraldAdapter::CONDITION_REGEXP, 50 + HeraldAdapter::CONDITION_NOT_REGEXP, 50 51 ); 51 52 case self::STANDARD_PHID: 52 53 return array( ··· 76 77 case self::STANDARD_TEXT_LIST: 77 78 return array( 78 79 HeraldAdapter::CONDITION_CONTAINS, 80 + HeraldAdapter::CONDITION_NOT_CONTAINS, 79 81 HeraldAdapter::CONDITION_REGEXP, 82 + HeraldAdapter::CONDITION_NOT_REGEXP, 80 83 ); 81 84 case self::STANDARD_TEXT_MAP: 82 85 return array( 83 86 HeraldAdapter::CONDITION_CONTAINS, 87 + HeraldAdapter::CONDITION_NOT_CONTAINS, 84 88 HeraldAdapter::CONDITION_REGEXP, 89 + HeraldAdapter::CONDITION_NOT_REGEXP, 85 90 HeraldAdapter::CONDITION_REGEXP_PAIR, 86 91 ); 87 92 }
+14
src/infrastructure/customfield/field/PhabricatorCustomField.php
··· 1446 1446 return null; 1447 1447 } 1448 1448 1449 + public function getHeraldFieldStandardType() { 1450 + if ($this->proxy) { 1451 + return $this->proxy->getHeraldFieldStandardType(); 1452 + } 1453 + return null; 1454 + } 1455 + 1456 + public function getHeraldDatasource() { 1457 + if ($this->proxy) { 1458 + return $this->proxy->getHeraldDatasource(); 1459 + } 1460 + return null; 1461 + } 1462 + 1449 1463 1450 1464 }
+12
src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldField.php
··· 65 65 return $this->getCustomField()->getHeraldFieldConditions(); 66 66 } 67 67 68 + protected function getHeraldFieldStandardType() { 69 + return $this->getCustomField()->getHeraldFieldStandardType(); 70 + } 71 + 68 72 public function getHeraldFieldValueType($condition) { 73 + if ($this->getHeraldFieldStandardType()) { 74 + return parent::getHeraldFieldValueType($condition); 75 + } 76 + 69 77 return $this->getCustomField()->getHeraldFieldValueType($condition); 78 + } 79 + 80 + protected function getDatasource() { 81 + return $this->getCustomField()->getHeraldDatasource(); 70 82 } 71 83 72 84 }
+4
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php
··· 129 129 ); 130 130 } 131 131 132 + public function getHeraldFieldStandardType() { 133 + return HeraldField::STANDARD_BOOL; 134 + } 135 + 132 136 protected function getHTTPParameterType() { 133 137 return new AphrontBoolHTTPParameterType(); 134 138 }
+5
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php
··· 77 77 HeraldAdapter::CONDITION_IS, 78 78 HeraldAdapter::CONDITION_IS_NOT, 79 79 HeraldAdapter::CONDITION_REGEXP, 80 + HeraldAdapter::CONDITION_NOT_REGEXP, 80 81 ); 82 + } 83 + 84 + public function getHeraldFieldStandardType() { 85 + return HeraldField::STANDARD_TEXT; 81 86 } 82 87 83 88 protected function getHTTPParameterType() {
+4
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php
··· 241 241 ); 242 242 } 243 243 244 + public function getHeraldFieldStandardType() { 245 + return HeraldField::STANDARD_PHID_NULLABLE; 246 + } 247 + 244 248 public function getHeraldFieldValue() { 245 249 // If the field has a `null` value, make sure we hand an `array()` to 246 250 // Herald.
+5
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php
··· 92 92 HeraldAdapter::CONDITION_IS, 93 93 HeraldAdapter::CONDITION_IS_NOT, 94 94 HeraldAdapter::CONDITION_REGEXP, 95 + HeraldAdapter::CONDITION_NOT_REGEXP, 95 96 ); 97 + } 98 + 99 + public function getHeraldFieldStandardType() { 100 + return HeraldField::STANDARD_TEXT; 96 101 } 97 102 98 103 protected function getHTTPParameterType() {
+5
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php
··· 60 60 HeraldAdapter::CONDITION_IS, 61 61 HeraldAdapter::CONDITION_IS_NOT, 62 62 HeraldAdapter::CONDITION_REGEXP, 63 + HeraldAdapter::CONDITION_NOT_REGEXP, 63 64 ); 65 + } 66 + 67 + public function getHeraldFieldStandardType() { 68 + return HeraldField::STANDARD_TEXT; 64 69 } 65 70 66 71 protected function getHTTPParameterType() {
+8
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php
··· 44 44 ->setDatasource($this->getDatasource()); 45 45 } 46 46 47 + public function getHeraldFieldStandardType() { 48 + return HeraldField::STANDARD_PHID_LIST; 49 + } 50 + 51 + public function getHeraldDatasource() { 52 + return $this->getDatasource(); 53 + } 54 + 47 55 }