[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @task customfield Custom Field Integration 5 */ 6 abstract class HeraldAdapter { 7 8 const FIELD_TITLE = 'title'; 9 const FIELD_BODY = 'body'; 10 const FIELD_AUTHOR = 'author'; 11 const FIELD_ASSIGNEE = 'assignee'; 12 const FIELD_REVIEWER = 'reviewer'; 13 const FIELD_REVIEWERS = 'reviewers'; 14 const FIELD_COMMITTER = 'committer'; 15 const FIELD_CC = 'cc'; 16 const FIELD_TAGS = 'tags'; 17 const FIELD_DIFF_FILE = 'diff-file'; 18 const FIELD_DIFF_CONTENT = 'diff-content'; 19 const FIELD_DIFF_ADDED_CONTENT = 'diff-added-content'; 20 const FIELD_DIFF_REMOVED_CONTENT = 'diff-removed-content'; 21 const FIELD_DIFF_ENORMOUS = 'diff-enormous'; 22 const FIELD_REPOSITORY = 'repository'; 23 const FIELD_REPOSITORY_PROJECTS = 'repository-projects'; 24 const FIELD_RULE = 'rule'; 25 const FIELD_AFFECTED_PACKAGE = 'affected-package'; 26 const FIELD_AFFECTED_PACKAGE_OWNER = 'affected-package-owner'; 27 const FIELD_CONTENT_SOURCE = 'contentsource'; 28 const FIELD_ALWAYS = 'always'; 29 const FIELD_AUTHOR_PROJECTS = 'authorprojects'; 30 const FIELD_PROJECTS = 'projects'; 31 const FIELD_PUSHER = 'pusher'; 32 const FIELD_PUSHER_PROJECTS = 'pusher-projects'; 33 const FIELD_DIFFERENTIAL_REVISION = 'differential-revision'; 34 const FIELD_DIFFERENTIAL_REVIEWERS = 'differential-reviewers'; 35 const FIELD_DIFFERENTIAL_CCS = 'differential-ccs'; 36 const FIELD_DIFFERENTIAL_ACCEPTED = 'differential-accepted'; 37 const FIELD_IS_MERGE_COMMIT = 'is-merge-commit'; 38 const FIELD_BRANCHES = 'branches'; 39 const FIELD_AUTHOR_RAW = 'author-raw'; 40 const FIELD_COMMITTER_RAW = 'committer-raw'; 41 const FIELD_IS_NEW_OBJECT = 'new-object'; 42 const FIELD_TASK_PRIORITY = 'taskpriority'; 43 const FIELD_TASK_STATUS = 'taskstatus'; 44 const FIELD_ARCANIST_PROJECT = 'arcanist-project'; 45 const FIELD_PUSHER_IS_COMMITTER = 'pusher-is-committer'; 46 const FIELD_PATH = 'path'; 47 48 const CONDITION_CONTAINS = 'contains'; 49 const CONDITION_NOT_CONTAINS = '!contains'; 50 const CONDITION_IS = 'is'; 51 const CONDITION_IS_NOT = '!is'; 52 const CONDITION_IS_ANY = 'isany'; 53 const CONDITION_IS_NOT_ANY = '!isany'; 54 const CONDITION_INCLUDE_ALL = 'all'; 55 const CONDITION_INCLUDE_ANY = 'any'; 56 const CONDITION_INCLUDE_NONE = 'none'; 57 const CONDITION_IS_ME = 'me'; 58 const CONDITION_IS_NOT_ME = '!me'; 59 const CONDITION_REGEXP = 'regexp'; 60 const CONDITION_RULE = 'conditions'; 61 const CONDITION_NOT_RULE = '!conditions'; 62 const CONDITION_EXISTS = 'exists'; 63 const CONDITION_NOT_EXISTS = '!exists'; 64 const CONDITION_UNCONDITIONALLY = 'unconditionally'; 65 const CONDITION_NEVER = 'never'; 66 const CONDITION_REGEXP_PAIR = 'regexp-pair'; 67 const CONDITION_HAS_BIT = 'bit'; 68 const CONDITION_NOT_BIT = '!bit'; 69 const CONDITION_IS_TRUE = 'true'; 70 const CONDITION_IS_FALSE = 'false'; 71 72 const ACTION_ADD_CC = 'addcc'; 73 const ACTION_REMOVE_CC = 'remcc'; 74 const ACTION_EMAIL = 'email'; 75 const ACTION_NOTHING = 'nothing'; 76 const ACTION_AUDIT = 'audit'; 77 const ACTION_FLAG = 'flag'; 78 const ACTION_ASSIGN_TASK = 'assigntask'; 79 const ACTION_ADD_PROJECTS = 'addprojects'; 80 const ACTION_ADD_REVIEWERS = 'addreviewers'; 81 const ACTION_ADD_BLOCKING_REVIEWERS = 'addblockingreviewers'; 82 const ACTION_APPLY_BUILD_PLANS = 'applybuildplans'; 83 const ACTION_BLOCK = 'block'; 84 const ACTION_REQUIRE_SIGNATURE = 'signature'; 85 86 const VALUE_TEXT = 'text'; 87 const VALUE_NONE = 'none'; 88 const VALUE_EMAIL = 'email'; 89 const VALUE_USER = 'user'; 90 const VALUE_TAG = 'tag'; 91 const VALUE_RULE = 'rule'; 92 const VALUE_REPOSITORY = 'repository'; 93 const VALUE_OWNERS_PACKAGE = 'package'; 94 const VALUE_PROJECT = 'project'; 95 const VALUE_FLAG_COLOR = 'flagcolor'; 96 const VALUE_CONTENT_SOURCE = 'contentsource'; 97 const VALUE_USER_OR_PROJECT = 'userorproject'; 98 const VALUE_BUILD_PLAN = 'buildplan'; 99 const VALUE_TASK_PRIORITY = 'taskpriority'; 100 const VALUE_TASK_STATUS = 'taskstatus'; 101 const VALUE_ARCANIST_PROJECT = 'arcanistprojects'; 102 const VALUE_LEGAL_DOCUMENTS = 'legaldocuments'; 103 104 private $contentSource; 105 private $isNewObject; 106 private $customFields = false; 107 private $customActions = null; 108 private $queuedTransactions = array(); 109 110 public function getCustomActions() { 111 if ($this->customActions === null) { 112 $custom_actions = id(new PhutilSymbolLoader()) 113 ->setAncestorClass('HeraldCustomAction') 114 ->loadObjects(); 115 116 foreach ($custom_actions as $key => $object) { 117 if (!$object->appliesToAdapter($this)) { 118 unset($custom_actions[$key]); 119 } 120 } 121 122 $this->customActions = array(); 123 foreach ($custom_actions as $action) { 124 $key = $action->getActionKey(); 125 126 if (array_key_exists($key, $this->customActions)) { 127 throw new Exception( 128 'More than one Herald custom action implementation '. 129 'handles the action key: \''.$key.'\'.'); 130 } 131 132 $this->customActions[$key] = $action; 133 } 134 } 135 136 return $this->customActions; 137 } 138 139 public function setContentSource(PhabricatorContentSource $content_source) { 140 $this->contentSource = $content_source; 141 return $this; 142 } 143 public function getContentSource() { 144 return $this->contentSource; 145 } 146 147 public function getIsNewObject() { 148 if (is_bool($this->isNewObject)) { 149 return $this->isNewObject; 150 } 151 152 throw new Exception(pht('You must setIsNewObject to a boolean first!')); 153 } 154 public function setIsNewObject($new) { 155 $this->isNewObject = (bool) $new; 156 return $this; 157 } 158 159 abstract public function getPHID(); 160 abstract public function getHeraldName(); 161 162 public function getHeraldField($field_name) { 163 switch ($field_name) { 164 case self::FIELD_RULE: 165 return null; 166 case self::FIELD_CONTENT_SOURCE: 167 return $this->getContentSource()->getSource(); 168 case self::FIELD_ALWAYS: 169 return true; 170 case self::FIELD_IS_NEW_OBJECT: 171 return $this->getIsNewObject(); 172 default: 173 if ($this->isHeraldCustomKey($field_name)) { 174 return $this->getCustomFieldValue($field_name); 175 } 176 177 throw new Exception( 178 "Unknown field '{$field_name}'!"); 179 } 180 } 181 182 abstract public function applyHeraldEffects(array $effects); 183 184 protected function handleCustomHeraldEffect(HeraldEffect $effect) { 185 $custom_action = idx($this->getCustomActions(), $effect->getAction()); 186 187 if ($custom_action !== null) { 188 return $custom_action->applyEffect( 189 $this, 190 $this->getObject(), 191 $effect); 192 } 193 194 return null; 195 } 196 197 public function isAvailableToUser(PhabricatorUser $viewer) { 198 $applications = id(new PhabricatorApplicationQuery()) 199 ->setViewer($viewer) 200 ->withInstalled(true) 201 ->withClasses(array($this->getAdapterApplicationClass())) 202 ->execute(); 203 204 return !empty($applications); 205 } 206 207 public function queueTransaction($transaction) { 208 $this->queuedTransactions[] = $transaction; 209 } 210 211 public function getQueuedTransactions() { 212 return $this->queuedTransactions; 213 } 214 215 216 /** 217 * NOTE: You generally should not override this; it exists to support legacy 218 * adapters which had hard-coded content types. 219 */ 220 public function getAdapterContentType() { 221 return get_class($this); 222 } 223 224 abstract public function getAdapterContentName(); 225 abstract public function getAdapterContentDescription(); 226 abstract public function getAdapterApplicationClass(); 227 abstract public function getObject(); 228 229 public function supportsRuleType($rule_type) { 230 return false; 231 } 232 233 public function canTriggerOnObject($object) { 234 return false; 235 } 236 237 public function explainValidTriggerObjects() { 238 return pht('This adapter can not trigger on objects.'); 239 } 240 241 public function getTriggerObjectPHIDs() { 242 return array($this->getPHID()); 243 } 244 245 public function getAdapterSortKey() { 246 return sprintf( 247 '%08d%s', 248 $this->getAdapterSortOrder(), 249 $this->getAdapterContentName()); 250 } 251 252 public function getAdapterSortOrder() { 253 return 1000; 254 } 255 256 257 /* -( Fields )------------------------------------------------------------- */ 258 259 260 public function getFields() { 261 $fields = array(); 262 263 $fields[] = self::FIELD_ALWAYS; 264 $fields[] = self::FIELD_RULE; 265 266 $custom_fields = $this->getCustomFields(); 267 if ($custom_fields) { 268 foreach ($custom_fields->getFields() as $custom_field) { 269 $key = $custom_field->getFieldKey(); 270 $fields[] = $this->getHeraldKeyFromCustomKey($key); 271 } 272 } 273 274 return $fields; 275 } 276 277 public function getFieldNameMap() { 278 return array( 279 self::FIELD_TITLE => pht('Title'), 280 self::FIELD_BODY => pht('Body'), 281 self::FIELD_AUTHOR => pht('Author'), 282 self::FIELD_ASSIGNEE => pht('Assignee'), 283 self::FIELD_COMMITTER => pht('Committer'), 284 self::FIELD_REVIEWER => pht('Reviewer'), 285 self::FIELD_REVIEWERS => pht('Reviewers'), 286 self::FIELD_CC => pht('CCs'), 287 self::FIELD_TAGS => pht('Tags'), 288 self::FIELD_DIFF_FILE => pht('Any changed filename'), 289 self::FIELD_DIFF_CONTENT => pht('Any changed file content'), 290 self::FIELD_DIFF_ADDED_CONTENT => pht('Any added file content'), 291 self::FIELD_DIFF_REMOVED_CONTENT => pht('Any removed file content'), 292 self::FIELD_DIFF_ENORMOUS => pht('Change is enormous'), 293 self::FIELD_REPOSITORY => pht('Repository'), 294 self::FIELD_REPOSITORY_PROJECTS => pht('Repository\'s projects'), 295 self::FIELD_RULE => pht('Another Herald rule'), 296 self::FIELD_AFFECTED_PACKAGE => pht('Any affected package'), 297 self::FIELD_AFFECTED_PACKAGE_OWNER => 298 pht("Any affected package's owner"), 299 self::FIELD_CONTENT_SOURCE => pht('Content Source'), 300 self::FIELD_ALWAYS => pht('Always'), 301 self::FIELD_AUTHOR_PROJECTS => pht("Author's projects"), 302 self::FIELD_PROJECTS => pht('Projects'), 303 self::FIELD_PUSHER => pht('Pusher'), 304 self::FIELD_PUSHER_PROJECTS => pht("Pusher's projects"), 305 self::FIELD_DIFFERENTIAL_REVISION => pht('Differential revision'), 306 self::FIELD_DIFFERENTIAL_REVIEWERS => pht('Differential reviewers'), 307 self::FIELD_DIFFERENTIAL_CCS => pht('Differential CCs'), 308 self::FIELD_DIFFERENTIAL_ACCEPTED 309 => pht('Accepted Differential revision'), 310 self::FIELD_IS_MERGE_COMMIT => pht('Commit is a merge'), 311 self::FIELD_BRANCHES => pht('Commit\'s branches'), 312 self::FIELD_AUTHOR_RAW => pht('Raw author name'), 313 self::FIELD_COMMITTER_RAW => pht('Raw committer name'), 314 self::FIELD_IS_NEW_OBJECT => pht('Is newly created?'), 315 self::FIELD_TASK_PRIORITY => pht('Task priority'), 316 self::FIELD_TASK_STATUS => pht('Task status'), 317 self::FIELD_ARCANIST_PROJECT => pht('Arcanist Project'), 318 self::FIELD_PUSHER_IS_COMMITTER => pht('Pusher same as committer'), 319 self::FIELD_PATH => pht('Path'), 320 ) + $this->getCustomFieldNameMap(); 321 } 322 323 324 /* -( Conditions )--------------------------------------------------------- */ 325 326 327 public function getConditionNameMap() { 328 return array( 329 self::CONDITION_CONTAINS => pht('contains'), 330 self::CONDITION_NOT_CONTAINS => pht('does not contain'), 331 self::CONDITION_IS => pht('is'), 332 self::CONDITION_IS_NOT => pht('is not'), 333 self::CONDITION_IS_ANY => pht('is any of'), 334 self::CONDITION_IS_TRUE => pht('is true'), 335 self::CONDITION_IS_FALSE => pht('is false'), 336 self::CONDITION_IS_NOT_ANY => pht('is not any of'), 337 self::CONDITION_INCLUDE_ALL => pht('include all of'), 338 self::CONDITION_INCLUDE_ANY => pht('include any of'), 339 self::CONDITION_INCLUDE_NONE => pht('do not include'), 340 self::CONDITION_IS_ME => pht('is myself'), 341 self::CONDITION_IS_NOT_ME => pht('is not myself'), 342 self::CONDITION_REGEXP => pht('matches regexp'), 343 self::CONDITION_RULE => pht('matches:'), 344 self::CONDITION_NOT_RULE => pht('does not match:'), 345 self::CONDITION_EXISTS => pht('exists'), 346 self::CONDITION_NOT_EXISTS => pht('does not exist'), 347 self::CONDITION_UNCONDITIONALLY => '', // don't show anything! 348 self::CONDITION_NEVER => '', // don't show anything! 349 self::CONDITION_REGEXP_PAIR => pht('matches regexp pair'), 350 self::CONDITION_HAS_BIT => pht('has bit'), 351 self::CONDITION_NOT_BIT => pht('lacks bit'), 352 ); 353 } 354 355 public function getConditionsForField($field) { 356 switch ($field) { 357 case self::FIELD_TITLE: 358 case self::FIELD_BODY: 359 case self::FIELD_COMMITTER_RAW: 360 case self::FIELD_AUTHOR_RAW: 361 case self::FIELD_PATH: 362 return array( 363 self::CONDITION_CONTAINS, 364 self::CONDITION_NOT_CONTAINS, 365 self::CONDITION_IS, 366 self::CONDITION_IS_NOT, 367 self::CONDITION_REGEXP, 368 ); 369 case self::FIELD_REVIEWER: 370 case self::FIELD_PUSHER: 371 case self::FIELD_TASK_PRIORITY: 372 case self::FIELD_TASK_STATUS: 373 case self::FIELD_ARCANIST_PROJECT: 374 return array( 375 self::CONDITION_IS_ANY, 376 self::CONDITION_IS_NOT_ANY, 377 ); 378 case self::FIELD_REPOSITORY: 379 case self::FIELD_ASSIGNEE: 380 case self::FIELD_AUTHOR: 381 case self::FIELD_COMMITTER: 382 return array( 383 self::CONDITION_IS_ANY, 384 self::CONDITION_IS_NOT_ANY, 385 self::CONDITION_EXISTS, 386 self::CONDITION_NOT_EXISTS, 387 ); 388 case self::FIELD_TAGS: 389 case self::FIELD_REVIEWERS: 390 case self::FIELD_CC: 391 case self::FIELD_AUTHOR_PROJECTS: 392 case self::FIELD_PROJECTS: 393 case self::FIELD_AFFECTED_PACKAGE: 394 case self::FIELD_AFFECTED_PACKAGE_OWNER: 395 case self::FIELD_PUSHER_PROJECTS: 396 case self::FIELD_REPOSITORY_PROJECTS: 397 return array( 398 self::CONDITION_INCLUDE_ALL, 399 self::CONDITION_INCLUDE_ANY, 400 self::CONDITION_INCLUDE_NONE, 401 self::CONDITION_EXISTS, 402 self::CONDITION_NOT_EXISTS, 403 ); 404 case self::FIELD_DIFF_FILE: 405 case self::FIELD_BRANCHES: 406 return array( 407 self::CONDITION_CONTAINS, 408 self::CONDITION_REGEXP, 409 ); 410 case self::FIELD_DIFF_CONTENT: 411 case self::FIELD_DIFF_ADDED_CONTENT: 412 case self::FIELD_DIFF_REMOVED_CONTENT: 413 return array( 414 self::CONDITION_CONTAINS, 415 self::CONDITION_REGEXP, 416 self::CONDITION_REGEXP_PAIR, 417 ); 418 case self::FIELD_RULE: 419 return array( 420 self::CONDITION_RULE, 421 self::CONDITION_NOT_RULE, 422 ); 423 case self::FIELD_CONTENT_SOURCE: 424 return array( 425 self::CONDITION_IS, 426 self::CONDITION_IS_NOT, 427 ); 428 case self::FIELD_ALWAYS: 429 return array( 430 self::CONDITION_UNCONDITIONALLY, 431 ); 432 case self::FIELD_DIFFERENTIAL_REVIEWERS: 433 return array( 434 self::CONDITION_EXISTS, 435 self::CONDITION_NOT_EXISTS, 436 self::CONDITION_INCLUDE_ALL, 437 self::CONDITION_INCLUDE_ANY, 438 self::CONDITION_INCLUDE_NONE, 439 ); 440 case self::FIELD_DIFFERENTIAL_CCS: 441 return array( 442 self::CONDITION_INCLUDE_ALL, 443 self::CONDITION_INCLUDE_ANY, 444 self::CONDITION_INCLUDE_NONE, 445 ); 446 case self::FIELD_DIFFERENTIAL_REVISION: 447 case self::FIELD_DIFFERENTIAL_ACCEPTED: 448 return array( 449 self::CONDITION_EXISTS, 450 self::CONDITION_NOT_EXISTS, 451 ); 452 case self::FIELD_IS_MERGE_COMMIT: 453 case self::FIELD_DIFF_ENORMOUS: 454 case self::FIELD_IS_NEW_OBJECT: 455 case self::FIELD_PUSHER_IS_COMMITTER: 456 return array( 457 self::CONDITION_IS_TRUE, 458 self::CONDITION_IS_FALSE, 459 ); 460 default: 461 if ($this->isHeraldCustomKey($field)) { 462 return $this->getCustomFieldConditions($field); 463 } 464 throw new Exception( 465 "This adapter does not define conditions for field '{$field}'!"); 466 } 467 } 468 469 public function doesConditionMatch( 470 HeraldEngine $engine, 471 HeraldRule $rule, 472 HeraldCondition $condition, 473 $field_value) { 474 475 $condition_type = $condition->getFieldCondition(); 476 $condition_value = $condition->getValue(); 477 478 switch ($condition_type) { 479 case self::CONDITION_CONTAINS: 480 // "Contains" can take an array of strings, as in "Any changed 481 // filename" for diffs. 482 foreach ((array)$field_value as $value) { 483 if (stripos($value, $condition_value) !== false) { 484 return true; 485 } 486 } 487 return false; 488 case self::CONDITION_NOT_CONTAINS: 489 return (stripos($field_value, $condition_value) === false); 490 case self::CONDITION_IS: 491 return ($field_value == $condition_value); 492 case self::CONDITION_IS_NOT: 493 return ($field_value != $condition_value); 494 case self::CONDITION_IS_ME: 495 return ($field_value == $rule->getAuthorPHID()); 496 case self::CONDITION_IS_NOT_ME: 497 return ($field_value != $rule->getAuthorPHID()); 498 case self::CONDITION_IS_ANY: 499 if (!is_array($condition_value)) { 500 throw new HeraldInvalidConditionException( 501 'Expected condition value to be an array.'); 502 } 503 $condition_value = array_fuse($condition_value); 504 return isset($condition_value[$field_value]); 505 case self::CONDITION_IS_NOT_ANY: 506 if (!is_array($condition_value)) { 507 throw new HeraldInvalidConditionException( 508 'Expected condition value to be an array.'); 509 } 510 $condition_value = array_fuse($condition_value); 511 return !isset($condition_value[$field_value]); 512 case self::CONDITION_INCLUDE_ALL: 513 if (!is_array($field_value)) { 514 throw new HeraldInvalidConditionException( 515 'Object produced non-array value!'); 516 } 517 if (!is_array($condition_value)) { 518 throw new HeraldInvalidConditionException( 519 'Expected condition value to be an array.'); 520 } 521 522 $have = array_select_keys(array_fuse($field_value), $condition_value); 523 return (count($have) == count($condition_value)); 524 case self::CONDITION_INCLUDE_ANY: 525 return (bool)array_select_keys( 526 array_fuse($field_value), 527 $condition_value); 528 case self::CONDITION_INCLUDE_NONE: 529 return !array_select_keys( 530 array_fuse($field_value), 531 $condition_value); 532 case self::CONDITION_EXISTS: 533 case self::CONDITION_IS_TRUE: 534 return (bool)$field_value; 535 case self::CONDITION_NOT_EXISTS: 536 case self::CONDITION_IS_FALSE: 537 return !$field_value; 538 case self::CONDITION_UNCONDITIONALLY: 539 return (bool)$field_value; 540 case self::CONDITION_NEVER: 541 return false; 542 case self::CONDITION_REGEXP: 543 foreach ((array)$field_value as $value) { 544 // We add the 'S' flag because we use the regexp multiple times. 545 // It shouldn't cause any troubles if the flag is already there 546 // - /.*/S is evaluated same as /.*/SS. 547 $result = @preg_match($condition_value.'S', $value); 548 if ($result === false) { 549 throw new HeraldInvalidConditionException( 550 'Regular expression is not valid!'); 551 } 552 if ($result) { 553 return true; 554 } 555 } 556 return false; 557 case self::CONDITION_REGEXP_PAIR: 558 // Match a JSON-encoded pair of regular expressions against a 559 // dictionary. The first regexp must match the dictionary key, and the 560 // second regexp must match the dictionary value. If any key/value pair 561 // in the dictionary matches both regexps, the condition is satisfied. 562 $regexp_pair = json_decode($condition_value, true); 563 if (!is_array($regexp_pair)) { 564 throw new HeraldInvalidConditionException( 565 'Regular expression pair is not valid JSON!'); 566 } 567 if (count($regexp_pair) != 2) { 568 throw new HeraldInvalidConditionException( 569 'Regular expression pair is not a pair!'); 570 } 571 572 $key_regexp = array_shift($regexp_pair); 573 $value_regexp = array_shift($regexp_pair); 574 575 foreach ((array)$field_value as $key => $value) { 576 $key_matches = @preg_match($key_regexp, $key); 577 if ($key_matches === false) { 578 throw new HeraldInvalidConditionException( 579 'First regular expression is invalid!'); 580 } 581 if ($key_matches) { 582 $value_matches = @preg_match($value_regexp, $value); 583 if ($value_matches === false) { 584 throw new HeraldInvalidConditionException( 585 'Second regular expression is invalid!'); 586 } 587 if ($value_matches) { 588 return true; 589 } 590 } 591 } 592 return false; 593 case self::CONDITION_RULE: 594 case self::CONDITION_NOT_RULE: 595 $rule = $engine->getRule($condition_value); 596 if (!$rule) { 597 throw new HeraldInvalidConditionException( 598 'Condition references a rule which does not exist!'); 599 } 600 601 $is_not = ($condition_type == self::CONDITION_NOT_RULE); 602 $result = $engine->doesRuleMatch($rule, $this); 603 if ($is_not) { 604 $result = !$result; 605 } 606 return $result; 607 case self::CONDITION_HAS_BIT: 608 return (($condition_value & $field_value) === (int) $condition_value); 609 case self::CONDITION_NOT_BIT: 610 return (($condition_value & $field_value) !== (int) $condition_value); 611 default: 612 throw new HeraldInvalidConditionException( 613 "Unknown condition '{$condition_type}'."); 614 } 615 } 616 617 public function willSaveCondition(HeraldCondition $condition) { 618 $condition_type = $condition->getFieldCondition(); 619 $condition_value = $condition->getValue(); 620 621 switch ($condition_type) { 622 case self::CONDITION_REGEXP: 623 $ok = @preg_match($condition_value, ''); 624 if ($ok === false) { 625 throw new HeraldInvalidConditionException( 626 pht( 627 'The regular expression "%s" is not valid. Regular expressions '. 628 'must have enclosing characters (e.g. "@/path/to/file@", not '. 629 '"/path/to/file") and be syntactically correct.', 630 $condition_value)); 631 } 632 break; 633 case self::CONDITION_REGEXP_PAIR: 634 $json = json_decode($condition_value, true); 635 if (!is_array($json)) { 636 throw new HeraldInvalidConditionException( 637 pht( 638 'The regular expression pair "%s" is not valid JSON. Enter a '. 639 'valid JSON array with two elements.', 640 $condition_value)); 641 } 642 643 if (count($json) != 2) { 644 throw new HeraldInvalidConditionException( 645 pht( 646 'The regular expression pair "%s" must have exactly two '. 647 'elements.', 648 $condition_value)); 649 } 650 651 $key_regexp = array_shift($json); 652 $val_regexp = array_shift($json); 653 654 $key_ok = @preg_match($key_regexp, ''); 655 if ($key_ok === false) { 656 throw new HeraldInvalidConditionException( 657 pht( 658 'The first regexp in the regexp pair, "%s", is not a valid '. 659 'regexp.', 660 $key_regexp)); 661 } 662 663 $val_ok = @preg_match($val_regexp, ''); 664 if ($val_ok === false) { 665 throw new HeraldInvalidConditionException( 666 pht( 667 'The second regexp in the regexp pair, "%s", is not a valid '. 668 'regexp.', 669 $val_regexp)); 670 } 671 break; 672 case self::CONDITION_CONTAINS: 673 case self::CONDITION_NOT_CONTAINS: 674 case self::CONDITION_IS: 675 case self::CONDITION_IS_NOT: 676 case self::CONDITION_IS_ANY: 677 case self::CONDITION_IS_NOT_ANY: 678 case self::CONDITION_INCLUDE_ALL: 679 case self::CONDITION_INCLUDE_ANY: 680 case self::CONDITION_INCLUDE_NONE: 681 case self::CONDITION_IS_ME: 682 case self::CONDITION_IS_NOT_ME: 683 case self::CONDITION_RULE: 684 case self::CONDITION_NOT_RULE: 685 case self::CONDITION_EXISTS: 686 case self::CONDITION_NOT_EXISTS: 687 case self::CONDITION_UNCONDITIONALLY: 688 case self::CONDITION_NEVER: 689 case self::CONDITION_HAS_BIT: 690 case self::CONDITION_NOT_BIT: 691 case self::CONDITION_IS_TRUE: 692 case self::CONDITION_IS_FALSE: 693 // No explicit validation for these types, although there probably 694 // should be in some cases. 695 break; 696 default: 697 throw new HeraldInvalidConditionException( 698 pht( 699 'Unknown condition "%s"!', 700 $condition_type)); 701 } 702 } 703 704 705 /* -( Actions )------------------------------------------------------------ */ 706 707 public function getCustomActionsForRuleType($rule_type) { 708 $results = array(); 709 foreach ($this->getCustomActions() as $custom_action) { 710 if ($custom_action->appliesToRuleType($rule_type)) { 711 $results[] = $custom_action; 712 } 713 } 714 return $results; 715 } 716 717 public function getActions($rule_type) { 718 $custom_actions = $this->getCustomActionsForRuleType($rule_type); 719 return mpull($custom_actions, 'getActionKey'); 720 } 721 722 public function getActionNameMap($rule_type) { 723 switch ($rule_type) { 724 case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: 725 case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: 726 $standard = array( 727 self::ACTION_NOTHING => pht('Do nothing'), 728 self::ACTION_ADD_CC => pht('Add emails to CC'), 729 self::ACTION_REMOVE_CC => pht('Remove emails from CC'), 730 self::ACTION_EMAIL => pht('Send an email to'), 731 self::ACTION_AUDIT => pht('Trigger an Audit by'), 732 self::ACTION_FLAG => pht('Mark with flag'), 733 self::ACTION_ASSIGN_TASK => pht('Assign task to'), 734 self::ACTION_ADD_PROJECTS => pht('Add projects'), 735 self::ACTION_ADD_REVIEWERS => pht('Add reviewers'), 736 self::ACTION_ADD_BLOCKING_REVIEWERS => pht('Add blocking reviewers'), 737 self::ACTION_APPLY_BUILD_PLANS => pht('Run build plans'), 738 self::ACTION_REQUIRE_SIGNATURE => pht('Require legal signatures'), 739 self::ACTION_BLOCK => pht('Block change with message'), 740 ); 741 break; 742 case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: 743 $standard = array( 744 self::ACTION_NOTHING => pht('Do nothing'), 745 self::ACTION_ADD_CC => pht('Add me to CC'), 746 self::ACTION_REMOVE_CC => pht('Remove me from CC'), 747 self::ACTION_EMAIL => pht('Send me an email'), 748 self::ACTION_AUDIT => pht('Trigger an Audit by me'), 749 self::ACTION_FLAG => pht('Mark with flag'), 750 self::ACTION_ASSIGN_TASK => pht('Assign task to me'), 751 self::ACTION_ADD_PROJECTS => pht('Add projects'), 752 self::ACTION_ADD_REVIEWERS => pht('Add me as a reviewer'), 753 self::ACTION_ADD_BLOCKING_REVIEWERS => 754 pht('Add me as a blocking reviewer'), 755 ); 756 break; 757 default: 758 throw new Exception("Unknown rule type '{$rule_type}'!"); 759 } 760 761 $custom_actions = $this->getCustomActionsForRuleType($rule_type); 762 $standard += mpull($custom_actions, 'getActionName', 'getActionKey'); 763 764 return $standard; 765 } 766 767 public function willSaveAction( 768 HeraldRule $rule, 769 HeraldAction $action) { 770 771 $target = $action->getTarget(); 772 if (is_array($target)) { 773 $target = array_keys($target); 774 } 775 776 $author_phid = $rule->getAuthorPHID(); 777 778 $rule_type = $rule->getRuleType(); 779 if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { 780 switch ($action->getAction()) { 781 case self::ACTION_EMAIL: 782 case self::ACTION_ADD_CC: 783 case self::ACTION_REMOVE_CC: 784 case self::ACTION_AUDIT: 785 case self::ACTION_ASSIGN_TASK: 786 case self::ACTION_ADD_REVIEWERS: 787 case self::ACTION_ADD_BLOCKING_REVIEWERS: 788 // For personal rules, force these actions to target the rule owner. 789 $target = array($author_phid); 790 break; 791 case self::ACTION_FLAG: 792 // Make sure flag color is valid; set to blue if not. 793 $color_map = PhabricatorFlagColor::getColorNameMap(); 794 if (empty($color_map[$target])) { 795 $target = PhabricatorFlagColor::COLOR_BLUE; 796 } 797 break; 798 case self::ACTION_BLOCK: 799 case self::ACTION_NOTHING: 800 break; 801 default: 802 throw new HeraldInvalidActionException( 803 pht( 804 'Unrecognized action type "%s"!', 805 $action->getAction())); 806 } 807 } 808 809 $action->setTarget($target); 810 } 811 812 813 814 /* -( Values )------------------------------------------------------------- */ 815 816 817 public function getValueTypeForFieldAndCondition($field, $condition) { 818 819 if ($this->isHeraldCustomKey($field)) { 820 $value_type = $this->getCustomFieldValueTypeForFieldAndCondition( 821 $field, 822 $condition); 823 if ($value_type !== null) { 824 return $value_type; 825 } 826 } 827 828 switch ($condition) { 829 case self::CONDITION_CONTAINS: 830 case self::CONDITION_NOT_CONTAINS: 831 case self::CONDITION_REGEXP: 832 case self::CONDITION_REGEXP_PAIR: 833 return self::VALUE_TEXT; 834 case self::CONDITION_IS: 835 case self::CONDITION_IS_NOT: 836 switch ($field) { 837 case self::FIELD_CONTENT_SOURCE: 838 return self::VALUE_CONTENT_SOURCE; 839 default: 840 return self::VALUE_TEXT; 841 } 842 break; 843 case self::CONDITION_IS_ANY: 844 case self::CONDITION_IS_NOT_ANY: 845 switch ($field) { 846 case self::FIELD_REPOSITORY: 847 return self::VALUE_REPOSITORY; 848 case self::FIELD_TASK_PRIORITY: 849 return self::VALUE_TASK_PRIORITY; 850 case self::FIELD_TASK_STATUS: 851 return self::VALUE_TASK_STATUS; 852 case self::FIELD_ARCANIST_PROJECT: 853 return self::VALUE_ARCANIST_PROJECT; 854 default: 855 return self::VALUE_USER; 856 } 857 break; 858 case self::CONDITION_INCLUDE_ALL: 859 case self::CONDITION_INCLUDE_ANY: 860 case self::CONDITION_INCLUDE_NONE: 861 switch ($field) { 862 case self::FIELD_REPOSITORY: 863 return self::VALUE_REPOSITORY; 864 case self::FIELD_CC: 865 return self::VALUE_EMAIL; 866 case self::FIELD_TAGS: 867 return self::VALUE_TAG; 868 case self::FIELD_AFFECTED_PACKAGE: 869 return self::VALUE_OWNERS_PACKAGE; 870 case self::FIELD_AUTHOR_PROJECTS: 871 case self::FIELD_PUSHER_PROJECTS: 872 case self::FIELD_PROJECTS: 873 case self::FIELD_REPOSITORY_PROJECTS: 874 return self::VALUE_PROJECT; 875 case self::FIELD_REVIEWERS: 876 return self::VALUE_USER_OR_PROJECT; 877 default: 878 return self::VALUE_USER; 879 } 880 break; 881 case self::CONDITION_IS_ME: 882 case self::CONDITION_IS_NOT_ME: 883 case self::CONDITION_EXISTS: 884 case self::CONDITION_NOT_EXISTS: 885 case self::CONDITION_UNCONDITIONALLY: 886 case self::CONDITION_NEVER: 887 case self::CONDITION_IS_TRUE: 888 case self::CONDITION_IS_FALSE: 889 return self::VALUE_NONE; 890 case self::CONDITION_RULE: 891 case self::CONDITION_NOT_RULE: 892 return self::VALUE_RULE; 893 default: 894 throw new Exception("Unknown condition '{$condition}'."); 895 } 896 } 897 898 public function getValueTypeForAction($action, $rule_type) { 899 $is_personal = ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); 900 901 if ($is_personal) { 902 switch ($action) { 903 case self::ACTION_ADD_CC: 904 case self::ACTION_REMOVE_CC: 905 case self::ACTION_EMAIL: 906 case self::ACTION_NOTHING: 907 case self::ACTION_AUDIT: 908 case self::ACTION_ASSIGN_TASK: 909 case self::ACTION_ADD_REVIEWERS: 910 case self::ACTION_ADD_BLOCKING_REVIEWERS: 911 return self::VALUE_NONE; 912 case self::ACTION_FLAG: 913 return self::VALUE_FLAG_COLOR; 914 case self::ACTION_ADD_PROJECTS: 915 return self::VALUE_PROJECT; 916 } 917 } else { 918 switch ($action) { 919 case self::ACTION_ADD_CC: 920 case self::ACTION_REMOVE_CC: 921 case self::ACTION_EMAIL: 922 return self::VALUE_EMAIL; 923 case self::ACTION_NOTHING: 924 return self::VALUE_NONE; 925 case self::ACTION_ADD_PROJECTS: 926 return self::VALUE_PROJECT; 927 case self::ACTION_FLAG: 928 return self::VALUE_FLAG_COLOR; 929 case self::ACTION_ASSIGN_TASK: 930 return self::VALUE_USER; 931 case self::ACTION_AUDIT: 932 case self::ACTION_ADD_REVIEWERS: 933 case self::ACTION_ADD_BLOCKING_REVIEWERS: 934 return self::VALUE_USER_OR_PROJECT; 935 case self::ACTION_APPLY_BUILD_PLANS: 936 return self::VALUE_BUILD_PLAN; 937 case self::ACTION_REQUIRE_SIGNATURE: 938 return self::VALUE_LEGAL_DOCUMENTS; 939 case self::ACTION_BLOCK: 940 return self::VALUE_TEXT; 941 } 942 } 943 944 $custom_action = idx($this->getCustomActions(), $action); 945 if ($custom_action !== null) { 946 return $custom_action->getActionType(); 947 } 948 949 throw new Exception("Unknown or invalid action '".$action."'."); 950 } 951 952 953 /* -( Repetition )--------------------------------------------------------- */ 954 955 956 public function getRepetitionOptions() { 957 return array( 958 HeraldRepetitionPolicyConfig::EVERY, 959 ); 960 } 961 962 963 public static function applyFlagEffect(HeraldEffect $effect, $phid) { 964 $color = $effect->getTarget(); 965 966 // TODO: Silly that we need to load this again here. 967 $rule = id(new HeraldRule())->load($effect->getRuleID()); 968 $user = id(new PhabricatorUser())->loadOneWhere( 969 'phid = %s', 970 $rule->getAuthorPHID()); 971 972 $flag = PhabricatorFlagQuery::loadUserFlag($user, $phid); 973 if ($flag) { 974 return new HeraldApplyTranscript( 975 $effect, 976 false, 977 pht('Object already flagged.')); 978 } 979 980 $handle = id(new PhabricatorHandleQuery()) 981 ->setViewer($user) 982 ->withPHIDs(array($phid)) 983 ->executeOne(); 984 985 $flag = new PhabricatorFlag(); 986 $flag->setOwnerPHID($user->getPHID()); 987 $flag->setType($handle->getType()); 988 $flag->setObjectPHID($handle->getPHID()); 989 990 // TOOD: Should really be transcript PHID, but it doesn't exist yet. 991 $flag->setReasonPHID($user->getPHID()); 992 993 $flag->setColor($color); 994 $flag->setNote( 995 pht('Flagged by Herald Rule "%s".', $rule->getName())); 996 $flag->save(); 997 998 return new HeraldApplyTranscript( 999 $effect, 1000 true, 1001 pht('Added flag.')); 1002 } 1003 1004 public static function getAllAdapters() { 1005 static $adapters; 1006 if (!$adapters) { 1007 $adapters = id(new PhutilSymbolLoader()) 1008 ->setAncestorClass(__CLASS__) 1009 ->loadObjects(); 1010 $adapters = msort($adapters, 'getAdapterSortKey'); 1011 } 1012 return $adapters; 1013 } 1014 1015 public static function getAdapterForContentType($content_type) { 1016 $adapters = self::getAllAdapters(); 1017 1018 foreach ($adapters as $adapter) { 1019 if ($adapter->getAdapterContentType() == $content_type) { 1020 return $adapter; 1021 } 1022 } 1023 1024 throw new Exception( 1025 pht( 1026 'No adapter exists for Herald content type "%s".', 1027 $content_type)); 1028 } 1029 1030 public static function getEnabledAdapterMap(PhabricatorUser $viewer) { 1031 $map = array(); 1032 1033 $adapters = HeraldAdapter::getAllAdapters(); 1034 foreach ($adapters as $adapter) { 1035 if (!$adapter->isAvailableToUser($viewer)) { 1036 continue; 1037 } 1038 $type = $adapter->getAdapterContentType(); 1039 $name = $adapter->getAdapterContentName(); 1040 $map[$type] = $name; 1041 } 1042 1043 return $map; 1044 } 1045 1046 public function renderRuleAsText(HeraldRule $rule, array $handles) { 1047 assert_instances_of($handles, 'PhabricatorObjectHandle'); 1048 1049 require_celerity_resource('herald-css'); 1050 1051 $icon = id(new PHUIIconView()) 1052 ->setIconFont('fa-chevron-circle-right lightgreytext') 1053 ->addClass('herald-list-icon'); 1054 1055 if ($rule->getMustMatchAll()) { 1056 $match_text = pht('When all of these conditions are met:'); 1057 } else { 1058 $match_text = pht('When any of these conditions are met:'); 1059 } 1060 1061 $match_title = phutil_tag( 1062 'p', 1063 array( 1064 'class' => 'herald-list-description', 1065 ), 1066 $match_text); 1067 1068 $match_list = array(); 1069 foreach ($rule->getConditions() as $condition) { 1070 $match_list[] = phutil_tag( 1071 'div', 1072 array( 1073 'class' => 'herald-list-item', 1074 ), 1075 array( 1076 $icon, 1077 $this->renderConditionAsText($condition, $handles), 1078 )); 1079 } 1080 1081 $integer_code_for_every = HeraldRepetitionPolicyConfig::toInt( 1082 HeraldRepetitionPolicyConfig::EVERY); 1083 1084 if ($rule->getRepetitionPolicy() == $integer_code_for_every) { 1085 $action_text = 1086 pht('Take these actions every time this rule matches:'); 1087 } else { 1088 $action_text = 1089 pht('Take these actions the first time this rule matches:'); 1090 } 1091 1092 $action_title = phutil_tag( 1093 'p', 1094 array( 1095 'class' => 'herald-list-description', 1096 ), 1097 $action_text); 1098 1099 $action_list = array(); 1100 foreach ($rule->getActions() as $action) { 1101 $action_list[] = phutil_tag( 1102 'div', 1103 array( 1104 'class' => 'herald-list-item', 1105 ), 1106 array( 1107 $icon, 1108 $this->renderActionAsText($action, $handles), 1109 )); 1110 } 1111 1112 return array( 1113 $match_title, 1114 $match_list, 1115 $action_title, 1116 $action_list, 1117 ); 1118 } 1119 1120 private function renderConditionAsText( 1121 HeraldCondition $condition, 1122 array $handles) { 1123 1124 $field_type = $condition->getFieldName(); 1125 1126 $default = $this->isHeraldCustomKey($field_type) 1127 ? pht('(Unknown Custom Field "%s")', $field_type) 1128 : pht('(Unknown Field "%s")', $field_type); 1129 1130 $field_name = idx($this->getFieldNameMap(), $field_type, $default); 1131 1132 $condition_type = $condition->getFieldCondition(); 1133 $condition_name = idx($this->getConditionNameMap(), $condition_type); 1134 1135 $value = $this->renderConditionValueAsText($condition, $handles); 1136 1137 return hsprintf(' %s %s %s', $field_name, $condition_name, $value); 1138 } 1139 1140 private function renderActionAsText( 1141 HeraldAction $action, 1142 array $handles) { 1143 $rule_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; 1144 1145 $action_type = $action->getAction(); 1146 $action_name = idx($this->getActionNameMap($rule_global), $action_type); 1147 1148 $target = $this->renderActionTargetAsText($action, $handles); 1149 1150 return hsprintf(' %s %s', $action_name, $target); 1151 } 1152 1153 private function renderConditionValueAsText( 1154 HeraldCondition $condition, 1155 array $handles) { 1156 1157 $value = $condition->getValue(); 1158 if (!is_array($value)) { 1159 $value = array($value); 1160 } 1161 switch ($condition->getFieldName()) { 1162 case self::FIELD_TASK_PRIORITY: 1163 $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); 1164 foreach ($value as $index => $val) { 1165 $name = idx($priority_map, $val); 1166 if ($name) { 1167 $value[$index] = $name; 1168 } 1169 } 1170 break; 1171 case self::FIELD_TASK_STATUS: 1172 $status_map = ManiphestTaskStatus::getTaskStatusMap(); 1173 foreach ($value as $index => $val) { 1174 $name = idx($status_map, $val); 1175 if ($name) { 1176 $value[$index] = $name; 1177 } 1178 } 1179 break; 1180 case HeraldPreCommitRefAdapter::FIELD_REF_CHANGE: 1181 $change_map = 1182 PhabricatorRepositoryPushLog::getHeraldChangeFlagConditionOptions(); 1183 foreach ($value as $index => $val) { 1184 $name = idx($change_map, $val); 1185 if ($name) { 1186 $value[$index] = $name; 1187 } 1188 } 1189 break; 1190 default: 1191 foreach ($value as $index => $val) { 1192 $handle = idx($handles, $val); 1193 if ($handle) { 1194 $value[$index] = $handle->renderLink(); 1195 } 1196 } 1197 break; 1198 } 1199 $value = phutil_implode_html(', ', $value); 1200 return $value; 1201 } 1202 1203 private function renderActionTargetAsText( 1204 HeraldAction $action, 1205 array $handles) { 1206 1207 $target = $action->getTarget(); 1208 if (!is_array($target)) { 1209 $target = array($target); 1210 } 1211 foreach ($target as $index => $val) { 1212 switch ($action->getAction()) { 1213 case self::ACTION_FLAG: 1214 $target[$index] = PhabricatorFlagColor::getColorName($val); 1215 break; 1216 default: 1217 $handle = idx($handles, $val); 1218 if ($handle) { 1219 $target[$index] = $handle->renderLink(); 1220 } 1221 break; 1222 } 1223 } 1224 $target = phutil_implode_html(', ', $target); 1225 return $target; 1226 } 1227 1228 /** 1229 * Given a @{class:HeraldRule}, this function extracts all the phids that 1230 * we'll want to load as handles later. 1231 * 1232 * This function performs a somewhat hacky approach to figuring out what 1233 * is and is not a phid - try to get the phid type and if the type is 1234 * *not* unknown assume its a valid phid. 1235 * 1236 * Don't try this at home. Use more strongly typed data at home. 1237 * 1238 * Think of the children. 1239 */ 1240 public static function getHandlePHIDs(HeraldRule $rule) { 1241 $phids = array($rule->getAuthorPHID()); 1242 foreach ($rule->getConditions() as $condition) { 1243 $value = $condition->getValue(); 1244 if (!is_array($value)) { 1245 $value = array($value); 1246 } 1247 foreach ($value as $val) { 1248 if (phid_get_type($val) != 1249 PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { 1250 $phids[] = $val; 1251 } 1252 } 1253 } 1254 1255 foreach ($rule->getActions() as $action) { 1256 $target = $action->getTarget(); 1257 if (!is_array($target)) { 1258 $target = array($target); 1259 } 1260 foreach ($target as $val) { 1261 if (phid_get_type($val) != 1262 PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { 1263 $phids[] = $val; 1264 } 1265 } 1266 } 1267 1268 if ($rule->isObjectRule()) { 1269 $phids[] = $rule->getTriggerObjectPHID(); 1270 } 1271 1272 return $phids; 1273 } 1274 1275 /* -( Custom Field Integration )------------------------------------------- */ 1276 1277 1278 /** 1279 * Return an object which custom fields can be generated from while editing 1280 * rules. Adapters must return an object from this method to enable custom 1281 * field rules. 1282 * 1283 * Normally, you'll return an empty version of the adapted object, assuming 1284 * it implements @{interface:PhabricatorCustomFieldInterface}: 1285 * 1286 * return new ApplicationObject(); 1287 * 1288 * This is normally the only adapter method you need to override to enable 1289 * Herald rules to run against custom fields. 1290 * 1291 * @return null|PhabricatorCustomFieldInterface Template object. 1292 * @task customfield 1293 */ 1294 protected function getCustomFieldTemplateObject() { 1295 return null; 1296 } 1297 1298 1299 /** 1300 * Returns the prefix used to namespace Herald fields which are based on 1301 * custom fields. 1302 * 1303 * @return string Key prefix. 1304 * @task customfield 1305 */ 1306 private function getCustomKeyPrefix() { 1307 return 'herald.custom/'; 1308 } 1309 1310 1311 /** 1312 * Determine if a field key is based on a custom field or a regular internal 1313 * field. 1314 * 1315 * @param string Field key. 1316 * @return bool True if the field key is based on a custom field. 1317 * @task customfield 1318 */ 1319 private function isHeraldCustomKey($key) { 1320 $prefix = $this->getCustomKeyPrefix(); 1321 return (strncmp($key, $prefix, strlen($prefix)) == 0); 1322 } 1323 1324 1325 /** 1326 * Convert a custom field key into a Herald field key. 1327 * 1328 * @param string Custom field key. 1329 * @return string Herald field key. 1330 * @task customfield 1331 */ 1332 private function getHeraldKeyFromCustomKey($key) { 1333 return $this->getCustomKeyPrefix().$key; 1334 } 1335 1336 1337 /** 1338 * Get custom fields for this adapter, if appliable. This will either return 1339 * a field list or `null` if the adapted object does not implement custom 1340 * fields or the adapter does not support them. 1341 * 1342 * @return PhabricatorCustomFieldList|null List of fields, or `null`. 1343 * @task customfield 1344 */ 1345 private function getCustomFields() { 1346 if ($this->customFields === false) { 1347 $this->customFields = null; 1348 1349 1350 $template_object = $this->getCustomFieldTemplateObject(); 1351 if ($template_object) { 1352 $object = $this->getObject(); 1353 if (!$object) { 1354 $object = $template_object; 1355 } 1356 1357 $fields = PhabricatorCustomField::getObjectFields( 1358 $object, 1359 PhabricatorCustomField::ROLE_HERALD); 1360 $fields->setViewer(PhabricatorUser::getOmnipotentUser()); 1361 $fields->readFieldsFromStorage($object); 1362 1363 $this->customFields = $fields; 1364 } 1365 } 1366 1367 return $this->customFields; 1368 } 1369 1370 1371 /** 1372 * Get a custom field by Herald field key, or `null` if it does not exist 1373 * or custom fields are not supported. 1374 * 1375 * @param string Herald field key. 1376 * @return PhabricatorCustomField|null Matching field, if it exists. 1377 * @task customfield 1378 */ 1379 private function getCustomField($herald_field_key) { 1380 $fields = $this->getCustomFields(); 1381 if (!$fields) { 1382 return null; 1383 } 1384 1385 foreach ($fields->getFields() as $custom_field) { 1386 $key = $custom_field->getFieldKey(); 1387 if ($this->getHeraldKeyFromCustomKey($key) == $herald_field_key) { 1388 return $custom_field; 1389 } 1390 } 1391 1392 return null; 1393 } 1394 1395 1396 /** 1397 * Get the field map for custom fields. 1398 * 1399 * @return map<string, string> Map of Herald field keys to field names. 1400 * @task customfield 1401 */ 1402 private function getCustomFieldNameMap() { 1403 $fields = $this->getCustomFields(); 1404 if (!$fields) { 1405 return array(); 1406 } 1407 1408 $map = array(); 1409 foreach ($fields->getFields() as $field) { 1410 $key = $field->getFieldKey(); 1411 $name = $field->getHeraldFieldName(); 1412 $map[$this->getHeraldKeyFromCustomKey($key)] = $name; 1413 } 1414 1415 return $map; 1416 } 1417 1418 1419 /** 1420 * Get the value for a custom field. 1421 * 1422 * @param string Herald field key. 1423 * @return wild Custom field value. 1424 * @task customfield 1425 */ 1426 private function getCustomFieldValue($field_key) { 1427 $field = $this->getCustomField($field_key); 1428 if (!$field) { 1429 return null; 1430 } 1431 1432 return $field->getHeraldFieldValue(); 1433 } 1434 1435 1436 /** 1437 * Get the Herald conditions for a custom field. 1438 * 1439 * @param string Herald field key. 1440 * @return list<const> List of Herald conditions. 1441 * @task customfield 1442 */ 1443 private function getCustomFieldConditions($field_key) { 1444 $field = $this->getCustomField($field_key); 1445 if (!$field) { 1446 return array( 1447 self::CONDITION_NEVER, 1448 ); 1449 } 1450 1451 return $field->getHeraldFieldConditions(); 1452 } 1453 1454 1455 /** 1456 * Get the Herald value type for a custom field and condition. 1457 * 1458 * @param string Herald field key. 1459 * @param const Herald condition constant. 1460 * @return const|null Herald value type constant, or null to use the default. 1461 * @task customfield 1462 */ 1463 private function getCustomFieldValueTypeForFieldAndCondition( 1464 $field_key, 1465 $condition) { 1466 1467 $field = $this->getCustomField($field_key); 1468 if (!$field) { 1469 return self::VALUE_NONE; 1470 } 1471 1472 return $field->getHeraldFieldValueType($condition); 1473 } 1474 1475 1476 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |