Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/herald/engine/engine/HeraldEngine.php b/src/applications/herald/engine/engine/HeraldEngine.php
index 9532a4862d..9547a4e486 100644
--- a/src/applications/herald/engine/engine/HeraldEngine.php
+++ b/src/applications/herald/engine/engine/HeraldEngine.php
@@ -1,520 +1,518 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class HeraldEngine {
protected $rules = array();
protected $results = array();
protected $stack = array();
protected $activeRule = null;
protected $fieldCache = array();
protected $object = null;
public static function loadAndApplyRules(HeraldObjectAdapter $object) {
$content_type = $object->getHeraldTypeName();
$rules = HeraldRule::loadAllByContentTypeWithFullData(
$content_type,
$object->getPHID());
$engine = new HeraldEngine();
$effects = $engine->applyRules($rules, $object);
$engine->applyEffects($effects, $object, $rules);
return $engine->getTranscript();
}
public function applyRules(array $rules, HeraldObjectAdapter $object) {
$t_start = microtime(true);
$rules = mpull($rules, null, 'getID');
$this->transcript = new HeraldTranscript();
$this->transcript->setObjectPHID((string)$object->getPHID());
$this->fieldCache = array();
$this->results = array();
$this->rules = $rules;
$this->object = $object;
$effects = array();
foreach ($rules as $id => $rule) {
$this->stack = array();
try {
if (($rule->getRepetitionPolicy() ==
HeraldRepetitionPolicyConfig::FIRST) &&
$rule->getRuleApplied($object->getPHID())) {
// This rule is only supposed to be applied a single time, and it's
// aleady been applied, so this is an automatic failure.
$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, ".
"and it has already been applied."
);
$this->transcript->addRuleTranscript($xscript);
$rule_matches = false;
} else {
$rule_matches = $this->doesRuleMatch($rule, $object);
}
} 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());
$xscript->setRuleOwner($rules[$rule_id]->getAuthorPHID());
$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();
$object_transcript->setPHID($object->getPHID());
$object_transcript->setName($object->getHeraldName());
$object_transcript->setType($object->getHeraldTypeName());
$object_transcript->setFields($this->fieldCache);
$this->transcript->setObjectTranscript($object_transcript);
$t_end = microtime(true);
$this->transcript->setDuration($t_end - $t_start);
return $effects;
}
public function applyEffects(
array $effects,
HeraldObjectAdapter $object,
array $rules) {
$this->transcript->setDryRun($object instanceof HeraldDryRunAdapter);
$xscripts = $object->applyHeraldEffects($effects);
foreach ($xscripts as $apply_xscript) {
if (!($apply_xscript instanceof HeraldApplyTranscript)) {
throw new Exception(
"Heraldable must return HeraldApplyTranscripts from ".
"applyHeraldEffect().");
}
$this->transcript->addApplyTranscript($apply_xscript);
}
if (!$this->transcript->getDryRun()) {
$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;
}
$rule = idx($rules, $rule_id);
if (!$rule) {
continue;
}
if ($rule->getRepetitionPolicy() == $first_policy) {
$applied_ids[] = $rule_id;
}
}
if ($applied_ids) {
$conn_w = id(new HeraldRule())->establishConnection('w');
$sql = array();
foreach ($applied_ids as $id) {
$sql[] = qsprintf(
$conn_w,
'(%s, %d)',
$object->getPHID(),
$id);
}
queryfx(
$conn_w,
'INSERT IGNORE INTO %T (phid, ruleID) VALUES %Q',
HeraldRule::TABLE_RULE_APPLIED,
implode(', ', $sql));
}
}
}
public function getTranscript() {
$this->transcript->save();
return $this->transcript;
}
protected function doesRuleMatch(
HeraldRule $rule,
HeraldObjectAdapter $object) {
$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) {
$reason = "Rule could not be processed, it was created with a newer ".
"version of Herald.";
$result = false;
} else if (!$conditions) {
$reason = "Rule failed automatically because it has no conditions.";
$result = false;
-/* TOOD: Restore this in some form?
- } else if (!is_fb_employee($rule->getAuthorPHID())) {
- $reason = "Rule failed automatically because its owner is not an ".
- "active employee.";
+ } else if ($rule->hasInvalidOwner()) {
+ $reason = "Rule failed automatically because its owner is invalid ".
+ "or disabled.";
$result = false;
-*/
} 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());
$rule_transcript->setRuleOwner($rule->getAuthorPHID());
$this->transcript->addRuleTranscript($rule_transcript);
return $result;
}
protected function doesConditionMatch(
HeraldRule $rule,
HeraldCondition $condition,
HeraldObjectAdapter $object) {
$object_value = $this->getConditionObjectValue($condition, $object);
$test_value = $condition->getValue();
$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);
$result = null;
switch ($cond) {
case HeraldConditionConfig::CONDITION_CONTAINS:
// "Contains" can take an array of strings, as in "Any changed
// filename" for diffs.
foreach ((array)$object_value as $value) {
$result = (stripos($value, $test_value) !== false);
if ($result) {
break;
}
}
break;
case HeraldConditionConfig::CONDITION_NOT_CONTAINS:
$result = (stripos($object_value, $test_value) === false);
break;
case HeraldConditionConfig::CONDITION_IS:
$result = ($object_value == $test_value);
break;
case HeraldConditionConfig::CONDITION_IS_NOT:
$result = ($object_value != $test_value);
break;
case HeraldConditionConfig::CONDITION_IS_ME:
$result = ($object_value == $rule->getAuthorPHID());
break;
case HeraldConditionConfig::CONDITION_IS_NOT_ME:
$result = ($object_value != $rule->getAuthorPHID());
break;
case HeraldConditionConfig::CONDITION_IS_ANY:
$test_value = array_flip($test_value);
$result = isset($test_value[$object_value]);
break;
case HeraldConditionConfig::CONDITION_IS_NOT_ANY:
$test_value = array_flip($test_value);
$result = !isset($test_value[$object_value]);
break;
case HeraldConditionConfig::CONDITION_INCLUDE_ALL:
if (!is_array($object_value)) {
$transcript->setNote('Object produced bad value!');
$result = false;
} else {
$have = array_select_keys(array_flip($object_value),
$test_value);
$result = (count($have) == count($test_value));
}
break;
case HeraldConditionConfig::CONDITION_INCLUDE_ANY:
$result = (bool)array_select_keys(array_flip($object_value),
$test_value);
break;
case HeraldConditionConfig::CONDITION_INCLUDE_NONE:
$result = !array_select_keys(array_flip($object_value),
$test_value);
break;
case HeraldConditionConfig::CONDITION_EXISTS:
$result = (bool)$object_value;
break;
case HeraldConditionConfig::CONDITION_NOT_EXISTS:
$result = !$object_value;
break;
case HeraldConditionConfig::CONDITION_REGEXP:
foreach ((array)$object_value as $value) {
$result = @preg_match($test_value, $value);
if ($result === false) {
$transcript->setNote(
"Regular expression is not valid!");
break;
}
if ($result) {
break;
}
}
$result = (bool)$result;
break;
case HeraldConditionConfig::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($test_value, true);
if (!is_array($regexp_pair)) {
$result = false;
$transcript->setNote("Regular expression pair is not valid JSON!");
break;
}
if (count($regexp_pair) != 2) {
$result = false;
$transcript->setNote("Regular expression pair is not a pair!");
break;
}
$key_regexp = array_shift($regexp_pair);
$value_regexp = array_shift($regexp_pair);
foreach ((array)$object_value as $key => $value) {
$key_matches = @preg_match($key_regexp, $key);
if ($key_matches === false) {
$result = false;
$transcript->setNote("First regular expression is invalid!");
break 2;
}
if ($key_matches) {
$value_matches = @preg_match($value_regexp, $value);
if ($value_matches === false) {
$result = false;
$transcript->setNote("Second regular expression is invalid!");
break 2;
}
if ($value_matches) {
$result = true;
break 2;
}
}
}
$result = false;
break;
case HeraldConditionConfig::CONDITION_RULE:
case HeraldConditionConfig::CONDITION_NOT_RULE:
$rule = idx($this->rules, $test_value);
if (!$rule) {
$transcript->setNote(
"Condition references a rule which does not exist!");
$result = false;
} else {
$is_not = ($cond == HeraldConditionConfig::CONDITION_NOT_RULE);
$result = $this->doesRuleMatch($rule, $object);
if ($is_not) {
$result = !$result;
}
}
break;
default:
throw new HeraldInvalidConditionException(
"Unknown condition '{$cond}'.");
}
$transcript->setResult($result);
$this->transcript->addConditionTranscript($transcript);
return $result;
}
protected function getConditionObjectValue(
HeraldCondition $condition,
HeraldObjectAdapter $object) {
$field = $condition->getFieldName();
return $this->getObjectFieldValue($field);
}
public function getObjectFieldValue($field) {
if (isset($this->fieldCache[$field])) {
return $this->fieldCache[$field];
}
$result = null;
switch ($field) {
case HeraldFieldConfig::FIELD_RULE:
$result = null;
break;
case HeraldFieldConfig::FIELD_TITLE:
case HeraldFieldConfig::FIELD_BODY:
case HeraldFieldConfig::FIELD_DIFF_FILE:
case HeraldFieldConfig::FIELD_DIFF_CONTENT:
// TODO: Type should be string.
$result = $this->object->getHeraldField($field);
break;
case HeraldFieldConfig::FIELD_AUTHOR:
case HeraldFieldConfig::FIELD_REPOSITORY:
case HeraldFieldConfig::FIELD_MERGE_REQUESTER:
// TODO: Type should be PHID.
$result = $this->object->getHeraldField($field);
break;
case HeraldFieldConfig::FIELD_TAGS:
case HeraldFieldConfig::FIELD_REVIEWER:
case HeraldFieldConfig::FIELD_REVIEWERS:
case HeraldFieldConfig::FIELD_CC:
case HeraldFieldConfig::FIELD_DIFFERENTIAL_REVIEWERS:
case HeraldFieldConfig::FIELD_DIFFERENTIAL_CCS:
// TODO: Type should be list.
$result = $this->object->getHeraldField($field);
break;
case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE:
case HeraldFieldConfig::FIELD_AFFECTED_PACKAGE_OWNER:
case HeraldFieldConfig::FIELD_NEED_AUDIT_FOR_PACKAGE:
$result = $this->object->getHeraldField($field);
if (!is_array($result)) {
throw new HeraldInvalidFieldException(
"Value of field type {$field} is not an array!");
}
break;
case HeraldFieldConfig::FIELD_DIFFERENTIAL_REVISION:
// TODO: Type should be boolean I guess.
$result = $this->object->getHeraldField($field);
break;
default:
throw new HeraldInvalidConditionException(
"Unknown field type '{$field}'!");
}
$this->fieldCache[$field] = $result;
return $result;
}
protected function getRuleEffects(
HeraldRule $rule,
HeraldObjectAdapter $object) {
$effects = array();
foreach ($rule->getActions() as $action) {
$effect = new HeraldEffect();
$effect->setObjectPHID($object->getPHID());
$effect->setAction($action->getAction());
$effect->setTarget($action->getTarget());
$effect->setRuleID($rule->getID());
$name = $rule->getName();
$id = $rule->getID();
$effect->setReason(
'Conditions were met for Herald rule "'.$name.'" (#'.$id.').');
$effects[] = $effect;
}
return $effects;
}
}
diff --git a/src/applications/herald/storage/rule/HeraldRule.php b/src/applications/herald/storage/rule/HeraldRule.php
index 97e564f1f1..6d507d0ea2 100644
--- a/src/applications/herald/storage/rule/HeraldRule.php
+++ b/src/applications/herald/storage/rule/HeraldRule.php
@@ -1,210 +1,241 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class HeraldRule extends HeraldDAO {
const TABLE_RULE_APPLIED = 'herald_ruleapplied';
protected $name;
protected $authorPHID;
protected $contentType;
protected $mustMatchAll;
protected $repetitionPolicy;
protected $ruleType;
protected $configVersion = 8;
private $ruleApplied = array(); // phids for which this rule has been applied
+ private $invalidOwner = false;
public static function loadAllByContentTypeWithFullData(
$content_type,
$object_phid) {
$rules = id(new HeraldRule())->loadAllWhere(
'contentType = %s',
$content_type);
if (!$rules) {
return array();
}
+ self::flagDisabledUserRules($rules);
+
$rule_ids = mpull($rules, 'getID');
$conditions = id(new HeraldCondition())->loadAllWhere(
'ruleID in (%Ld)',
$rule_ids);
$actions = id(new HeraldAction())->loadAllWhere(
'ruleID in (%Ld)',
$rule_ids);
$applied = queryfx_all(
id(new HeraldRule())->establishConnection('r'),
'SELECT * FROM %T WHERE phid = %s',
self::TABLE_RULE_APPLIED,
$object_phid);
$applied = ipull($applied, null, 'ruleID');
$conditions = mgroup($conditions, 'getRuleID');
$actions = mgroup($actions, 'getRuleID');
$applied = igroup($applied, 'ruleID');
foreach ($rules as $rule) {
$rule->setRuleApplied($object_phid, isset($applied[$rule->getID()]));
$rule->attachConditions(idx($conditions, $rule->getID(), array()));
$rule->attachActions(idx($actions, $rule->getID(), array()));
}
return $rules;
}
+ private static function flagDisabledUserRules(array $rules) {
+
+ $users = array();
+ foreach ($rules as $rule) {
+ if ($rule->getRuleType() != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
+ continue;
+ }
+ $users[$rule->getAuthorPHID()] = true;
+ }
+
+ $handles = id(new PhabricatorObjectHandleData(array_keys($users)))
+ ->loadHandles();
+
+ foreach ($rules as $key => $rule) {
+ if ($rule->getRuleType() != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
+ continue;
+ }
+ $handle = $handles[$rule->getAuthorPHID()];
+ if (!$handle->isComplete() || $handle->isDisabled()) {
+ $rule->invalidOwner = true;
+ }
+ }
+ }
+
public function getRuleApplied($phid) {
if (idx($this->ruleApplied, $phid) === null) {
throw new Exception("Call setRuleApplied() before getRuleApplied()!");
}
return $this->ruleApplied[$phid];
}
public function setRuleApplied($phid, $applied) {
$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) {
$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.
$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 attachEdits(array $edits) {
$this->edits = $edits;
return $this;
}
public function getEdits() {
if ($this->edits === null) {
throw new Exception("Attach edits before accessing them!");
}
return $this->edits;
}
public function saveEdit($editor_phid) {
$edit = new HeraldRuleEdit();
$edit->setRuleID($this->getID());
$edit->setEditorPHID($editor_phid);
$edit->save();
}
public function saveConditions(array $conditions) {
return $this->saveChildren(
id(new HeraldCondition())->getTableName(),
$conditions);
}
public function saveActions(array $actions) {
return $this->saveChildren(
id(new HeraldAction())->getTableName(),
$actions);
}
protected function saveChildren($table_name, array $children) {
if (!$this->getID()) {
throw new Exception("Save rule before saving children.");
}
foreach ($children as $child) {
$child->setRuleID($this->getID());
}
// TODO:
// $this->openTransaction();
queryfx(
$this->establishConnection('w'),
'DELETE FROM %T WHERE ruleID = %d',
$table_name,
$this->getID());
foreach ($children as $child) {
$child->save();
}
// $this->saveTransaction();
}
public function delete() {
// TODO:
// $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());
parent::delete();
// $this->saveTransaction();
}
+ public function hasInvalidOwner() {
+ return $this->invalidOwner;
+ }
+
}
diff --git a/src/applications/herald/storage/rule/__init__.php b/src/applications/herald/storage/rule/__init__.php
index 7078b9f324..d9227c43d1 100644
--- a/src/applications/herald/storage/rule/__init__.php
+++ b/src/applications/herald/storage/rule/__init__.php
@@ -1,18 +1,20 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
+phutil_require_module('phabricator', 'applications/herald/config/ruletype');
phutil_require_module('phabricator', 'applications/herald/storage/action');
phutil_require_module('phabricator', 'applications/herald/storage/base');
phutil_require_module('phabricator', 'applications/herald/storage/condition');
phutil_require_module('phabricator', 'applications/herald/storage/edithistory');
+phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phutil', 'utils');
phutil_require_source('HeraldRule.php');

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 1, 11:27 PM (1 d, 11 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
431362
Default Alt Text
(25 KB)

Event Timeline