Path: blob/master/src/applications/herald/action/HeraldAction.php
12256 views
<?php12abstract class HeraldAction extends Phobject {34private $adapter;5private $viewer;6private $applyLog = array();78const STANDARD_NONE = 'standard.none';9const STANDARD_PHID_LIST = 'standard.phid.list';10const STANDARD_TEXT = 'standard.text';11const STANDARD_REMARKUP = 'standard.remarkup';1213const DO_STANDARD_EMPTY = 'do.standard.empty';14const DO_STANDARD_NO_EFFECT = 'do.standard.no-effect';15const DO_STANDARD_INVALID = 'do.standard.invalid';16const DO_STANDARD_UNLOADABLE = 'do.standard.unloadable';17const DO_STANDARD_PERMISSION = 'do.standard.permission';18const DO_STANDARD_INVALID_ACTION = 'do.standard.invalid-action';19const DO_STANDARD_WRONG_RULE_TYPE = 'do.standard.wrong-rule-type';20const DO_STANDARD_FORBIDDEN = 'do.standard.forbidden';2122abstract public function getHeraldActionName();23abstract public function supportsObject($object);24abstract public function supportsRuleType($rule_type);25abstract public function applyEffect($object, HeraldEffect $effect);2627abstract public function renderActionDescription($value);2829public function getRequiredAdapterStates() {30return array();31}3233protected function renderActionEffectDescription($type, $data) {34return null;35}3637public function getActionGroupKey() {38return null;39}4041public function getActionsForObject($object) {42return array($this->getActionConstant() => $this);43}4445protected function getDatasource() {46throw new PhutilMethodNotImplementedException();47}4849protected function getDatasourceValueMap() {50return null;51}5253public function getHeraldActionStandardType() {54throw new PhutilMethodNotImplementedException();55}5657public function getHeraldActionValueType() {58switch ($this->getHeraldActionStandardType()) {59case self::STANDARD_NONE:60return new HeraldEmptyFieldValue();61case self::STANDARD_TEXT:62return new HeraldTextFieldValue();63case self::STANDARD_REMARKUP:64return new HeraldRemarkupFieldValue();65case self::STANDARD_PHID_LIST:66$tokenizer = id(new HeraldTokenizerFieldValue())67->setKey($this->getHeraldActionName())68->setDatasource($this->getDatasource());6970$value_map = $this->getDatasourceValueMap();71if ($value_map !== null) {72$tokenizer->setValueMap($value_map);73}7475return $tokenizer;76}7778throw new PhutilMethodNotImplementedException();79}8081public function willSaveActionValue($value) {82try {83$type = $this->getHeraldActionStandardType();84} catch (PhutilMethodNotImplementedException $ex) {85return $value;86}8788switch ($type) {89case self::STANDARD_PHID_LIST:90return array_keys($value);91}9293return $value;94}9596public function getEditorValue(PhabricatorUser $viewer, $target) {97try {98$type = $this->getHeraldActionStandardType();99} catch (PhutilMethodNotImplementedException $ex) {100return $target;101}102103switch ($type) {104case self::STANDARD_PHID_LIST:105$datasource = $this->getDatasource();106107if (!$datasource) {108return array();109}110111return $datasource112->setViewer($viewer)113->getWireTokens($target);114}115116return $target;117}118119final public function setAdapter(HeraldAdapter $adapter) {120$this->adapter = $adapter;121return $this;122}123124final public function getAdapter() {125return $this->adapter;126}127128final public function setViewer(PhabricatorUser $viewer) {129$this->viewer = $viewer;130return $this;131}132133final public function getViewer() {134return $this->viewer;135}136137final public function getActionConstant() {138return $this->getPhobjectClassConstant('ACTIONCONST', 64);139}140141final public static function getAllActions() {142return id(new PhutilClassMapQuery())143->setAncestorClass(__CLASS__)144->setUniqueMethod('getActionConstant')145->execute();146}147148protected function logEffect($type, $data = null) {149if (!is_string($type)) {150throw new Exception(151pht(152'Effect type passed to "%s" must be a scalar string.',153'logEffect()'));154}155156$this->applyLog[] = array(157'type' => $type,158'data' => $data,159);160161return $this;162}163164final public function getApplyTranscript(HeraldEffect $effect) {165$context = $this->applyLog;166$this->applyLog = array();167return new HeraldApplyTranscript($effect, true, $context);168}169170protected function getActionEffectMap() {171throw new PhutilMethodNotImplementedException();172}173174private function getActionEffectSpec($type) {175$map = $this->getActionEffectMap() + $this->getStandardEffectMap();176return idx($map, $type, array());177}178179final public function renderActionEffectIcon($type, $data) {180$map = $this->getActionEffectSpec($type);181return idx($map, 'icon');182}183184final public function renderActionEffectColor($type, $data) {185$map = $this->getActionEffectSpec($type);186return idx($map, 'color');187}188189final public function renderActionEffectName($type, $data) {190$map = $this->getActionEffectSpec($type);191return idx($map, 'name');192}193194protected function renderHandleList($phids) {195if (!is_array($phids)) {196return pht('(Invalid List)');197}198199return $this->getViewer()200->renderHandleList($phids)201->setAsInline(true)202->render();203}204205protected function loadStandardTargets(206array $phids,207array $allowed_types,208array $current_value) {209210$phids = array_fuse($phids);211if (!$phids) {212$this->logEffect(self::DO_STANDARD_EMPTY);213}214215$current_value = array_fuse($current_value);216$no_effect = array();217foreach ($phids as $phid) {218if (isset($current_value[$phid])) {219$no_effect[] = $phid;220unset($phids[$phid]);221}222}223224if ($no_effect) {225$this->logEffect(self::DO_STANDARD_NO_EFFECT, $no_effect);226}227228if (!$phids) {229return;230}231232$allowed_types = array_fuse($allowed_types);233$invalid = array();234foreach ($phids as $phid) {235$type = phid_get_type($phid);236if ($type == PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {237$invalid[] = $phid;238unset($phids[$phid]);239continue;240}241242if ($allowed_types && empty($allowed_types[$type])) {243$invalid[] = $phid;244unset($phids[$phid]);245continue;246}247}248249if ($invalid) {250$this->logEffect(self::DO_STANDARD_INVALID, $invalid);251}252253if (!$phids) {254return;255}256257$targets = id(new PhabricatorObjectQuery())258->setViewer(PhabricatorUser::getOmnipotentUser())259->withPHIDs($phids)260->execute();261$targets = mpull($targets, null, 'getPHID');262263$unloadable = array();264foreach ($phids as $phid) {265if (empty($targets[$phid])) {266$unloadable[] = $phid;267unset($phids[$phid]);268}269}270271if ($unloadable) {272$this->logEffect(self::DO_STANDARD_UNLOADABLE, $unloadable);273}274275if (!$phids) {276return;277}278279$adapter = $this->getAdapter();280$object = $adapter->getObject();281282if ($object instanceof PhabricatorPolicyInterface) {283$no_permission = array();284foreach ($targets as $phid => $target) {285if (!($target instanceof PhabricatorUser)) {286continue;287}288289$can_view = PhabricatorPolicyFilter::hasCapability(290$target,291$object,292PhabricatorPolicyCapability::CAN_VIEW);293if ($can_view) {294continue;295}296297$no_permission[] = $phid;298unset($targets[$phid]);299}300}301302if ($no_permission) {303$this->logEffect(self::DO_STANDARD_PERMISSION, $no_permission);304}305306return $targets;307}308309protected function getStandardEffectMap() {310return array(311self::DO_STANDARD_EMPTY => array(312'icon' => 'fa-ban',313'color' => 'grey',314'name' => pht('No Targets'),315),316self::DO_STANDARD_NO_EFFECT => array(317'icon' => 'fa-circle-o',318'color' => 'grey',319'name' => pht('No Effect'),320),321self::DO_STANDARD_INVALID => array(322'icon' => 'fa-ban',323'color' => 'red',324'name' => pht('Invalid Targets'),325),326self::DO_STANDARD_UNLOADABLE => array(327'icon' => 'fa-ban',328'color' => 'red',329'name' => pht('Unloadable Targets'),330),331self::DO_STANDARD_PERMISSION => array(332'icon' => 'fa-lock',333'color' => 'red',334'name' => pht('No Permission'),335),336self::DO_STANDARD_INVALID_ACTION => array(337'icon' => 'fa-ban',338'color' => 'red',339'name' => pht('Invalid Action'),340),341self::DO_STANDARD_WRONG_RULE_TYPE => array(342'icon' => 'fa-ban',343'color' => 'red',344'name' => pht('Wrong Rule Type'),345),346self::DO_STANDARD_FORBIDDEN => array(347'icon' => 'fa-ban',348'color' => 'violet',349'name' => pht('Forbidden'),350),351);352}353354final public function renderEffectDescription($type, $data) {355$result = $this->renderActionEffectDescription($type, $data);356if ($result !== null) {357return $result;358}359360switch ($type) {361case self::DO_STANDARD_EMPTY:362return pht(363'This action specifies no targets.');364case self::DO_STANDARD_NO_EFFECT:365if ($data && is_array($data)) {366return pht(367'This action has no effect on %s target(s): %s.',368phutil_count($data),369$this->renderHandleList($data));370} else {371return pht('This action has no effect.');372}373case self::DO_STANDARD_INVALID:374return pht(375'%s target(s) are invalid or of the wrong type: %s.',376phutil_count($data),377$this->renderHandleList($data));378case self::DO_STANDARD_UNLOADABLE:379return pht(380'%s target(s) could not be loaded: %s.',381phutil_count($data),382$this->renderHandleList($data));383case self::DO_STANDARD_PERMISSION:384return pht(385'%s target(s) do not have permission to see this object: %s.',386phutil_count($data),387$this->renderHandleList($data));388case self::DO_STANDARD_INVALID_ACTION:389return pht(390'No implementation is available for rule "%s".',391$data);392case self::DO_STANDARD_WRONG_RULE_TYPE:393return pht(394'This action does not support rules of type "%s".',395$data);396case self::DO_STANDARD_FORBIDDEN:397return HeraldStateReasons::getExplanation($data);398}399400return null;401}402403public function getPHIDsAffectedByAction(HeraldActionRecord $record) {404return array();405}406407public function isActionAvailable() {408return true;409}410411}412413414