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; | |||||
| } | |||||
| } | } | ||||