Path: blob/master/src/applications/herald/storage/HeraldRule.php
12256 views
<?php12final class HeraldRule extends HeraldDAO3implements4PhabricatorApplicationTransactionInterface,5PhabricatorFlaggableInterface,6PhabricatorPolicyInterface,7PhabricatorDestructibleInterface,8PhabricatorIndexableInterface,9PhabricatorSubscribableInterface {1011const TABLE_RULE_APPLIED = 'herald_ruleapplied';1213protected $name;14protected $authorPHID;1516protected $contentType;17protected $mustMatchAll;18protected $repetitionPolicy;19protected $ruleType;20protected $isDisabled = 0;21protected $triggerObjectPHID;2223protected $configVersion = 38;2425// PHIDs for which this rule has been applied26private $ruleApplied = self::ATTACHABLE;27private $validAuthor = self::ATTACHABLE;28private $author = self::ATTACHABLE;29private $conditions;30private $actions;31private $triggerObject = self::ATTACHABLE;3233const REPEAT_EVERY = 'every';34const REPEAT_FIRST = 'first';35const REPEAT_CHANGE = 'change';3637protected function getConfiguration() {38return array(39self::CONFIG_AUX_PHID => true,40self::CONFIG_COLUMN_SCHEMA => array(41'name' => 'sort255',42'contentType' => 'text255',43'mustMatchAll' => 'bool',44'configVersion' => 'uint32',45'repetitionPolicy' => 'text32',46'ruleType' => 'text32',47'isDisabled' => 'uint32',48'triggerObjectPHID' => 'phid?',49),50self::CONFIG_KEY_SCHEMA => array(51'key_name' => array(52'columns' => array('name(128)'),53),54'key_author' => array(55'columns' => array('authorPHID'),56),57'key_ruletype' => array(58'columns' => array('ruleType'),59),60'key_trigger' => array(61'columns' => array('triggerObjectPHID'),62),63),64) + parent::getConfiguration();65}6667public function generatePHID() {68return PhabricatorPHID::generateNewPHID(HeraldRulePHIDType::TYPECONST);69}7071public function getRuleApplied($phid) {72return $this->assertAttachedKey($this->ruleApplied, $phid);73}7475public function setRuleApplied($phid, $applied) {76if ($this->ruleApplied === self::ATTACHABLE) {77$this->ruleApplied = array();78}79$this->ruleApplied[$phid] = $applied;80return $this;81}8283public function loadConditions() {84if (!$this->getID()) {85return array();86}87return id(new HeraldCondition())->loadAllWhere(88'ruleID = %d',89$this->getID());90}9192public function attachConditions(array $conditions) {93assert_instances_of($conditions, 'HeraldCondition');94$this->conditions = $conditions;95return $this;96}9798public function getConditions() {99// TODO: validate conditions have been attached.100return $this->conditions;101}102103public function loadActions() {104if (!$this->getID()) {105return array();106}107return id(new HeraldActionRecord())->loadAllWhere(108'ruleID = %d',109$this->getID());110}111112public function attachActions(array $actions) {113// TODO: validate actions have been attached.114assert_instances_of($actions, 'HeraldActionRecord');115$this->actions = $actions;116return $this;117}118119public function getActions() {120return $this->actions;121}122123public function saveConditions(array $conditions) {124assert_instances_of($conditions, 'HeraldCondition');125return $this->saveChildren(126id(new HeraldCondition())->getTableName(),127$conditions);128}129130public function saveActions(array $actions) {131assert_instances_of($actions, 'HeraldActionRecord');132return $this->saveChildren(133id(new HeraldActionRecord())->getTableName(),134$actions);135}136137protected function saveChildren($table_name, array $children) {138assert_instances_of($children, 'HeraldDAO');139140if (!$this->getID()) {141throw new PhutilInvalidStateException('save');142}143144foreach ($children as $child) {145$child->setRuleID($this->getID());146}147148$this->openTransaction();149queryfx(150$this->establishConnection('w'),151'DELETE FROM %T WHERE ruleID = %d',152$table_name,153$this->getID());154foreach ($children as $child) {155$child->save();156}157$this->saveTransaction();158}159160public function delete() {161$this->openTransaction();162queryfx(163$this->establishConnection('w'),164'DELETE FROM %T WHERE ruleID = %d',165id(new HeraldCondition())->getTableName(),166$this->getID());167queryfx(168$this->establishConnection('w'),169'DELETE FROM %T WHERE ruleID = %d',170id(new HeraldActionRecord())->getTableName(),171$this->getID());172$result = parent::delete();173$this->saveTransaction();174175return $result;176}177178public function hasValidAuthor() {179return $this->assertAttached($this->validAuthor);180}181182public function attachValidAuthor($valid) {183$this->validAuthor = $valid;184return $this;185}186187public function getAuthor() {188return $this->assertAttached($this->author);189}190191public function attachAuthor(PhabricatorUser $user) {192$this->author = $user;193return $this;194}195196public function isGlobalRule() {197return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_GLOBAL);198}199200public function isPersonalRule() {201return ($this->getRuleType() === HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);202}203204public function isObjectRule() {205return ($this->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_OBJECT);206}207208public function attachTriggerObject($trigger_object) {209$this->triggerObject = $trigger_object;210return $this;211}212213public function getTriggerObject() {214return $this->assertAttached($this->triggerObject);215}216217/**218* Get a sortable key for rule execution order.219*220* Rules execute in a well-defined order: personal rules first, then object221* rules, then global rules. Within each rule type, rules execute from lowest222* ID to highest ID.223*224* This ordering allows more powerful rules (like global rules) to override225* weaker rules (like personal rules) when multiple rules exist which try to226* affect the same field. Executing from low IDs to high IDs makes227* interactions easier to understand when adding new rules, because the newest228* rules always happen last.229*230* @return string A sortable key for this rule.231*/232public function getRuleExecutionOrderSortKey() {233234$rule_type = $this->getRuleType();235236switch ($rule_type) {237case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:238$type_order = 1;239break;240case HeraldRuleTypeConfig::RULE_TYPE_OBJECT:241$type_order = 2;242break;243case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:244$type_order = 3;245break;246default:247throw new Exception(pht('Unknown rule type "%s"!', $rule_type));248}249250return sprintf('~%d%010d', $type_order, $this->getID());251}252253public function getMonogram() {254return 'H'.$this->getID();255}256257public function getURI() {258return '/'.$this->getMonogram();259}260261public function getEditorSortVector() {262return id(new PhutilSortVector())263->addInt($this->getIsDisabled() ? 1 : 0)264->addString($this->getName());265}266267public function getEditorDisplayName() {268$name = pht('%s %s', $this->getMonogram(), $this->getName());269270if ($this->getIsDisabled()) {271$name = pht('%s (Disabled)', $name);272}273274return $name;275}276277278/* -( Repetition Policies )------------------------------------------------ */279280281public function getRepetitionPolicyStringConstant() {282return $this->getRepetitionPolicy();283}284285public function setRepetitionPolicyStringConstant($value) {286$map = self::getRepetitionPolicyMap();287288if (!isset($map[$value])) {289throw new Exception(290pht(291'Rule repetition string constant "%s" is unknown.',292$value));293}294295return $this->setRepetitionPolicy($value);296}297298public function isRepeatEvery() {299return ($this->getRepetitionPolicyStringConstant() === self::REPEAT_EVERY);300}301302public function isRepeatFirst() {303return ($this->getRepetitionPolicyStringConstant() === self::REPEAT_FIRST);304}305306public function isRepeatOnChange() {307return ($this->getRepetitionPolicyStringConstant() === self::REPEAT_CHANGE);308}309310public static function getRepetitionPolicySelectOptionMap() {311$map = self::getRepetitionPolicyMap();312return ipull($map, 'select');313}314315private static function getRepetitionPolicyMap() {316return array(317self::REPEAT_EVERY => array(318'select' => pht('every time this rule matches:'),319),320self::REPEAT_FIRST => array(321'select' => pht('only the first time this rule matches:'),322),323self::REPEAT_CHANGE => array(324'select' => pht('if this rule did not match the last time:'),325),326);327}328329330/* -( PhabricatorApplicationTransactionInterface )------------------------- */331332333public function getApplicationTransactionEditor() {334return new HeraldRuleEditor();335}336337public function getApplicationTransactionTemplate() {338return new HeraldRuleTransaction();339}340341342/* -( PhabricatorPolicyInterface )----------------------------------------- */343344345public function getCapabilities() {346return array(347PhabricatorPolicyCapability::CAN_VIEW,348PhabricatorPolicyCapability::CAN_EDIT,349);350}351352public function getPolicy($capability) {353if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {354return PhabricatorPolicies::getMostOpenPolicy();355}356357if ($this->isGlobalRule()) {358$app = 'PhabricatorHeraldApplication';359$herald = PhabricatorApplication::getByClass($app);360$global = HeraldManageGlobalRulesCapability::CAPABILITY;361return $herald->getPolicy($global);362} else if ($this->isObjectRule()) {363return $this->getTriggerObject()->getPolicy($capability);364} else {365return $this->getAuthorPHID();366}367}368369public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {370return false;371}372373public function describeAutomaticCapability($capability) {374if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {375return null;376}377378if ($this->isGlobalRule()) {379return pht(380'Global Herald rules can be edited by users with the "Can Manage '.381'Global Rules" Herald application permission.');382} else if ($this->isObjectRule()) {383return pht('Object rules inherit the edit policies of their objects.');384} else {385return pht('A personal rule can only be edited by its owner.');386}387}388389390/* -( PhabricatorSubscribableInterface )----------------------------------- */391392393public function isAutomaticallySubscribed($phid) {394return $this->isPersonalRule() && $phid == $this->getAuthorPHID();395}396397398/* -( PhabricatorDestructibleInterface )----------------------------------- */399400401public function destroyObjectPermanently(402PhabricatorDestructionEngine $engine) {403404$this->openTransaction();405$this->delete();406$this->saveTransaction();407}408409}410411412