2015-07-17 19:27:38 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
abstract class HeraldAction extends Phobject {
|
|
|
|
|
|
|
|
private $adapter;
|
2015-07-18 14:54:26 +02:00
|
|
|
private $viewer;
|
2015-07-17 19:27:38 +02:00
|
|
|
private $applyLog = array();
|
|
|
|
|
|
|
|
const STANDARD_NONE = 'standard.none';
|
|
|
|
const STANDARD_PHID_LIST = 'standard.phid.list';
|
2015-08-03 21:54:40 +02:00
|
|
|
const STANDARD_TEXT = 'standard.text';
|
2015-07-17 19:27:38 +02:00
|
|
|
|
2015-08-03 15:01:24 +02:00
|
|
|
const DO_STANDARD_EMPTY = 'do.standard.empty';
|
|
|
|
const DO_STANDARD_NO_EFFECT = 'do.standard.no-effect';
|
|
|
|
const DO_STANDARD_INVALID = 'do.standard.invalid';
|
|
|
|
const DO_STANDARD_UNLOADABLE = 'do.standard.unloadable';
|
|
|
|
const DO_STANDARD_PERMISSION = 'do.standard.permission';
|
2015-08-03 21:54:40 +02:00
|
|
|
const DO_STANDARD_INVALID_ACTION = 'do.standard.invalid-action';
|
|
|
|
const DO_STANDARD_WRONG_RULE_TYPE = 'do.standard.wrong-rule-type';
|
2015-08-03 15:01:24 +02:00
|
|
|
|
2015-07-17 19:27:38 +02:00
|
|
|
abstract public function getHeraldActionName();
|
|
|
|
abstract public function supportsObject($object);
|
|
|
|
abstract public function supportsRuleType($rule_type);
|
|
|
|
abstract public function applyEffect($object, HeraldEffect $effect);
|
2015-08-03 15:01:24 +02:00
|
|
|
|
2015-08-04 00:26:17 +02:00
|
|
|
abstract public function renderActionDescription($value);
|
|
|
|
|
2015-08-03 15:01:24 +02:00
|
|
|
protected function renderActionEffectDescription($type, $data) {
|
|
|
|
return null;
|
|
|
|
}
|
2015-07-17 19:27:38 +02:00
|
|
|
|
|
|
|
public function getActionGroupKey() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getActionsForObject($object) {
|
|
|
|
return array($this->getActionConstant() => $this);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getDatasource() {
|
|
|
|
throw new PhutilMethodNotImplementedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getDatasourceValueMap() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getHeraldActionStandardType() {
|
|
|
|
throw new PhutilMethodNotImplementedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getHeraldActionValueType() {
|
|
|
|
switch ($this->getHeraldActionStandardType()) {
|
|
|
|
case self::STANDARD_NONE:
|
|
|
|
return new HeraldEmptyFieldValue();
|
2015-08-03 21:54:40 +02:00
|
|
|
case self::STANDARD_TEXT:
|
|
|
|
return new HeraldTextFieldValue();
|
2015-07-17 19:27:38 +02:00
|
|
|
case self::STANDARD_PHID_LIST:
|
|
|
|
$tokenizer = id(new HeraldTokenizerFieldValue())
|
2015-07-18 17:07:31 +02:00
|
|
|
->setKey($this->getHeraldActionName())
|
2015-07-17 19:27:38 +02:00
|
|
|
->setDatasource($this->getDatasource());
|
|
|
|
|
|
|
|
$value_map = $this->getDatasourceValueMap();
|
|
|
|
if ($value_map !== null) {
|
|
|
|
$tokenizer->setValueMap($value_map);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $tokenizer;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new PhutilMethodNotImplementedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function willSaveActionValue($value) {
|
2015-07-18 17:07:31 +02:00
|
|
|
try {
|
|
|
|
$type = $this->getHeraldActionStandardType();
|
|
|
|
} catch (PhutilMethodNotImplementedException $ex) {
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ($type) {
|
|
|
|
case self::STANDARD_PHID_LIST:
|
|
|
|
return array_keys($value);
|
|
|
|
}
|
|
|
|
|
2015-07-17 19:27:38 +02:00
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
2015-08-04 02:05:21 +02:00
|
|
|
public function getEditorValue(PhabricatorUser $viewer, $target) {
|
|
|
|
try {
|
|
|
|
$type = $this->getHeraldActionStandardType();
|
|
|
|
} catch (PhutilMethodNotImplementedException $ex) {
|
|
|
|
return $target;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ($type) {
|
|
|
|
case self::STANDARD_PHID_LIST:
|
|
|
|
$handles = $viewer->loadHandles($target);
|
|
|
|
$handles = iterator_to_array($handles);
|
|
|
|
return mpull($handles, 'getName', 'getPHID');
|
|
|
|
}
|
|
|
|
|
|
|
|
return $target;
|
|
|
|
}
|
|
|
|
|
2015-07-17 19:27:38 +02:00
|
|
|
final public function setAdapter(HeraldAdapter $adapter) {
|
|
|
|
$this->adapter = $adapter;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
final public function getAdapter() {
|
|
|
|
return $this->adapter;
|
|
|
|
}
|
|
|
|
|
2015-07-18 14:54:26 +02:00
|
|
|
final public function setViewer(PhabricatorUser $viewer) {
|
|
|
|
$this->viewer = $viewer;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
final public function getViewer() {
|
|
|
|
return $this->viewer;
|
|
|
|
}
|
|
|
|
|
2015-07-17 19:27:38 +02:00
|
|
|
final public function getActionConstant() {
|
|
|
|
$class = new ReflectionClass($this);
|
|
|
|
|
|
|
|
$const = $class->getConstant('ACTIONCONST');
|
|
|
|
if ($const === false) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'"%s" class "%s" must define a "%s" property.',
|
|
|
|
__CLASS__,
|
|
|
|
get_class($this),
|
|
|
|
'ACTIONCONST'));
|
|
|
|
}
|
|
|
|
|
|
|
|
$limit = self::getActionConstantByteLimit();
|
|
|
|
if (!is_string($const) || (strlen($const) > $limit)) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'"%s" class "%s" has an invalid "%s" property. Action constants '.
|
|
|
|
'must be strings and no more than %s bytes in length.',
|
|
|
|
__CLASS__,
|
|
|
|
get_class($this),
|
|
|
|
'ACTIONCONST',
|
|
|
|
new PhutilNumber($limit)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $const;
|
|
|
|
}
|
|
|
|
|
|
|
|
final public static function getActionConstantByteLimit() {
|
|
|
|
return 64;
|
|
|
|
}
|
|
|
|
|
|
|
|
final public static function getAllActions() {
|
|
|
|
return id(new PhutilClassMapQuery())
|
|
|
|
->setAncestorClass(__CLASS__)
|
|
|
|
->setUniqueMethod('getActionConstant')
|
|
|
|
->execute();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function logEffect($type, $data = null) {
|
2015-07-18 14:54:26 +02:00
|
|
|
if (!is_string($type)) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Effect type passed to "%s" must be a scalar string.',
|
|
|
|
'logEffect()'));
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->applyLog[] = array(
|
|
|
|
'type' => $type,
|
|
|
|
'data' => $data,
|
|
|
|
);
|
|
|
|
|
|
|
|
return $this;
|
2015-07-17 19:27:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
final public function getApplyTranscript(HeraldEffect $effect) {
|
2015-07-18 14:54:26 +02:00
|
|
|
$context = $this->applyLog;
|
2015-07-17 19:27:38 +02:00
|
|
|
$this->applyLog = array();
|
|
|
|
return new HeraldApplyTranscript($effect, true, $context);
|
|
|
|
}
|
|
|
|
|
2015-07-18 14:54:26 +02:00
|
|
|
protected function getActionEffectMap() {
|
|
|
|
throw new PhutilMethodNotImplementedException();
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getActionEffectSpec($type) {
|
2015-08-03 15:01:24 +02:00
|
|
|
$map = $this->getActionEffectMap() + $this->getStandardEffectMap();
|
2015-07-18 14:54:26 +02:00
|
|
|
return idx($map, $type, array());
|
|
|
|
}
|
|
|
|
|
2015-08-03 15:01:24 +02:00
|
|
|
final public function renderActionEffectIcon($type, $data) {
|
2015-07-18 14:54:26 +02:00
|
|
|
$map = $this->getActionEffectSpec($type);
|
|
|
|
return idx($map, 'icon');
|
|
|
|
}
|
|
|
|
|
2015-08-03 15:01:24 +02:00
|
|
|
final public function renderActionEffectColor($type, $data) {
|
2015-07-18 14:54:26 +02:00
|
|
|
$map = $this->getActionEffectSpec($type);
|
|
|
|
return idx($map, 'color');
|
|
|
|
}
|
|
|
|
|
2015-08-03 15:01:24 +02:00
|
|
|
final public function renderActionEffectName($type, $data) {
|
2015-07-18 14:54:26 +02:00
|
|
|
$map = $this->getActionEffectSpec($type);
|
|
|
|
return idx($map, 'name');
|
|
|
|
}
|
|
|
|
|
2015-07-18 17:07:31 +02:00
|
|
|
protected function renderHandleList($phids) {
|
|
|
|
if (!is_array($phids)) {
|
|
|
|
return pht('(Invalid List)');
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->getViewer()
|
|
|
|
->renderHandleList($phids)
|
|
|
|
->setAsInline(true)
|
|
|
|
->render();
|
|
|
|
}
|
|
|
|
|
2015-08-03 15:01:24 +02:00
|
|
|
protected function loadStandardTargets(
|
|
|
|
array $phids,
|
|
|
|
array $allowed_types,
|
|
|
|
array $current_value) {
|
|
|
|
|
|
|
|
$phids = array_fuse($phids);
|
|
|
|
if (!$phids) {
|
|
|
|
$this->logEffect(self::DO_STANDARD_EMPTY);
|
|
|
|
}
|
|
|
|
|
|
|
|
$current_value = array_fuse($current_value);
|
|
|
|
$no_effect = array();
|
|
|
|
foreach ($phids as $phid) {
|
|
|
|
if (isset($current_value[$phid])) {
|
|
|
|
$no_effect[] = $phid;
|
|
|
|
unset($phids[$phid]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($no_effect) {
|
|
|
|
$this->logEffect(self::DO_STANDARD_NO_EFFECT, $no_effect);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$phids) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$allowed_types = array_fuse($allowed_types);
|
|
|
|
$invalid = array();
|
|
|
|
foreach ($phids as $phid) {
|
|
|
|
$type = phid_get_type($phid);
|
|
|
|
if ($type == PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
|
|
|
|
$invalid[] = $phid;
|
|
|
|
unset($phids[$phid]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($allowed_types && empty($allowed_types[$type])) {
|
|
|
|
$invalid[] = $phid;
|
|
|
|
unset($phids[$phid]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($invalid) {
|
|
|
|
$this->logEffect(self::DO_STANDARD_INVALID, $invalid);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$phids) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$targets = id(new PhabricatorObjectQuery())
|
|
|
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
|
|
|
->withPHIDs($phids)
|
|
|
|
->execute();
|
|
|
|
$targets = mpull($targets, null, 'getPHID');
|
|
|
|
|
|
|
|
$unloadable = array();
|
|
|
|
foreach ($phids as $phid) {
|
|
|
|
if (empty($targets[$phid])) {
|
|
|
|
$unloadable[] = $phid;
|
|
|
|
unset($phids[$phid]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($unloadable) {
|
|
|
|
$this->logEffect(self::DO_STANDARD_UNLOADABLE, $unloadable);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$phids) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$adapter = $this->getAdapter();
|
|
|
|
$object = $adapter->getObject();
|
|
|
|
|
|
|
|
if ($object instanceof PhabricatorPolicyInterface) {
|
|
|
|
$no_permission = array();
|
|
|
|
foreach ($targets as $phid => $target) {
|
|
|
|
if (!($target instanceof PhabricatorUser)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$can_view = PhabricatorPolicyFilter::hasCapability(
|
|
|
|
$target,
|
|
|
|
$object,
|
|
|
|
PhabricatorPolicyCapability::CAN_VIEW);
|
|
|
|
if ($can_view) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$no_permission[] = $phid;
|
|
|
|
unset($targets[$phid]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($no_permission) {
|
|
|
|
$this->logEffect(self::DO_STANDARD_PERMISSION, $no_permission);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $targets;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getStandardEffectMap() {
|
|
|
|
return array(
|
|
|
|
self::DO_STANDARD_EMPTY => array(
|
|
|
|
'icon' => 'fa-ban',
|
|
|
|
'color' => 'grey',
|
|
|
|
'name' => pht('No Targets'),
|
|
|
|
),
|
|
|
|
self::DO_STANDARD_NO_EFFECT => array(
|
|
|
|
'icon' => 'fa-circle-o',
|
|
|
|
'color' => 'grey',
|
|
|
|
'name' => pht('No Effect'),
|
|
|
|
),
|
|
|
|
self::DO_STANDARD_INVALID => array(
|
|
|
|
'icon' => 'fa-ban',
|
|
|
|
'color' => 'red',
|
|
|
|
'name' => pht('Invalid Targets'),
|
|
|
|
),
|
|
|
|
self::DO_STANDARD_UNLOADABLE => array(
|
|
|
|
'icon' => 'fa-ban',
|
|
|
|
'color' => 'red',
|
|
|
|
'name' => pht('Unloadable Targets'),
|
|
|
|
),
|
|
|
|
self::DO_STANDARD_PERMISSION => array(
|
|
|
|
'icon' => 'fa-lock',
|
|
|
|
'color' => 'red',
|
|
|
|
'name' => pht('No Permission'),
|
|
|
|
),
|
2015-08-03 21:54:40 +02:00
|
|
|
self::DO_STANDARD_INVALID_ACTION => array(
|
|
|
|
'icon' => 'fa-ban',
|
|
|
|
'color' => 'red',
|
|
|
|
'name' => pht('Invalid Action'),
|
|
|
|
),
|
|
|
|
self::DO_STANDARD_WRONG_RULE_TYPE => array(
|
|
|
|
'icon' => 'fa-ban',
|
|
|
|
'color' => 'red',
|
|
|
|
'name' => pht('Wrong Rule Type'),
|
|
|
|
),
|
2015-08-03 15:01:24 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
final public function renderEffectDescription($type, $data) {
|
|
|
|
$result = $this->renderActionEffectDescription($type, $data);
|
|
|
|
if ($result !== null) {
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ($type) {
|
|
|
|
case self::DO_STANDARD_EMPTY:
|
|
|
|
return pht(
|
|
|
|
'This action specifies no targets.');
|
|
|
|
case self::DO_STANDARD_NO_EFFECT:
|
|
|
|
return pht(
|
|
|
|
'This action has no effect on %s target(s): %s.',
|
|
|
|
new PhutilNumber(count($data)),
|
|
|
|
$this->renderHandleList($data));
|
|
|
|
case self::DO_STANDARD_INVALID:
|
|
|
|
return pht(
|
|
|
|
'%s target(s) are invalid or of the wrong type: %s.',
|
|
|
|
new PhutilNumber(count($data)),
|
|
|
|
$this->renderHandleList($data));
|
|
|
|
case self::DO_STANDARD_UNLOADABLE:
|
|
|
|
return pht(
|
|
|
|
'%s target(s) could not be loaded: %s.',
|
|
|
|
new PhutilNumber(count($data)),
|
|
|
|
$this->renderHandleList($data));
|
|
|
|
case self::DO_STANDARD_PERMISSION:
|
|
|
|
return pht(
|
|
|
|
'%s target(s) do not have permission to see this object: %s.',
|
|
|
|
new PhutilNumber(count($data)),
|
|
|
|
$this->renderHandleList($data));
|
2015-08-03 21:54:40 +02:00
|
|
|
case self::DO_STANDARD_INVALID_ACTION:
|
|
|
|
return pht(
|
|
|
|
'No implementation is available for rule "%s".',
|
|
|
|
$data);
|
|
|
|
case self::DO_STANDARD_WRONG_RULE_TYPE:
|
|
|
|
return pht(
|
|
|
|
'This action does not support rules of type "%s".',
|
|
|
|
$data);
|
2015-08-03 15:01:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-07-17 19:27:38 +02:00
|
|
|
}
|