Index: resources/sql/patches/20131227.heraldobject.sql =================================================================== --- /dev/null +++ resources/sql/patches/20131227.heraldobject.sql @@ -0,0 +1,6 @@ +ALTER TABLE {$NAMESPACE}_herald.herald_rule + ADD triggerObjectPHID VARCHAR(64) COLLATE utf8_bin; + +ALTER TABLE {$NAMESPACE}_herald.herald_rule + ADD KEY `key_trigger` (triggerObjectPHID); + Index: src/applications/herald/config/HeraldRuleTypeConfig.php =================================================================== --- src/applications/herald/config/HeraldRuleTypeConfig.php +++ src/applications/herald/config/HeraldRuleTypeConfig.php @@ -3,12 +3,14 @@ final class HeraldRuleTypeConfig { const RULE_TYPE_GLOBAL = 'global'; + const RULE_TYPE_OBJECT = 'object'; const RULE_TYPE_PERSONAL = 'personal'; public static function getRuleTypeMap() { $map = array( - self::RULE_TYPE_GLOBAL => pht('Global'), - self::RULE_TYPE_PERSONAL => pht('Personal'), + self::RULE_TYPE_PERSONAL => pht('Personal'), + self::RULE_TYPE_OBJECT => pht('Object'), + self::RULE_TYPE_GLOBAL => pht('Global'), ); return $map; } Index: src/applications/herald/controller/HeraldDisableController.php =================================================================== --- src/applications/herald/controller/HeraldDisableController.php +++ src/applications/herald/controller/HeraldDisableController.php @@ -28,7 +28,7 @@ return new Aphront404Response(); } - if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) { + if ($rule->isGlobalRule()) { $this->requireApplicationCapability( HeraldCapabilityManageGlobalRules::CAPABILITY); } Index: src/applications/herald/controller/HeraldNewController.php =================================================================== --- src/applications/herald/controller/HeraldNewController.php +++ src/applications/herald/controller/HeraldNewController.php @@ -147,13 +147,18 @@ private function renderRuleTypeControl(array $rule_type_map, $e_rule) { $request = $this->getRequest(); - // Reorder array to put "personal" first. + // Reorder array to put less powerful rules first. $rule_type_map = array_select_keys( $rule_type_map, array( HeraldRuleTypeConfig::RULE_TYPE_PERSONAL, + HeraldRuleTypeConfig::RULE_TYPE_OBJECT, + HeraldRuleTypeConfig::RULE_TYPE_GLOBAL, )) + $rule_type_map; + // TODO: Enable this. + unset($rule_type_map[HeraldRuleTypeConfig::RULE_TYPE_OBJECT]); + list($can_global, $global_link) = $this->explainApplicationCapability( HeraldCapabilityManageGlobalRules::CAPABILITY, pht('You have permission to create and manage global rules.'), Index: src/applications/herald/controller/HeraldRuleController.php =================================================================== --- src/applications/herald/controller/HeraldRuleController.php +++ src/applications/herald/controller/HeraldRuleController.php @@ -49,7 +49,7 @@ $cancel_uri = $this->getApplicationURI(); } - if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) { + if ($rule->isGlobalRule()) { $this->requireApplicationCapability( HeraldCapabilityManageGlobalRules::CAPABILITY); } @@ -561,7 +561,17 @@ ->withContentTypes(array($rule->getContentType())) ->execute(); - if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { + if ($rule->isObjectRule()) { + // Object rules may depend on other rules for the same object. + $all_rules += id(new HeraldRuleQuery()) + ->setViewer($viewer) + ->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_OBJECT)) + ->withContentTypes(array($rule->getContentType())) + ->withTriggerObjectPHIDs(array($rule->getTriggerObjectPHID())) + ->execute(); + } + + if ($rule->isPersonalRule()) { // Personal rules may depend upon your other personal rules. $all_rules += id(new HeraldRuleQuery()) ->setViewer($viewer) Index: src/applications/herald/engine/HeraldEngine.php =================================================================== --- src/applications/herald/engine/HeraldEngine.php +++ src/applications/herald/engine/HeraldEngine.php @@ -256,6 +256,11 @@ "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); @@ -381,8 +386,8 @@ HeraldRule $rule, HeraldAdapter $adapter) { - // Authorship is irrelevant for global rules. - if ($rule->isGlobalRule()) { + // Authorship is irrelevant for global rules and object rules. + if ($rule->isGlobalRule() || $rule->isObjectRule()) { return true; } @@ -405,4 +410,25 @@ PhabricatorPolicyCapability::CAN_VIEW); } + 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; + } + } Index: src/applications/herald/query/HeraldRuleQuery.php =================================================================== --- src/applications/herald/query/HeraldRuleQuery.php +++ src/applications/herald/query/HeraldRuleQuery.php @@ -9,6 +9,7 @@ private $ruleTypes; private $contentTypes; private $disabled; + private $triggerObjectPHIDs; private $needConditionsAndActions; private $needAppliedToPHIDs; @@ -49,6 +50,11 @@ return $this; } + public function withTriggerObjectPHIDs(array $phids) { + $this->triggerObjectPHIDs = $phids; + return $this; + } + public function needConditionsAndActions($need) { $this->needConditionsAndActions = $need; return $this; @@ -137,6 +143,35 @@ } } + $object_phids = array(); + foreach ($rules as $rule) { + if ($rule->isObjectRule()) { + $object_phids[] = $rule->getTriggerObjectPHID(); + } + } + + if ($object_phids) { + $objects = id(new PhabricatorObjectQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($object_phids) + ->execute(); + $objects = mpull($objects, null, 'getPHID'); + } else { + $objects = array(); + } + + foreach ($rules as $key => $rule) { + if ($rule->isObjectRule()) { + $object = idx($objects, $rule->getTriggerObjectPHID()); + if (!$object) { + unset($rules[$key]); + continue; + } + $rule->attachTriggerObject($object); + } + } + return $rules; } @@ -185,6 +220,13 @@ (int)$this->disabled); } + if ($this->triggerObjectPHIDs) { + $where[] = qsprintf( + $conn_r, + 'rule.triggerObjectPHID IN (%Ls)', + $this->triggerObjectPHIDs); + } + $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); @@ -192,9 +234,9 @@ private function validateRuleAuthors(array $rules) { - // "Global" rules always have valid authors. + // "Global" and "Object" rules always have valid authors. foreach ($rules as $key => $rule) { - if ($rule->isGlobalRule()) { + if ($rule->isGlobalRule() || $rule->isObjectRule()) { $rule->attachValidAuthor(true); unset($rules[$key]); continue; Index: src/applications/herald/storage/HeraldRule.php =================================================================== --- src/applications/herald/storage/HeraldRule.php +++ src/applications/herald/storage/HeraldRule.php @@ -15,8 +15,9 @@ protected $repetitionPolicy; protected $ruleType; protected $isDisabled = 0; + protected $triggerObjectPHID; - protected $configVersion = 20; + protected $configVersion = 22; // phids for which this rule has been applied private $ruleApplied = self::ATTACHABLE; @@ -24,6 +25,7 @@ private $author = self::ATTACHABLE; private $conditions; private $actions; + private $triggerObject = self::ATTACHABLE; public function getConfiguration() { return array( @@ -146,9 +148,7 @@ } public function delete() { - -// TODO: -// $this->openTransaction(); + $this->openTransaction(); queryfx( $this->establishConnection('w'), 'DELETE FROM %T WHERE ruleID = %d', @@ -159,8 +159,10 @@ 'DELETE FROM %T WHERE ruleID = %d', id(new HeraldAction())->getTableName(), $this->getID()); - parent::delete(); -// $this->saveTransaction(); + $result = parent::delete(); + $this->saveTransaction(); + + return $result; } public function hasValidAuthor() { @@ -189,6 +191,19 @@ return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); } + public function isObjectRule() { + return ($this->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_OBJECT); + } + + public function attachTriggerObject($trigger_object) { + $this->triggerObject = $trigger_object; + return $this; + } + + public function getTriggerObject() { + return $this->assertAttached($this->triggerObject); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -211,6 +226,8 @@ $global = HeraldCapabilityManageGlobalRules::CAPABILITY; return $herald->getPolicy($global); } + } else if ($this->isObjectRule()) { + return $this->getTriggerObject()->getPolicy($capability); } else { return PhabricatorPolicies::POLICY_NOONE; } @@ -227,6 +244,8 @@ public function describeAutomaticCapability($capability) { if ($this->isPersonalRule()) { return pht("A personal rule's owner can always view and edit it."); + } else if ($this->isObjectRule()) { + return pht("Object rules inherit the policies of their objects."); } return null; Index: src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php =================================================================== --- src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1852,6 +1852,10 @@ 'type' => 'sql', 'name' => $this->getPatchPath('20131224.harbormanual.sql'), ), + '20131227.heraldobject.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20131227.heraldobject.sql'), + ), ); } }