diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index a478284231..b8a13a12ec 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -1,1629 +1,1638 @@ emailPHIDs); } public function getForcedEmailPHIDs() { return array_values($this->forcedEmailPHIDs); } public function getCustomActions() { if ($this->customActions === null) { $custom_actions = id(new PhutilSymbolLoader()) ->setAncestorClass('HeraldCustomAction') ->loadObjects(); foreach ($custom_actions as $key => $object) { if (!$object->appliesToAdapter($this)) { unset($custom_actions[$key]); } } $this->customActions = array(); foreach ($custom_actions as $action) { $key = $action->getActionKey(); if (array_key_exists($key, $this->customActions)) { throw new Exception( 'More than one Herald custom action implementation '. 'handles the action key: \''.$key.'\'.'); } $this->customActions[$key] = $action; } } return $this->customActions; } public function setContentSource(PhabricatorContentSource $content_source) { $this->contentSource = $content_source; return $this; } public function getContentSource() { return $this->contentSource; } public function getIsNewObject() { if (is_bool($this->isNewObject)) { return $this->isNewObject; } throw new Exception(pht('You must setIsNewObject to a boolean first!')); } public function setIsNewObject($new) { $this->isNewObject = (bool) $new; return $this; } public function setApplicationEmail( PhabricatorMetaMTAApplicationEmail $email) { $this->applicationEmail = $email; return $this; } public function getApplicationEmail() { return $this->applicationEmail; } abstract public function getPHID(); abstract public function getHeraldName(); public function getHeraldField($field_name) { switch ($field_name) { case self::FIELD_RULE: return null; case self::FIELD_CONTENT_SOURCE: return $this->getContentSource()->getSource(); case self::FIELD_ALWAYS: return true; case self::FIELD_IS_NEW_OBJECT: return $this->getIsNewObject(); case self::FIELD_APPLICATION_EMAIL: $value = array(); // while there is only one match by implementation, we do set // comparisons on phids, so return an array with just the phid if ($this->getApplicationEmail()) { $value[] = $this->getApplicationEmail()->getPHID(); } return $value; default: if ($this->isHeraldCustomKey($field_name)) { return $this->getCustomFieldValue($field_name); } throw new Exception( "Unknown field '{$field_name}'!"); } } abstract public function applyHeraldEffects(array $effects); protected function handleCustomHeraldEffect(HeraldEffect $effect) { $custom_action = idx($this->getCustomActions(), $effect->getAction()); if ($custom_action !== null) { return $custom_action->applyEffect( $this, $this->getObject(), $effect); } return null; } public function isAvailableToUser(PhabricatorUser $viewer) { $applications = id(new PhabricatorApplicationQuery()) ->setViewer($viewer) ->withInstalled(true) ->withClasses(array($this->getAdapterApplicationClass())) ->execute(); return !empty($applications); } public function queueTransaction($transaction) { $this->queuedTransactions[] = $transaction; } public function getQueuedTransactions() { return $this->queuedTransactions; } protected function newTransaction() { - $object = $this->getObject(); + $object = $this->newObject(); if (!($object instanceof PhabricatorApplicationTransactionInterface)) { throw new Exception( pht( 'Unable to build a new transaction for adapter object; it does '. 'not implement "%s".', 'PhabricatorApplicationTransactionInterface')); } return $object->getApplicationTransactionTemplate(); } - - /** * NOTE: You generally should not override this; it exists to support legacy * adapters which had hard-coded content types. */ public function getAdapterContentType() { return get_class($this); } abstract public function getAdapterContentName(); abstract public function getAdapterContentDescription(); abstract public function getAdapterApplicationClass(); abstract public function getObject(); + + /** + * Return a new characteristic object for this adapter. + * + * The adapter will use this object to test for interfaces, generate + * transactions, and interact with custom fields. + * + * Adapters must return an object from this method to enable custom + * field rules and various implicit actions. + * + * Normally, you'll return an empty version of the adapted object: + * + * return new ApplicationObject(); + * + * @return null|object Template object. + */ + protected function newObject() { + return null; + } + public function supportsRuleType($rule_type) { return false; } public function canTriggerOnObject($object) { return false; } public function explainValidTriggerObjects() { return pht('This adapter can not trigger on objects.'); } public function getTriggerObjectPHIDs() { return array($this->getPHID()); } public function getAdapterSortKey() { return sprintf( '%08d%s', $this->getAdapterSortOrder(), $this->getAdapterContentName()); } public function getAdapterSortOrder() { return 1000; } /* -( Fields )------------------------------------------------------------- */ public function getFields() { $fields = array(); $fields[] = self::FIELD_ALWAYS; $fields[] = self::FIELD_RULE; $custom_fields = $this->getCustomFields(); if ($custom_fields) { foreach ($custom_fields->getFields() as $custom_field) { $key = $custom_field->getFieldKey(); $fields[] = $this->getHeraldKeyFromCustomKey($key); } } return $fields; } public function getFieldNameMap() { return array( self::FIELD_TITLE => pht('Title'), self::FIELD_BODY => pht('Body'), self::FIELD_AUTHOR => pht('Author'), self::FIELD_ASSIGNEE => pht('Assignee'), self::FIELD_COMMITTER => pht('Committer'), self::FIELD_REVIEWER => pht('Reviewer'), self::FIELD_REVIEWERS => pht('Reviewers'), self::FIELD_CC => pht('CCs'), self::FIELD_TAGS => pht('Tags'), self::FIELD_DIFF_FILE => pht('Any changed filename'), self::FIELD_DIFF_CONTENT => pht('Any changed file content'), self::FIELD_DIFF_ADDED_CONTENT => pht('Any added file content'), self::FIELD_DIFF_REMOVED_CONTENT => pht('Any removed file content'), self::FIELD_DIFF_ENORMOUS => pht('Change is enormous'), self::FIELD_REPOSITORY => pht('Repository'), self::FIELD_REPOSITORY_PROJECTS => pht('Repository\'s projects'), self::FIELD_RULE => pht('Another Herald rule'), self::FIELD_AFFECTED_PACKAGE => pht('Any affected package'), self::FIELD_AFFECTED_PACKAGE_OWNER => pht("Any affected package's owner"), self::FIELD_CONTENT_SOURCE => pht('Content Source'), self::FIELD_ALWAYS => pht('Always'), self::FIELD_AUTHOR_PROJECTS => pht("Author's projects"), self::FIELD_PROJECTS => pht('Projects'), self::FIELD_PUSHER => pht('Pusher'), self::FIELD_PUSHER_PROJECTS => pht("Pusher's projects"), self::FIELD_DIFFERENTIAL_REVISION => pht('Differential revision'), self::FIELD_DIFFERENTIAL_REVIEWERS => pht('Differential reviewers'), self::FIELD_DIFFERENTIAL_CCS => pht('Differential CCs'), self::FIELD_DIFFERENTIAL_ACCEPTED => pht('Accepted Differential revision'), self::FIELD_IS_MERGE_COMMIT => pht('Commit is a merge'), self::FIELD_BRANCHES => pht('Commit\'s branches'), self::FIELD_AUTHOR_RAW => pht('Raw author name'), self::FIELD_COMMITTER_RAW => pht('Raw committer name'), self::FIELD_IS_NEW_OBJECT => pht('Is newly created?'), self::FIELD_APPLICATION_EMAIL => pht('Receiving email address'), self::FIELD_TASK_PRIORITY => pht('Task priority'), self::FIELD_TASK_STATUS => pht('Task status'), self::FIELD_ARCANIST_PROJECT => pht('Arcanist Project'), self::FIELD_PUSHER_IS_COMMITTER => pht('Pusher same as committer'), self::FIELD_PATH => pht('Path'), ) + $this->getCustomFieldNameMap(); } /* -( Conditions )--------------------------------------------------------- */ public function getConditionNameMap() { return array( self::CONDITION_CONTAINS => pht('contains'), self::CONDITION_NOT_CONTAINS => pht('does not contain'), self::CONDITION_IS => pht('is'), self::CONDITION_IS_NOT => pht('is not'), self::CONDITION_IS_ANY => pht('is any of'), self::CONDITION_IS_TRUE => pht('is true'), self::CONDITION_IS_FALSE => pht('is false'), self::CONDITION_IS_NOT_ANY => pht('is not any of'), self::CONDITION_INCLUDE_ALL => pht('include all of'), self::CONDITION_INCLUDE_ANY => pht('include any of'), self::CONDITION_INCLUDE_NONE => pht('do not include'), self::CONDITION_IS_ME => pht('is myself'), self::CONDITION_IS_NOT_ME => pht('is not myself'), self::CONDITION_REGEXP => pht('matches regexp'), self::CONDITION_RULE => pht('matches:'), self::CONDITION_NOT_RULE => pht('does not match:'), self::CONDITION_EXISTS => pht('exists'), self::CONDITION_NOT_EXISTS => pht('does not exist'), self::CONDITION_UNCONDITIONALLY => '', // don't show anything! self::CONDITION_NEVER => '', // don't show anything! self::CONDITION_REGEXP_PAIR => pht('matches regexp pair'), self::CONDITION_HAS_BIT => pht('has bit'), self::CONDITION_NOT_BIT => pht('lacks bit'), ); } public function getConditionsForField($field) { switch ($field) { case self::FIELD_TITLE: case self::FIELD_BODY: case self::FIELD_COMMITTER_RAW: case self::FIELD_AUTHOR_RAW: case self::FIELD_PATH: return array( self::CONDITION_CONTAINS, self::CONDITION_NOT_CONTAINS, self::CONDITION_IS, self::CONDITION_IS_NOT, self::CONDITION_REGEXP, ); case self::FIELD_REVIEWER: case self::FIELD_PUSHER: case self::FIELD_TASK_PRIORITY: case self::FIELD_TASK_STATUS: case self::FIELD_ARCANIST_PROJECT: return array( self::CONDITION_IS_ANY, self::CONDITION_IS_NOT_ANY, ); case self::FIELD_REPOSITORY: case self::FIELD_ASSIGNEE: case self::FIELD_AUTHOR: case self::FIELD_COMMITTER: return array( self::CONDITION_IS_ANY, self::CONDITION_IS_NOT_ANY, self::CONDITION_EXISTS, self::CONDITION_NOT_EXISTS, ); case self::FIELD_TAGS: case self::FIELD_REVIEWERS: case self::FIELD_CC: case self::FIELD_AUTHOR_PROJECTS: case self::FIELD_PROJECTS: case self::FIELD_AFFECTED_PACKAGE: case self::FIELD_AFFECTED_PACKAGE_OWNER: case self::FIELD_PUSHER_PROJECTS: case self::FIELD_REPOSITORY_PROJECTS: return array( self::CONDITION_INCLUDE_ALL, self::CONDITION_INCLUDE_ANY, self::CONDITION_INCLUDE_NONE, self::CONDITION_EXISTS, self::CONDITION_NOT_EXISTS, ); case self::FIELD_APPLICATION_EMAIL: return array( self::CONDITION_INCLUDE_ANY, self::CONDITION_INCLUDE_NONE, self::CONDITION_EXISTS, self::CONDITION_NOT_EXISTS, ); case self::FIELD_DIFF_FILE: case self::FIELD_BRANCHES: return array( self::CONDITION_CONTAINS, self::CONDITION_REGEXP, ); case self::FIELD_DIFF_CONTENT: case self::FIELD_DIFF_ADDED_CONTENT: case self::FIELD_DIFF_REMOVED_CONTENT: return array( self::CONDITION_CONTAINS, self::CONDITION_REGEXP, self::CONDITION_REGEXP_PAIR, ); case self::FIELD_RULE: return array( self::CONDITION_RULE, self::CONDITION_NOT_RULE, ); case self::FIELD_CONTENT_SOURCE: return array( self::CONDITION_IS, self::CONDITION_IS_NOT, ); case self::FIELD_ALWAYS: return array( self::CONDITION_UNCONDITIONALLY, ); case self::FIELD_DIFFERENTIAL_REVIEWERS: return array( self::CONDITION_EXISTS, self::CONDITION_NOT_EXISTS, self::CONDITION_INCLUDE_ALL, self::CONDITION_INCLUDE_ANY, self::CONDITION_INCLUDE_NONE, ); case self::FIELD_DIFFERENTIAL_CCS: return array( self::CONDITION_INCLUDE_ALL, self::CONDITION_INCLUDE_ANY, self::CONDITION_INCLUDE_NONE, ); case self::FIELD_DIFFERENTIAL_REVISION: case self::FIELD_DIFFERENTIAL_ACCEPTED: return array( self::CONDITION_EXISTS, self::CONDITION_NOT_EXISTS, ); case self::FIELD_IS_MERGE_COMMIT: case self::FIELD_DIFF_ENORMOUS: case self::FIELD_IS_NEW_OBJECT: case self::FIELD_PUSHER_IS_COMMITTER: return array( self::CONDITION_IS_TRUE, self::CONDITION_IS_FALSE, ); default: if ($this->isHeraldCustomKey($field)) { return $this->getCustomFieldConditions($field); } throw new Exception( "This adapter does not define conditions for field '{$field}'!"); } } public function doesConditionMatch( HeraldEngine $engine, HeraldRule $rule, HeraldCondition $condition, $field_value) { $condition_type = $condition->getFieldCondition(); $condition_value = $condition->getValue(); switch ($condition_type) { case self::CONDITION_CONTAINS: // "Contains" can take an array of strings, as in "Any changed // filename" for diffs. foreach ((array)$field_value as $value) { if (stripos($value, $condition_value) !== false) { return true; } } return false; case self::CONDITION_NOT_CONTAINS: return (stripos($field_value, $condition_value) === false); case self::CONDITION_IS: return ($field_value == $condition_value); case self::CONDITION_IS_NOT: return ($field_value != $condition_value); case self::CONDITION_IS_ME: return ($field_value == $rule->getAuthorPHID()); case self::CONDITION_IS_NOT_ME: return ($field_value != $rule->getAuthorPHID()); case self::CONDITION_IS_ANY: if (!is_array($condition_value)) { throw new HeraldInvalidConditionException( 'Expected condition value to be an array.'); } $condition_value = array_fuse($condition_value); return isset($condition_value[$field_value]); case self::CONDITION_IS_NOT_ANY: if (!is_array($condition_value)) { throw new HeraldInvalidConditionException( 'Expected condition value to be an array.'); } $condition_value = array_fuse($condition_value); return !isset($condition_value[$field_value]); case self::CONDITION_INCLUDE_ALL: if (!is_array($field_value)) { throw new HeraldInvalidConditionException( 'Object produced non-array value!'); } if (!is_array($condition_value)) { throw new HeraldInvalidConditionException( 'Expected condition value to be an array.'); } $have = array_select_keys(array_fuse($field_value), $condition_value); return (count($have) == count($condition_value)); case self::CONDITION_INCLUDE_ANY: return (bool)array_select_keys( array_fuse($field_value), $condition_value); case self::CONDITION_INCLUDE_NONE: return !array_select_keys( array_fuse($field_value), $condition_value); case self::CONDITION_EXISTS: case self::CONDITION_IS_TRUE: return (bool)$field_value; case self::CONDITION_NOT_EXISTS: case self::CONDITION_IS_FALSE: return !$field_value; case self::CONDITION_UNCONDITIONALLY: return (bool)$field_value; case self::CONDITION_NEVER: return false; case self::CONDITION_REGEXP: foreach ((array)$field_value as $value) { // We add the 'S' flag because we use the regexp multiple times. // It shouldn't cause any troubles if the flag is already there // - /.*/S is evaluated same as /.*/SS. $result = @preg_match($condition_value.'S', $value); if ($result === false) { throw new HeraldInvalidConditionException( 'Regular expression is not valid!'); } if ($result) { return true; } } return false; case self::CONDITION_REGEXP_PAIR: // Match a JSON-encoded pair of regular expressions against a // dictionary. The first regexp must match the dictionary key, and the // second regexp must match the dictionary value. If any key/value pair // in the dictionary matches both regexps, the condition is satisfied. $regexp_pair = json_decode($condition_value, true); if (!is_array($regexp_pair)) { throw new HeraldInvalidConditionException( 'Regular expression pair is not valid JSON!'); } if (count($regexp_pair) != 2) { throw new HeraldInvalidConditionException( 'Regular expression pair is not a pair!'); } $key_regexp = array_shift($regexp_pair); $value_regexp = array_shift($regexp_pair); foreach ((array)$field_value as $key => $value) { $key_matches = @preg_match($key_regexp, $key); if ($key_matches === false) { throw new HeraldInvalidConditionException( 'First regular expression is invalid!'); } if ($key_matches) { $value_matches = @preg_match($value_regexp, $value); if ($value_matches === false) { throw new HeraldInvalidConditionException( 'Second regular expression is invalid!'); } if ($value_matches) { return true; } } } return false; case self::CONDITION_RULE: case self::CONDITION_NOT_RULE: $rule = $engine->getRule($condition_value); if (!$rule) { throw new HeraldInvalidConditionException( 'Condition references a rule which does not exist!'); } $is_not = ($condition_type == self::CONDITION_NOT_RULE); $result = $engine->doesRuleMatch($rule, $this); if ($is_not) { $result = !$result; } return $result; case self::CONDITION_HAS_BIT: return (($condition_value & $field_value) === (int) $condition_value); case self::CONDITION_NOT_BIT: return (($condition_value & $field_value) !== (int) $condition_value); default: throw new HeraldInvalidConditionException( "Unknown condition '{$condition_type}'."); } } public function willSaveCondition(HeraldCondition $condition) { $condition_type = $condition->getFieldCondition(); $condition_value = $condition->getValue(); switch ($condition_type) { case self::CONDITION_REGEXP: $ok = @preg_match($condition_value, ''); if ($ok === false) { throw new HeraldInvalidConditionException( pht( 'The regular expression "%s" is not valid. Regular expressions '. 'must have enclosing characters (e.g. "@/path/to/file@", not '. '"/path/to/file") and be syntactically correct.', $condition_value)); } break; case self::CONDITION_REGEXP_PAIR: $json = json_decode($condition_value, true); if (!is_array($json)) { throw new HeraldInvalidConditionException( pht( 'The regular expression pair "%s" is not valid JSON. Enter a '. 'valid JSON array with two elements.', $condition_value)); } if (count($json) != 2) { throw new HeraldInvalidConditionException( pht( 'The regular expression pair "%s" must have exactly two '. 'elements.', $condition_value)); } $key_regexp = array_shift($json); $val_regexp = array_shift($json); $key_ok = @preg_match($key_regexp, ''); if ($key_ok === false) { throw new HeraldInvalidConditionException( pht( 'The first regexp in the regexp pair, "%s", is not a valid '. 'regexp.', $key_regexp)); } $val_ok = @preg_match($val_regexp, ''); if ($val_ok === false) { throw new HeraldInvalidConditionException( pht( 'The second regexp in the regexp pair, "%s", is not a valid '. 'regexp.', $val_regexp)); } break; case self::CONDITION_CONTAINS: case self::CONDITION_NOT_CONTAINS: case self::CONDITION_IS: case self::CONDITION_IS_NOT: case self::CONDITION_IS_ANY: case self::CONDITION_IS_NOT_ANY: case self::CONDITION_INCLUDE_ALL: case self::CONDITION_INCLUDE_ANY: case self::CONDITION_INCLUDE_NONE: case self::CONDITION_IS_ME: case self::CONDITION_IS_NOT_ME: case self::CONDITION_RULE: case self::CONDITION_NOT_RULE: case self::CONDITION_EXISTS: case self::CONDITION_NOT_EXISTS: case self::CONDITION_UNCONDITIONALLY: case self::CONDITION_NEVER: case self::CONDITION_HAS_BIT: case self::CONDITION_NOT_BIT: case self::CONDITION_IS_TRUE: case self::CONDITION_IS_FALSE: // No explicit validation for these types, although there probably // should be in some cases. break; default: throw new HeraldInvalidConditionException( pht( 'Unknown condition "%s"!', $condition_type)); } } /* -( Actions )------------------------------------------------------------ */ public function getCustomActionsForRuleType($rule_type) { $results = array(); foreach ($this->getCustomActions() as $custom_action) { if ($custom_action->appliesToRuleType($rule_type)) { $results[] = $custom_action; } } return $results; } public function getActions($rule_type) { $custom_actions = $this->getCustomActionsForRuleType($rule_type); - return mpull($custom_actions, 'getActionKey'); + $custom_actions = mpull($custom_actions, 'getActionKey'); + + $actions = $custom_actions; + + $object = $this->newObject(); + + if (($object instanceof PhabricatorProjectInterface)) { + if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) { + $actions[] = self::ACTION_ADD_PROJECTS; + } + } + + return $actions; } public function getActionNameMap($rule_type) { switch ($rule_type) { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: $standard = array( self::ACTION_NOTHING => pht('Do nothing'), self::ACTION_ADD_CC => pht('Add emails to CC'), self::ACTION_REMOVE_CC => pht('Remove emails from CC'), self::ACTION_EMAIL => pht('Send an email to'), self::ACTION_AUDIT => pht('Trigger an Audit by'), self::ACTION_FLAG => pht('Mark with flag'), self::ACTION_ASSIGN_TASK => pht('Assign task to'), self::ACTION_ADD_PROJECTS => pht('Add projects'), self::ACTION_ADD_REVIEWERS => pht('Add reviewers'), self::ACTION_ADD_BLOCKING_REVIEWERS => pht('Add blocking reviewers'), self::ACTION_APPLY_BUILD_PLANS => pht('Run build plans'), self::ACTION_REQUIRE_SIGNATURE => pht('Require legal signatures'), self::ACTION_BLOCK => pht('Block change with message'), ); break; case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: $standard = array( self::ACTION_NOTHING => pht('Do nothing'), self::ACTION_ADD_CC => pht('Add me to CC'), self::ACTION_REMOVE_CC => pht('Remove me from CC'), self::ACTION_EMAIL => pht('Send me an email'), self::ACTION_AUDIT => pht('Trigger an Audit by me'), self::ACTION_FLAG => pht('Mark with flag'), self::ACTION_ASSIGN_TASK => pht('Assign task to me'), self::ACTION_ADD_PROJECTS => pht('Add projects'), self::ACTION_ADD_REVIEWERS => pht('Add me as a reviewer'), self::ACTION_ADD_BLOCKING_REVIEWERS => pht('Add me as a blocking reviewer'), ); break; default: throw new Exception("Unknown rule type '{$rule_type}'!"); } $custom_actions = $this->getCustomActionsForRuleType($rule_type); $standard += mpull($custom_actions, 'getActionName', 'getActionKey'); return $standard; } public function willSaveAction( HeraldRule $rule, HeraldAction $action) { $target = $action->getTarget(); if (is_array($target)) { $target = array_keys($target); } $author_phid = $rule->getAuthorPHID(); $rule_type = $rule->getRuleType(); if ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { switch ($action->getAction()) { case self::ACTION_EMAIL: case self::ACTION_ADD_CC: case self::ACTION_REMOVE_CC: case self::ACTION_AUDIT: case self::ACTION_ASSIGN_TASK: case self::ACTION_ADD_REVIEWERS: case self::ACTION_ADD_BLOCKING_REVIEWERS: // For personal rules, force these actions to target the rule owner. $target = array($author_phid); break; case self::ACTION_FLAG: // Make sure flag color is valid; set to blue if not. $color_map = PhabricatorFlagColor::getColorNameMap(); if (empty($color_map[$target])) { $target = PhabricatorFlagColor::COLOR_BLUE; } break; case self::ACTION_BLOCK: case self::ACTION_NOTHING: break; default: throw new HeraldInvalidActionException( pht( 'Unrecognized action type "%s"!', $action->getAction())); } } $action->setTarget($target); } /* -( Values )------------------------------------------------------------- */ public function getValueTypeForFieldAndCondition($field, $condition) { if ($this->isHeraldCustomKey($field)) { $value_type = $this->getCustomFieldValueTypeForFieldAndCondition( $field, $condition); if ($value_type !== null) { return $value_type; } } switch ($condition) { case self::CONDITION_CONTAINS: case self::CONDITION_NOT_CONTAINS: case self::CONDITION_REGEXP: case self::CONDITION_REGEXP_PAIR: return self::VALUE_TEXT; case self::CONDITION_IS: case self::CONDITION_IS_NOT: switch ($field) { case self::FIELD_CONTENT_SOURCE: return self::VALUE_CONTENT_SOURCE; default: return self::VALUE_TEXT; } break; case self::CONDITION_IS_ANY: case self::CONDITION_IS_NOT_ANY: switch ($field) { case self::FIELD_REPOSITORY: return self::VALUE_REPOSITORY; case self::FIELD_TASK_PRIORITY: return self::VALUE_TASK_PRIORITY; case self::FIELD_TASK_STATUS: return self::VALUE_TASK_STATUS; case self::FIELD_ARCANIST_PROJECT: return self::VALUE_ARCANIST_PROJECT; default: return self::VALUE_USER; } break; case self::CONDITION_INCLUDE_ALL: case self::CONDITION_INCLUDE_ANY: case self::CONDITION_INCLUDE_NONE: switch ($field) { case self::FIELD_REPOSITORY: return self::VALUE_REPOSITORY; case self::FIELD_CC: return self::VALUE_EMAIL; case self::FIELD_TAGS: return self::VALUE_TAG; case self::FIELD_AFFECTED_PACKAGE: return self::VALUE_OWNERS_PACKAGE; case self::FIELD_AUTHOR_PROJECTS: case self::FIELD_PUSHER_PROJECTS: case self::FIELD_PROJECTS: case self::FIELD_REPOSITORY_PROJECTS: return self::VALUE_PROJECT; case self::FIELD_REVIEWERS: return self::VALUE_USER_OR_PROJECT; case self::FIELD_APPLICATION_EMAIL: return self::VALUE_APPLICATION_EMAIL; default: return self::VALUE_USER; } break; case self::CONDITION_IS_ME: case self::CONDITION_IS_NOT_ME: case self::CONDITION_EXISTS: case self::CONDITION_NOT_EXISTS: case self::CONDITION_UNCONDITIONALLY: case self::CONDITION_NEVER: case self::CONDITION_IS_TRUE: case self::CONDITION_IS_FALSE: return self::VALUE_NONE; case self::CONDITION_RULE: case self::CONDITION_NOT_RULE: return self::VALUE_RULE; default: throw new Exception("Unknown condition '{$condition}'."); } } public function getValueTypeForAction($action, $rule_type) { $is_personal = ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); if ($is_personal) { switch ($action) { case self::ACTION_ADD_CC: case self::ACTION_REMOVE_CC: case self::ACTION_EMAIL: case self::ACTION_NOTHING: case self::ACTION_AUDIT: case self::ACTION_ASSIGN_TASK: case self::ACTION_ADD_REVIEWERS: case self::ACTION_ADD_BLOCKING_REVIEWERS: return self::VALUE_NONE; case self::ACTION_FLAG: return self::VALUE_FLAG_COLOR; case self::ACTION_ADD_PROJECTS: return self::VALUE_PROJECT; } } else { switch ($action) { case self::ACTION_ADD_CC: case self::ACTION_REMOVE_CC: case self::ACTION_EMAIL: return self::VALUE_EMAIL; case self::ACTION_NOTHING: return self::VALUE_NONE; case self::ACTION_ADD_PROJECTS: return self::VALUE_PROJECT; case self::ACTION_FLAG: return self::VALUE_FLAG_COLOR; case self::ACTION_ASSIGN_TASK: return self::VALUE_USER; case self::ACTION_AUDIT: case self::ACTION_ADD_REVIEWERS: case self::ACTION_ADD_BLOCKING_REVIEWERS: return self::VALUE_USER_OR_PROJECT; case self::ACTION_APPLY_BUILD_PLANS: return self::VALUE_BUILD_PLAN; case self::ACTION_REQUIRE_SIGNATURE: return self::VALUE_LEGAL_DOCUMENTS; case self::ACTION_BLOCK: return self::VALUE_TEXT; } } $custom_action = idx($this->getCustomActions(), $action); if ($custom_action !== null) { return $custom_action->getActionType(); } throw new Exception("Unknown or invalid action '".$action."'."); } /* -( Repetition )--------------------------------------------------------- */ public function getRepetitionOptions() { return array( HeraldRepetitionPolicyConfig::EVERY, ); } public static function getAllAdapters() { static $adapters; if (!$adapters) { $adapters = id(new PhutilSymbolLoader()) ->setAncestorClass(__CLASS__) ->loadObjects(); $adapters = msort($adapters, 'getAdapterSortKey'); } return $adapters; } public static function getAdapterForContentType($content_type) { $adapters = self::getAllAdapters(); foreach ($adapters as $adapter) { if ($adapter->getAdapterContentType() == $content_type) { return $adapter; } } throw new Exception( pht( 'No adapter exists for Herald content type "%s".', $content_type)); } public static function getEnabledAdapterMap(PhabricatorUser $viewer) { $map = array(); $adapters = HeraldAdapter::getAllAdapters(); foreach ($adapters as $adapter) { if (!$adapter->isAvailableToUser($viewer)) { continue; } $type = $adapter->getAdapterContentType(); $name = $adapter->getAdapterContentName(); $map[$type] = $name; } return $map; } public function renderRuleAsText( HeraldRule $rule, PhabricatorHandleList $handles) { require_celerity_resource('herald-css'); $icon = id(new PHUIIconView()) ->setIconFont('fa-chevron-circle-right lightgreytext') ->addClass('herald-list-icon'); if ($rule->getMustMatchAll()) { $match_text = pht('When all of these conditions are met:'); } else { $match_text = pht('When any of these conditions are met:'); } $match_title = phutil_tag( 'p', array( 'class' => 'herald-list-description', ), $match_text); $match_list = array(); foreach ($rule->getConditions() as $condition) { $match_list[] = phutil_tag( 'div', array( 'class' => 'herald-list-item', ), array( $icon, $this->renderConditionAsText($condition, $handles), )); } $integer_code_for_every = HeraldRepetitionPolicyConfig::toInt( HeraldRepetitionPolicyConfig::EVERY); if ($rule->getRepetitionPolicy() == $integer_code_for_every) { $action_text = pht('Take these actions every time this rule matches:'); } else { $action_text = pht('Take these actions the first time this rule matches:'); } $action_title = phutil_tag( 'p', array( 'class' => 'herald-list-description', ), $action_text); $action_list = array(); foreach ($rule->getActions() as $action) { $action_list[] = phutil_tag( 'div', array( 'class' => 'herald-list-item', ), array( $icon, $this->renderActionAsText($action, $handles), )); } return array( $match_title, $match_list, $action_title, $action_list, ); } private function renderConditionAsText( HeraldCondition $condition, PhabricatorHandleList $handles) { $field_type = $condition->getFieldName(); $default = $this->isHeraldCustomKey($field_type) ? pht('(Unknown Custom Field "%s")', $field_type) : pht('(Unknown Field "%s")', $field_type); $field_name = idx($this->getFieldNameMap(), $field_type, $default); $condition_type = $condition->getFieldCondition(); $condition_name = idx($this->getConditionNameMap(), $condition_type); $value = $this->renderConditionValueAsText($condition, $handles); return hsprintf(' %s %s %s', $field_name, $condition_name, $value); } private function renderActionAsText( HeraldAction $action, PhabricatorHandleList $handles) { $rule_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; $action_type = $action->getAction(); $action_name = idx($this->getActionNameMap($rule_global), $action_type); $target = $this->renderActionTargetAsText($action, $handles); return hsprintf(' %s %s', $action_name, $target); } private function renderConditionValueAsText( HeraldCondition $condition, PhabricatorHandleList $handles) { $value = $condition->getValue(); if (!is_array($value)) { $value = array($value); } switch ($condition->getFieldName()) { case self::FIELD_TASK_PRIORITY: $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); foreach ($value as $index => $val) { $name = idx($priority_map, $val); if ($name) { $value[$index] = $name; } } break; case self::FIELD_TASK_STATUS: $status_map = ManiphestTaskStatus::getTaskStatusMap(); foreach ($value as $index => $val) { $name = idx($status_map, $val); if ($name) { $value[$index] = $name; } } break; case HeraldPreCommitRefAdapter::FIELD_REF_CHANGE: $change_map = PhabricatorRepositoryPushLog::getHeraldChangeFlagConditionOptions(); foreach ($value as $index => $val) { $name = idx($change_map, $val); if ($name) { $value[$index] = $name; } } break; default: foreach ($value as $index => $val) { $handle = $handles->getHandleIfExists($val); if ($handle) { $value[$index] = $handle->renderLink(); } } break; } $value = phutil_implode_html(', ', $value); return $value; } private function renderActionTargetAsText( HeraldAction $action, PhabricatorHandleList $handles) { $target = $action->getTarget(); if (!is_array($target)) { $target = array($target); } foreach ($target as $index => $val) { switch ($action->getAction()) { case self::ACTION_FLAG: $target[$index] = PhabricatorFlagColor::getColorName($val); break; default: $handle = $handles->getHandleIfExists($val); if ($handle) { $target[$index] = $handle->renderLink(); } break; } } $target = phutil_implode_html(', ', $target); return $target; } /** * Given a @{class:HeraldRule}, this function extracts all the phids that * we'll want to load as handles later. * * This function performs a somewhat hacky approach to figuring out what * is and is not a phid - try to get the phid type and if the type is * *not* unknown assume its a valid phid. * * Don't try this at home. Use more strongly typed data at home. * * Think of the children. */ public static function getHandlePHIDs(HeraldRule $rule) { $phids = array($rule->getAuthorPHID()); foreach ($rule->getConditions() as $condition) { $value = $condition->getValue(); if (!is_array($value)) { $value = array($value); } foreach ($value as $val) { if (phid_get_type($val) != PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { $phids[] = $val; } } } foreach ($rule->getActions() as $action) { $target = $action->getTarget(); if (!is_array($target)) { $target = array($target); } foreach ($target as $val) { if (phid_get_type($val) != PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { $phids[] = $val; } } } if ($rule->isObjectRule()) { $phids[] = $rule->getTriggerObjectPHID(); } return $phids; } /* -( Custom Field Integration )------------------------------------------- */ - /** - * Return an object which custom fields can be generated from while editing - * rules. Adapters must return an object from this method to enable custom - * field rules. - * - * Normally, you'll return an empty version of the adapted object, assuming - * it implements @{interface:PhabricatorCustomFieldInterface}: - * - * return new ApplicationObject(); - * - * This is normally the only adapter method you need to override to enable - * Herald rules to run against custom fields. - * - * @return null|PhabricatorCustomFieldInterface Template object. - * @task customfield - */ - protected function getCustomFieldTemplateObject() { - return null; - } - - /** * Returns the prefix used to namespace Herald fields which are based on * custom fields. * * @return string Key prefix. * @task customfield */ private function getCustomKeyPrefix() { return 'herald.custom/'; } /** * Determine if a field key is based on a custom field or a regular internal * field. * * @param string Field key. * @return bool True if the field key is based on a custom field. * @task customfield */ private function isHeraldCustomKey($key) { $prefix = $this->getCustomKeyPrefix(); return (strncmp($key, $prefix, strlen($prefix)) == 0); } /** * Convert a custom field key into a Herald field key. * * @param string Custom field key. * @return string Herald field key. * @task customfield */ private function getHeraldKeyFromCustomKey($key) { return $this->getCustomKeyPrefix().$key; } /** * Get custom fields for this adapter, if appliable. This will either return * a field list or `null` if the adapted object does not implement custom * fields or the adapter does not support them. * * @return PhabricatorCustomFieldList|null List of fields, or `null`. * @task customfield */ private function getCustomFields() { if ($this->customFields === false) { $this->customFields = null; - $template_object = $this->getCustomFieldTemplateObject(); - if ($template_object) { + $template_object = $this->newObject(); + if ($template_object instanceof PhabricatorCustomFieldInterface) { $object = $this->getObject(); if (!$object) { $object = $template_object; } $fields = PhabricatorCustomField::getObjectFields( $object, PhabricatorCustomField::ROLE_HERALD); $fields->setViewer(PhabricatorUser::getOmnipotentUser()); $fields->readFieldsFromStorage($object); $this->customFields = $fields; } } return $this->customFields; } /** * Get a custom field by Herald field key, or `null` if it does not exist * or custom fields are not supported. * * @param string Herald field key. * @return PhabricatorCustomField|null Matching field, if it exists. * @task customfield */ private function getCustomField($herald_field_key) { $fields = $this->getCustomFields(); if (!$fields) { return null; } foreach ($fields->getFields() as $custom_field) { $key = $custom_field->getFieldKey(); if ($this->getHeraldKeyFromCustomKey($key) == $herald_field_key) { return $custom_field; } } return null; } /** * Get the field map for custom fields. * * @return map Map of Herald field keys to field names. * @task customfield */ private function getCustomFieldNameMap() { $fields = $this->getCustomFields(); if (!$fields) { return array(); } $map = array(); foreach ($fields->getFields() as $field) { $key = $field->getFieldKey(); $name = $field->getHeraldFieldName(); $map[$this->getHeraldKeyFromCustomKey($key)] = $name; } return $map; } /** * Get the value for a custom field. * * @param string Herald field key. * @return wild Custom field value. * @task customfield */ private function getCustomFieldValue($field_key) { $field = $this->getCustomField($field_key); if (!$field) { return null; } return $field->getHeraldFieldValue(); } /** * Get the Herald conditions for a custom field. * * @param string Herald field key. * @return list List of Herald conditions. * @task customfield */ private function getCustomFieldConditions($field_key) { $field = $this->getCustomField($field_key); if (!$field) { return array( self::CONDITION_NEVER, ); } return $field->getHeraldFieldConditions(); } /** * Get the Herald value type for a custom field and condition. * * @param string Herald field key. * @param const Herald condition constant. * @return const|null Herald value type constant, or null to use the default. * @task customfield */ private function getCustomFieldValueTypeForFieldAndCondition( $field_key, $condition) { $field = $this->getCustomField($field_key); if (!$field) { return self::VALUE_NONE; } return $field->getHeraldFieldValueType($condition); } /* -( Applying Effects )--------------------------------------------------- */ /** * @task apply */ protected function applyStandardEffect(HeraldEffect $effect) { $action = $effect->getAction(); $rule_type = $effect->getRule()->getRuleType(); $supported = $this->getActions($rule_type); $supported = array_fuse($supported); if (empty($supported[$action])) { throw new Exception( pht( 'Adapter "%s" does not support action "%s" for rule type "%s".', get_class($this), $action, $rule_type)); } switch ($action) { case self::ACTION_ADD_PROJECTS: return $this->applyProjectsEffect($effect); case self::ACTION_FLAG: return $this->applyFlagEffect($effect); case self::ACTION_EMAIL: return $this->applyEmailEffect($effect); default: break; } $result = $this->handleCustomHeraldEffect($effect); if (!$result) { throw new Exception( pht( 'No custom action exists to handle rule action "%s".', $action)); } return $result; } /** * @task apply */ private function applyProjectsEffect(HeraldEffect $effect) { $kind = '+'; $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $project_phids = $effect->getTarget(); $xaction = $this->newTransaction() ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $project_type) ->setNewValue( array( $kind => array_fuse($project_phids), )); $this->queueTransaction($xaction); return new HeraldApplyTranscript( $effect, true, pht('Added projects.')); } /** * @task apply */ private function applyFlagEffect(HeraldEffect $effect) { $phid = $this->getPHID(); $color = $effect->getTarget(); $rule = $effect->getRule(); $user = $rule->getAuthor(); $flag = PhabricatorFlagQuery::loadUserFlag($user, $phid); if ($flag) { return new HeraldApplyTranscript( $effect, false, pht('Object already flagged.')); } $handle = id(new PhabricatorHandleQuery()) ->setViewer($user) ->withPHIDs(array($phid)) ->executeOne(); $flag = new PhabricatorFlag(); $flag->setOwnerPHID($user->getPHID()); $flag->setType($handle->getType()); $flag->setObjectPHID($handle->getPHID()); // TOOD: Should really be transcript PHID, but it doesn't exist yet. $flag->setReasonPHID($user->getPHID()); $flag->setColor($color); $flag->setNote( pht('Flagged by Herald Rule "%s".', $rule->getName())); $flag->save(); return new HeraldApplyTranscript( $effect, true, pht('Added flag.')); } /** * @task apply */ private function applyEmailEffect(HeraldEffect $effect) { foreach ($effect->getTarget() as $phid) { $this->emailPHIDs[$phid] = $phid; // If this is a personal rule, we'll force delivery of a real email. This // effect is stronger than notification preferences, so you get an actual // email even if your preferences are set to "Notify" or "Ignore". $rule = $effect->getRule(); if ($rule->isPersonalRule()) { $this->forcedEmailPHIDs[$phid] = $phid; } } return new HeraldApplyTranscript( $effect, true, pht('Added mailable to mail targets.')); } } diff --git a/src/applications/herald/adapter/HeraldCommitAdapter.php b/src/applications/herald/adapter/HeraldCommitAdapter.php index e697d99bbf..4ca916d13a 100644 --- a/src/applications/herald/adapter/HeraldCommitAdapter.php +++ b/src/applications/herald/adapter/HeraldCommitAdapter.php @@ -1,537 +1,541 @@ commit; } public function getAdapterContentType() { return 'commit'; } public function getAdapterContentName() { return pht('Commits'); } public function getAdapterContentDescription() { return pht( "React to new commits appearing in tracked repositories.\n". "Commit rules can send email, flag commits, trigger audits, ". "and run build plans."); } public function supportsRuleType($rule_type) { switch ($rule_type) { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: return true; default: return false; } } public function canTriggerOnObject($object) { if ($object instanceof PhabricatorRepository) { return true; } if ($object instanceof PhabricatorProject) { return true; } return false; } public function getTriggerObjectPHIDs() { return array_merge( array( $this->repository->getPHID(), $this->getPHID(), ), $this->repository->getProjectPHIDs()); } public function explainValidTriggerObjects() { return pht('This rule can trigger for **repositories** and **projects**.'); } public function getFieldNameMap() { return array( self::FIELD_NEED_AUDIT_FOR_PACKAGE => pht('Affected packages that need audit'), self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH => pht('Commit is on closing branch'), ) + parent::getFieldNameMap(); } public function getFields() { return array_merge( array( self::FIELD_BODY, self::FIELD_AUTHOR, self::FIELD_COMMITTER, self::FIELD_REVIEWER, self::FIELD_REPOSITORY, self::FIELD_REPOSITORY_PROJECTS, self::FIELD_DIFF_FILE, self::FIELD_DIFF_CONTENT, self::FIELD_DIFF_ADDED_CONTENT, self::FIELD_DIFF_REMOVED_CONTENT, self::FIELD_DIFF_ENORMOUS, self::FIELD_AFFECTED_PACKAGE, self::FIELD_AFFECTED_PACKAGE_OWNER, self::FIELD_NEED_AUDIT_FOR_PACKAGE, self::FIELD_DIFFERENTIAL_REVISION, self::FIELD_DIFFERENTIAL_ACCEPTED, self::FIELD_DIFFERENTIAL_REVIEWERS, self::FIELD_DIFFERENTIAL_CCS, self::FIELD_BRANCHES, self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH, ), parent::getFields()); } public function getConditionsForField($field) { switch ($field) { case self::FIELD_NEED_AUDIT_FOR_PACKAGE: return array( self::CONDITION_INCLUDE_ANY, self::CONDITION_INCLUDE_NONE, ); case self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH: return array( self::CONDITION_UNCONDITIONALLY, ); } return parent::getConditionsForField($field); } public function getActions($rule_type) { switch ($rule_type) { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: return array_merge( array( self::ACTION_ADD_CC, self::ACTION_EMAIL, self::ACTION_AUDIT, self::ACTION_APPLY_BUILD_PLANS, self::ACTION_NOTHING, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( self::ACTION_ADD_CC, self::ACTION_EMAIL, self::ACTION_FLAG, self::ACTION_AUDIT, self::ACTION_NOTHING, ), parent::getActions($rule_type)); } } public function getValueTypeForFieldAndCondition($field, $condition) { switch ($field) { case self::FIELD_DIFFERENTIAL_CCS: return self::VALUE_EMAIL; case self::FIELD_NEED_AUDIT_FOR_PACKAGE: return self::VALUE_OWNERS_PACKAGE; } return parent::getValueTypeForFieldAndCondition($field, $condition); } public static function newLegacyAdapter( PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $commit_data) { $object = new HeraldCommitAdapter(); $commit->attachRepository($repository); $object->repository = $repository; $object->commit = $commit; $object->commitData = $commit_data; return $object; } public function setCommit(PhabricatorRepositoryCommit $commit) { $viewer = PhabricatorUser::getOmnipotentUser(); $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withIDs(array($commit->getRepositoryID())) ->needProjectPHIDs(true) ->executeOne(); if (!$repository) { throw new Exception(pht('Unable to load repository!')); } $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere( 'commitID = %d', $commit->getID()); if (!$data) { throw new Exception(pht('Unable to load commit data!')); } $this->commit = clone $commit; $this->commit->attachRepository($repository); $this->commit->attachCommitData($data); $this->repository = $repository; $this->commitData = $data; return $this; } public function getPHID() { return $this->commit->getPHID(); } public function getAddCCMap() { return $this->addCCPHIDs; } public function getAuditMap() { return $this->auditMap; } public function getBuildPlans() { return $this->buildPlans; } public function getHeraldName() { return 'r'. $this->repository->getCallsign(). $this->commit->getCommitIdentifier(); } public function loadAffectedPaths() { if ($this->affectedPaths === null) { $result = PhabricatorOwnerPathQuery::loadAffectedPaths( $this->repository, $this->commit, PhabricatorUser::getOmnipotentUser()); $this->affectedPaths = $result; } return $this->affectedPaths; } public function loadAffectedPackages() { if ($this->affectedPackages === null) { $packages = PhabricatorOwnersPackage::loadAffectedPackages( $this->repository, $this->loadAffectedPaths()); $this->affectedPackages = $packages; } return $this->affectedPackages; } public function loadAuditNeededPackage() { if ($this->auditNeededPackages === null) { $status_arr = array( PhabricatorAuditStatusConstants::AUDIT_REQUIRED, PhabricatorAuditStatusConstants::CONCERNED, ); $requests = id(new PhabricatorRepositoryAuditRequest()) ->loadAllWhere( 'commitPHID = %s AND auditStatus IN (%Ls)', $this->commit->getPHID(), $status_arr); $packages = mpull($requests, 'getAuditorPHID'); $this->auditNeededPackages = $packages; } return $this->auditNeededPackages; } public function loadDifferentialRevision() { if ($this->affectedRevision === null) { $this->affectedRevision = false; $data = $this->commitData; $revision_id = $data->getCommitDetail('differential.revisionID'); if ($revision_id) { // NOTE: The Herald rule owner might not actually have access to // the revision, and can control which revision a commit is // associated with by putting text in the commit message. However, // the rules they can write against revisions don't actually expose // anything interesting, so it seems reasonable to load unconditionally // here. $revision = id(new DifferentialRevisionQuery()) ->withIDs(array($revision_id)) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->needRelationships(true) ->needReviewerStatus(true) ->executeOne(); if ($revision) { $this->affectedRevision = $revision; } } } return $this->affectedRevision; } public static function getEnormousByteLimit() { return 1024 * 1024 * 1024; // 1GB } public static function getEnormousTimeLimit() { return 60 * 15; // 15 Minutes } private function loadCommitDiff() { $drequest = DiffusionRequest::newFromDictionary( array( 'user' => PhabricatorUser::getOmnipotentUser(), 'repository' => $this->repository, 'commit' => $this->commit->getCommitIdentifier(), )); $byte_limit = self::getEnormousByteLimit(); $raw = DiffusionQuery::callConduitWithDiffusionRequest( PhabricatorUser::getOmnipotentUser(), $drequest, 'diffusion.rawdiffquery', array( 'commit' => $this->commit->getCommitIdentifier(), 'timeout' => self::getEnormousTimeLimit(), 'byteLimit' => $byte_limit, 'linesOfContext' => 0, )); if (strlen($raw) >= $byte_limit) { throw new Exception( pht( 'The raw text of this change is enormous (larger than %d bytes). '. 'Herald can not process it.', $byte_limit)); } $parser = new ArcanistDiffParser(); $changes = $parser->parseDiff($raw); $diff = DifferentialDiff::newEphemeralFromRawChanges( $changes); return $diff; } private function getDiffContent($type) { if ($this->commitDiff === null) { try { $this->commitDiff = $this->loadCommitDiff(); } catch (Exception $ex) { $this->commitDiff = $ex; phlog($ex); } } if ($this->commitDiff instanceof Exception) { $ex = $this->commitDiff; $ex_class = get_class($ex); $ex_message = pht('Failed to load changes: %s', $ex->getMessage()); return array( '<'.$ex_class.'>' => $ex_message, ); } $changes = $this->commitDiff->getChangesets(); $result = array(); foreach ($changes as $change) { $lines = array(); foreach ($change->getHunks() as $hunk) { switch ($type) { case '-': $lines[] = $hunk->makeOldFile(); break; case '+': $lines[] = $hunk->makeNewFile(); break; case '*': $lines[] = $hunk->makeChanges(); break; default: throw new Exception("Unknown content selection '{$type}'!"); } } $result[$change->getFilename()] = implode("\n", $lines); } return $result; } public function getHeraldField($field) { $data = $this->commitData; switch ($field) { case self::FIELD_BODY: return $data->getCommitMessage(); case self::FIELD_AUTHOR: return $data->getCommitDetail('authorPHID'); case self::FIELD_COMMITTER: return $data->getCommitDetail('committerPHID'); case self::FIELD_REVIEWER: return $data->getCommitDetail('reviewerPHID'); case self::FIELD_DIFF_FILE: return $this->loadAffectedPaths(); case self::FIELD_REPOSITORY: return $this->repository->getPHID(); case self::FIELD_REPOSITORY_PROJECTS: return $this->repository->getProjectPHIDs(); case self::FIELD_DIFF_CONTENT: return $this->getDiffContent('*'); case self::FIELD_DIFF_ADDED_CONTENT: return $this->getDiffContent('+'); case self::FIELD_DIFF_REMOVED_CONTENT: return $this->getDiffContent('-'); case self::FIELD_DIFF_ENORMOUS: $this->getDiffContent('*'); return ($this->commitDiff instanceof Exception); case self::FIELD_AFFECTED_PACKAGE: $packages = $this->loadAffectedPackages(); return mpull($packages, 'getPHID'); case self::FIELD_AFFECTED_PACKAGE_OWNER: $packages = $this->loadAffectedPackages(); $owners = PhabricatorOwnersOwner::loadAllForPackages($packages); return mpull($owners, 'getUserPHID'); case self::FIELD_NEED_AUDIT_FOR_PACKAGE: return $this->loadAuditNeededPackage(); case self::FIELD_DIFFERENTIAL_REVISION: $revision = $this->loadDifferentialRevision(); if (!$revision) { return null; } return $revision->getID(); case self::FIELD_DIFFERENTIAL_ACCEPTED: $revision = $this->loadDifferentialRevision(); if (!$revision) { return null; } $status = $data->getCommitDetail( 'precommitRevisionStatus', $revision->getStatus()); switch ($status) { case ArcanistDifferentialRevisionStatus::ACCEPTED: case ArcanistDifferentialRevisionStatus::CLOSED: return $revision->getPHID(); } return null; case self::FIELD_DIFFERENTIAL_REVIEWERS: $revision = $this->loadDifferentialRevision(); if (!$revision) { return array(); } return $revision->getReviewers(); case self::FIELD_DIFFERENTIAL_CCS: $revision = $this->loadDifferentialRevision(); if (!$revision) { return array(); } return $revision->getCCPHIDs(); case self::FIELD_BRANCHES: $params = array( 'callsign' => $this->repository->getCallsign(), 'contains' => $this->commit->getCommitIdentifier(), ); $result = id(new ConduitCall('diffusion.branchquery', $params)) ->setUser(PhabricatorUser::getOmnipotentUser()) ->execute(); $refs = DiffusionRepositoryRef::loadAllFromDictionaries($result); return mpull($refs, 'getShortName'); case self::FIELD_REPOSITORY_AUTOCLOSE_BRANCH: return $this->repository->shouldAutocloseCommit($this->commit); } return parent::getHeraldField($field); } public function applyHeraldEffects(array $effects) { assert_instances_of($effects, 'HeraldEffect'); $result = array(); foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { case self::ACTION_NOTHING: $result[] = new HeraldApplyTranscript( $effect, true, pht('Great success at doing nothing.')); break; case self::ACTION_ADD_CC: foreach ($effect->getTarget() as $phid) { if (empty($this->addCCPHIDs[$phid])) { $this->addCCPHIDs[$phid] = array(); } $this->addCCPHIDs[$phid][] = $effect->getRule()->getID(); } $result[] = new HeraldApplyTranscript( $effect, true, pht('Added address to CC.')); break; case self::ACTION_AUDIT: foreach ($effect->getTarget() as $phid) { if (empty($this->auditMap[$phid])) { $this->auditMap[$phid] = array(); } $this->auditMap[$phid][] = $effect->getRule()->getID(); } $result[] = new HeraldApplyTranscript( $effect, true, pht('Triggered an audit.')); break; case self::ACTION_APPLY_BUILD_PLANS: foreach ($effect->getTarget() as $phid) { $this->buildPlans[] = $phid; } $result[] = new HeraldApplyTranscript( $effect, true, pht('Applied build plans.')); break; default: $result[] = $this->applyStandardEffect($effect); break; } } return $result; } } diff --git a/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php b/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php index 28404744b1..3ad5c4722e 100644 --- a/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php @@ -1,414 +1,418 @@ revision; } public function getDiff() { return $this->diff; } public function getAdapterContentType() { return 'differential'; } public function getAdapterContentName() { return pht('Differential Revisions'); } public function getAdapterContentDescription() { return pht( "React to revisions being created or updated.\n". "Revision rules can send email, flag revisions, add reviewers, ". "and run build plans."); } public function supportsRuleType($rule_type) { switch ($rule_type) { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return true; case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: default: return false; } } public function getFields() { return array_merge( array( self::FIELD_TITLE, self::FIELD_BODY, self::FIELD_AUTHOR, self::FIELD_AUTHOR_PROJECTS, self::FIELD_REVIEWERS, self::FIELD_CC, self::FIELD_REPOSITORY, self::FIELD_REPOSITORY_PROJECTS, self::FIELD_DIFF_FILE, self::FIELD_DIFF_CONTENT, self::FIELD_DIFF_ADDED_CONTENT, self::FIELD_DIFF_REMOVED_CONTENT, self::FIELD_AFFECTED_PACKAGE, self::FIELD_AFFECTED_PACKAGE_OWNER, self::FIELD_IS_NEW_OBJECT, self::FIELD_ARCANIST_PROJECT, ), parent::getFields()); } public function getRepetitionOptions() { return array( HeraldRepetitionPolicyConfig::EVERY, HeraldRepetitionPolicyConfig::FIRST, ); } public static function newLegacyAdapter( DifferentialRevision $revision, DifferentialDiff $diff) { $object = new HeraldDifferentialRevisionAdapter(); // Reload the revision to pick up relationship information. $revision = id(new DifferentialRevisionQuery()) ->withIDs(array($revision->getID())) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->needRelationships(true) ->needReviewerStatus(true) ->executeOne(); $object->revision = $revision; $object->diff = $diff; return $object; } public function setExplicitCCs($explicit_ccs) { $this->explicitCCs = $explicit_ccs; return $this; } public function setExplicitReviewers($explicit_reviewers) { $this->explicitReviewers = $explicit_reviewers; return $this; } public function setForbiddenCCs($forbidden_ccs) { $this->forbiddenCCs = $forbidden_ccs; return $this; } public function getCCsAddedByHerald() { return array_diff_key($this->newCCs, $this->remCCs); } public function getCCsRemovedByHerald() { return $this->remCCs; } public function getReviewersAddedByHerald() { return $this->addReviewerPHIDs; } public function getBlockingReviewersAddedByHerald() { return $this->blockingReviewerPHIDs; } public function getRequiredSignatureDocumentPHIDs() { return $this->requiredSignatureDocumentPHIDs; } public function getBuildPlans() { return $this->buildPlans; } public function getPHID() { return $this->revision->getPHID(); } public function getHeraldName() { return $this->revision->getTitle(); } protected function loadChangesets() { if ($this->changesets === null) { $this->changesets = $this->diff->loadChangesets(); } return $this->changesets; } protected function loadChangesetsWithHunks() { $changesets = $this->loadChangesets(); if ($changesets && !$this->haveHunks) { $this->haveHunks = true; id(new DifferentialHunkQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withChangesets($changesets) ->needAttachToChangesets(true) ->execute(); } return $changesets; } public function loadAffectedPackages() { if ($this->affectedPackages === null) { $this->affectedPackages = array(); $repository = $this->loadRepository(); if ($repository) { $packages = PhabricatorOwnersPackage::loadAffectedPackages( $repository, $this->loadAffectedPaths()); $this->affectedPackages = $packages; } } return $this->affectedPackages; } public function getHeraldField($field) { switch ($field) { case self::FIELD_TITLE: return $this->revision->getTitle(); break; case self::FIELD_BODY: return $this->revision->getSummary()."\n". $this->revision->getTestPlan(); break; case self::FIELD_AUTHOR: return $this->revision->getAuthorPHID(); break; case self::FIELD_AUTHOR_PROJECTS: $author_phid = $this->revision->getAuthorPHID(); if (!$author_phid) { return array(); } $projects = id(new PhabricatorProjectQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withMemberPHIDs(array($author_phid)) ->execute(); return mpull($projects, 'getPHID'); case self::FIELD_DIFF_FILE: return $this->loadAffectedPaths(); case self::FIELD_CC: if (isset($this->explicitCCs)) { return array_keys($this->explicitCCs); } else { return $this->revision->getCCPHIDs(); } case self::FIELD_REVIEWERS: if (isset($this->explicitReviewers)) { return array_keys($this->explicitReviewers); } else { return $this->revision->getReviewers(); } case self::FIELD_REPOSITORY: $repository = $this->loadRepository(); if (!$repository) { return null; } return $repository->getPHID(); case self::FIELD_REPOSITORY_PROJECTS: $repository = $this->loadRepository(); if (!$repository) { return array(); } return $repository->getProjectPHIDs(); case self::FIELD_DIFF_CONTENT: return $this->loadContentDictionary(); case self::FIELD_DIFF_ADDED_CONTENT: return $this->loadAddedContentDictionary(); case self::FIELD_DIFF_REMOVED_CONTENT: return $this->loadRemovedContentDictionary(); case self::FIELD_AFFECTED_PACKAGE: $packages = $this->loadAffectedPackages(); return mpull($packages, 'getPHID'); case self::FIELD_AFFECTED_PACKAGE_OWNER: $packages = $this->loadAffectedPackages(); return PhabricatorOwnersOwner::loadAffiliatedUserPHIDs( mpull($packages, 'getID')); case self::FIELD_ARCANIST_PROJECT: return $this->revision->getArcanistProjectPHID(); } return parent::getHeraldField($field); } public function getActions($rule_type) { switch ($rule_type) { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: return array_merge( array( self::ACTION_ADD_CC, self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_ADD_REVIEWERS, self::ACTION_ADD_BLOCKING_REVIEWERS, self::ACTION_APPLY_BUILD_PLANS, self::ACTION_REQUIRE_SIGNATURE, self::ACTION_NOTHING, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( self::ACTION_ADD_CC, self::ACTION_REMOVE_CC, self::ACTION_EMAIL, self::ACTION_FLAG, self::ACTION_ADD_REVIEWERS, self::ACTION_ADD_BLOCKING_REVIEWERS, self::ACTION_NOTHING, ), parent::getActions($rule_type)); } } public function applyHeraldEffects(array $effects) { assert_instances_of($effects, 'HeraldEffect'); $result = array(); if ($this->explicitCCs) { $effect = new HeraldEffect(); $effect->setAction(self::ACTION_ADD_CC); $effect->setTarget(array_keys($this->explicitCCs)); $effect->setReason( pht('CCs provided explicitly by revision author or carried over '. 'from a previous version of the revision.')); $result[] = new HeraldApplyTranscript( $effect, true, pht('Added addresses to CC list.')); } $forbidden_ccs = array_fill_keys( nonempty($this->forbiddenCCs, array()), true); foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { case self::ACTION_NOTHING: $result[] = new HeraldApplyTranscript( $effect, true, pht('OK, did nothing.')); break; case self::ACTION_ADD_CC: $base_target = $effect->getTarget(); $forbidden = array(); foreach ($base_target as $key => $fbid) { if (isset($forbidden_ccs[$fbid])) { $forbidden[] = $fbid; unset($base_target[$key]); } else { $this->newCCs[$fbid] = true; } } if ($forbidden) { $failed = clone $effect; $failed->setTarget($forbidden); if ($base_target) { $effect->setTarget($base_target); $result[] = new HeraldApplyTranscript( $effect, true, pht('Added these addresses to CC list. '. 'Others could not be added.')); } $result[] = new HeraldApplyTranscript( $failed, false, pht('CC forbidden, these addresses have unsubscribed.')); } else { $result[] = new HeraldApplyTranscript( $effect, true, pht('Added addresses to CC list.')); } break; case self::ACTION_REMOVE_CC: foreach ($effect->getTarget() as $fbid) { $this->remCCs[$fbid] = true; } $result[] = new HeraldApplyTranscript( $effect, true, pht('Removed addresses from CC list.')); break; case self::ACTION_ADD_REVIEWERS: foreach ($effect->getTarget() as $phid) { $this->addReviewerPHIDs[$phid] = true; } $result[] = new HeraldApplyTranscript( $effect, true, pht('Added reviewers.')); break; case self::ACTION_ADD_BLOCKING_REVIEWERS: // This adds reviewers normally, it just also marks them blocking. foreach ($effect->getTarget() as $phid) { $this->addReviewerPHIDs[$phid] = true; $this->blockingReviewerPHIDs[$phid] = true; } $result[] = new HeraldApplyTranscript( $effect, true, pht('Added blocking reviewers.')); break; case self::ACTION_APPLY_BUILD_PLANS: foreach ($effect->getTarget() as $phid) { $this->buildPlans[] = $phid; } $result[] = new HeraldApplyTranscript( $effect, true, pht('Applied build plans.')); break; case self::ACTION_REQUIRE_SIGNATURE: foreach ($effect->getTarget() as $phid) { $this->requiredSignatureDocumentPHIDs[] = $phid; } $result[] = new HeraldApplyTranscript( $effect, true, pht('Required signatures.')); break; default: $result[] = $this->applyStandardEffect($effect); break; } } return $result; } } diff --git a/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php b/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php index f25d5fa296..cb4c7988c0 100644 --- a/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php +++ b/src/applications/herald/adapter/HeraldManiphestTaskAdapter.php @@ -1,187 +1,186 @@ task = $task; return $this; } public function getTask() { return $this->task; } public function getObject() { return $this->task; } private function setCcPHIDs(array $cc_phids) { $this->ccPHIDs = $cc_phids; return $this; } public function getCcPHIDs() { return $this->ccPHIDs; } public function setAssignPHID($assign_phid) { $this->assignPHID = $assign_phid; return $this; } public function getAssignPHID() { return $this->assignPHID; } public function getAdapterContentName() { return pht('Maniphest Tasks'); } public function getFields() { return array_merge( array( self::FIELD_TITLE, self::FIELD_BODY, self::FIELD_AUTHOR, self::FIELD_ASSIGNEE, self::FIELD_CC, self::FIELD_CONTENT_SOURCE, self::FIELD_PROJECTS, self::FIELD_TASK_PRIORITY, self::FIELD_TASK_STATUS, self::FIELD_IS_NEW_OBJECT, self::FIELD_APPLICATION_EMAIL, ), parent::getFields()); } public function getActions($rule_type) { switch ($rule_type) { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: return array_merge( array( self::ACTION_ADD_CC, self::ACTION_EMAIL, self::ACTION_ASSIGN_TASK, - self::ACTION_ADD_PROJECTS, self::ACTION_NOTHING, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( self::ACTION_ADD_CC, self::ACTION_EMAIL, self::ACTION_FLAG, self::ACTION_ASSIGN_TASK, self::ACTION_NOTHING, ), parent::getActions($rule_type)); } } public function getPHID() { return $this->getTask()->getPHID(); } public function getHeraldName() { return 'T'.$this->getTask()->getID(); } public function getHeraldField($field) { switch ($field) { case self::FIELD_TITLE: return $this->getTask()->getTitle(); case self::FIELD_BODY: return $this->getTask()->getDescription(); case self::FIELD_AUTHOR: return $this->getTask()->getAuthorPHID(); case self::FIELD_ASSIGNEE: return $this->getTask()->getOwnerPHID(); case self::FIELD_CC: return PhabricatorSubscribersQuery::loadSubscribersForPHID( $this->getTask()->getPHID()); case self::FIELD_PROJECTS: return PhabricatorEdgeQuery::loadDestinationPHIDs( $this->getTask()->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); case self::FIELD_TASK_PRIORITY: return $this->getTask()->getPriority(); case self::FIELD_TASK_STATUS: return $this->getTask()->getStatus(); } return parent::getHeraldField($field); } public function applyHeraldEffects(array $effects) { assert_instances_of($effects, 'HeraldEffect'); $result = array(); foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { case self::ACTION_NOTHING: $result[] = new HeraldApplyTranscript( $effect, true, pht('Great success at doing nothing.')); break; case self::ACTION_ADD_CC: foreach ($effect->getTarget() as $phid) { $this->ccPHIDs[] = $phid; } $result[] = new HeraldApplyTranscript( $effect, true, pht('Added addresses to cc list.')); break; case self::ACTION_ASSIGN_TASK: $target_array = $effect->getTarget(); $assign_phid = reset($target_array); $this->setAssignPHID($assign_phid); $result[] = new HeraldApplyTranscript( $effect, true, pht('Assigned task.')); break; default: $result[] = $this->applyStandardEffect($effect); break; } } return $result; } - protected function getCustomFieldTemplateObject() { - return new ManiphestTask(); - } - } diff --git a/src/applications/herald/adapter/HeraldPholioMockAdapter.php b/src/applications/herald/adapter/HeraldPholioMockAdapter.php index 974ac3e669..1ba96c56ed 100644 --- a/src/applications/herald/adapter/HeraldPholioMockAdapter.php +++ b/src/applications/herald/adapter/HeraldPholioMockAdapter.php @@ -1,142 +1,146 @@ mock; } public function setMock(PholioMock $mock) { $this->mock = $mock; return $this; } public function getMock() { return $this->mock; } private function setCcPHIDs(array $cc_phids) { $this->ccPHIDs = $cc_phids; return $this; } public function getCcPHIDs() { return $this->ccPHIDs; } public function getAdapterContentName() { return pht('Pholio Mocks'); } public function supportsRuleType($rule_type) { switch ($rule_type) { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return true; case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: default: return false; } } public function getFields() { return array_merge( array( self::FIELD_TITLE, self::FIELD_BODY, self::FIELD_AUTHOR, self::FIELD_CC, self::FIELD_PROJECTS, self::FIELD_IS_NEW_OBJECT, ), parent::getFields()); } public function getActions($rule_type) { switch ($rule_type) { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: return array_merge( array( self::ACTION_ADD_CC, self::ACTION_NOTHING, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( self::ACTION_ADD_CC, self::ACTION_FLAG, self::ACTION_NOTHING, ), parent::getActions($rule_type)); } } public function getPHID() { return $this->getMock()->getPHID(); } public function getHeraldName() { return 'M'.$this->getMock()->getID(); } public function getHeraldField($field) { switch ($field) { case self::FIELD_TITLE: return $this->getMock()->getName(); case self::FIELD_BODY: return $this->getMock()->getDescription(); case self::FIELD_AUTHOR: return $this->getMock()->getAuthorPHID(); case self::FIELD_CC: return PhabricatorSubscribersQuery::loadSubscribersForPHID( $this->getMock()->getPHID()); case self::FIELD_PROJECTS: return PhabricatorEdgeQuery::loadDestinationPHIDs( $this->getMock()->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); } return parent::getHeraldField($field); } public function applyHeraldEffects(array $effects) { assert_instances_of($effects, 'HeraldEffect'); $result = array(); foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { case self::ACTION_NOTHING: $result[] = new HeraldApplyTranscript( $effect, true, pht('Great success at doing nothing.')); break; case self::ACTION_ADD_CC: foreach ($effect->getTarget() as $phid) { $this->ccPHIDs[] = $phid; } $result[] = new HeraldApplyTranscript( $effect, true, pht('Added address to cc list.')); break; default: $result[] = $this->applyStandardEffect($effect); break; } } return $result; } } diff --git a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php index 0a14410ac2..3946f191cb 100644 --- a/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php +++ b/src/applications/phriction/herald/PhrictionDocumentHeraldAdapter.php @@ -1,143 +1,147 @@ document; } public function setDocument(PhrictionDocument $document) { $this->document = $document; return $this; } public function getDocument() { return $this->document; } private function setCcPHIDs(array $cc_phids) { $this->ccPHIDs = $cc_phids; return $this; } public function getCcPHIDs() { return $this->ccPHIDs; } public function getAdapterContentName() { return pht('Phriction Documents'); } public function supportsRuleType($rule_type) { switch ($rule_type) { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return true; case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: default: return false; } } public function getFields() { return array_merge( array( self::FIELD_TITLE, self::FIELD_BODY, self::FIELD_AUTHOR, self::FIELD_IS_NEW_OBJECT, self::FIELD_CC, self::FIELD_PATH, ), parent::getFields()); } public function getActions($rule_type) { switch ($rule_type) { case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: return array_merge( array( self::ACTION_ADD_CC, self::ACTION_EMAIL, self::ACTION_NOTHING, ), parent::getActions($rule_type)); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array_merge( array( self::ACTION_ADD_CC, self::ACTION_EMAIL, self::ACTION_FLAG, self::ACTION_NOTHING, ), parent::getActions($rule_type)); } } public function getPHID() { return $this->getDocument()->getPHID(); } public function getHeraldName() { return pht('Wiki Document %d', $this->getDocument()->getID()); } public function getHeraldField($field) { switch ($field) { case self::FIELD_TITLE: return $this->getDocument()->getContent()->getTitle(); case self::FIELD_BODY: return $this->getDocument()->getContent()->getContent(); case self::FIELD_AUTHOR: return $this->getDocument()->getContent()->getAuthorPHID(); case self::FIELD_CC: return PhabricatorSubscribersQuery::loadSubscribersForPHID( $this->getDocument()->getPHID()); case self::FIELD_PATH: return $this->getDocument()->getContent()->getSlug(); } return parent::getHeraldField($field); } public function applyHeraldEffects(array $effects) { assert_instances_of($effects, 'HeraldEffect'); $result = array(); foreach ($effects as $effect) { $action = $effect->getAction(); switch ($action) { case self::ACTION_NOTHING: $result[] = new HeraldApplyTranscript( $effect, true, pht('Great success at doing nothing.')); break; case self::ACTION_ADD_CC: foreach ($effect->getTarget() as $phid) { $this->ccPHIDs[] = $phid; } $result[] = new HeraldApplyTranscript( $effect, true, pht('Added address to cc list.')); break; default: $result[] = $this->applyStandardEffect($effect); break; } } return $result; } }