2011-03-22 21:22:40 +01:00
|
|
|
<?php
|
|
|
|
|
2012-03-14 00:21:04 +01:00
|
|
|
final class HeraldEngine {
|
2011-03-22 21:22:40 +01:00
|
|
|
|
|
|
|
protected $rules = array();
|
|
|
|
protected $results = array();
|
|
|
|
protected $stack = array();
|
|
|
|
protected $activeRule = null;
|
|
|
|
|
|
|
|
protected $fieldCache = array();
|
|
|
|
protected $object = null;
|
2013-08-07 15:47:55 +02:00
|
|
|
private $dryRun;
|
|
|
|
|
|
|
|
public function setDryRun($dry_run) {
|
|
|
|
$this->dryRun = $dry_run;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getDryRun() {
|
|
|
|
return $this->dryRun;
|
|
|
|
}
|
2011-03-22 21:22:40 +01:00
|
|
|
|
2013-08-05 19:02:40 +02:00
|
|
|
public function getRule($id) {
|
|
|
|
return idx($this->rules, $id);
|
|
|
|
}
|
|
|
|
|
Add Herald support for blocking ref changes
Summary: Ref T4195. Allows users to write Herald rules which block ref changes. For example, you can write a rule like `alincoln can not create branches`, or `no one can push to the branch "frozen"`.
Test Plan:
This covers a lot of ground. I created and pushed a bunch of rules, then looked at transcripts, in general. Here are some bits in detail:
Here's a hook-based reject message:
>>> orbital ~/repos/POEMS $ git push
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 274 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: +---------------------------------------------------------------+
remote: | * * * PUSH REJECTED BY EVIL DRAGON BUREAUCRATS * * * |
remote: +---------------------------------------------------------------+
remote: \
remote: \ ^ /^
remote: \ / \ // \
remote: \ |\___/| / \// .\
remote: \ /V V \__ / // | \ \ *----*
remote: / / \/_/ // | \ \ \ |
remote: @___@` \/_ // | \ \ \/\ \
remote: 0/0/| \/_ // | \ \ \ \
remote: 0/0/0/0/| \/// | \ \ | |
remote: 0/0/0/0/0/_|_ / ( // | \ _\ | /
remote: 0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\.-~ / /
remote: ,-} _ *-.|.-~-. .~ ~
remote: \ \__/ `/\ / ~-. _ .-~ /
remote: \____(Oo) *. } { /
remote: ( (--) .----~-.\ \-` .~
remote: //__\\ \ DENIED! ///.----..< \ _ -~
remote: // \\ ///-._ _ _ _ _ _ _{^ - - - - ~
remote:
remote:
remote: This commit was rejected by Herald pre-commit rule H24.
remote: Rule: No Branches Called Blarp
remote: Reason: "blarp" is a bad branch name
remote:
To ssh://dweller@localhost/diffusion/POEMS/
! [remote rejected] blarp -> blarp (pre-receive hook declined)
error: failed to push some refs to 'ssh://dweller@localhost/diffusion/POEMS/'
Here's a transcript, showing that all the field values populate sensibly:
{F90453}
Here's a rule:
{F90454}
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T4195
Differential Revision: https://secure.phabricator.com/D7782
2013-12-18 00:23:55 +01:00
|
|
|
public function loadRulesForAdapter(HeraldAdapter $adapter) {
|
|
|
|
return id(new HeraldRuleQuery())
|
2013-08-06 20:32:13 +02:00
|
|
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
2013-10-07 02:10:29 +02:00
|
|
|
->withDisabled(false)
|
2013-08-06 20:32:13 +02:00
|
|
|
->withContentTypes(array($adapter->getAdapterContentType()))
|
|
|
|
->needConditionsAndActions(true)
|
|
|
|
->needAppliedToPHIDs(array($adapter->getPHID()))
|
|
|
|
->needValidateAuthors(true)
|
|
|
|
->execute();
|
Add Herald support for blocking ref changes
Summary: Ref T4195. Allows users to write Herald rules which block ref changes. For example, you can write a rule like `alincoln can not create branches`, or `no one can push to the branch "frozen"`.
Test Plan:
This covers a lot of ground. I created and pushed a bunch of rules, then looked at transcripts, in general. Here are some bits in detail:
Here's a hook-based reject message:
>>> orbital ~/repos/POEMS $ git push
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 274 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: +---------------------------------------------------------------+
remote: | * * * PUSH REJECTED BY EVIL DRAGON BUREAUCRATS * * * |
remote: +---------------------------------------------------------------+
remote: \
remote: \ ^ /^
remote: \ / \ // \
remote: \ |\___/| / \// .\
remote: \ /V V \__ / // | \ \ *----*
remote: / / \/_/ // | \ \ \ |
remote: @___@` \/_ // | \ \ \/\ \
remote: 0/0/| \/_ // | \ \ \ \
remote: 0/0/0/0/| \/// | \ \ | |
remote: 0/0/0/0/0/_|_ / ( // | \ _\ | /
remote: 0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\.-~ / /
remote: ,-} _ *-.|.-~-. .~ ~
remote: \ \__/ `/\ / ~-. _ .-~ /
remote: \____(Oo) *. } { /
remote: ( (--) .----~-.\ \-` .~
remote: //__\\ \ DENIED! ///.----..< \ _ -~
remote: // \\ ///-._ _ _ _ _ _ _{^ - - - - ~
remote:
remote:
remote: This commit was rejected by Herald pre-commit rule H24.
remote: Rule: No Branches Called Blarp
remote: Reason: "blarp" is a bad branch name
remote:
To ssh://dweller@localhost/diffusion/POEMS/
! [remote rejected] blarp -> blarp (pre-receive hook declined)
error: failed to push some refs to 'ssh://dweller@localhost/diffusion/POEMS/'
Here's a transcript, showing that all the field values populate sensibly:
{F90453}
Here's a rule:
{F90454}
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T4195
Differential Revision: https://secure.phabricator.com/D7782
2013-12-18 00:23:55 +01:00
|
|
|
}
|
2011-03-22 21:22:40 +01:00
|
|
|
|
Add Herald support for blocking ref changes
Summary: Ref T4195. Allows users to write Herald rules which block ref changes. For example, you can write a rule like `alincoln can not create branches`, or `no one can push to the branch "frozen"`.
Test Plan:
This covers a lot of ground. I created and pushed a bunch of rules, then looked at transcripts, in general. Here are some bits in detail:
Here's a hook-based reject message:
>>> orbital ~/repos/POEMS $ git push
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 274 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: +---------------------------------------------------------------+
remote: | * * * PUSH REJECTED BY EVIL DRAGON BUREAUCRATS * * * |
remote: +---------------------------------------------------------------+
remote: \
remote: \ ^ /^
remote: \ / \ // \
remote: \ |\___/| / \// .\
remote: \ /V V \__ / // | \ \ *----*
remote: / / \/_/ // | \ \ \ |
remote: @___@` \/_ // | \ \ \/\ \
remote: 0/0/| \/_ // | \ \ \ \
remote: 0/0/0/0/| \/// | \ \ | |
remote: 0/0/0/0/0/_|_ / ( // | \ _\ | /
remote: 0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\.-~ / /
remote: ,-} _ *-.|.-~-. .~ ~
remote: \ \__/ `/\ / ~-. _ .-~ /
remote: \____(Oo) *. } { /
remote: ( (--) .----~-.\ \-` .~
remote: //__\\ \ DENIED! ///.----..< \ _ -~
remote: // \\ ///-._ _ _ _ _ _ _{^ - - - - ~
remote:
remote:
remote: This commit was rejected by Herald pre-commit rule H24.
remote: Rule: No Branches Called Blarp
remote: Reason: "blarp" is a bad branch name
remote:
To ssh://dweller@localhost/diffusion/POEMS/
! [remote rejected] blarp -> blarp (pre-receive hook declined)
error: failed to push some refs to 'ssh://dweller@localhost/diffusion/POEMS/'
Here's a transcript, showing that all the field values populate sensibly:
{F90453}
Here's a rule:
{F90454}
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T4195
Differential Revision: https://secure.phabricator.com/D7782
2013-12-18 00:23:55 +01:00
|
|
|
public static function loadAndApplyRules(HeraldAdapter $adapter) {
|
2011-03-22 21:22:40 +01:00
|
|
|
$engine = new HeraldEngine();
|
Add Herald support for blocking ref changes
Summary: Ref T4195. Allows users to write Herald rules which block ref changes. For example, you can write a rule like `alincoln can not create branches`, or `no one can push to the branch "frozen"`.
Test Plan:
This covers a lot of ground. I created and pushed a bunch of rules, then looked at transcripts, in general. Here are some bits in detail:
Here's a hook-based reject message:
>>> orbital ~/repos/POEMS $ git push
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 274 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: +---------------------------------------------------------------+
remote: | * * * PUSH REJECTED BY EVIL DRAGON BUREAUCRATS * * * |
remote: +---------------------------------------------------------------+
remote: \
remote: \ ^ /^
remote: \ / \ // \
remote: \ |\___/| / \// .\
remote: \ /V V \__ / // | \ \ *----*
remote: / / \/_/ // | \ \ \ |
remote: @___@` \/_ // | \ \ \/\ \
remote: 0/0/| \/_ // | \ \ \ \
remote: 0/0/0/0/| \/// | \ \ | |
remote: 0/0/0/0/0/_|_ / ( // | \ _\ | /
remote: 0/0/0/0/0/0/`/,_ _ _/ ) ; -. | _ _\.-~ / /
remote: ,-} _ *-.|.-~-. .~ ~
remote: \ \__/ `/\ / ~-. _ .-~ /
remote: \____(Oo) *. } { /
remote: ( (--) .----~-.\ \-` .~
remote: //__\\ \ DENIED! ///.----..< \ _ -~
remote: // \\ ///-._ _ _ _ _ _ _{^ - - - - ~
remote:
remote:
remote: This commit was rejected by Herald pre-commit rule H24.
remote: Rule: No Branches Called Blarp
remote: Reason: "blarp" is a bad branch name
remote:
To ssh://dweller@localhost/diffusion/POEMS/
! [remote rejected] blarp -> blarp (pre-receive hook declined)
error: failed to push some refs to 'ssh://dweller@localhost/diffusion/POEMS/'
Here's a transcript, showing that all the field values populate sensibly:
{F90453}
Here's a rule:
{F90454}
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T4195
Differential Revision: https://secure.phabricator.com/D7782
2013-12-18 00:23:55 +01:00
|
|
|
|
|
|
|
$rules = $engine->loadRulesForAdapter($adapter);
|
2013-08-06 20:32:13 +02:00
|
|
|
$effects = $engine->applyRules($rules, $adapter);
|
|
|
|
$engine->applyEffects($effects, $adapter, $rules);
|
2011-03-22 21:22:40 +01:00
|
|
|
|
|
|
|
return $engine->getTranscript();
|
|
|
|
}
|
|
|
|
|
2013-08-02 17:38:17 +02:00
|
|
|
public function applyRules(array $rules, HeraldAdapter $object) {
|
2012-04-03 21:10:45 +02:00
|
|
|
assert_instances_of($rules, 'HeraldRule');
|
2011-03-22 21:22:40 +01:00
|
|
|
$t_start = microtime(true);
|
|
|
|
|
|
|
|
$rules = mpull($rules, null, 'getID');
|
|
|
|
|
|
|
|
$this->transcript = new HeraldTranscript();
|
2011-03-25 05:32:26 +01:00
|
|
|
$this->transcript->setObjectPHID((string)$object->getPHID());
|
2011-03-22 21:22:40 +01:00
|
|
|
$this->fieldCache = array();
|
|
|
|
$this->results = array();
|
|
|
|
$this->rules = $rules;
|
|
|
|
$this->object = $object;
|
|
|
|
|
|
|
|
$effects = array();
|
|
|
|
foreach ($rules as $id => $rule) {
|
|
|
|
$this->stack = array();
|
|
|
|
try {
|
2013-09-13 20:38:49 +02:00
|
|
|
if (!$this->getDryRun() &&
|
|
|
|
($rule->getRepetitionPolicy() ==
|
2011-05-28 00:52:26 +02:00
|
|
|
HeraldRepetitionPolicyConfig::FIRST) &&
|
|
|
|
$rule->getRuleApplied($object->getPHID())) {
|
2013-09-13 20:38:49 +02:00
|
|
|
// This is not a dry run, and this rule is only supposed to be
|
|
|
|
// applied a single time, and it's already been applied...
|
|
|
|
// That means automatic failure.
|
2011-05-28 00:52:26 +02:00
|
|
|
$xscript = id(new HeraldRuleTranscript())
|
|
|
|
->setRuleID($id)
|
|
|
|
->setResult(false)
|
|
|
|
->setRuleName($rule->getName())
|
|
|
|
->setRuleOwner($rule->getAuthorPHID())
|
|
|
|
->setReason(
|
|
|
|
"This rule is only supposed to be repeated a single time, ".
|
2013-02-19 22:33:10 +01:00
|
|
|
"and it has already been applied.");
|
2011-05-28 00:52:26 +02:00
|
|
|
$this->transcript->addRuleTranscript($xscript);
|
|
|
|
$rule_matches = false;
|
|
|
|
} else {
|
|
|
|
$rule_matches = $this->doesRuleMatch($rule, $object);
|
|
|
|
}
|
2011-03-22 21:22:40 +01:00
|
|
|
} catch (HeraldRecursiveConditionsException $ex) {
|
|
|
|
$names = array();
|
|
|
|
foreach ($this->stack as $rule_id => $ignored) {
|
|
|
|
$names[] = '"'.$rules[$rule_id]->getName().'"';
|
|
|
|
}
|
|
|
|
$names = implode(', ', $names);
|
|
|
|
foreach ($this->stack as $rule_id => $ignored) {
|
|
|
|
$xscript = new HeraldRuleTranscript();
|
|
|
|
$xscript->setRuleID($rule_id);
|
|
|
|
$xscript->setResult(false);
|
|
|
|
$xscript->setReason(
|
|
|
|
"Rules {$names} are recursively dependent upon one another! ".
|
|
|
|
"Don't do this! You have formed an unresolvable cycle in the ".
|
|
|
|
"dependency graph!");
|
|
|
|
$xscript->setRuleName($rules[$rule_id]->getName());
|
2011-03-25 05:32:26 +01:00
|
|
|
$xscript->setRuleOwner($rules[$rule_id]->getAuthorPHID());
|
2011-03-22 21:22:40 +01:00
|
|
|
$this->transcript->addRuleTranscript($xscript);
|
|
|
|
}
|
|
|
|
$rule_matches = false;
|
|
|
|
}
|
|
|
|
$this->results[$id] = $rule_matches;
|
|
|
|
|
|
|
|
if ($rule_matches) {
|
|
|
|
foreach ($this->getRuleEffects($rule, $object) as $effect) {
|
|
|
|
$effects[] = $effect;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$object_transcript = new HeraldObjectTranscript();
|
2011-03-25 05:32:26 +01:00
|
|
|
$object_transcript->setPHID($object->getPHID());
|
2011-03-22 21:22:40 +01:00
|
|
|
$object_transcript->setName($object->getHeraldName());
|
2013-08-02 21:03:54 +02:00
|
|
|
$object_transcript->setType($object->getAdapterContentType());
|
2011-03-22 21:22:40 +01:00
|
|
|
$object_transcript->setFields($this->fieldCache);
|
|
|
|
|
|
|
|
$this->transcript->setObjectTranscript($object_transcript);
|
|
|
|
|
|
|
|
$t_end = microtime(true);
|
|
|
|
|
|
|
|
$this->transcript->setDuration($t_end - $t_start);
|
|
|
|
|
|
|
|
return $effects;
|
|
|
|
}
|
|
|
|
|
2012-01-25 20:53:39 +01:00
|
|
|
public function applyEffects(
|
|
|
|
array $effects,
|
2013-08-07 15:47:55 +02:00
|
|
|
HeraldAdapter $adapter,
|
2012-01-25 20:53:39 +01:00
|
|
|
array $rules) {
|
2012-04-03 21:10:45 +02:00
|
|
|
assert_instances_of($effects, 'HeraldEffect');
|
|
|
|
assert_instances_of($rules, 'HeraldRule');
|
2012-01-25 20:53:39 +01:00
|
|
|
|
2013-08-07 15:47:55 +02:00
|
|
|
$this->transcript->setDryRun((int)$this->getDryRun());
|
2011-05-28 00:52:26 +02:00
|
|
|
|
2013-08-07 15:47:55 +02:00
|
|
|
if ($this->getDryRun()) {
|
|
|
|
$xscripts = array();
|
|
|
|
foreach ($effects as $effect) {
|
|
|
|
$xscripts[] = new HeraldApplyTranscript(
|
|
|
|
$effect,
|
|
|
|
false,
|
|
|
|
pht('This was a dry run, so no actions were actually taken.'));
|
2011-03-22 21:22:40 +01:00
|
|
|
}
|
2013-08-07 15:47:55 +02:00
|
|
|
} else {
|
|
|
|
$xscripts = $adapter->applyHeraldEffects($effects);
|
2011-03-22 21:22:40 +01:00
|
|
|
}
|
2011-05-28 00:52:26 +02:00
|
|
|
|
2013-08-07 15:47:55 +02:00
|
|
|
assert_instances_of($xscripts, 'HeraldApplyTranscript');
|
|
|
|
foreach ($xscripts as $apply_xscript) {
|
|
|
|
$this->transcript->addApplyTranscript($apply_xscript);
|
|
|
|
}
|
2012-01-25 20:53:39 +01:00
|
|
|
|
2013-08-07 15:47:55 +02:00
|
|
|
// For dry runs, don't mark the rule as having applied to the object.
|
|
|
|
if ($this->getDryRun()) {
|
|
|
|
return;
|
|
|
|
}
|
2012-01-25 20:53:39 +01:00
|
|
|
|
2013-08-07 15:47:55 +02:00
|
|
|
$rules = mpull($rules, null, 'getID');
|
|
|
|
$applied_ids = array();
|
|
|
|
$first_policy = HeraldRepetitionPolicyConfig::toInt(
|
|
|
|
HeraldRepetitionPolicyConfig::FIRST);
|
|
|
|
|
|
|
|
// Mark all the rules that have had their effects applied as having been
|
|
|
|
// executed for the current object.
|
|
|
|
$rule_ids = mpull($xscripts, 'getRuleID');
|
|
|
|
|
|
|
|
foreach ($rule_ids as $rule_id) {
|
|
|
|
if (!$rule_id) {
|
|
|
|
// Some apply transcripts are purely informational and not associated
|
|
|
|
// with a rule, e.g. carryover emails from earlier revisions.
|
|
|
|
continue;
|
|
|
|
}
|
2012-01-25 20:53:39 +01:00
|
|
|
|
2013-08-07 15:47:55 +02:00
|
|
|
$rule = idx($rules, $rule_id);
|
|
|
|
if (!$rule) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-01-25 20:53:39 +01:00
|
|
|
|
2013-08-07 15:47:55 +02:00
|
|
|
if ($rule->getRepetitionPolicy() == $first_policy) {
|
|
|
|
$applied_ids[] = $rule_id;
|
2012-01-25 20:53:39 +01:00
|
|
|
}
|
2013-08-07 15:47:55 +02:00
|
|
|
}
|
2012-01-25 20:53:39 +01:00
|
|
|
|
2013-08-07 15:47:55 +02:00
|
|
|
if ($applied_ids) {
|
|
|
|
$conn_w = id(new HeraldRule())->establishConnection('w');
|
|
|
|
$sql = array();
|
|
|
|
foreach ($applied_ids as $id) {
|
|
|
|
$sql[] = qsprintf(
|
2012-01-25 20:53:39 +01:00
|
|
|
$conn_w,
|
2013-08-07 15:47:55 +02:00
|
|
|
'(%s, %d)',
|
|
|
|
$adapter->getPHID(),
|
|
|
|
$id);
|
2011-05-28 00:52:26 +02:00
|
|
|
}
|
2013-08-07 15:47:55 +02:00
|
|
|
queryfx(
|
|
|
|
$conn_w,
|
|
|
|
'INSERT IGNORE INTO %T (phid, ruleID) VALUES %Q',
|
|
|
|
HeraldRule::TABLE_RULE_APPLIED,
|
|
|
|
implode(', ', $sql));
|
2011-05-28 00:52:26 +02:00
|
|
|
}
|
2011-03-22 21:22:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getTranscript() {
|
|
|
|
$this->transcript->save();
|
|
|
|
return $this->transcript;
|
|
|
|
}
|
|
|
|
|
2013-08-05 19:02:40 +02:00
|
|
|
public function doesRuleMatch(
|
2011-03-25 05:32:26 +01:00
|
|
|
HeraldRule $rule,
|
2013-08-02 17:38:17 +02:00
|
|
|
HeraldAdapter $object) {
|
2011-03-25 05:32:26 +01:00
|
|
|
|
2011-03-22 21:22:40 +01:00
|
|
|
$id = $rule->getID();
|
|
|
|
|
|
|
|
if (isset($this->results[$id])) {
|
|
|
|
// If we've already evaluated this rule because another rule depends
|
|
|
|
// on it, we don't need to reevaluate it.
|
|
|
|
return $this->results[$id];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($this->stack[$id])) {
|
|
|
|
// We've recursed, fail all of the rules on the stack. This happens when
|
|
|
|
// there's a dependency cycle with "Rule conditions match for rule ..."
|
|
|
|
// conditions.
|
|
|
|
foreach ($this->stack as $rule_id => $ignored) {
|
|
|
|
$this->results[$rule_id] = false;
|
|
|
|
}
|
|
|
|
throw new HeraldRecursiveConditionsException();
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->stack[$id] = true;
|
|
|
|
|
|
|
|
$all = $rule->getMustMatchAll();
|
|
|
|
|
|
|
|
$conditions = $rule->getConditions();
|
|
|
|
|
|
|
|
$result = null;
|
|
|
|
|
|
|
|
$local_version = id(new HeraldRule())->getConfigVersion();
|
|
|
|
if ($rule->getConfigVersion() > $local_version) {
|
2013-10-05 21:55:34 +02:00
|
|
|
$reason = pht(
|
|
|
|
"Rule could not be processed, it was created with a newer version ".
|
|
|
|
"of Herald.");
|
2011-03-22 21:22:40 +01:00
|
|
|
$result = false;
|
|
|
|
} else if (!$conditions) {
|
2013-10-05 21:55:34 +02:00
|
|
|
$reason = pht(
|
|
|
|
"Rule failed automatically because it has no conditions.");
|
2011-03-22 21:22:40 +01:00
|
|
|
$result = false;
|
2013-08-06 20:32:13 +02:00
|
|
|
} else if (!$rule->hasValidAuthor()) {
|
2013-10-05 21:55:34 +02:00
|
|
|
$reason = pht(
|
|
|
|
"Rule failed automatically because its owner is invalid ".
|
|
|
|
"or disabled.");
|
|
|
|
$result = false;
|
|
|
|
} else if (!$this->canAuthorViewObject($rule, $object)) {
|
|
|
|
$reason = pht(
|
|
|
|
"Rule failed automatically because it is a personal rule and its ".
|
|
|
|
"owner can not see the object.");
|
2011-03-22 21:22:40 +01:00
|
|
|
$result = false;
|
2013-12-27 22:17:10 +01:00
|
|
|
} else if (!$this->canRuleApplyToObject($rule, $object)) {
|
|
|
|
$reason = pht(
|
|
|
|
"Rule failed automatically because it is an object rule which is ".
|
|
|
|
"not relevant for this object.");
|
|
|
|
$result = false;
|
2011-03-22 21:22:40 +01:00
|
|
|
} else {
|
|
|
|
foreach ($conditions as $condition) {
|
|
|
|
$match = $this->doesConditionMatch($rule, $condition, $object);
|
|
|
|
|
|
|
|
if (!$all && $match) {
|
|
|
|
$reason = "Any condition matched.";
|
|
|
|
$result = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($all && !$match) {
|
|
|
|
$reason = "Not all conditions matched.";
|
|
|
|
$result = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($result === null) {
|
|
|
|
if ($all) {
|
|
|
|
$reason = "All conditions matched.";
|
|
|
|
$result = true;
|
|
|
|
} else {
|
|
|
|
$reason = "No conditions matched.";
|
|
|
|
$result = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$rule_transcript = new HeraldRuleTranscript();
|
|
|
|
$rule_transcript->setRuleID($rule->getID());
|
|
|
|
$rule_transcript->setResult($result);
|
|
|
|
$rule_transcript->setReason($reason);
|
|
|
|
$rule_transcript->setRuleName($rule->getName());
|
2011-03-25 05:32:26 +01:00
|
|
|
$rule_transcript->setRuleOwner($rule->getAuthorPHID());
|
2011-03-22 21:22:40 +01:00
|
|
|
|
|
|
|
$this->transcript->addRuleTranscript($rule_transcript);
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function doesConditionMatch(
|
|
|
|
HeraldRule $rule,
|
|
|
|
HeraldCondition $condition,
|
2013-08-02 17:38:17 +02:00
|
|
|
HeraldAdapter $object) {
|
2011-03-22 21:22:40 +01:00
|
|
|
|
|
|
|
$object_value = $this->getConditionObjectValue($condition, $object);
|
|
|
|
$test_value = $condition->getValue();
|
|
|
|
|
2011-03-25 05:32:26 +01:00
|
|
|
$cond = $condition->getFieldCondition();
|
2011-03-22 21:22:40 +01:00
|
|
|
|
|
|
|
$transcript = new HeraldConditionTranscript();
|
|
|
|
$transcript->setRuleID($rule->getID());
|
|
|
|
$transcript->setConditionID($condition->getID());
|
|
|
|
$transcript->setFieldName($condition->getFieldName());
|
|
|
|
$transcript->setCondition($cond);
|
|
|
|
$transcript->setTestValue($test_value);
|
|
|
|
|
2013-08-05 19:02:40 +02:00
|
|
|
try {
|
|
|
|
$result = $object->doesConditionMatch(
|
|
|
|
$this,
|
|
|
|
$rule,
|
|
|
|
$condition,
|
|
|
|
$object_value);
|
|
|
|
} catch (HeraldInvalidConditionException $ex) {
|
|
|
|
$result = false;
|
|
|
|
$transcript->setNote($ex->getMessage());
|
2011-03-22 21:22:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$transcript->setResult($result);
|
|
|
|
|
|
|
|
$this->transcript->addConditionTranscript($transcript);
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getConditionObjectValue(
|
|
|
|
HeraldCondition $condition,
|
2013-08-02 17:38:17 +02:00
|
|
|
HeraldAdapter $object) {
|
2011-03-22 21:22:40 +01:00
|
|
|
|
|
|
|
$field = $condition->getFieldName();
|
|
|
|
|
|
|
|
return $this->getObjectFieldValue($field);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getObjectFieldValue($field) {
|
|
|
|
if (isset($this->fieldCache[$field])) {
|
|
|
|
return $this->fieldCache[$field];
|
|
|
|
}
|
|
|
|
|
2013-08-05 19:02:40 +02:00
|
|
|
$result = $this->object->getHeraldField($field);
|
2011-03-22 21:22:40 +01:00
|
|
|
|
|
|
|
$this->fieldCache[$field] = $result;
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2011-03-25 05:32:26 +01:00
|
|
|
protected function getRuleEffects(
|
|
|
|
HeraldRule $rule,
|
2013-08-02 17:38:17 +02:00
|
|
|
HeraldAdapter $object) {
|
2011-03-25 05:32:26 +01:00
|
|
|
|
2011-03-22 21:22:40 +01:00
|
|
|
$effects = array();
|
|
|
|
foreach ($rule->getActions() as $action) {
|
|
|
|
$effect = new HeraldEffect();
|
2011-03-25 05:32:26 +01:00
|
|
|
$effect->setObjectPHID($object->getPHID());
|
2011-03-22 21:22:40 +01:00
|
|
|
$effect->setAction($action->getAction());
|
|
|
|
$effect->setTarget($action->getTarget());
|
|
|
|
|
|
|
|
$effect->setRuleID($rule->getID());
|
2013-12-18 00:23:45 +01:00
|
|
|
$effect->setRulePHID($rule->getPHID());
|
2011-03-22 21:22:40 +01:00
|
|
|
|
|
|
|
$name = $rule->getName();
|
|
|
|
$id = $rule->getID();
|
|
|
|
$effect->setReason(
|
2013-12-18 21:00:18 +01:00
|
|
|
pht(
|
|
|
|
'Conditions were met for %s',
|
|
|
|
"H{$id} {$name}"));
|
2011-03-22 21:22:40 +01:00
|
|
|
|
|
|
|
$effects[] = $effect;
|
|
|
|
}
|
|
|
|
return $effects;
|
|
|
|
}
|
|
|
|
|
2013-10-05 21:55:34 +02:00
|
|
|
private function canAuthorViewObject(
|
|
|
|
HeraldRule $rule,
|
|
|
|
HeraldAdapter $adapter) {
|
|
|
|
|
2013-12-27 22:17:10 +01:00
|
|
|
// Authorship is irrelevant for global rules and object rules.
|
|
|
|
if ($rule->isGlobalRule() || $rule->isObjectRule()) {
|
2013-10-05 21:55:34 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The author must be able to create rules for the adapter's content type.
|
|
|
|
// In particular, this means that the application must be installed and
|
|
|
|
// accessible to the user. For example, if a user writes a Differential
|
|
|
|
// rule and then loses access to Differential, this disables the rule.
|
|
|
|
$enabled = HeraldAdapter::getEnabledAdapterMap($rule->getAuthor());
|
|
|
|
if (empty($enabled[$adapter->getAdapterContentType()])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, the author must be able to see the object itself. You can't
|
|
|
|
// write a personal rule that CC's you on revisions you wouldn't otherwise
|
|
|
|
// be able to see, for example.
|
|
|
|
$object = $adapter->getObject();
|
|
|
|
return PhabricatorPolicyFilter::hasCapability(
|
|
|
|
$rule->getAuthor(),
|
|
|
|
$object,
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW);
|
|
|
|
}
|
|
|
|
|
2013-12-27 22:17:10 +01:00
|
|
|
private function canRuleApplyToObject(
|
|
|
|
HeraldRule $rule,
|
|
|
|
HeraldAdapter $adapter) {
|
|
|
|
|
|
|
|
// Rules which are not object rules can apply to anything.
|
|
|
|
if (!$rule->isObjectRule()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$trigger_phid = $rule->getTriggerObjectPHID();
|
|
|
|
$object_phid = $adapter->getPHID();
|
|
|
|
|
|
|
|
if ($trigger_phid == $object_phid) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: We should also handle projects.
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-03-22 21:22:40 +01:00
|
|
|
}
|