diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:15:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:15:40 +0000 |
commit | b7fd908d538ed19fe41f03c0a3f93351d8da64e9 (patch) | |
tree | 46e14f318948cd4f5d7e874f83e7dfcc5d42fc64 /library/Businessprocess/Modification | |
parent | Initial commit. (diff) | |
download | icingaweb2-module-businessprocess-upstream.tar.xz icingaweb2-module-businessprocess-upstream.zip |
Adding upstream version 2.5.0.upstream/2.5.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/Businessprocess/Modification')
9 files changed, 1232 insertions, 0 deletions
diff --git a/library/Businessprocess/Modification/NodeAction.php b/library/Businessprocess/Modification/NodeAction.php new file mode 100644 index 0000000..b5baa5d --- /dev/null +++ b/library/Businessprocess/Modification/NodeAction.php @@ -0,0 +1,179 @@ +<?php + +namespace Icinga\Module\Businessprocess\Modification; + +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\Exception\ModificationError; +use Icinga\Module\Businessprocess\Node; +use Icinga\Exception\ProgrammingError; + +/** + * Abstract NodeAction class + * + * Every instance of a NodeAction represents a single applied change. Changes are pushed to + * a stack and consumed from there. When persisted, NodeActions are serialized with their name, + * node name and optionally additional properties according preserveProperties. For each property + * that should be preserved, getter and setter methods have to be defined. + * + * @package Icinga\Module\Businessprocess + */ +abstract class NodeAction +{ + /** @var string Name of this action (currently create, modify, remove) */ + protected $actionName; + + /** @var string Name of the node this action applies to */ + protected $nodeName; + + /** @var array Properties which should be preserved when serializing this action */ + protected $preserveProperties = array(); + + /** + * NodeAction constructor. + * + * @param Node|string $node + */ + public function __construct($node = null) + { + if ($node !== null) { + $this->nodeName = (string) $node; + } + } + + /** + * Every NodeAction must be able to apply itself to a BusinessProcess + * + * @param BpConfig $config + * @return mixed + */ + abstract public function applyTo(BpConfig $config); + + /** + * Every NodeAction must be able to tell whether it can be applied to a BusinessProcess + * + * @param BpConfig $config + * + * @throws ModificationError + * + * @return bool + */ + abstract public function appliesTo(BpConfig $config); + + /** + * The name of the node this modification applies to + * + * @return string + */ + public function getNodeName() + { + return $this->nodeName; + } + + public function hasNode() + { + return $this->nodeName !== null; + } + + /** + * Whether this is an instance of a given action name + * + * @param string $actionName + * @return bool + */ + public function is($actionName) + { + return $this->getActionName() === $actionName; + } + + /** + * Throw a ModificationError + * + * @param string $msg + * @param mixed ... + * + * @throws ModificationError + */ + protected function error($msg) + { + $error = ModificationError::create(func_get_args()); + /** @var ModificationError $error */ + throw $error; + } + + /** + * Create an instance of a given actionName for a specific Node + * + * @param string $actionName + * @param string $nodeName + * + * @return static + */ + public static function create($actionName, $nodeName) + { + $className = __NAMESPACE__ . '\\Node' . ucfirst($actionName) . 'Action'; + + return new $className($nodeName); + } + + /** + * Returns a JSON-encoded serialized NodeAction + * + * @return string + */ + public function serialize() + { + $object = (object) array( + 'actionName' => $this->getActionName(), + 'nodeName' => $this->getNodeName(), + 'properties' => array() + ); + + foreach ($this->preserveProperties as $key) { + $func = 'get' . ucfirst($key); + $object->properties[$key] = $this->$func(); + } + + return json_encode($object); + } + + /** + * Decodes a JSON-serialized NodeAction and returns an object instance + * + * @param $string + * @return NodeAction + */ + public static function unSerialize($string) + { + $object = json_decode($string, true); + $action = self::create($object['actionName'], $object['nodeName']); + + foreach ($object['properties'] as $key => $val) { + $func = 'set' . ucfirst($key); + $action->$func($val); + } + + return $action; + } + + /** + * Returns the defined action name or determines such from the class name + * + * @return string The action name + * + * @throws ProgrammingError when no such class exists + */ + public function getActionName() + { + if ($this->actionName === null) { + if (! preg_match('/\\\Node(\w+)Action$/', get_class($this), $m)) { + throw new ProgrammingError( + '"%s" is not a NodeAction class', + get_class($this) + ); + } + $this->actionName = lcfirst($m[1]); + } + + return $this->actionName; + } +} diff --git a/library/Businessprocess/Modification/NodeAddChildrenAction.php b/library/Businessprocess/Modification/NodeAddChildrenAction.php new file mode 100644 index 0000000..162c380 --- /dev/null +++ b/library/Businessprocess/Modification/NodeAddChildrenAction.php @@ -0,0 +1,74 @@ +<?php + +namespace Icinga\Module\Businessprocess\Modification; + +use Icinga\Module\Businessprocess\BpConfig; + +class NodeAddChildrenAction extends NodeAction +{ + protected $children = array(); + + protected $preserveProperties = array('children'); + + /** + * @inheritdoc + */ + public function appliesTo(BpConfig $config) + { + $name = $this->getNodeName(); + + if (! $config->hasBpNode($name)) { + $this->error('Process "%s" not found', $name); + } + + return true; + } + + /** + * @inheritdoc + */ + public function applyTo(BpConfig $config) + { + $node = $config->getBpNode($this->getNodeName()); + + foreach ($this->children as $name) { + if (! $config->hasNode($name) || $config->getNode($name)->getBpConfig()->getName() !== $config->getName()) { + [$prefix, $suffix] = BpConfig::splitNodeName($name); + if ($suffix !== null) { + if ($suffix === 'Hoststatus') { + $config->createHost($prefix); + } else { + $config->createService($prefix, $suffix); + } + } elseif ($name[0] === '@' && strpos($name, ':') !== false) { + list($configName, $nodeName) = preg_split('~:\s*~', substr($name, 1), 2); + $config->createImportedNode($configName, $nodeName); + } + } + $node->addChild($config->getNode($name)); + } + + return $this; + } + + /** + * @param array|string $children + * @return $this + */ + public function setChildren($children) + { + if (is_string($children)) { + $children = array($children); + } + $this->children = $children; + return $this; + } + + /** + * @return array + */ + public function getChildren() + { + return $this->children; + } +} diff --git a/library/Businessprocess/Modification/NodeApplyManualOrderAction.php b/library/Businessprocess/Modification/NodeApplyManualOrderAction.php new file mode 100644 index 0000000..4ad53e0 --- /dev/null +++ b/library/Businessprocess/Modification/NodeApplyManualOrderAction.php @@ -0,0 +1,35 @@ +<?php + +namespace Icinga\Module\Businessprocess\Modification; + +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\Common\Sort; + +class NodeApplyManualOrderAction extends NodeAction +{ + use Sort; + + public function appliesTo(BpConfig $config) + { + return $config->getMetadata()->get('ManualOrder') !== 'yes'; + } + + public function applyTo(BpConfig $config) + { + $i = 0; + foreach ($config->getBpNodes() as $name => $node) { + if ($node->getDisplay() > 0) { + $node->setDisplay(++$i); + } + + if ($node->hasChildren()) { + $node->setChildNames(array_keys( + $this->setSort('display_name asc') + ->sort($node->getChildren()) + )); + } + } + + $config->getMetadata()->set('ManualOrder', 'yes'); + } +} diff --git a/library/Businessprocess/Modification/NodeCopyAction.php b/library/Businessprocess/Modification/NodeCopyAction.php new file mode 100644 index 0000000..80d781b --- /dev/null +++ b/library/Businessprocess/Modification/NodeCopyAction.php @@ -0,0 +1,48 @@ +<?php + +namespace Icinga\Module\Businessprocess\Modification; + +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\Common\Sort; + +class NodeCopyAction extends NodeAction +{ + use Sort; + + /** + * @param BpConfig $config + * @return bool + */ + public function appliesTo(BpConfig $config) + { + $name = $this->getNodeName(); + + if (! $config->hasBpNode($name)) { + $this->error('Process "%s" not found', $name); + } + + if ($config->hasRootNode($name)) { + $this->error('A toplevel node with name "%s" already exists', $name); + } + + return true; + } + + /** + * @param BpConfig $config + */ + public function applyTo(BpConfig $config) + { + $name = $this->getNodeName(); + + $display = 1; + if ($config->getMetadata()->isManuallyOrdered()) { + $rootNodes = self::applyManualSorting($config->getRootNodes()); + $display = end($rootNodes)->getDisplay() + 1; + } + + $config->addRootNode($name) + ->getBpNode($name) + ->setDisplay($display); + } +} diff --git a/library/Businessprocess/Modification/NodeCreateAction.php b/library/Businessprocess/Modification/NodeCreateAction.php new file mode 100644 index 0000000..167d3bc --- /dev/null +++ b/library/Businessprocess/Modification/NodeCreateAction.php @@ -0,0 +1,129 @@ +<?php + +namespace Icinga\Module\Businessprocess\Modification; + +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\BpNode; +use Icinga\Module\Businessprocess\Node; + +class NodeCreateAction extends NodeAction +{ + /** @var string */ + protected $parentName; + + /** @var array */ + protected $properties = array(); + + /** @var array */ + protected $preserveProperties = array('parentName', 'properties'); + + /** + * @param Node $name + */ + public function setParent(Node $name) + { + $this->parentName = $name->getName(); + } + + /** + * @return bool + */ + public function hasParent() + { + return $this->parentName !== null; + } + + /** + * @return string + */ + public function getParentName() + { + return $this->parentName; + } + + /** + * @param string $name + */ + public function setParentName($name) + { + $this->parentName = $name; + } + + /** + * @return array + */ + public function getProperties() + { + return $this->properties; + } + + /** + * @param array $properties + * @return $this + */ + public function setProperties($properties) + { + $this->properties = (array) $properties; + return $this; + } + + /** + * @inheritdoc + */ + public function appliesTo(BpConfig $config) + { + $name = $this->getNodeName(); + if ($config->hasNode($name)) { + $this->error('A node with name "%s" already exists', $name); + } + + $parent = $this->getParentName(); + if ($parent !== null && !$config->hasBpNode($parent)) { + $this->error('Parent process "%s" missing', $parent); + } + + return true; + } + + /** + * @inheritdoc + */ + public function applyTo(BpConfig $config) + { + $name = $this->getNodeName(); + + $properties = array( + 'name' => $name, + 'operator' => $this->properties['operator'], + ); + if (array_key_exists('childNames', $this->properties)) { + $properties['child_names'] = $this->properties['childNames']; + } else { + $properties['child_names'] = array(); + } + $node = new BpNode((object) $properties); + $node->setBpConfig($config); + + foreach ($this->getProperties() as $key => $val) { + if ($key === 'parentName') { + $config->getBpNode($val)->addChild($node); + continue; + } + $func = 'set' . ucfirst($key); + $node->$func($val); + } + + if ($node->getDisplay() > 1) { + $i = $node->getDisplay(); + foreach ($config->getRootNodes() as $_ => $rootNode) { + if ($rootNode->getDisplay() >= $node->getDisplay()) { + $rootNode->setDisplay(++$i); + } + } + } + + $config->addNode($name, $node); + + return $node; + } +} diff --git a/library/Businessprocess/Modification/NodeModifyAction.php b/library/Businessprocess/Modification/NodeModifyAction.php new file mode 100644 index 0000000..1b33094 --- /dev/null +++ b/library/Businessprocess/Modification/NodeModifyAction.php @@ -0,0 +1,121 @@ +<?php + +namespace Icinga\Module\Businessprocess\Modification; + +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\Node; + +class NodeModifyAction extends NodeAction +{ + protected $properties = array(); + + protected $formerProperties = array(); + + protected $preserveProperties = array('formerProperties', 'properties'); + + /** + * Set properties for a specific node + * + * Can be called multiple times + * + * @param Node $node + * @param array $properties + * + * @return $this + */ + public function setNodeProperties(Node $node, array $properties) + { + foreach (array_keys($properties) as $key) { + $this->properties[$key] = $properties[$key]; + + if (array_key_exists($key, $this->formerProperties)) { + continue; + } + + $func = 'get' . ucfirst($key); + $this->formerProperties[$key] = $node->$func(); + } + + return $this; + } + + /** + * @inheritdoc + */ + public function appliesTo(BpConfig $config) + { + $name = $this->getNodeName(); + + if (! $config->hasNode($name)) { + $this->error('Node "%s" not found', $name); + } + + $node = $config->getNode($name); + + foreach ($this->properties as $key => $val) { + $currentVal = $node->{'get' . ucfirst($key)}(); + if ($this->formerProperties[$key] !== $currentVal) { + $this->error( + 'Property %s of node "%s" changed its value from "%s" to "%s"', + $key, + $name, + $this->formerProperties[$key], + $currentVal + ); + } + } + + return true; + } + + /** + * @inheritdoc + */ + public function applyTo(BpConfig $config) + { + $node = $config->getNode($this->getNodeName()); + + foreach ($this->properties as $key => $val) { + $func = 'set' . ucfirst($key); + $node->$func($val); + } + + return $this; + } + + /** + * @param $properties + * @return $this + */ + public function setProperties($properties) + { + $this->properties = $properties; + return $this; + } + + /** + * @param $properties + * @return $this + */ + public function setFormerProperties($properties) + { + $this->formerProperties = $properties; + return $this; + } + + /** + * @return array + */ + public function getProperties() + { + return $this->properties; + } + + /** + * @return array + */ + public function getFormerProperties() + { + return $this->formerProperties; + } +} diff --git a/library/Businessprocess/Modification/NodeMoveAction.php b/library/Businessprocess/Modification/NodeMoveAction.php new file mode 100644 index 0000000..4c4305d --- /dev/null +++ b/library/Businessprocess/Modification/NodeMoveAction.php @@ -0,0 +1,227 @@ +<?php + +namespace Icinga\Module\Businessprocess\Modification; + +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\BpNode; +use Icinga\Module\Businessprocess\Common\Sort; + +class NodeMoveAction extends NodeAction +{ + use Sort; + + /** + * @var string + */ + protected $parent; + + /** + * @var string + */ + protected $newParent; + + /** + * @var int + */ + protected $from; + + /** + * @var int + */ + protected $to; + + protected $preserveProperties = ['parent', 'newParent', 'from', 'to']; + + public function setParent($name) + { + $this->parent = $name; + } + + public function getParent() + { + return $this->parent; + } + + public function setNewParent($name) + { + $this->newParent = $name; + } + + public function getNewParent() + { + return $this->newParent; + } + + public function setFrom($from) + { + $this->from = (int) $from; + } + + public function getFrom() + { + return $this->from; + } + + public function setTo($to) + { + $this->to = (int) $to; + } + + public function getTo() + { + return $this->to; + } + + public function appliesTo(BpConfig $config) + { + if (! $config->getMetadata()->isManuallyOrdered()) { + $this->error('Process configuration is not manually ordered yet'); + } + + $name = $this->getNodeName(); + if ($this->parent !== null) { + if (! $config->hasBpNode($this->parent)) { + $this->error('Parent process "%s" missing', $this->parent); + } + $parent = $config->getBpNode($this->parent); + if (! $parent->hasChild($name)) { + $this->error('Node "%s" not found in process "%s"', $name, $this->parent); + } + + $nodes = $parent->getChildNames(); + if (! isset($nodes[$this->from]) || $nodes[$this->from] !== $name) { + $reversedNodes = array_reverse($nodes); // The user may have reversed the sort direction + if (! isset($reversedNodes[$this->from]) || $reversedNodes[$this->from] !== $name) { + $this->error('Node "%s" not found at position %d', $name, $this->from); + } else { + $this->from = array_search($reversedNodes[$this->from], $nodes, true); + $this->to = array_search($reversedNodes[$this->to], $nodes, true); + } + } + } else { + if (! $config->hasRootNode($name)) { + $this->error('Toplevel process "%s" not found', $name); + } + + $nodes = array_keys(self::applyManualSorting($config->getRootNodes())); + if (! isset($nodes[$this->from]) || $nodes[$this->from] !== $name) { + $reversedNodes = array_reverse($nodes); // The user may have reversed the sort direction + if (! isset($reversedNodes[$this->from]) || $reversedNodes[$this->from] !== $name) { + $this->error('Toplevel process "%s" not found at position %d', $name, $this->from); + } else { + $this->from = array_search($reversedNodes[$this->from], $nodes, true); + $this->to = array_search($reversedNodes[$this->to], $nodes, true); + } + } + } + + if ($this->parent !== $this->newParent) { + if ($this->newParent !== null) { + if (! $config->hasBpNode($this->newParent)) { + $this->error('New parent process "%s" missing', $this->newParent); + } elseif ($config->getBpNode($this->newParent)->hasChild($name)) { + $this->error( + 'New parent process "%s" already has a node with the name "%s"', + $this->newParent, + $name + ); + } + + $childrenCount = $config->getBpNode($this->newParent)->countChildren(); + if ($this->to > 0 && $childrenCount < $this->to) { + $this->error( + 'New parent process "%s" has not enough children. Target position %d out of range', + $this->newParent, + $this->to + ); + } + } else { + if ($config->hasRootNode($name)) { + $this->error('Process "%s" is already a toplevel process', $name); + } + + $childrenCount = $config->countChildren(); + if ($this->to > 0 && $childrenCount < $this->to) { + $this->error( + 'Process configuration has not enough toplevel processes. Target position %d out of range', + $this->to + ); + } + } + } + + return true; + } + + public function applyTo(BpConfig $config) + { + $name = $this->getNodeName(); + if ($this->parent !== null) { + $nodes = $config->getBpNode($this->parent)->getChildren(); + } else { + $nodes = self::applyManualSorting($config->getRootNodes()); + } + + $node = $nodes[$name]; + $nodes = array_merge( + array_slice($nodes, 0, $this->from, true), + array_slice($nodes, $this->from + 1, null, true) + ); + if ($this->parent === $this->newParent) { + $nodes = array_merge( + array_slice($nodes, 0, $this->to, true), + [$name => $node], + array_slice($nodes, $this->to, null, true) + ); + } else { + if ($this->newParent !== null) { + $newNodes = $config->getBpNode($this->newParent)->getChildren(); + } else { + $newNodes = self::applyManualSorting($config->getRootNodes()); + } + + $newNodes = array_merge( + array_slice($newNodes, 0, $this->to, true), + [$name => $node], + array_slice($newNodes, $this->to, null, true) + ); + + if ($this->newParent !== null) { + $config->getBpNode($this->newParent)->setChildNames(array_keys($newNodes)); + } else { + $config->addRootNode($name); + + $i = 0; + foreach ($newNodes as $newName => $newNode) { + /** @var BpNode $newNode */ + if ($newNode->getDisplay() > 0 || $newName === $name) { + $i += 1; + if ($newNode->getDisplay() !== $i) { + $newNode->setDisplay($i); + } + } + } + } + } + + if ($this->parent !== null) { + $config->getBpNode($this->parent)->setChildNames(array_keys($nodes)); + } else { + if ($this->newParent !== null) { + $config->removeRootNode($name); + $node->setDisplay(0); + } + + $i = 0; + foreach ($nodes as $_ => $oldNode) { + /** @var BpNode $oldNode */ + if ($oldNode->getDisplay() > 0) { + $i += 1; + if ($oldNode->getDisplay() !== $i) { + $oldNode->setDisplay($i); + } + } + } + } + } +} diff --git a/library/Businessprocess/Modification/NodeRemoveAction.php b/library/Businessprocess/Modification/NodeRemoveAction.php new file mode 100644 index 0000000..6100146 --- /dev/null +++ b/library/Businessprocess/Modification/NodeRemoveAction.php @@ -0,0 +1,125 @@ +<?php + +namespace Icinga\Module\Businessprocess\Modification; + +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\BpNode; +use Icinga\Module\Businessprocess\Node; + +/** + * NodeRemoveAction + * + * Tracks removed nodes + * + * @package Icinga\Module\Businessprocess + */ +class NodeRemoveAction extends NodeAction +{ + protected $preserveProperties = array('parentName'); + + protected $parentName; + + /** + * @param $parentName + * @return $this + */ + public function setParentName($parentName = null) + { + $this->parentName = $parentName; + return $this; + } + + /** + * @return mixed + */ + public function getParentName() + { + return $this->parentName; + } + + /** + * @inheritdoc + */ + public function appliesTo(BpConfig $config) + { + $name = $this->getNodeName(); + $parent = $this->getParentName(); + if ($parent === null) { + if (!$config->hasNode($name)) { + $this->error('Toplevel process "%s" not found', $name); + } + } else { + if (! $config->hasNode($parent)) { + $this->error('Parent process "%s" missing', $parent); + } elseif (! $config->getBpNode($parent)->hasChild($name)) { + $this->error('Node "%s" not found in process "%s"', $name, $parent); + } + } + + return true; + } + + /** + * @inheritdoc + */ + public function applyTo(BpConfig $config) + { + $name = $this->getNodeName(); + $parentName = $this->getParentName(); + $node = $config->getNode($name); + + /** @var ?BpNode $parentBpNode */ + $parentBpNode = $parentName ? $config->getNode($parentName) : null; + $this->updateStateOverrides($node, $parentBpNode); + + if ($parentName === null) { + if (! $config->hasBpNode($name)) { + $config->removeNode($name); + } else { + $oldDisplay = $config->getBpNode($name)->getDisplay(); + $config->removeNode($name); + if ($config->getMetadata()->isManuallyOrdered()) { + foreach ($config->getRootNodes() as $_ => $node) { + $nodeDisplay = $node->getDisplay(); + if ($nodeDisplay > $oldDisplay) { + $node->setDisplay($node->getDisplay() - 1); + } elseif ($nodeDisplay === $oldDisplay) { + break; // Stop immediately to not make things worse ;) + } + } + } + } + } else { + $parent = $config->getBpNode($parentName); + $parent->removeChild($name); + $node->removeParent($parentName); + if (! $node->hasParents()) { + $config->removeNode($name); + } + } + } + + /** + * Update state overrides + * + * @param Node $node + * @param BpNode|null $nodeParent + * + * @return void + */ + private function updateStateOverrides(Node $node, ?BpNode $nodeParent): void + { + $parents = []; + if ($nodeParent !== null) { + $parents = [$nodeParent]; + } else { + $parents = $node->getParents(); + } + + foreach ($parents as $parent) { + $parentStateOverrides = $parent->getStateOverrides(); + unset($parentStateOverrides[$node->getName()]); + $parent->setStateOverrides($parentStateOverrides); + } + } +} diff --git a/library/Businessprocess/Modification/ProcessChanges.php b/library/Businessprocess/Modification/ProcessChanges.php new file mode 100644 index 0000000..9257558 --- /dev/null +++ b/library/Businessprocess/Modification/ProcessChanges.php @@ -0,0 +1,294 @@ +<?php + +namespace Icinga\Module\Businessprocess\Modification; + +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\Node; +use Icinga\Web\Session\SessionNamespace as Session; + +class ProcessChanges +{ + /** @var NodeAction[] */ + protected $changes = array(); + + /** @var Session */ + protected $session; + + /** @var BpConfig */ + protected $config; + + /** @var bool */ + protected $hasBeenModified = false; + + /** @var string Session storage key for this processes changes */ + protected $sessionKey; + + /** + * ProcessChanges constructor. + * + * Direct access is not allowed + */ + private function __construct() + { + } + + /** + * @param BpConfig $bp + * @param Session $session + * + * @return ProcessChanges + */ + public static function construct(BpConfig $bp, Session $session) + { + $key = 'changes.' . $bp->getName(); + $changes = new ProcessChanges(); + $changes->sessionKey = $key; + + if ($actions = $session->get($key)) { + foreach ($actions as $string) { + $changes->push(NodeAction::unSerialize($string)); + } + } + $changes->session = $session; + $changes->config = $bp; + + return $changes; + } + + /** + * @param Node $node + * @param $properties + * + * @return $this + */ + public function modifyNode(Node $node, $properties) + { + $action = new NodeModifyAction($node); + $action->setNodeProperties($node, $properties); + return $this->push($action, true); + } + + /** + * @param Node $node + * @param $properties + * + * @return $this + */ + public function addChildrenToNode($children, Node $node = null) + { + $action = new NodeAddChildrenAction($node); + $action->setChildren($children); + return $this->push($action, true); + } + + /** + * @param Node|string $nodeName + * @param array $properties + * @param Node $parent + * + * @return $this + */ + public function createNode($nodeName, $properties, Node $parent = null) + { + $action = new NodeCreateAction($nodeName); + $action->setProperties($properties); + if ($parent !== null) { + $action->setParent($parent); + } + return $this->push($action, true); + } + + /** + * @param $nodeName + * @return $this + */ + public function copyNode($nodeName) + { + $action = new NodeCopyAction($nodeName); + return $this->push($action, true); + } + + /** + * @param Node $node + * @param string $parentName + * @return $this + */ + public function deleteNode(Node $node, $parentName = null) + { + $action = new NodeRemoveAction($node); + if ($parentName !== null) { + $action->setParentName($parentName); + } + + return $this->push($action, true); + } + + /** + * Move the given node + * + * @param Node $node + * @param int $from + * @param int $to + * @param string $newParent + * @param string $parent + * + * @return $this + */ + public function moveNode(Node $node, $from, $to, $newParent, $parent = null) + { + $action = new NodeMoveAction($node); + $action->setParent($parent); + $action->setNewParent($newParent); + $action->setFrom($from); + $action->setTo($to); + + return $this->push($action, true); + } + + /** + * Apply manual order on the entire bp configuration file + * + * @return $this + */ + public function applyManualOrder() + { + return $this->push(new NodeApplyManualOrderAction(), true); + } + + /** + * Add a new action to the stack + * + * @param NodeAction $change + * @param bool $apply + * + * @return $this + */ + public function push(NodeAction $change, $apply = false) + { + if ($apply && $change->appliesTo($this->config)) { + $change->applyTo($this->config); + } + + $this->changes[] = $change; + $this->hasBeenModified = true; + return $this; + } + + /** + * Get all stacked actions + * + * @return NodeAction[] + */ + public function getChanges() + { + return $this->changes; + } + + /** + * Forget all changes and remove them from the Session + * + * @return $this + */ + public function clear() + { + $this->hasBeenModified = true; + $this->changes = array(); + $this->session->set($this->getSessionKey(), null); + return $this; + } + + /** + * Whether there are no stacked changes + * + * @return bool + */ + public function isEmpty() + { + return $this->count() === 0; + } + + /** + * Number of stacked changes + * + * @return int + */ + public function count() + { + return count($this->changes); + } + + /** + * Get the first change on the stack, false if empty + * + * @return NodeAction|boolean + */ + public function shift() + { + if ($this->isEmpty()) { + return false; + } + + $this->hasBeenModified = true; + return array_shift($this->changes); + } + + /** + * Get the last change on the stack, false if empty + * + * @return NodeAction|boolean + */ + public function pop() + { + if ($this->isEmpty()) { + return false; + } + + $this->hasBeenModified = true; + return array_pop($this->changes); + } + + /** + * The identifier used for this processes changes in our Session storage + * + * @return string + */ + protected function getSessionKey() + { + return $this->sessionKey; + } + + protected function hasBeenModified() + { + return $this->hasBeenModified; + } + + /** + * @return array + */ + public function serialize() + { + $serialized = array(); + foreach ($this->getChanges() as $change) { + $serialized[] = $change->serialize(); + } + + return $serialized; + } + + /** + * Persist to session on destruction + */ + public function __destruct() + { + if (! $this->hasBeenModified()) { + unset($this->session); + return; + } + $session = $this->session; + $key = $this->getSessionKey(); + if (! $this->isEmpty()) { + $session->set($key, $this->serialize()); + } + unset($this->session); + } +} |