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