Changeset View
Changeset View
Standalone View
Standalone View
src/applications/herald/engine/HeraldEngine.php
<?php | <?php | ||||
final class HeraldEngine extends Phobject { | final class HeraldEngine extends Phobject { | ||||
protected $rules = array(); | protected $rules = array(); | ||||
protected $results = array(); | protected $results = array(); | ||||
protected $stack = array(); | protected $stack = array(); | ||||
protected $activeRule; | protected $activeRule; | ||||
protected $transcript; | protected $transcript; | ||||
protected $fieldCache = array(); | protected $fieldCache = array(); | ||||
protected $object; | protected $object; | ||||
private $dryRun; | private $dryRun; | ||||
private $forbiddenFields = array(); | |||||
private $forbiddenActions = array(); | |||||
public function setDryRun($dry_run) { | public function setDryRun($dry_run) { | ||||
$this->dryRun = $dry_run; | $this->dryRun = $dry_run; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function getDryRun() { | public function getDryRun() { | ||||
return $this->dryRun; | return $this->dryRun; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | foreach ($rules as $phid => $rule) { | ||||
try { | try { | ||||
if (!$this->getDryRun() && | if (!$this->getDryRun() && | ||||
$is_first_only && | $is_first_only && | ||||
$rule->getRuleApplied($object->getPHID())) { | $rule->getRuleApplied($object->getPHID())) { | ||||
// This is not a dry run, and this rule is only supposed to be | // This is not a dry run, and this rule is only supposed to be | ||||
// applied a single time, and it's already been applied... | // applied a single time, and it's already been applied... | ||||
// That means automatic failure. | // That means automatic failure. | ||||
$xscript = id(new HeraldRuleTranscript()) | $this->newRuleTranscript($rule) | ||||
->setRuleID($rule->getID()) | |||||
->setResult(false) | ->setResult(false) | ||||
->setRuleName($rule->getName()) | |||||
->setRuleOwner($rule->getAuthorPHID()) | |||||
->setReason( | ->setReason( | ||||
pht( | pht( | ||||
'This rule is only supposed to be repeated a single time, '. | 'This rule is only supposed to be repeated a single time, '. | ||||
'and it has already been applied.')); | 'and it has already been applied.')); | ||||
$this->transcript->addRuleTranscript($xscript); | |||||
$rule_matches = false; | |||||
} else { | |||||
if ($this->isForbidden($rule, $object)) { | |||||
$this->newRuleTranscript($rule) | |||||
->setResult(HeraldRuleTranscript::RESULT_FORBIDDEN) | |||||
->setReason( | |||||
pht( | |||||
'Object state is not compatible with rule.')); | |||||
$rule_matches = false; | $rule_matches = false; | ||||
} else { | } else { | ||||
$rule_matches = $this->doesRuleMatch($rule, $object); | $rule_matches = $this->doesRuleMatch($rule, $object); | ||||
} | } | ||||
} | |||||
} catch (HeraldRecursiveConditionsException $ex) { | } catch (HeraldRecursiveConditionsException $ex) { | ||||
$names = array(); | $names = array(); | ||||
foreach ($this->stack as $rule_id => $ignored) { | foreach ($this->stack as $rule_phid => $ignored) { | ||||
$names[] = '"'.$rules[$rule_id]->getName().'"'; | $names[] = '"'.$rules[$rule_phid]->getName().'"'; | ||||
} | } | ||||
$names = implode(', ', $names); | $names = implode(', ', $names); | ||||
foreach ($this->stack as $rule_id => $ignored) { | foreach ($this->stack as $rule_phid => $ignored) { | ||||
$xscript = new HeraldRuleTranscript(); | $this->newRuleTranscript($rules[$rule_phid]) | ||||
$xscript->setRuleID($rule_id); | ->setResult(false) | ||||
$xscript->setResult(false); | ->setReason( | ||||
$xscript->setReason( | |||||
pht( | pht( | ||||
"Rules %s are recursively dependent upon one another! ". | "Rules %s are recursively dependent upon one another! ". | ||||
"Don't do this! You have formed an unresolvable cycle in the ". | "Don't do this! You have formed an unresolvable cycle in the ". | ||||
"dependency graph!", | "dependency graph!", | ||||
$names)); | $names)); | ||||
$xscript->setRuleName($rules[$rule_id]->getName()); | |||||
$xscript->setRuleOwner($rules[$rule_id]->getAuthorPHID()); | |||||
$this->transcript->addRuleTranscript($xscript); | |||||
} | } | ||||
$rule_matches = false; | $rule_matches = false; | ||||
} | } | ||||
$this->results[$phid] = $rule_matches; | $this->results[$phid] = $rule_matches; | ||||
if ($rule_matches) { | if ($rule_matches) { | ||||
foreach ($this->getRuleEffects($rule, $object) as $effect) { | foreach ($this->getRuleEffects($rule, $object) as $effect) { | ||||
$effects[] = $effect; | $effects[] = $effect; | ||||
▲ Show 20 Lines • Show All 184 Lines • ▼ Show 20 Lines | if ($rule->getConfigVersion() > $local_version) { | ||||
$result = true; | $result = true; | ||||
} else { | } else { | ||||
$reason = pht('No conditions matched.'); | $reason = pht('No conditions matched.'); | ||||
$result = false; | $result = false; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
$rule_transcript = new HeraldRuleTranscript(); | $this->newRuleTranscript($rule) | ||||
$rule_transcript->setRuleID($rule->getID()); | ->setResult($result) | ||||
$rule_transcript->setResult($result); | ->setReason($reason); | ||||
$rule_transcript->setReason($reason); | |||||
$rule_transcript->setRuleName($rule->getName()); | |||||
$rule_transcript->setRuleOwner($rule->getAuthorPHID()); | |||||
$this->transcript->addRuleTranscript($rule_transcript); | |||||
return $result; | return $result; | ||||
} | } | ||||
protected function doesConditionMatch( | protected function doesConditionMatch( | ||||
HeraldRule $rule, | HeraldRule $rule, | ||||
HeraldCondition $condition, | HeraldCondition $condition, | ||||
HeraldAdapter $object) { | HeraldAdapter $object) { | ||||
$object_value = $this->getConditionObjectValue($condition, $object); | $object_value = $this->getConditionObjectValue($condition, $object); | ||||
$test_value = $condition->getValue(); | $transcript = $this->newConditionTranscript($rule, $condition); | ||||
$cond = $condition->getFieldCondition(); | |||||
$transcript = new HeraldConditionTranscript(); | |||||
$transcript->setRuleID($rule->getID()); | |||||
$transcript->setConditionID($condition->getID()); | |||||
$transcript->setFieldName($condition->getFieldName()); | |||||
$transcript->setCondition($cond); | |||||
$transcript->setTestValue($test_value); | |||||
try { | try { | ||||
$result = $object->doesConditionMatch( | $result = $object->doesConditionMatch( | ||||
$this, | $this, | ||||
$rule, | $rule, | ||||
$condition, | $condition, | ||||
$object_value); | $object_value); | ||||
} catch (HeraldInvalidConditionException $ex) { | } catch (HeraldInvalidConditionException $ex) { | ||||
$result = false; | $result = false; | ||||
$transcript->setNote($ex->getMessage()); | $transcript->setNote($ex->getMessage()); | ||||
} | } | ||||
$transcript->setResult($result); | $transcript->setResult($result); | ||||
$this->transcript->addConditionTranscript($transcript); | |||||
return $result; | return $result; | ||||
} | } | ||||
protected function getConditionObjectValue( | protected function getConditionObjectValue( | ||||
HeraldCondition $condition, | HeraldCondition $condition, | ||||
HeraldAdapter $object) { | HeraldAdapter $object) { | ||||
$field = $condition->getFieldName(); | $field = $condition->getFieldName(); | ||||
▲ Show 20 Lines • Show All 77 Lines • ▼ Show 20 Lines | if ($object_phids) { | ||||
if (in_array($trigger_phid, $object_phids)) { | if (in_array($trigger_phid, $object_phids)) { | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
private function newRuleTranscript(HeraldRule $rule) { | |||||
$xscript = id(new HeraldRuleTranscript()) | |||||
->setRuleID($rule->getID()) | |||||
->setRuleName($rule->getName()) | |||||
->setRuleOwner($rule->getAuthorPHID()); | |||||
$this->transcript->addRuleTranscript($xscript); | |||||
return $xscript; | |||||
} | |||||
private function newConditionTranscript( | |||||
HeraldRule $rule, | |||||
HeraldCondition $condition) { | |||||
$xscript = id(new HeraldConditionTranscript()) | |||||
->setRuleID($rule->getID()) | |||||
->setConditionID($condition->getID()) | |||||
->setFieldName($condition->getFieldName()) | |||||
->setCondition($condition->getFieldCondition()) | |||||
->setTestValue($condition->getValue()); | |||||
$this->transcript->addConditionTranscript($xscript); | |||||
return $xscript; | |||||
} | |||||
private function newApplyTranscript( | |||||
HeraldAdapter $adapter, | |||||
HeraldRule $rule, | |||||
HeraldActionRecord $action) { | |||||
$effect = id(new HeraldEffect()) | |||||
->setObjectPHID($adapter->getPHID()) | |||||
->setAction($action->getAction()) | |||||
->setTarget($action->getTarget()) | |||||
->setRule($rule); | |||||
$xscript = new HeraldApplyTranscript($effect, false); | |||||
$this->transcript->addApplyTranscript($xscript); | |||||
return $xscript; | |||||
} | |||||
private function isForbidden( | |||||
HeraldRule $rule, | |||||
HeraldAdapter $adapter) { | |||||
$forbidden = $adapter->getForbiddenActions(); | |||||
if (!$forbidden) { | |||||
return false; | |||||
} | |||||
$forbidden = array_fuse($forbidden); | |||||
$is_forbidden = false; | |||||
foreach ($rule->getConditions() as $condition) { | |||||
$field_key = $condition->getFieldName(); | |||||
if (!isset($this->forbiddenFields[$field_key])) { | |||||
$reason = null; | |||||
try { | |||||
$states = $adapter->getRequiredFieldStates($field_key); | |||||
} catch (Exception $ex) { | |||||
$states = array(); | |||||
} | |||||
foreach ($states as $state) { | |||||
if (!isset($forbidden[$state])) { | |||||
continue; | |||||
} | |||||
$reason = $adapter->getForbiddenReason($state); | |||||
break; | |||||
} | |||||
$this->forbiddenFields[$field_key] = $reason; | |||||
} | |||||
$forbidden_reason = $this->forbiddenFields[$field_key]; | |||||
if ($forbidden_reason !== null) { | |||||
$this->newConditionTranscript($rule, $condition) | |||||
->setResult(HeraldConditionTranscript::RESULT_FORBIDDEN) | |||||
->setNote($forbidden_reason); | |||||
$is_forbidden = true; | |||||
} | |||||
} | |||||
foreach ($rule->getActions() as $action_record) { | |||||
$action_key = $action_record->getAction(); | |||||
if (!isset($this->forbiddenActions[$action_key])) { | |||||
$reason = null; | |||||
try { | |||||
$states = $adapter->getRequiredActionStates($action_key); | |||||
} catch (Exception $ex) { | |||||
$states = array(); | |||||
} | |||||
foreach ($states as $state) { | |||||
if (!isset($forbidden[$state])) { | |||||
continue; | |||||
} | |||||
$reason = $adapter->getForbiddenReason($state); | |||||
break; | |||||
} | |||||
$this->forbiddenActions[$action_key] = $reason; | |||||
} | |||||
$forbidden_reason = $this->forbiddenActions[$action_key]; | |||||
if ($forbidden_reason !== null) { | |||||
$this->newApplyTranscript($adapter, $rule, $action_record) | |||||
->setAppliedReason( | |||||
array( | |||||
array( | |||||
'type' => HeraldAction::DO_STANDARD_FORBIDDEN, | |||||
'data' => $forbidden_reason, | |||||
), | |||||
)); | |||||
$is_forbidden = true; | |||||
} | |||||
} | |||||
return $is_forbidden; | |||||
} | |||||
} | } |