diff --git a/src/applications/diffusion/conduit/ConduitAPI_diffusion_branchquery_Method.php b/src/applications/diffusion/conduit/ConduitAPI_diffusion_branchquery_Method.php index 98a1b7932e..e4831cfc07 100644 --- a/src/applications/diffusion/conduit/ConduitAPI_diffusion_branchquery_Method.php +++ b/src/applications/diffusion/conduit/ConduitAPI_diffusion_branchquery_Method.php @@ -1,128 +1,135 @@ '; } protected function defineCustomParamTypes() { return array( 'limit' => 'optional int', 'offset' => 'optional int', 'contains' => 'optional string', ); } protected function getGitResult(ConduitAPIRequest $request) { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $contains = $request->getValue('contains'); if (strlen($contains)) { // NOTE: We can't use DiffusionLowLevelGitRefQuery here because // `git for-each-ref` does not support `--contains`. if ($repository->isWorkingCopyBare()) { list($stdout) = $repository->execxLocalCommand( 'branch --verbose --no-abbrev --contains %s --', $contains); $ref_map = DiffusionGitBranch::parseLocalBranchOutput( $stdout); } else { list($stdout) = $repository->execxLocalCommand( 'branch -r --verbose --no-abbrev --contains %s --', $contains); $ref_map = DiffusionGitBranch::parseRemoteBranchOutput( $stdout, DiffusionGitBranch::DEFAULT_GIT_REMOTE); } $refs = array(); foreach ($ref_map as $ref => $commit) { $refs[] = id(new DiffusionRepositoryRef()) ->setShortName($ref) ->setCommitIdentifier($commit); } } else { $refs = id(new DiffusionLowLevelGitRefQuery()) ->setRepository($repository) ->withIsOriginBranch(true) ->execute(); } return $this->processBranchRefs($request, $refs); } protected function getMercurialResult(ConduitAPIRequest $request) { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $refs = id(new DiffusionLowLevelMercurialBranchesQuery()) ->setRepository($repository) ->execute(); // If we have a 'contains' query, filter these branches down to just the // ones which contain the commit. $contains = $request->getValue('contains'); if (strlen($contains)) { list($branches_raw) = $repository->execxLocalCommand( 'log --template %s --limit 1 --rev %s --', '{branches}', hgsprintf('%s', $contains)); $branches_raw = trim($branches_raw); if (!strlen($branches_raw)) { $containing_branches = array('default'); } else { $containing_branches = explode(' ', $branches_raw); } $containing_branches = array_fuse($containing_branches); // NOTE: We get this very slightly wrong: a branch may have multiple // heads and we'll keep all of the heads of the branch, even if the // commit is only on some of the heads. This should be rare, is probably // more clear to users as is, and would potentially be expensive to get // right since we'd have to do additional checks. foreach ($refs as $key => $ref) { if (empty($containing_branches[$ref->getShortName()])) { unset($refs[$key]); } } } return $this->processBranchRefs($request, $refs); } + protected function getSVNResult() { + // Since SVN doesn't have meaningful branches, just return nothing for all + // queries. + return array(); + } + private function processBranchRefs(ConduitAPIRequest $request, array $refs) { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $offset = $request->getValue('offset'); $limit = $request->getValue('limit'); foreach ($refs as $key => $ref) { if (!$repository->shouldTrackBranch($ref->getShortName())) { unset($refs[$key]); } } // NOTE: We can't apply the offset or limit until here, because we may have // filtered untrackable branches out of the result set. if ($offset) { $refs = array_slice($refs, $offset); } if ($limit) { $refs = array_slice($refs, 0, $limit); } return mpull($refs, 'toDictionary'); } + } diff --git a/src/applications/herald/adapter/HeraldCommitAdapter.php b/src/applications/herald/adapter/HeraldCommitAdapter.php index 34f1c51301..cd0e869217 100644 --- a/src/applications/herald/adapter/HeraldCommitAdapter.php +++ b/src/applications/herald/adapter/HeraldCommitAdapter.php @@ -1,539 +1,553 @@ 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('On autoclose branch'), + 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_RULE, 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( self::ACTION_ADD_CC, self::ACTION_EMAIL, self::ACTION_AUDIT, self::ACTION_APPLY_BUILD_PLANS, self::ACTION_NOTHING ); case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: return array( self::ACTION_ADD_CC, self::ACTION_EMAIL, self::ACTION_FLAG, self::ACTION_AUDIT, self::ACTION_NOTHING, ); } } 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 getEmailPHIDs() { return array_keys($this->emailPHIDs); } 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::newFromRawChanges($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; } // after a revision is accepted, it can be closed (say via arc land) // so use this function to figure out if it was accepted at one point // *and* not later rejected... what a function! $reviewed_by = $revision->loadReviewedBy(); if (!$reviewed_by) { return null; } return $revision->getPHID(); 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, $this->commitData); } 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_EMAIL: foreach ($effect->getTarget() as $phid) { $this->emailPHIDs[$phid] = true; } $result[] = new HeraldApplyTranscript( $effect, true, pht('Added address to email targets.')); break; case self::ACTION_ADD_CC: foreach ($effect->getTarget() as $phid) { if (empty($this->addCCPHIDs[$phid])) { $this->addCCPHIDs[$phid] = array(); } $this->addCCPHIDs[$phid][] = $effect->getRuleID(); } $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->getRuleID(); } $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; case self::ACTION_FLAG: $result[] = parent::applyFlagEffect( $effect, $this->commit->getPHID()); break; default: throw new Exception("No rules to handle action '{$action}'."); } } return $result; } } diff --git a/src/applications/herald/controller/HeraldTestConsoleController.php b/src/applications/herald/controller/HeraldTestConsoleController.php index c2e1cfb781..7ec11b9d19 100644 --- a/src/applications/herald/controller/HeraldTestConsoleController.php +++ b/src/applications/herald/controller/HeraldTestConsoleController.php @@ -1,115 +1,113 @@ getRequest(); $user = $request->getUser(); $request = $this->getRequest(); $object_name = trim($request->getStr('object_name')); $e_name = true; $errors = array(); if ($request->isFormPost()) { if (!$object_name) { $e_name = pht('Required'); $errors[] = pht('An object name is required.'); } if (!$errors) { $object = id(new PhabricatorObjectQuery()) ->setViewer($user) ->withNames(array($object_name)) ->executeOne(); if (!$object) { $e_name = pht('Invalid'); $errors[] = pht('No object exists with that name.'); } if (!$errors) { // TODO: Let the adapters claim objects instead. if ($object instanceof DifferentialRevision) { $adapter = HeraldDifferentialRevisionAdapter::newLegacyAdapter( $object, $object->loadActiveDiff()); } else if ($object instanceof PhabricatorRepositoryCommit) { $adapter = id(new HeraldCommitAdapter()) ->setCommit($object); } else if ($object instanceof ManiphestTask) { $adapter = id(new HeraldManiphestTaskAdapter()) ->setTask($object); } else if ($object instanceof PholioMock) { $adapter = id(new HeraldPholioMockAdapter()) ->setMock($object); } else { throw new Exception("Can not build adapter for object!"); } $rules = id(new HeraldRuleQuery()) ->setViewer($user) ->withContentTypes(array($adapter->getAdapterContentType())) ->withDisabled(false) ->needConditionsAndActions(true) ->needAppliedToPHIDs(array($object->getPHID())) ->needValidateAuthors(true) ->execute(); $engine = id(new HeraldEngine()) ->setDryRun(true); $effects = $engine->applyRules($rules, $adapter); $engine->applyEffects($effects, $adapter, $rules); $xscript = $engine->getTranscript(); return id(new AphrontRedirectResponse()) ->setURI('/herald/transcript/'.$xscript->getID().'/'); } } } - $text = pht( - 'Enter an object to test rules for, like a Diffusion commit (e.g., '. - 'rX123) or a Differential revision (e.g., D123). You will be shown '. - 'the results of a dry run on the object.'); - $form = id(new AphrontFormView()) ->setUser($user) - ->appendChild( - phutil_tag('p', array('class' => 'aphront-form-instructions'), $text)) + ->appendRemarkupInstructions( + pht( + 'Enter an object to test rules for, like a Diffusion commit (e.g., '. + '`rX123`) or a Differential revision (e.g., `D123`). You will be '. + 'shown the results of a dry run on the object.')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Object Name')) ->setName('object_name') ->setError($e_name) ->setValue($object_name)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Test Rules'))); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Herald Test Console')) ->setFormErrors($errors) ->setForm($form); $crumbs = id($this->buildApplicationCrumbs()) ->addTextCrumb( pht('Transcripts'), $this->getApplicationURI('/transcript/')) ->addTextCrumb(pht('Test Console')); return $this->buildApplicationPage( $box, array( 'title' => pht('Test Console'), 'device' => true, )); } } diff --git a/src/applications/herald/storage/HeraldRule.php b/src/applications/herald/storage/HeraldRule.php index cd6bfa4b9c..82eb9926b6 100644 --- a/src/applications/herald/storage/HeraldRule.php +++ b/src/applications/herald/storage/HeraldRule.php @@ -1,254 +1,253 @@ true, ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(HeraldPHIDTypeRule::TYPECONST); } public function getRuleApplied($phid) { return $this->assertAttachedKey($this->ruleApplied, $phid); } public function setRuleApplied($phid, $applied) { if ($this->ruleApplied === self::ATTACHABLE) { $this->ruleApplied = array(); } $this->ruleApplied[$phid] = $applied; return $this; } public function loadConditions() { if (!$this->getID()) { return array(); } return id(new HeraldCondition())->loadAllWhere( 'ruleID = %d', $this->getID()); } public function attachConditions(array $conditions) { assert_instances_of($conditions, 'HeraldCondition'); $this->conditions = $conditions; return $this; } public function getConditions() { // TODO: validate conditions have been attached. return $this->conditions; } public function loadActions() { if (!$this->getID()) { return array(); } return id(new HeraldAction())->loadAllWhere( 'ruleID = %d', $this->getID()); } public function attachActions(array $actions) { // TODO: validate actions have been attached. assert_instances_of($actions, 'HeraldAction'); $this->actions = $actions; return $this; } public function getActions() { return $this->actions; } public function loadEdits() { if (!$this->getID()) { return array(); } $edits = id(new HeraldRuleEdit())->loadAllWhere( 'ruleID = %d ORDER BY dateCreated DESC', $this->getID()); return $edits; } public function logEdit($editor_phid, $action) { id(new HeraldRuleEdit()) ->setRuleID($this->getID()) ->setRuleName($this->getName()) ->setEditorPHID($editor_phid) ->setAction($action) ->save(); } public function saveConditions(array $conditions) { assert_instances_of($conditions, 'HeraldCondition'); return $this->saveChildren( id(new HeraldCondition())->getTableName(), $conditions); } public function saveActions(array $actions) { assert_instances_of($actions, 'HeraldAction'); return $this->saveChildren( id(new HeraldAction())->getTableName(), $actions); } protected function saveChildren($table_name, array $children) { assert_instances_of($children, 'HeraldDAO'); if (!$this->getID()) { throw new Exception("Save rule before saving children."); } foreach ($children as $child) { $child->setRuleID($this->getID()); } -// TODO: -// $this->openTransaction(); + $this->openTransaction(); queryfx( $this->establishConnection('w'), 'DELETE FROM %T WHERE ruleID = %d', $table_name, $this->getID()); foreach ($children as $child) { $child->save(); } -// $this->saveTransaction(); + $this->saveTransaction(); } public function delete() { $this->openTransaction(); queryfx( $this->establishConnection('w'), 'DELETE FROM %T WHERE ruleID = %d', id(new HeraldCondition())->getTableName(), $this->getID()); queryfx( $this->establishConnection('w'), 'DELETE FROM %T WHERE ruleID = %d', id(new HeraldAction())->getTableName(), $this->getID()); $result = parent::delete(); $this->saveTransaction(); return $result; } public function hasValidAuthor() { return $this->assertAttached($this->validAuthor); } public function attachValidAuthor($valid) { $this->validAuthor = $valid; return $this; } public function getAuthor() { return $this->assertAttached($this->author); } public function attachAuthor(PhabricatorUser $user) { $this->author = $user; return $this; } public function isGlobalRule() { return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_GLOBAL); } public function isPersonalRule() { return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); } public function isObjectRule() { return ($this->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_OBJECT); } public function attachTriggerObject($trigger_object) { $this->triggerObject = $trigger_object; return $this; } public function getTriggerObject() { return $this->assertAttached($this->triggerObject); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { if ($this->isGlobalRule()) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::POLICY_USER; case PhabricatorPolicyCapability::CAN_EDIT: $app = 'PhabricatorApplicationHerald'; $herald = PhabricatorApplication::getByClass($app); $global = HeraldCapabilityManageGlobalRules::CAPABILITY; return $herald->getPolicy($global); } } else if ($this->isObjectRule()) { return $this->getTriggerObject()->getPolicy($capability); } else { return PhabricatorPolicies::POLICY_NOONE; } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { if ($this->isPersonalRule()) { return ($viewer->getPHID() == $this->getAuthorPHID()); } else { return false; } } public function describeAutomaticCapability($capability) { if ($this->isPersonalRule()) { return pht("A personal rule's owner can always view and edit it."); } else if ($this->isObjectRule()) { return pht("Object rules inherit the policies of their objects."); } return null; } }