示例#1
0
 public function doesRuleMatch(HeraldRule $rule, HeraldAdapter $object)
 {
     $phid = $rule->getPHID();
     if (isset($this->results[$phid])) {
         // If we've already evaluated this rule because another rule depends
         // on it, we don't need to reevaluate it.
         return $this->results[$phid];
     }
     if (isset($this->stack[$phid])) {
         // 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_phid => $ignored) {
             $this->results[$rule_phid] = false;
         }
         throw new HeraldRecursiveConditionsException();
     }
     $this->stack[$phid] = true;
     $all = $rule->getMustMatchAll();
     $conditions = $rule->getConditions();
     $result = null;
     $local_version = id(new HeraldRule())->getConfigVersion();
     if ($rule->getConfigVersion() > $local_version) {
         $reason = pht('Rule could not be processed, it was created with a newer version ' . 'of Herald.');
         $result = false;
     } else {
         if (!$conditions) {
             $reason = pht('Rule failed automatically because it has no conditions.');
             $result = false;
         } else {
             if (!$rule->hasValidAuthor()) {
                 $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.');
                     $result = false;
                 } 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;
                     } 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;
 }
 private function setupEditorBehavior(HeraldRule $rule, array $handles, HeraldAdapter $adapter)
 {
     $serial_conditions = array(array('default', 'default', ''));
     if ($rule->getConditions()) {
         $serial_conditions = array();
         foreach ($rule->getConditions() as $condition) {
             $value = $adapter->getEditorValueForCondition($this->getViewer(), $condition, $handles);
             $serial_conditions[] = array($condition->getFieldName(), $condition->getFieldCondition(), $value);
         }
     }
     $serial_actions = array(array('default', ''));
     if ($rule->getActions()) {
         $serial_actions = array();
         foreach ($rule->getActions() as $action) {
             switch ($action->getAction()) {
                 case HeraldAdapter::ACTION_FLAG:
                 case HeraldAdapter::ACTION_BLOCK:
                     $current_value = $action->getTarget();
                     break;
                 default:
                     if (is_array($action->getTarget())) {
                         $target_map = array();
                         foreach ((array) $action->getTarget() as $fbid) {
                             $target_map[$fbid] = $handles[$fbid]->getName();
                         }
                         $current_value = $target_map;
                     } else {
                         $current_value = $action->getTarget();
                     }
                     break;
             }
             $serial_actions[] = array($action->getAction(), $current_value);
         }
     }
     $all_rules = $this->loadRulesThisRuleMayDependUpon($rule);
     $all_rules = mpull($all_rules, 'getName', 'getPHID');
     asort($all_rules);
     $all_fields = $adapter->getFieldNameMap();
     $all_conditions = $adapter->getConditionNameMap();
     $all_actions = $adapter->getActionNameMap($rule->getRuleType());
     $fields = $adapter->getFields();
     $field_map = array_select_keys($all_fields, $fields);
     // Populate any fields which exist in the rule but which we don't know the
     // names of, so that saving a rule without touching anything doesn't change
     // it.
     foreach ($rule->getConditions() as $condition) {
         $field_name = $condition->getFieldName();
         if (empty($field_map[$field_name])) {
             $field_map[$field_name] = pht('<Unknown Field "%s">', $field_name);
         }
     }
     $actions = $adapter->getActions($rule->getRuleType());
     $action_map = array_select_keys($all_actions, $actions);
     // Populate any actions which exist in the rule but which we don't know the
     // names of, so that saving a rule without touching anything doesn't change
     // it.
     foreach ($rule->getActions() as $action) {
         $action_name = $action->getAction();
         if (empty($action_map[$action_name])) {
             $action_map[$action_name] = pht('<Unknown Action "%s">', $action_name);
         }
     }
     $config_info = array();
     $config_info['fields'] = $field_map;
     $config_info['conditions'] = $all_conditions;
     $config_info['actions'] = $action_map;
     foreach ($config_info['fields'] as $field => $name) {
         try {
             $field_conditions = $adapter->getConditionsForField($field);
         } catch (Exception $ex) {
             $field_conditions = array(HeraldAdapter::CONDITION_UNCONDITIONALLY);
         }
         $config_info['conditionMap'][$field] = $field_conditions;
     }
     foreach ($config_info['fields'] as $field => $fname) {
         foreach ($config_info['conditionMap'][$field] as $condition) {
             $value_type = $adapter->getValueTypeForFieldAndCondition($field, $condition);
             $config_info['values'][$field][$condition] = $value_type;
         }
     }
     $config_info['rule_type'] = $rule->getRuleType();
     foreach ($config_info['actions'] as $action => $name) {
         try {
             $action_value = $adapter->getValueTypeForAction($action, $rule->getRuleType());
         } catch (Exception $ex) {
             $action_value = array(HeraldAdapter::VALUE_NONE);
         }
         $config_info['targets'][$action] = $action_value;
     }
     $changeflag_options = PhabricatorRepositoryPushLog::getHeraldChangeFlagConditionOptions();
     Javelin::initBehavior('herald-rule-editor', array('root' => 'herald-rule-edit-form', 'conditions' => (object) $serial_conditions, 'actions' => (object) $serial_actions, 'select' => array(HeraldAdapter::VALUE_CONTENT_SOURCE => array('options' => PhabricatorContentSource::getSourceNameMap(), 'default' => PhabricatorContentSource::SOURCE_WEB), HeraldAdapter::VALUE_FLAG_COLOR => array('options' => PhabricatorFlagColor::getColorNameMap(), 'default' => PhabricatorFlagColor::COLOR_BLUE), HeraldPreCommitRefAdapter::VALUE_REF_TYPE => array('options' => array(PhabricatorRepositoryPushLog::REFTYPE_BRANCH => pht('branch (git/hg)'), PhabricatorRepositoryPushLog::REFTYPE_TAG => pht('tag (git)'), PhabricatorRepositoryPushLog::REFTYPE_BOOKMARK => pht('bookmark (hg)')), 'default' => PhabricatorRepositoryPushLog::REFTYPE_BRANCH), HeraldPreCommitRefAdapter::VALUE_REF_CHANGE => array('options' => $changeflag_options, 'default' => PhabricatorRepositoryPushLog::CHANGEFLAG_ADD)), 'template' => $this->buildTokenizerTemplates($handles) + array('rules' => $all_rules), 'author' => array($rule->getAuthorPHID() => $handles[$rule->getAuthorPHID()]->getName()), 'info' => $config_info));
 }
示例#3
0
 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;
         } 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;
 }
示例#4
0
 /**
  * Given a @{class:HeraldRule}, this function extracts all the phids that
  * we'll want to load as handles later.
  *
  * This function performs a somewhat hacky approach to figuring out what
  * is and is not a phid - try to get the phid type and if the type is
  * *not* unknown assume its a valid phid.
  *
  * Don't try this at home. Use more strongly typed data at home.
  *
  * Think of the children.
  */
 public static function getHandlePHIDs(HeraldRule $rule)
 {
     $phids = array($rule->getAuthorPHID());
     foreach ($rule->getConditions() as $condition) {
         $value = $condition->getValue();
         if (!is_array($value)) {
             $value = array($value);
         }
         foreach ($value as $val) {
             if (phid_get_type($val) != PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
                 $phids[] = $val;
             }
         }
     }
     foreach ($rule->getActions() as $action) {
         $target = $action->getTarget();
         if (!is_array($target)) {
             $target = array($target);
         }
         foreach ($target as $val) {
             if (phid_get_type($val) != PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
                 $phids[] = $val;
             }
         }
     }
     if ($rule->isObjectRule()) {
         $phids[] = $rule->getTriggerObjectPHID();
     }
     return $phids;
 }
 private function setupEditorBehavior(HeraldRule $rule, array $handles, HeraldAdapter $adapter)
 {
     $serial_conditions = array(array('default', 'default', ''));
     if ($rule->getConditions()) {
         $serial_conditions = array();
         foreach ($rule->getConditions() as $condition) {
             $value = $adapter->getEditorValueForCondition($this->getViewer(), $condition);
             $serial_conditions[] = array($condition->getFieldName(), $condition->getFieldCondition(), $value);
         }
     }
     $serial_actions = array(array('default', ''));
     if ($rule->getActions()) {
         $serial_actions = array();
         foreach ($rule->getActions() as $action) {
             $value = $adapter->getEditorValueForAction($this->getViewer(), $action);
             $serial_actions[] = array($action->getAction(), $value);
         }
     }
     $all_rules = $this->loadRulesThisRuleMayDependUpon($rule);
     $all_rules = mpull($all_rules, 'getName', 'getPHID');
     asort($all_rules);
     $all_fields = $adapter->getFieldNameMap();
     $all_conditions = $adapter->getConditionNameMap();
     $all_actions = $adapter->getActionNameMap($rule->getRuleType());
     $fields = $adapter->getFields();
     $field_map = array_select_keys($all_fields, $fields);
     // Populate any fields which exist in the rule but which we don't know the
     // names of, so that saving a rule without touching anything doesn't change
     // it.
     foreach ($rule->getConditions() as $condition) {
         $field_name = $condition->getFieldName();
         if (empty($field_map[$field_name])) {
             $field_map[$field_name] = pht('<Unknown Field "%s">', $field_name);
         }
     }
     $actions = $adapter->getActions($rule->getRuleType());
     $action_map = array_select_keys($all_actions, $actions);
     // Populate any actions which exist in the rule but which we don't know the
     // names of, so that saving a rule without touching anything doesn't change
     // it.
     foreach ($rule->getActions() as $action) {
         $action_name = $action->getAction();
         if (empty($action_map[$action_name])) {
             $action_map[$action_name] = pht('<Unknown Action "%s">', $action_name);
         }
     }
     $config_info = array();
     $config_info['fields'] = $this->getFieldGroups($adapter, $field_map);
     $config_info['conditions'] = $all_conditions;
     $config_info['actions'] = $this->getActionGroups($adapter, $action_map);
     $config_info['valueMap'] = array();
     foreach ($field_map as $field => $name) {
         try {
             $field_conditions = $adapter->getConditionsForField($field);
         } catch (Exception $ex) {
             $field_conditions = array(HeraldAdapter::CONDITION_UNCONDITIONALLY);
         }
         $config_info['conditionMap'][$field] = $field_conditions;
     }
     foreach ($field_map as $field => $fname) {
         foreach ($config_info['conditionMap'][$field] as $condition) {
             $value_key = $adapter->getValueTypeForFieldAndCondition($field, $condition);
             if ($value_key instanceof HeraldFieldValue) {
                 $value_key->setViewer($this->getViewer());
                 $spec = $value_key->getControlSpecificationDictionary();
                 $value_key = $value_key->getFieldValueKey();
                 $config_info['valueMap'][$value_key] = $spec;
             }
             $config_info['values'][$field][$condition] = $value_key;
         }
     }
     $config_info['rule_type'] = $rule->getRuleType();
     foreach ($action_map as $action => $name) {
         try {
             $value_key = $adapter->getValueTypeForAction($action, $rule->getRuleType());
         } catch (Exception $ex) {
             $value_key = new HeraldEmptyFieldValue();
         }
         if ($value_key instanceof HeraldFieldValue) {
             $value_key->setViewer($this->getViewer());
             $spec = $value_key->getControlSpecificationDictionary();
             $value_key = $value_key->getFieldValueKey();
             $config_info['valueMap'][$value_key] = $spec;
         }
         $config_info['targets'][$action] = $value_key;
     }
     Javelin::initBehavior('herald-rule-editor', array('root' => 'herald-rule-edit-form', 'conditions' => (object) $serial_conditions, 'actions' => (object) $serial_actions, 'template' => $this->buildTokenizerTemplates() + array('rules' => $all_rules), 'info' => $config_info));
 }
 public function serializeRule(HeraldRule $rule)
 {
     return $this->serializeRuleComponents((bool) $rule->getMustMatchAll(), $rule->getConditions(), $rule->getActions(), HeraldRepetitionPolicyConfig::toString($rule->getRepetitionPolicy()));
 }
 private function setupEditorBehavior(HeraldRule $rule, array $handles, HeraldAdapter $adapter)
 {
     $serial_conditions = array(array('default', 'default', ''));
     if ($rule->getConditions()) {
         $serial_conditions = array();
         foreach ($rule->getConditions() as $condition) {
             $value = $adapter->getEditorValueForCondition($this->getViewer(), $condition, $handles);
             $serial_conditions[] = array($condition->getFieldName(), $condition->getFieldCondition(), $value);
         }
     }
     $serial_actions = array(array('default', ''));
     if ($rule->getActions()) {
         $serial_actions = array();
         foreach ($rule->getActions() as $action) {
             switch ($action->getAction()) {
                 case HeraldAdapter::ACTION_FLAG:
                 case HeraldAdapter::ACTION_BLOCK:
                     $current_value = $action->getTarget();
                     break;
                 default:
                     if (is_array($action->getTarget())) {
                         $target_map = array();
                         foreach ((array) $action->getTarget() as $fbid) {
                             $target_map[$fbid] = $handles[$fbid]->getName();
                         }
                         $current_value = $target_map;
                     } else {
                         $current_value = $action->getTarget();
                     }
                     break;
             }
             $serial_actions[] = array($action->getAction(), $current_value);
         }
     }
     $all_rules = $this->loadRulesThisRuleMayDependUpon($rule);
     $all_rules = mpull($all_rules, 'getName', 'getPHID');
     asort($all_rules);
     $all_fields = $adapter->getFieldNameMap();
     $all_conditions = $adapter->getConditionNameMap();
     $all_actions = $adapter->getActionNameMap($rule->getRuleType());
     $fields = $adapter->getFields();
     $field_map = array_select_keys($all_fields, $fields);
     // Populate any fields which exist in the rule but which we don't know the
     // names of, so that saving a rule without touching anything doesn't change
     // it.
     foreach ($rule->getConditions() as $condition) {
         $field_name = $condition->getFieldName();
         if (empty($field_map[$field_name])) {
             $field_map[$field_name] = pht('<Unknown Field "%s">', $field_name);
         }
     }
     $actions = $adapter->getActions($rule->getRuleType());
     $action_map = array_select_keys($all_actions, $actions);
     // Populate any actions which exist in the rule but which we don't know the
     // names of, so that saving a rule without touching anything doesn't change
     // it.
     foreach ($rule->getActions() as $action) {
         $action_name = $action->getAction();
         if (empty($action_map[$action_name])) {
             $action_map[$action_name] = pht('<Unknown Action "%s">', $action_name);
         }
     }
     $group_map = array();
     foreach ($field_map as $field_key => $field_name) {
         $group_key = $adapter->getFieldGroupKey($field_key);
         $group_map[$group_key][$field_key] = $field_name;
     }
     $field_groups = HeraldFieldGroup::getAllFieldGroups();
     $groups = array();
     foreach ($group_map as $group_key => $options) {
         asort($options);
         $field_group = idx($field_groups, $group_key);
         if ($field_group) {
             $group_label = $field_group->getGroupLabel();
             $group_order = $field_group->getSortKey();
         } else {
             $group_label = nonempty($group_key, pht('Other'));
             $group_order = 'Z';
         }
         $groups[] = array('label' => $group_label, 'options' => $options, 'order' => $group_order);
     }
     $groups = array_values(isort($groups, 'order'));
     $config_info = array();
     $config_info['fields'] = $groups;
     $config_info['conditions'] = $all_conditions;
     $config_info['actions'] = $action_map;
     $config_info['valueMap'] = array();
     foreach ($field_map as $field => $name) {
         try {
             $field_conditions = $adapter->getConditionsForField($field);
         } catch (Exception $ex) {
             $field_conditions = array(HeraldAdapter::CONDITION_UNCONDITIONALLY);
         }
         $config_info['conditionMap'][$field] = $field_conditions;
     }
     foreach ($field_map as $field => $fname) {
         foreach ($config_info['conditionMap'][$field] as $condition) {
             $value_key = $adapter->getValueTypeForFieldAndCondition($field, $condition);
             if ($value_key instanceof HeraldFieldValue) {
                 $value_key->setViewer($this->getViewer());
                 $spec = $value_key->getControlSpecificationDictionary();
                 $value_key = $value_key->getFieldValueKey();
                 $config_info['valueMap'][$value_key] = $spec;
             }
             $config_info['values'][$field][$condition] = $value_key;
         }
     }
     $config_info['rule_type'] = $rule->getRuleType();
     foreach ($config_info['actions'] as $action => $name) {
         try {
             $value_key = $adapter->getValueTypeForAction($action, $rule->getRuleType());
         } catch (Exception $ex) {
             $value_key = new HeraldEmptyFieldValue();
         }
         if ($value_key instanceof HeraldFieldValue) {
             $value_key->setViewer($this->getViewer());
             $spec = $value_key->getControlSpecificationDictionary();
             $value_key = $value_key->getFieldValueKey();
             $config_info['valueMap'][$value_key] = $spec;
         }
         $config_info['targets'][$action] = $value_key;
     }
     Javelin::initBehavior('herald-rule-editor', array('root' => 'herald-rule-edit-form', 'conditions' => (object) $serial_conditions, 'actions' => (object) $serial_actions, 'template' => $this->buildTokenizerTemplates() + array('rules' => $all_rules), 'info' => $config_info));
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $content_type_map = HeraldContentTypeConfig::getContentTypeMap();
     if ($this->id) {
         $rule = id(new HeraldRule())->load($this->id);
         if (!$rule) {
             return new Aphront404Response();
         }
         if ($rule->getAuthorPHID() != $user->getPHID()) {
             throw new Exception("You don't own this rule and can't edit it.");
         }
     } else {
         $rule = new HeraldRule();
         $rule->setAuthorPHID($user->getPHID());
         $rule->setMustMatchAll(true);
         $type = $request->getStr('type');
         if (!isset($content_type_map[$type])) {
             $type = HeraldContentTypeConfig::CONTENT_TYPE_DIFFERENTIAL;
         }
         $rule->setContentType($type);
     }
     $local_version = id(new HeraldRule())->getConfigVersion();
     if ($rule->getConfigVersion() > $local_version) {
         throw new Exception("This rule was created with a newer version of Herald. You can not " . "view or edit it in this older version. Try dev or wait for a push.");
     }
     // Upgrade rule version to our version, since we might add newly-defined
     // conditions, etc.
     $rule->setConfigVersion($local_version);
     $rule_conditions = $rule->loadConditions();
     $rule_actions = $rule->loadActions();
     $rule->attachConditions($rule_conditions);
     $rule->attachActions($rule_actions);
     $e_name = true;
     $errors = array();
     if ($request->isFormPost() && $request->getStr('save')) {
         $rule->setName($request->getStr('name'));
         $rule->setMustMatchAll($request->getStr('must_match') == 'all');
         $repetition_policy_param = $request->getStr('repetition_policy');
         $rule->setRepetitionPolicy(HeraldRepetitionPolicyConfig::toInt($repetition_policy_param));
         if (!strlen($rule->getName())) {
             $e_name = "Required";
             $errors[] = "Rule must have a name.";
         }
         $data = json_decode($request->getStr('rule'), true);
         if (!is_array($data) || !$data['conditions'] || !$data['actions']) {
             throw new Exception("Failed to decode rule data.");
         }
         $conditions = array();
         foreach ($data['conditions'] as $condition) {
             if ($condition === null) {
                 // We manage this as a sparse array on the client, so may receive
                 // NULL if conditions have been removed.
                 continue;
             }
             $obj = new HeraldCondition();
             $obj->setFieldName($condition[0]);
             $obj->setFieldCondition($condition[1]);
             if (is_array($condition[2])) {
                 $obj->setValue(array_keys($condition[2]));
             } else {
                 $obj->setValue($condition[2]);
             }
             $cond_type = $obj->getFieldCondition();
             if ($cond_type == HeraldConditionConfig::CONDITION_REGEXP) {
                 if (@preg_match($obj->getValue(), '') === false) {
                     $errors[] = 'The regular expression "' . $obj->getValue() . '" is not valid. ' . 'Regular expressions must have enclosing characters (e.g. ' . '"@/path/to/file@", not "/path/to/file") and be syntactically ' . 'correct.';
                 }
             }
             if ($cond_type == HeraldConditionConfig::CONDITION_REGEXP_PAIR) {
                 $json = json_decode($obj->getValue(), true);
                 if (!is_array($json)) {
                     $errors[] = 'The regular expression pair "' . $obj->getValue() . '" is not ' . 'valid JSON. Enter a valid JSON array with two elements.';
                 } else {
                     if (count($json) != 2) {
                         $errors[] = 'The regular expression pair "' . $obj->getValue() . '" must have ' . 'exactly two elements.';
                     } else {
                         $key_regexp = array_shift($json);
                         $val_regexp = array_shift($json);
                         if (@preg_match($key_regexp, '') === false) {
                             $errors[] = 'The first regexp, "' . $key_regexp . '" in the regexp pair ' . 'is not a valid regexp.';
                         }
                         if (@preg_match($val_regexp, '') === false) {
                             $errors[] = 'The second regexp, "' . $val_regexp . '" in the regexp pair ' . 'is not a valid regexp.';
                         }
                     }
                 }
             }
             $conditions[] = $obj;
         }
         $actions = array();
         foreach ($data['actions'] as $action) {
             if ($action === null) {
                 // Sparse on the client; removals can give us NULLs.
                 continue;
             }
             $obj = new HeraldAction();
             $obj->setAction($action[0]);
             if (!isset($action[1])) {
                 // Legitimate for any action which doesn't need a target, like
                 // "Do nothing".
                 $action[1] = null;
             }
             if (is_array($action[1])) {
                 $obj->setTarget(array_keys($action[1]));
             } else {
                 $obj->setTarget($action[1]);
             }
             $actions[] = $obj;
         }
         $rule->attachConditions($conditions);
         $rule->attachActions($actions);
         if (!$errors) {
             try {
                 // TODO
                 //          $rule->openTransaction();
                 $rule->save();
                 $rule->saveConditions($conditions);
                 $rule->saveActions($actions);
                 //          $rule->saveTransaction();
                 $uri = '/herald/view/' . $rule->getContentType() . '/';
                 return id(new AphrontRedirectResponse())->setURI($uri);
             } catch (AphrontQueryDuplicateKeyException $ex) {
                 $e_name = "Not Unique";
                 $errors[] = "Rule name is not unique. Choose a unique name.";
             }
         }
     }
     $phids = array();
     $phids[] = $rule->getAuthorPHID();
     foreach ($rule->getActions() as $action) {
         if (!is_array($action->getTarget())) {
             continue;
         }
         foreach ($action->getTarget() as $target) {
             $target = (array) $target;
             foreach ($target as $phid) {
                 $phids[] = $phid;
             }
         }
     }
     foreach ($rule->getConditions() as $condition) {
         $value = $condition->getValue();
         if (is_array($value)) {
             foreach ($value as $phid) {
                 $phids[] = $phid;
             }
         }
     }
     $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
     if ($errors) {
         $error_view = new AphrontErrorView();
         $error_view->setTitle('Form Errors');
         $error_view->setErrors($errors);
     } else {
         $error_view = null;
     }
     $options = array('all' => 'all of', 'any' => 'any of');
     $selected = $rule->getMustMatchAll() ? 'all' : 'any';
     $must_match = array();
     foreach ($options as $key => $option) {
         $must_match[] = phutil_render_tag('option', array('selected' => $selected == $key ? 'selected' : null, 'value' => $key), phutil_escape_html($option));
     }
     $must_match = '<select name="must_match">' . implode("\n", $must_match) . '</select>';
     if ($rule->getID()) {
         $action = '/herald/rule/' . $rule->getID() . '/';
     } else {
         $action = '/herald/rule/' . $rule->getID() . '/';
     }
     // Make the selector for choosing how often this rule should be repeated
     $repetition_selector = "";
     $repetition_policy = HeraldRepetitionPolicyConfig::toString($rule->getRepetitionPolicy());
     $repetition_options = HeraldRepetitionPolicyConfig::getMapForContentType($rule->getContentType());
     if (empty($repetition_options)) {
         // default option is 'every time'
         $repetition_selector = idx(HeraldRepetitionPolicyConfig::getMap(), HeraldRepetitionPolicyConfig::EVERY);
     } else {
         if (count($repetition_options) == 1) {
             // if there's only 1 option, just pick it for the user
             $repetition_selector = reset($repetition_options);
         } else {
             // give the user all the options for this rule type
             $tags = array();
             foreach ($repetition_options as $name => $option) {
                 $tags[] = phutil_render_tag('option', array('selected' => $repetition_policy == $name ? 'selected' : null, 'value' => $name), phutil_escape_html($option));
             }
             $repetition_selector = '<select name="repetition_policy">' . implode("\n", $tags) . '</select>';
         }
     }
     require_celerity_resource('herald-css');
     $type_name = $content_type_map[$rule->getContentType()];
     $form = id(new AphrontFormView())->setUser($user)->setID('herald-rule-edit-form')->addHiddenInput('type', $rule->getContentType())->addHiddenInput('save', 1)->appendChild(javelin_render_tag('input', array('type' => 'hidden', 'name' => 'rule', 'sigil' => 'rule')))->appendChild(id(new AphrontFormTextControl())->setLabel('Rule Name')->setName('name')->setError($e_name)->setValue($rule->getName()))->appendChild(id(new AphrontFormStaticControl())->setLabel('Author')->setValue($handles[$rule->getAuthorPHID()]->getName()))->appendChild(id(new AphrontFormMarkupControl())->setValue("This rule triggers for <strong>{$type_name}</strong>."))->appendChild('<h1>Conditions</h1>' . '<div class="aphront-form-inset">' . '<div style="float: right;">' . javelin_render_tag('a', array('href' => '#', 'class' => 'button green', 'sigil' => 'create-condition', 'mustcapture' => true), 'Create New Condition') . '</div>' . '<p>When ' . $must_match . ' these conditions are met:</p>' . '<div style="clear: both;"></div>' . javelin_render_tag('table', array('sigil' => 'rule-conditions', 'class' => 'herald-condition-table'), '') . '</div>')->appendChild('<h1>Action</h1>' . '<div class="aphront-form-inset">' . '<div style="float: right;">' . javelin_render_tag('a', array('href' => '#', 'class' => 'button green', 'sigil' => 'create-action', 'mustcapture' => true), 'Create New Action') . '</div>' . '<p>' . 'Take these actions ' . $repetition_selector . ' this rule matches:' . '</p>' . '<div style="clear: both;"></div>' . javelin_render_tag('table', array('sigil' => 'rule-actions', 'class' => 'herald-action-table'), '') . '</div>')->appendChild(id(new AphrontFormSubmitControl())->setValue('Save Rule')->addCancelButton('/herald/view/' . $rule->getContentType() . '/'));
     $serial_conditions = array(array('default', 'default', ''));
     if ($rule->getConditions()) {
         $serial_conditions = array();
         foreach ($rule->getConditions() as $condition) {
             $value = $condition->getValue();
             if (is_array($value)) {
                 $value_map = array();
                 foreach ($value as $k => $fbid) {
                     $value_map[$fbid] = $handles[$fbid]->getName();
                 }
                 $value = $value_map;
             }
             $serial_conditions[] = array($condition->getFieldName(), $condition->getFieldCondition(), $value);
         }
     }
     $serial_actions = array(array('default', ''));
     if ($rule->getActions()) {
         $serial_actions = array();
         foreach ($rule->getActions() as $action) {
             $target_map = array();
             foreach ((array) $action->getTarget() as $fbid) {
                 $target_map[$fbid] = $handles[$fbid]->getName();
             }
             $serial_actions[] = array($action->getAction(), $target_map);
         }
     }
     $all_rules = id(new HeraldRule())->loadAllWhere('authorPHID = %d AND contentType = %s', $rule->getAuthorPHID(), $rule->getContentType());
     $all_rules = mpull($all_rules, 'getName', 'getID');
     asort($all_rules);
     unset($all_rules[$rule->getID()]);
     $config_info = array();
     $config_info['fields'] = HeraldFieldConfig::getFieldMapForContentType($rule->getContentType());
     $config_info['conditions'] = HeraldConditionConfig::getConditionMap();
     foreach ($config_info['fields'] as $field => $name) {
         $config_info['conditionMap'][$field] = array_keys(HeraldConditionConfig::getConditionMapForField($field));
     }
     foreach ($config_info['fields'] as $field => $fname) {
         foreach ($config_info['conditions'] as $condition => $cname) {
             $config_info['values'][$field][$condition] = HeraldValueTypeConfig::getValueTypeForFieldAndCondition($field, $condition);
         }
     }
     $config_info['actions'] = HeraldActionConfig::getActionMapForContentType($rule->getContentType());
     foreach ($config_info['actions'] as $action => $name) {
         $config_info['targets'][$action] = HeraldValueTypeConfig::getValueTypeForAction($action);
     }
     Javelin::initBehavior('herald-rule-editor', array('root' => 'herald-rule-edit-form', 'conditions' => (object) $serial_conditions, 'actions' => (object) $serial_actions, 'template' => $this->buildTokenizerTemplates() + array('rules' => $all_rules), 'info' => $config_info));
     $panel = new AphrontPanelView();
     $panel->setHeader('Edit Herald Rule');
     $panel->setWidth(AphrontPanelView::WIDTH_WIDE);
     $panel->appendChild($form);
     return $this->buildStandardPageResponse(array($error_view, $panel), array('title' => 'Edit Rule'));
 }