@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3abstract class HeraldAction extends Phobject {
4
5 private $adapter;
6 private $viewer;
7 private $applyLog = array();
8
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';
13
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';
22
23 /**
24 * @return string Text of option in first dropdown of a Herald Action
25 */
26 abstract public function getHeraldActionName();
27 abstract public function supportsObject($object);
28 abstract public function supportsRuleType($rule_type);
29 abstract public function applyEffect($object, HeraldEffect $effect);
30
31 /**
32 * @return string|PhutilSafeHTML Description of the action performed by a
33 * Herald rule, shown under 'Take these actions...' on a Herald rule page
34 */
35 abstract public function renderActionDescription($value);
36
37 public function getRequiredAdapterStates() {
38 return array();
39 }
40
41 protected function renderActionEffectDescription($type, $data) {
42 return null;
43 }
44
45 public function getActionGroupKey() {
46 return null;
47 }
48
49 public function getActionsForObject($object) {
50 return array($this->getActionConstant() => $this);
51 }
52
53 protected function getDatasource() {
54 throw new PhutilMethodNotImplementedException();
55 }
56
57 protected function getDatasourceValueMap() {
58 return null;
59 }
60
61 public function getHeraldActionStandardType() {
62 throw new PhutilMethodNotImplementedException();
63 }
64
65 public function getHeraldActionValueType() {
66 switch ($this->getHeraldActionStandardType()) {
67 case self::STANDARD_NONE:
68 return new HeraldEmptyFieldValue();
69 case self::STANDARD_TEXT:
70 return new HeraldTextFieldValue();
71 case self::STANDARD_REMARKUP:
72 return new HeraldRemarkupFieldValue();
73 case self::STANDARD_PHID_LIST:
74 $tokenizer = id(new HeraldTokenizerFieldValue())
75 ->setKey($this->getHeraldActionName())
76 ->setDatasource($this->getDatasource());
77
78 $value_map = $this->getDatasourceValueMap();
79 if ($value_map !== null) {
80 $tokenizer->setValueMap($value_map);
81 }
82
83 return $tokenizer;
84 }
85
86 throw new PhutilMethodNotImplementedException();
87 }
88
89 public function willSaveActionValue($value) {
90 try {
91 $type = $this->getHeraldActionStandardType();
92 } catch (PhutilMethodNotImplementedException $ex) {
93 return $value;
94 }
95
96 switch ($type) {
97 case self::STANDARD_PHID_LIST:
98 return array_keys($value);
99 }
100
101 return $value;
102 }
103
104 public function getEditorValue(PhabricatorUser $viewer, $target) {
105 try {
106 $type = $this->getHeraldActionStandardType();
107 } catch (PhutilMethodNotImplementedException $ex) {
108 return $target;
109 }
110
111 switch ($type) {
112 case self::STANDARD_PHID_LIST:
113 $datasource = $this->getDatasource();
114
115 if (!$datasource) {
116 return array();
117 }
118
119 return $datasource
120 ->setViewer($viewer)
121 ->getWireTokens($target);
122 }
123
124 return $target;
125 }
126
127 final public function setAdapter(HeraldAdapter $adapter) {
128 $this->adapter = $adapter;
129 return $this;
130 }
131
132 /*
133 * @return HeraldAdapter HeraldAdapter class of the action
134 **/
135 final public function getAdapter() {
136 return $this->adapter;
137 }
138
139 final public function setViewer(PhabricatorUser $viewer) {
140 $this->viewer = $viewer;
141 return $this;
142 }
143
144 final public function getViewer() {
145 return $this->viewer;
146 }
147
148 final public function getActionConstant() {
149 return $this->getPhobjectClassConstant('ACTIONCONST', 64);
150 }
151
152 final public static function getAllActions() {
153 return id(new PhutilClassMapQuery())
154 ->setAncestorClass(self::class)
155 ->setUniqueMethod('getActionConstant')
156 ->execute();
157 }
158
159 protected function logEffect($type, $data = null) {
160 if (!is_string($type)) {
161 throw new Exception(
162 pht(
163 'Effect type passed to "%s" must be a scalar string.',
164 'logEffect()'));
165 }
166
167 $this->applyLog[] = array(
168 'type' => $type,
169 'data' => $data,
170 );
171
172 return $this;
173 }
174
175 final public function getApplyTranscript(HeraldEffect $effect) {
176 $context = $this->applyLog;
177 $this->applyLog = array();
178 return new HeraldApplyTranscript($effect, true, $context);
179 }
180
181 protected function getActionEffectMap() {
182 throw new PhutilMethodNotImplementedException();
183 }
184
185 private function getActionEffectSpec($type) {
186 $map = $this->getActionEffectMap() + $this->getStandardEffectMap();
187 return idx($map, $type, array());
188 }
189
190 final public function renderActionEffectIcon($type, $data) {
191 $map = $this->getActionEffectSpec($type);
192 return idx($map, 'icon');
193 }
194
195 final public function renderActionEffectColor($type, $data) {
196 $map = $this->getActionEffectSpec($type);
197 return idx($map, 'color');
198 }
199
200 final public function renderActionEffectName($type, $data) {
201 $map = $this->getActionEffectSpec($type);
202 return idx($map, 'name');
203 }
204
205 protected function renderHandleList($phids) {
206 if (!is_array($phids)) {
207 return pht('(Invalid List)');
208 }
209
210 return $this->getViewer()
211 ->renderHandleList($phids)
212 ->setAsInline(true)
213 ->render();
214 }
215
216 protected function loadStandardTargets(
217 array $phids,
218 array $allowed_types,
219 array $current_value) {
220
221 $phids = array_fuse($phids);
222 if (!$phids) {
223 $this->logEffect(self::DO_STANDARD_EMPTY);
224 }
225
226 $current_value = array_fuse($current_value);
227 $no_effect = array();
228 foreach ($phids as $phid) {
229 if (isset($current_value[$phid])) {
230 $no_effect[] = $phid;
231 unset($phids[$phid]);
232 }
233 }
234
235 if ($no_effect) {
236 $this->logEffect(self::DO_STANDARD_NO_EFFECT, $no_effect);
237 }
238
239 if (!$phids) {
240 return;
241 }
242
243 $allowed_types = array_fuse($allowed_types);
244 $invalid = array();
245 foreach ($phids as $phid) {
246 $type = phid_get_type($phid);
247 if ($type == PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
248 $invalid[] = $phid;
249 unset($phids[$phid]);
250 continue;
251 }
252
253 if ($allowed_types && empty($allowed_types[$type])) {
254 $invalid[] = $phid;
255 unset($phids[$phid]);
256 continue;
257 }
258 }
259
260 if ($invalid) {
261 $this->logEffect(self::DO_STANDARD_INVALID, $invalid);
262 }
263
264 if (!$phids) {
265 return;
266 }
267
268 $targets = id(new PhabricatorObjectQuery())
269 ->setViewer(PhabricatorUser::getOmnipotentUser())
270 ->withPHIDs($phids)
271 ->execute();
272 $targets = mpull($targets, null, 'getPHID');
273
274 $unloadable = array();
275 foreach ($phids as $phid) {
276 if (empty($targets[$phid])) {
277 $unloadable[] = $phid;
278 unset($phids[$phid]);
279 }
280 }
281
282 if ($unloadable) {
283 $this->logEffect(self::DO_STANDARD_UNLOADABLE, $unloadable);
284 }
285
286 if (!$phids) {
287 return;
288 }
289
290 $adapter = $this->getAdapter();
291 $object = $adapter->getObject();
292
293 if ($object instanceof PhabricatorPolicyInterface) {
294 $no_permission = array();
295 foreach ($targets as $phid => $target) {
296 if (!($target instanceof PhabricatorUser)) {
297 continue;
298 }
299
300 $can_view = PhabricatorPolicyFilter::hasCapability(
301 $target,
302 $object,
303 PhabricatorPolicyCapability::CAN_VIEW);
304 if ($can_view) {
305 continue;
306 }
307
308 $no_permission[] = $phid;
309 unset($targets[$phid]);
310 }
311 if ($no_permission) {
312 $this->logEffect(self::DO_STANDARD_PERMISSION, $no_permission);
313 }
314 }
315
316 return $targets;
317 }
318
319 protected function getStandardEffectMap() {
320 return array(
321 self::DO_STANDARD_EMPTY => array(
322 'icon' => 'fa-ban',
323 'color' => 'grey',
324 'name' => pht('No Targets'),
325 ),
326 self::DO_STANDARD_NO_EFFECT => array(
327 'icon' => 'fa-circle-o',
328 'color' => 'grey',
329 'name' => pht('No Effect'),
330 ),
331 self::DO_STANDARD_INVALID => array(
332 'icon' => 'fa-ban',
333 'color' => 'red',
334 'name' => pht('Invalid Targets'),
335 ),
336 self::DO_STANDARD_UNLOADABLE => array(
337 'icon' => 'fa-ban',
338 'color' => 'red',
339 'name' => pht('Unloadable Targets'),
340 ),
341 self::DO_STANDARD_PERMISSION => array(
342 'icon' => 'fa-lock',
343 'color' => 'red',
344 'name' => pht('No Permission'),
345 ),
346 self::DO_STANDARD_INVALID_ACTION => array(
347 'icon' => 'fa-ban',
348 'color' => 'red',
349 'name' => pht('Invalid Action'),
350 ),
351 self::DO_STANDARD_WRONG_RULE_TYPE => array(
352 'icon' => 'fa-ban',
353 'color' => 'red',
354 'name' => pht('Wrong Rule Type'),
355 ),
356 self::DO_STANDARD_FORBIDDEN => array(
357 'icon' => 'fa-ban',
358 'color' => 'violet',
359 'name' => pht('Forbidden'),
360 ),
361 );
362 }
363
364 final public function renderEffectDescription($type, $data) {
365 $result = $this->renderActionEffectDescription($type, $data);
366 if ($result !== null) {
367 return $result;
368 }
369
370 switch ($type) {
371 case self::DO_STANDARD_EMPTY:
372 return pht(
373 'This action specifies no targets.');
374 case self::DO_STANDARD_NO_EFFECT:
375 if ($data && is_array($data)) {
376 return pht(
377 'This action has no effect on %s target(s): %s.',
378 phutil_count($data),
379 $this->renderHandleList($data));
380 } else {
381 return pht('This action has no effect.');
382 }
383 case self::DO_STANDARD_INVALID:
384 return pht(
385 '%s target(s) are invalid or of the wrong type: %s.',
386 phutil_count($data),
387 $this->renderHandleList($data));
388 case self::DO_STANDARD_UNLOADABLE:
389 return pht(
390 '%s target(s) could not be loaded: %s.',
391 phutil_count($data),
392 $this->renderHandleList($data));
393 case self::DO_STANDARD_PERMISSION:
394 return pht(
395 '%s target(s) do not have permission to see this object: %s.',
396 phutil_count($data),
397 $this->renderHandleList($data));
398 case self::DO_STANDARD_INVALID_ACTION:
399 return pht(
400 'No implementation is available for rule "%s".',
401 $data);
402 case self::DO_STANDARD_WRONG_RULE_TYPE:
403 return pht(
404 'This action does not support rules of type "%s".',
405 $data);
406 case self::DO_STANDARD_FORBIDDEN:
407 return HeraldStateReasons::getExplanation($data);
408 }
409
410 return null;
411 }
412
413 public function getPHIDsAffectedByAction(HeraldActionRecord $record) {
414 return array();
415 }
416
417 public function isActionAvailable() {
418 return true;
419 }
420
421}