vendor/jackalope/jackalope/src/Jackalope/Node.php line 1731

Open in your IDE?
  1. <?php
  2. namespace Jackalope;
  3. use ArrayIterator;
  4. use Iterator;
  5. use IteratorAggregate;
  6. use Exception;
  7. use InvalidArgumentException;
  8. use Jackalope\NodeType\NodeType;
  9. use LogicException;
  10. use PHPCR\AccessDeniedException;
  11. use PHPCR\Lock\LockException;
  12. use PHPCR\NamespaceException;
  13. use PHPCR\NodeType\NodeDefinitionInterface;
  14. use PHPCR\NodeType\NodeTypeInterface;
  15. use PHPCR\PropertyType;
  16. use PHPCR\NodeInterface;
  17. use PHPCR\NodeType\ConstraintViolationException;
  18. use PHPCR\NodeType\NoSuchNodeTypeException;
  19. use PHPCR\RepositoryException;
  20. use PHPCR\PathNotFoundException;
  21. use PHPCR\ItemNotFoundException;
  22. use PHPCR\InvalidItemStateException;
  23. use PHPCR\ItemExistsException;
  24. use PHPCR\UnsupportedRepositoryOperationException;
  25. use PHPCR\Util\PathHelper;
  26. use PHPCR\Util\NodeHelper;
  27. use PHPCR\Util\UUIDHelper;
  28. use PHPCR\ValueFormatException;
  29. use PHPCR\Version\VersionException;
  30. /**
  31.  * {@inheritDoc}
  32.  *
  33.  * @license http://www.apache.org/licenses Apache License Version 2.0, January 2004
  34.  * @license http://opensource.org/licenses/MIT MIT License
  35.  *
  36.  * @api
  37.  */
  38. class Node extends Item implements IteratorAggregateNodeInterface
  39. {
  40.     /**
  41.      * The index if this is a same-name sibling.
  42.      *
  43.      * TODO: fully implement same-name siblings
  44.      * @var int
  45.      */
  46.     protected $index 1;
  47.     /**
  48.      * The primary type name of this node
  49.      *
  50.      * @var string
  51.      */
  52.     protected $primaryType;
  53.     /**
  54.      * mapping of property name to PropertyInterface objects.
  55.      *
  56.      * all properties are instantiated in the constructor
  57.      *
  58.      * OPTIMIZE: lazy instantiate property objects, just have local array of values
  59.      *
  60.      * @var Property[]
  61.      */
  62.     protected $properties = [];
  63.     /**
  64.      * keep track of properties to be deleted until the save operation was successful.
  65.      *
  66.      * this is needed in order to track deletions in case of refresh
  67.      *
  68.      * keys are the property names, values the properties (in state deleted)
  69.      */
  70.     protected $deletedProperties = [];
  71.     /**
  72.      * ordered list of the child node names
  73.      *
  74.      * @var array
  75.      */
  76.     protected $nodes = [];
  77.     /**
  78.      * ordered list of the child node names as known to be at the backend
  79.      *
  80.      * used to calculate reordering operations if orderBefore() was used
  81.      *
  82.      * @var array
  83.      */
  84.     protected $originalNodesOrder null;
  85.     /**
  86.      * Cached instance of the node definition that defines this node
  87.      *
  88.      * @var NodeDefinitionInterface
  89.      * @see Node::getDefinition()
  90.      */
  91.     protected $definition;
  92.     /**
  93.      * Create a new node instance with data from the storage layer
  94.      *
  95.      * This is only to be called by the Factory::get() method even inside the
  96.      * Jackalope implementation to allow for custom implementations of Nodes.
  97.      *
  98.      * @param FactoryInterface $factory       the object factory
  99.      * @param array            $rawData       in the format as returned from TransportInterface::getNode
  100.      * @param string           $path          the absolute path of this node
  101.      * @param Session          $session
  102.      * @param ObjectManager    $objectManager
  103.      * @param boolean          $new           set to true if this is a new node being created.
  104.      *      Defaults to false which means the node is loaded from storage.
  105.      *
  106.      * @see TransportInterface::getNode()
  107.      *
  108.      * @throws RepositoryException
  109.      *
  110.      * @private
  111.      */
  112.     public function __construct(FactoryInterface $factory$rawData$pathSession $sessionObjectManager $objectManager$new false)
  113.     {
  114.         parent::__construct($factory$path$session$objectManager$new);
  115.         $this->isNode true;
  116.         $this->parseData($rawDatafalse);
  117.     }
  118.     /**
  119.      * Initialize or update this object with raw data from backend.
  120.      *
  121.      * @param array $rawData in the format as returned from Jackalope\Transport\TransportInterface
  122.      * @param boolean $update whether to initialize this object or update
  123.      * @param boolean $keepChanges only used if $update is true, same as $keepChanges in refresh()
  124.      *
  125.      * @see Node::__construct()
  126.      * @see Node::refresh()
  127.      *
  128.      * @throws \InvalidArgumentException
  129.      * @throws LockException
  130.      * @throws ConstraintViolationException
  131.      * @throws RepositoryException
  132.      * @throws ValueFormatException
  133.      * @throws VersionException
  134.      */
  135.     private function parseData($rawData$update$keepChanges false)
  136.     {
  137.         //TODO: refactor to use hash array instead of stdClass struct
  138.         if ($update) {
  139.             // keep backup of old state so we can remove what needs to be removed
  140.             $oldNodes array_flip(array_values($this->nodes));
  141.             $oldProperties $this->properties;
  142.         }
  143.         /*
  144.          * we collect all nodes coming from the backend. if we update with
  145.          * $keepChanges, we use this to update the node list rather than losing
  146.          * reorders
  147.          *
  148.          * properties are easy as they are not ordered.
  149.          */
  150.         $nodesInBackend = [];
  151.         foreach ($rawData as $key => $value) {
  152.             $node false// reset to avoid trouble
  153.             if (is_object($value)) {
  154.                 // this is a node. add it if
  155.                 if (! $update // init new node
  156.                     || ! $keepChanges // want to discard changes
  157.                     || isset($oldNodes[$key]) // it was already existing before reloading
  158.                     || ! ($node $this->objectManager->getCachedNode($this->path '/' $key)) // we know nothing about it
  159.                 ) {
  160.                     // for all those cases, if the node was moved away or is deleted in current session, we do not add it
  161.                     if (! $this->objectManager->isNodeMoved($this->path '/' $key)
  162.                         && ! $this->objectManager->isNodeDeleted($this->path '/' $key)
  163.                     ) {
  164.                         // otherwise we (re)load a node from backend but a child has been moved away already
  165.                         $nodesInBackend[] = $key;
  166.                     }
  167.                 }
  168.                 if ($update) {
  169.                     unset($oldNodes[$key]);
  170.                 }
  171.             } else {
  172.                 //property or meta information
  173.                 /* Property type declarations start with :, the value then is
  174.                  * the type string from the NodeType constants. We skip that and
  175.                  * look at the type when we encounter the value of the property.
  176.                  *
  177.                  * If its a binary data, we only get the type declaration and
  178.                  * no data. Then the $value of the type declaration is not the
  179.                  * type string for binary, but the number of bytes of the
  180.                  * property - resp. array of number of bytes.
  181.                  *
  182.                  * The magic property ::NodeIteratorSize tells this node has no
  183.                  * children. Ignore that info for now. We might optimize with
  184.                  * this info once we do prefetch nodes.
  185.                  */
  186.                 if (=== strpos($key':')) {
  187.                     if ((is_int($value) || is_array($value))
  188.                          && $key != '::NodeIteratorSize'
  189.                     ) {
  190.                         // This is a binary property and we just got its length with no data
  191.                         $key substr($key1);
  192.                         if (!isset($rawData->$key)) {
  193.                             $binaries[$key] = $value;
  194.                             if ($update) {
  195.                                 unset($oldProperties[$key]);
  196.                             }
  197.                             if (isset($this->properties[$key])) {
  198.                                 // refresh existing binary, this will only happen in update
  199.                                 // only update length
  200.                                 if (! ($keepChanges && $this->properties[$key]->isModified())) {
  201.                                     $this->properties[$key]->_setLength($value);
  202.                                     if ($this->properties[$key]->isDirty()) {
  203.                                         $this->properties[$key]->setClean();
  204.                                     }
  205.                                 }
  206.                             } else {
  207.                                 // this will always fall into the creation mode
  208.                                 $this->_setProperty($key$valuePropertyType::BINARYtrue);
  209.                             }
  210.                         }
  211.                     } //else this is a type declaration
  212.                     //skip this entry (if its binary, its already processed
  213.                     continue;
  214.                 }
  215.                 if ($update && array_key_exists($key$this->properties)) {
  216.                     unset($oldProperties[$key]);
  217.                     $prop $this->properties[$key];
  218.                     if ($keepChanges && $prop->isModified()) {
  219.                         continue;
  220.                     }
  221.                 } elseif ($update && array_key_exists($key$this->deletedProperties)) {
  222.                     if ($keepChanges) {
  223.                         // keep the delete
  224.                         continue;
  225.                     } else {
  226.                         // restore the property
  227.                         $this->properties[$key] = $this->deletedProperties[$key];
  228.                         $this->properties[$key]->setClean();
  229.                         // now let the loop update the value. no need to talk to ObjectManager as it
  230.                         // does not store property deletions
  231.                     }
  232.                 }
  233.                 switch ($key) {
  234.                     case 'jcr:index':
  235.                         $this->index $value;
  236.                         break;
  237.                     case 'jcr:primaryType':
  238.                         $this->primaryType $value;
  239.                         // type information is exposed as property too,
  240.                         // although there exist more specific methods
  241.                         $this->_setProperty('jcr:primaryType'$valuePropertyType::NAMEtrue);
  242.                         break;
  243.                     case 'jcr:mixinTypes':
  244.                         // type information is exposed as property too,
  245.                         // although there exist more specific methods
  246.                         $this->_setProperty($key$valuePropertyType::NAMEtrue);
  247.                         break;
  248.                     // OPTIMIZE: do not instantiate properties until needed
  249.                     default:
  250.                         if (isset($rawData->{':' $key})) {
  251.                             /*
  252.                              * this is an inconsistency between jackrabbit and
  253.                              * dbal transport: jackrabbit has type name, dbal
  254.                              * delivers numeric type.
  255.                              * we should eventually fix the format returned by
  256.                              * transport and either have jackrabbit transport
  257.                              * do the conversion or let dbal store a string
  258.                              * value instead of numerical.
  259.                              */
  260.                             $type is_numeric($rawData->{':' $key})
  261.                                     ? $rawData->{':' $key}
  262.                                     : PropertyType::valueFromName($rawData->{':' $key});
  263.                         } else {
  264.                             $type $this->valueConverter->determineType($value);
  265.                         }
  266.                         $this->_setProperty($key$value$typetrue);
  267.                         break;
  268.                 }
  269.             }
  270.         }
  271.         if ($update) {
  272.             if ($keepChanges) {
  273.                 // we keep changes. merge new nodes to the right place
  274.                 $previous null;
  275.                 $newFromBackend array_diff($nodesInBackendarray_intersect($this->nodes$nodesInBackend));
  276.                 foreach ($newFromBackend as $name) {
  277.                     $pos array_search($name$nodesInBackend);
  278.                     if (is_array($this->originalNodesOrder)) {
  279.                         // update original order to send the correct reorderings
  280.                         array_splice($this->originalNodesOrder$pos0$name);
  281.                     }
  282.                     if ($pos === 0) {
  283.                         array_unshift($this->nodes$name);
  284.                     } else {
  285.                         // do we find the predecessor of the new node in the list?
  286.                         $insert array_search($nodesInBackend[$pos-1], $this->nodes);
  287.                         if (false !== $insert) {
  288.                             array_splice($this->nodes$insert 10$name);
  289.                         } else {
  290.                             // failed to find predecessor, add to the end
  291.                             $this->nodes[] = $name;
  292.                         }
  293.                     }
  294.                 }
  295.             } else {
  296.                 // discard changes, just overwrite node list
  297.                 $this->nodes $nodesInBackend;
  298.                 $this->originalNodesOrder null;
  299.             }
  300.             foreach ($oldProperties as $name => $property) {
  301.                 if (! ($keepChanges && ($property->isNew()))) {
  302.                     // may not call remove(), we don't want another delete with
  303.                     // the backend to be attempted
  304.                     $this->properties[$name]->setDeleted();
  305.                     unset($this->properties[$name]);
  306.                 }
  307.             }
  308.             // notify nodes that where not received again that they disappeared
  309.             foreach ($oldNodes as $name => $index) {
  310.                 if ($this->objectManager->purgeDisappearedNode($this->path '/' $name$keepChanges)) {
  311.                     // drop, it was not a new child
  312.                     if ($keepChanges) { // otherwise we overwrote $this->nodes with the backend
  313.                         $id array_search($name$this->nodes);
  314.                         if (false !== $id) {
  315.                             unset($this->nodes[$id]);
  316.                         }
  317.                     }
  318.                 }
  319.             }
  320.         } else {
  321.             // new node loaded from backend
  322.             $this->nodes $nodesInBackend;
  323.         }
  324.     }
  325.     /**
  326.      * Creates a new node at the specified $relPath
  327.      *
  328.      * {@inheritDoc}
  329.      *
  330.      * In Jackalope, the child node type definition is immediately applied if no
  331.      * primaryNodeTypeName is specified.
  332.      *
  333.      * The PathNotFoundException and ConstraintViolationException are thrown
  334.      * immediately.
  335.      * Version and Lock related exceptions are delayed until save.
  336.      *
  337.      * @api
  338.      */
  339.     public function addNode($relPath$primaryNodeTypeName null)
  340.     {
  341.         $relPath = (string)$relPath;
  342.         $this->checkState();
  343.         $ntm $this->session->getWorkspace()->getNodeTypeManager();
  344.         // are we not the immediate parent?
  345.         if (strpos($relPath'/') !== false) {
  346.             // forward to real parent
  347.             $relPath PathHelper::absolutizePath($relPath$this->getPath(), true);
  348.             $parentPath PathHelper::getParentPath($relPath);
  349.             $newName PathHelper::getNodeName($relPath);
  350.             try {
  351.                 $parentNode $this->objectManager->getNodeByPath($parentPath);
  352.             } catch (ItemNotFoundException $e) {
  353.                 //we have to throw a different exception if there is a property
  354.                 // with that name than if there is nothing at the path at all.
  355.                 // lets see if the property exists
  356.                 if ($this->session->propertyExists($parentPath)) {
  357.                     throw new ConstraintViolationException("Node '{$this->path}': Not allowed to add a node below property at $parentPath");
  358.                 }
  359.                 throw new PathNotFoundException($e->getMessage(), $e->getCode(), $e);
  360.             }
  361.             return $parentNode->addNode($newName$primaryNodeTypeName);
  362.         }
  363.         if (null === $primaryNodeTypeName) {
  364.             if ($this->primaryType === 'rep:root') {
  365.                 $primaryNodeTypeName 'nt:unstructured';
  366.             } else {
  367.                 $type $ntm->getNodeType($this->primaryType);
  368.                 $nodeDefinitions $type->getChildNodeDefinitions();
  369.                 foreach ($nodeDefinitions as $def) {
  370.                     if (!is_null($def->getDefaultPrimaryType())) {
  371.                         $primaryNodeTypeName $def->getDefaultPrimaryTypeName();
  372.                         break;
  373.                     }
  374.                 }
  375.             }
  376.             if (is_null($primaryNodeTypeName)) {
  377.                 throw new ConstraintViolationException("No matching child node definition found for `$relPath' in type `{$this->primaryType}' for node '{$this->path}'. Please specify the type explicitly.");
  378.             }
  379.         }
  380.         // create child node
  381.         //sanity check: no index allowed. TODO: we should verify this is a valid node name
  382.         if (false !== strpos($relPath']')) {
  383.             throw new RepositoryException("The node '{$this->path}' does not allow an index in name of newly created node: $relPath");
  384.         }
  385.         if (in_array($relPath$this->nodestrue)) {
  386.             throw new ItemExistsException("The node '{$this->path}' already has a child named '$relPath''."); //TODO: same-name siblings if nodetype allows for them
  387.         }
  388.         $data = ['jcr:primaryType' => $primaryNodeTypeName];
  389.         $path $this->getChildPath($relPath);
  390.         $node $this->factory->get(Node::class, [$data$path$this->session$this->objectManagertrue]);
  391.         $this->addChildNode($nodefalse); // no need to check the state, we just checked when entering this method
  392.         $this->objectManager->addNode($path$node);
  393.         if (is_array($this->originalNodesOrder)) {
  394.             // new nodes are added at the end
  395.             $this->originalNodesOrder[] = $relPath;
  396.         }
  397.         //by definition, adding a node sets the parent to modified
  398.         $this->setModified();
  399.         return $node;
  400.     }
  401.     /**
  402.      * {@inheritDoc}
  403.      *
  404.      * @api
  405.      * @throws InvalidArgumentException
  406.      * @throws ItemExistsException
  407.      * @throws PathNotFoundException
  408.      * @throws RepositoryException
  409.      */
  410.     public function addNodeAutoNamed($nameHint null$primaryNodeTypeName null)
  411.     {
  412.         $name NodeHelper::generateAutoNodeName(
  413.             $this->nodes,
  414.             $this->session->getWorkspace()->getNamespaceRegistry()->getNamespaces(),
  415.             'jcr',
  416.             $nameHint
  417.         );
  418.         return $this->addNode($name$primaryNodeTypeName);
  419.     }
  420.     /**
  421.      * Jackalope implements this feature and updates the position of the
  422.      * existing child at srcChildRelPath to be in the list immediately before
  423.      * destChildRelPath.
  424.      *
  425.      * {@inheritDoc}
  426.      *
  427.      * Jackalope has no implementation-specific ordering restriction so no
  428.      * \PHPCR\ConstraintViolationException is expected. VersionException and
  429.      * LockException are not tested immediately but thrown on save.
  430.      *
  431.      * @api
  432.      */
  433.     public function orderBefore($srcChildRelPath$destChildRelPath)
  434.     {
  435.         if ($srcChildRelPath === $destChildRelPath) {
  436.             //nothing to move
  437.             return;
  438.         }
  439.         if (null === $this->originalNodesOrder) {
  440.             $this->originalNodesOrder $this->nodes;
  441.         }
  442.         $this->nodes NodeHelper::orderBeforeArray($srcChildRelPath$destChildRelPath$this->nodes);
  443.         $this->setModified();
  444.     }
  445.     /**
  446.      * {@inheritDoc}
  447.      *
  448.      * @throws PathNotFoundException
  449.      *
  450.      * @api
  451.      * @throws AccessDeniedException
  452.      * @throws ItemNotFoundException
  453.      * @throws \InvalidArgumentException
  454.      */
  455.     public function rename($newName)
  456.     {
  457.         $names = (array) $this->getParent()->getNodeNames();
  458.         $pos array_search($this->name$names);
  459.         $next = isset($names[$pos 1]) ? $names[$pos 1] : null;
  460.         $newPath $this->parentPath '/' $newName;
  461.         if (substr($newPath02) === '//') {
  462.             $newPath substr($newPath1);
  463.         }
  464.         $this->session->move($this->path$newPath);
  465.         if ($next) {
  466.             $this->getParent()->orderBefore($newName$next);
  467.         }
  468.     }
  469.     /**
  470.      * Determine whether the children of this node need to be reordered
  471.      *
  472.      * @return boolean
  473.      *
  474.      * @private
  475.      */
  476.     public function needsChildReordering()
  477.     {
  478.         return (bool) $this->originalNodesOrder;
  479.     }
  480.     /**
  481.      * Returns the orderBefore commands to be applied to the childnodes
  482.      * to get from the original order to the new one
  483.      *
  484.      * @return array of arrays with 2 fields: name of node to order before second name
  485.      *
  486.      * @throws AccessDeniedException
  487.      * @throws ItemNotFoundException
  488.      *
  489.      * @private
  490.      */
  491.     public function getOrderCommands()
  492.     {
  493.         if (! $this->originalNodesOrder) {
  494.             return [];
  495.         }
  496.         $reorders NodeHelper::calculateOrderBefore($this->originalNodesOrder$this->nodes);
  497.         $this->originalNodesOrder null;
  498.         return $reorders;
  499.     }
  500.     /**
  501.      * {@inheritDoc}
  502.      *
  503.      * @param boolean $validate When false, node types are not asked to validate
  504.      *                          whether operation is allowed
  505.      *
  506.      * @throws InvalidItemStateException
  507.      * @throws NamespaceException
  508.      * @throws \InvalidArgumentException
  509.      * @throws AccessDeniedException
  510.      * @throws ItemNotFoundException
  511.      *
  512.      * @api
  513.      */
  514.     public function setProperty($name$value$type PropertyType::UNDEFINED$validate true)
  515.     {
  516.         $this->checkState();
  517.         // abort early when the node value is not changed
  518.         // for multivalue, === is only true when array keys and values match. this is exactly what we need.
  519.         try {
  520.             if (array_key_exists($name$this->properties) && $this->properties[$name]->getValue() === $value) {
  521.                 return $this->properties[$name];
  522.             }
  523.         } catch (RepositoryException $e) {
  524.             // if anything goes wrong trying to get the property value, move on and don't return early
  525.         }
  526.         if ($validate && 'jcr:uuid' === $name && !$this->isNodeType('mix:referenceable')) {
  527.             throw new ConstraintViolationException('You can only change the uuid of newly created nodes that have "referenceable" mixin.');
  528.         }
  529.         if ($validate) {
  530.             if (is_array($value)) {
  531.                 foreach ($value as $key => $v) {
  532.                     if (null === $v) {
  533.                         unset($value[$key]);
  534.                     }
  535.                 }
  536.             }
  537.             $types $this->getMixinNodeTypes();
  538.             array_push($types$this->getPrimaryNodeType());
  539.             if (null !== $value) {
  540.                 $exception null;
  541.                 foreach ($types as $nt) {
  542.                     /** @var $nt NodeType */
  543.                     try {
  544.                         $nt->canSetProperty($name$valuetrue);
  545.                         $exception null;
  546.                         break; // exit loop, we found a valid definition
  547.                     } catch (RepositoryException $e) {
  548.                         if (null === $exception) {
  549.                             $exception $e;
  550.                         }
  551.                     }
  552.                 }
  553.                 if (null !== $exception) {
  554.                     $types 'Primary type '.$this->primaryType;
  555.                     if (isset($this->properties['jcr:mixinTypes'])) {
  556.                         $types .= ', mixins '.implode(','$this->getPropertyValue('jcr:mixinTypes'PropertyType::STRING));
  557.                     }
  558.                     $msg sprintf('Can not set property %s on node %s. Node types do not allow for this: %s'$name$this->path$types);
  559.                     throw new ConstraintViolationException($msg0$exception);
  560.                 }
  561.             } else {
  562.                 // $value is null for property removal
  563.                 // if any type forbids, throw exception
  564.                 foreach ($types as $nt) {
  565.                     /** @var $nt \Jackalope\NodeType\NodeType */
  566.                     $nt->canRemoveProperty($nametrue);
  567.                 }
  568.             }
  569.         }
  570.         //try to get a namespace for the set property
  571.         if (strpos($name':') !== false) {
  572.             list($prefix) = explode(':'$name);
  573.             //Check if the namespace exists. If not, throw an NamespaceException
  574.             $this->session->getNamespaceURI($prefix);
  575.         }
  576.         if (is_null($value)) {
  577.             if (isset($this->properties[$name])) {
  578.                 $this->properties[$name]->remove();
  579.             }
  580.             return null;
  581.         }
  582.         // if the property is the UUID, then register the UUID against the path
  583.         // of this node.
  584.         if ('jcr:uuid' === $name) {
  585.             $this->objectManager->registerUuid($value$this->getPath());
  586.         }
  587.         return $this->_setProperty($name$value$typefalse);
  588.     }
  589.     /**
  590.      * {@inheritDoc}
  591.      *
  592.      * @throws InvalidItemStateException
  593.      *
  594.      * @api
  595.      */
  596.     public function getNode($relPath)
  597.     {
  598.         $this->checkState();
  599.         $relPath = (string) $relPath;
  600.         if ('' === $relPath || '/' === $relPath[0]) {
  601.             throw new PathNotFoundException("$relPath is not a relative path");
  602.         }
  603.         try {
  604.             $node $this->objectManager->getNodeByPath(PathHelper::absolutizePath($relPath$this->path));
  605.         } catch (ItemNotFoundException $e) {
  606.             throw new PathNotFoundException($e->getMessage(), $e->getCode(), $e);
  607.         }
  608.         return $node;
  609.     }
  610.     /**
  611.      * {@inheritDoc}
  612.      *
  613.      * @api
  614.      */
  615.     public function getNodes($nameFilter null$typeFilter null)
  616.     {
  617.         $this->checkState();
  618.         $names self::filterNames($nameFilter$this->nodes);
  619.         $result = [];
  620.         if (count($names)) {
  621.             $paths = [];
  622.             foreach ($names as $name) {
  623.                 $paths[] = PathHelper::absolutizePath($name$this->path);
  624.             }
  625.             $nodes $this->objectManager->getNodesByPath($pathsNode::class, $typeFilter);
  626.             // OPTIMIZE if we lazy-load in ObjectManager we should not do this loop
  627.             foreach ($nodes as $node) {
  628.                 $result[$node->getName()] = $node;
  629.             }
  630.         }
  631.         return new ArrayIterator($result);
  632.     }
  633.     /**
  634.      * {@inheritDoc}
  635.      *
  636.      * @api
  637.      */
  638.     public function getNodeNames($nameFilter null$typeFilter null)
  639.     {
  640.         $this->checkState();
  641.         if (null !== $typeFilter) {
  642.             return $this->objectManager->filterChildNodeNamesByType($this$nameFilter$typeFilter);
  643.         }
  644.         $names self::filterNames($nameFilter$this->nodes);
  645.         return new ArrayIterator($names);
  646.     }
  647.     /**
  648.      * {@inheritDoc}
  649.      *
  650.      * @throws InvalidItemStateException
  651.      *
  652.      * @api
  653.      */
  654.     public function getProperty($relPath)
  655.     {
  656.         $this->checkState();
  657.         if (false === strpos($relPath'/')) {
  658.             if (!isset($this->properties[$relPath])) {
  659.                 throw new PathNotFoundException("Property $relPath in ".$this->path);
  660.             }
  661.             if ($this->properties[$relPath]->isDeleted()) {
  662.                 throw new PathNotFoundException("Property '$relPath' of " $this->path ' is deleted');
  663.             }
  664.             return $this->properties[$relPath];
  665.         }
  666.         return $this->session->getProperty($this->getChildPath($relPath));
  667.     }
  668.     /**
  669.      * This method is only meant for the transport to be able to still build a
  670.      * store request for afterwards deleted nodes to support the operationslog.
  671.      *
  672.      * @return Property[] with just the jcr:primaryType property in it
  673.      *
  674.      * @see \Jackalope\Transport\WritingInterface::storeNodes
  675.      *
  676.      * @throws InvalidItemStateException
  677.      * @throws RepositoryException
  678.      *
  679.      * @private
  680.      */
  681.     public function getPropertiesForStoreDeletedNode()
  682.     {
  683.         if (! $this->isDeleted()) {
  684.             throw new InvalidItemStateException('You are not supposed to call this on a not deleted node');
  685.         }
  686.         $myProperty $this->properties['jcr:primaryType'];
  687.         $myProperty->setClean();
  688.         $path $this->getChildPath('jcr:primaryType');
  689.         $property $this->factory->get(
  690.             'Property',
  691.             [['type' => $myProperty->getType(), 'value' => $myProperty->getValue()],
  692.                 $path,
  693.                 $this->session,
  694.                 $this->objectManager
  695.             ]
  696.         );
  697.         $myProperty->setDeleted();
  698.         return ['jcr:primaryType' => $property];
  699.     }
  700.     /**
  701.      * {@inheritDoc}
  702.      *
  703.      * @throws InvalidItemStateException
  704.      * @throws \InvalidArgumentException
  705.      *
  706.      * @api
  707.      */
  708.     public function getPropertyValue($name$type null)
  709.     {
  710.         $this->checkState();
  711.         $val $this->getProperty($name)->getValue();
  712.         if (null !== $type) {
  713.             $val $this->valueConverter->convertType($val$type);
  714.         }
  715.         return $val;
  716.     }
  717.     /**
  718.      * {@inheritDoc}
  719.      *
  720.      * @throws \InvalidArgumentException
  721.      *
  722.      * @throws InvalidItemStateException
  723.      * @throws PathNotFoundException
  724.      * @throws ValueFormatException
  725.      *
  726.      * @api
  727.      */
  728.     public function getPropertyValueWithDefault($relPath$defaultValue)
  729.     {
  730.         if ($this->hasProperty($relPath)) {
  731.             return $this->getPropertyValue($relPath);
  732.         }
  733.         return $defaultValue;
  734.     }
  735.     /**
  736.      * {@inheritDoc}
  737.      *
  738.      * @api
  739.      */
  740.     public function getProperties($nameFilter null)
  741.     {
  742.         $this->checkState();
  743.         //OPTIMIZE: lazy iterator?
  744.         $names self::filterNames($nameFilterarray_keys($this->properties));
  745.         $result = [];
  746.         foreach ($names as $name) {
  747.             //we know for sure the properties exist, as they come from the
  748.             // array keys of the array we are accessing
  749.             $result[$name] = $this->properties[$name];
  750.         }
  751.         return new ArrayIterator($result);
  752.     }
  753.     /**
  754.      * {@inheritDoc}
  755.      *
  756.      * @throws \InvalidArgumentException
  757.      * @throws InvalidItemStateException
  758.      * @throws ValueFormatException
  759.      * @throws ItemNotFoundException
  760.      *
  761.      * @api
  762.      */
  763.     public function getPropertiesValues($nameFilter null$dereference true)
  764.     {
  765.         $this->checkState();
  766.         // OPTIMIZE: do not create properties in constructor, go over array here
  767.         $names self::filterNames($nameFilterarray_keys($this->properties));
  768.         $result = [];
  769.         foreach ($names as $name) {
  770.             //we know for sure the properties exist, as they come from the
  771.             // array keys of the array we are accessing
  772.             $type $this->properties[$name]->getType();
  773.             if (! $dereference &&
  774.                     (PropertyType::REFERENCE === $type
  775.                     || PropertyType::WEAKREFERENCE === $type
  776.                     || PropertyType::PATH === $type)
  777.             ) {
  778.                 $result[$name] = $this->properties[$name]->getString();
  779.             } else {
  780.                 // OPTIMIZE: collect the paths and call objectmanager->getNodesByPath once
  781.                 $result[$name] = $this->properties[$name]->getValue();
  782.             }
  783.         }
  784.         return $result;
  785.     }
  786.     /**
  787.      * {@inheritDoc}
  788.      *
  789.      * @api
  790.      */
  791.     public function getPrimaryItem()
  792.     {
  793.         try {
  794.             $primary_item null;
  795.             $item_name $this->getPrimaryNodeType()->getPrimaryItemName();
  796.             if ($item_name !== null) {
  797.                 $primary_item $this->session->getItem($this->path '/' $item_name);
  798.             }
  799.         } catch (Exception $ex) {
  800.             throw new RepositoryException("An error occured while reading the primary item of the node '{$this->path}': " $ex->getMessage());
  801.         }
  802.         if ($primary_item === null) {
  803.             throw new ItemNotFoundException("No primary item found for node '{$this->path}'");
  804.         }
  805.         return $primary_item;
  806.     }
  807.     /**
  808.      * @return string a universally unique id.
  809.      */
  810.     protected function generateUuid()
  811.     {
  812.         return UUIDHelper::generateUUID();
  813.     }
  814.     /**
  815.      * {@inheritDoc}
  816.      *
  817.      * @throws \InvalidArgumentException
  818.      *
  819.      * @throws AccessDeniedException
  820.      * @throws InvalidItemStateException
  821.      * @throws ItemNotFoundException
  822.      * @throws LockException
  823.      * @throws NamespaceException
  824.      * @throws ConstraintViolationException
  825.      * @throws ValueFormatException
  826.      * @throws VersionException
  827.      * @throws PathNotFoundException
  828.      *
  829.      * @api
  830.      */
  831.     public function getIdentifier()
  832.     {
  833.         $this->checkState();
  834.         if ($this->isNodeType('mix:referenceable')) {
  835.             if (empty($this->properties['jcr:uuid'])) {
  836.                 $this->setProperty('jcr:uuid'$this->generateUuid());
  837.             }
  838.             return $this->getPropertyValue('jcr:uuid');
  839.         }
  840.         return $this->getPath();
  841.     }
  842.     /**
  843.      * {@inheritDoc}
  844.      *
  845.      * @api
  846.      */
  847.     public function getIndex()
  848.     {
  849.         $this->checkState();
  850.         return $this->index;
  851.     }
  852.     /**
  853.      * {@inheritDoc}
  854.      *
  855.      * @api
  856.      */
  857.     public function getReferences($name null)
  858.     {
  859.         $this->checkState();
  860.         return $this->objectManager->getReferences($this->path$name);
  861.     }
  862.     /**
  863.      * {@inheritDoc}
  864.      *
  865.      * @api
  866.      */
  867.     public function getWeakReferences($name null)
  868.     {
  869.         $this->checkState();
  870.         return $this->objectManager->getWeakReferences($this->path$name);
  871.     }
  872.     /**
  873.      * {@inheritDoc}
  874.      *
  875.      * @api
  876.      */
  877.     public function hasNode($relPath)
  878.     {
  879.         $this->checkState();
  880.         if (false === strpos($relPath'/')) {
  881.             return array_search($relPath$this->nodes) !== false;
  882.         }
  883.         if (! strlen($relPath) || $relPath[0] === '/') {
  884.             throw new InvalidArgumentException("'$relPath' is not a relative path");
  885.         }
  886.         return $this->session->nodeExists($this->getChildPath($relPath));
  887.     }
  888.     /**
  889.      * {@inheritDoc}
  890.      *
  891.      * @api
  892.      */
  893.     public function hasProperty($relPath)
  894.     {
  895.         $this->checkState();
  896.         if (false === strpos($relPath'/')) {
  897.             return isset($this->properties[$relPath]);
  898.         }
  899.         if (! strlen($relPath) || $relPath[0] === '/') {
  900.             throw new InvalidArgumentException("'$relPath' is not a relative path");
  901.         }
  902.         return $this->session->propertyExists($this->getChildPath($relPath));
  903.     }
  904.     /**
  905.      * {@inheritDoc}
  906.      *
  907.      * @api
  908.      */
  909.     public function hasNodes()
  910.     {
  911.         $this->checkState();
  912.         return count($this->nodes) !== 0;
  913.     }
  914.     /**
  915.      * {@inheritDoc}
  916.      *
  917.      * @api
  918.      */
  919.     public function hasProperties()
  920.     {
  921.         $this->checkState();
  922.         return count($this->properties) !== 0;
  923.     }
  924.     /**
  925.      * {@inheritDoc}
  926.      *
  927.      * @api
  928.      */
  929.     public function getPrimaryNodeType()
  930.     {
  931.         $this->checkState();
  932.         $ntm $this->session->getWorkspace()->getNodeTypeManager();
  933.         return $ntm->getNodeType($this->primaryType);
  934.     }
  935.     /**
  936.      * {@inheritDoc}
  937.      *
  938.      * @api
  939.      */
  940.     public function getMixinNodeTypes()
  941.     {
  942.         $this->checkState();
  943.         if (!isset($this->properties['jcr:mixinTypes'])) {
  944.             return [];
  945.         }
  946.         $res = [];
  947.         $ntm $this->session->getWorkspace()->getNodeTypeManager();
  948.         foreach ($this->properties['jcr:mixinTypes']->getValue() as $type) {
  949.             $res[] = $ntm->getNodeType($type);
  950.         }
  951.         return $res;
  952.     }
  953.     /**
  954.      * {@inheritDoc}
  955.      *
  956.      * @api
  957.      */
  958.     public function isNodeType($nodeTypeName)
  959.     {
  960.         $this->checkState();
  961.         // is it the primary type?
  962.         if ($this->primaryType === $nodeTypeName) {
  963.             return true;
  964.         }
  965.         // is it one of the mixin types?
  966.         if (isset($this->properties['jcr:mixinTypes'])) {
  967.             if (in_array($nodeTypeName$this->properties["jcr:mixinTypes"]->getValue())) {
  968.                 return true;
  969.             }
  970.         }
  971.         $ntm $this->session->getWorkspace()->getNodeTypeManager();
  972.         // is the primary type a subtype of the type?
  973.         if ($ntm->getNodeType($this->primaryType)->isNodeType($nodeTypeName)) {
  974.             return true;
  975.         }
  976.         // if there are no mixin types, then we now know this node is not of that type
  977.         if (! isset($this->properties["jcr:mixinTypes"])) {
  978.             return false;
  979.         }
  980.         // is it an ancestor of any of the mixin types?
  981.         foreach ($this->properties['jcr:mixinTypes'] as $mixin) {
  982.             if ($ntm->getNodeType($mixin)->isNodeType($nodeTypeName)) {
  983.                 return true;
  984.             }
  985.         }
  986.         return false;
  987.     }
  988.     /**
  989.      * Changes the primary node type of this node to nodeTypeName.
  990.      *
  991.      * {@inheritDoc}
  992.      *
  993.      * Jackalope only validates type conflicts on save.
  994.      *
  995.      * @throws InvalidItemStateException
  996.      *
  997.      * @api
  998.      */
  999.     public function setPrimaryType($nodeTypeName)
  1000.     {
  1001.         $this->checkState();
  1002.         throw new NotImplementedException('Write');
  1003.     }
  1004.     /**
  1005.      * {@inheritDoc}
  1006.      *
  1007.      * Jackalope validates type conflicts only on save, not immediately.
  1008.      * It is possible to add mixin types after the first save.
  1009.      *
  1010.      * @api
  1011.      */
  1012.     public function addMixin($mixinName)
  1013.     {
  1014.         // Check if mixinName exists as a mixin type
  1015.         $typemgr $this->session->getWorkspace()->getNodeTypeManager();
  1016.         $nodeType $typemgr->getNodeType($mixinName);
  1017.         if (! $nodeType->isMixin()) {
  1018.             throw new ConstraintViolationException("Trying to add a mixin '$mixinName' that is a primary type");
  1019.         }
  1020.         $this->checkState();
  1021.         // TODO handle LockException & VersionException cases
  1022.         if ($this->hasProperty('jcr:mixinTypes')) {
  1023.             if (!in_array($mixinName$this->properties['jcr:mixinTypes']->getValue())) {
  1024.                 $this->properties['jcr:mixinTypes']->addValue($mixinName);
  1025.                 $this->setModified();
  1026.             }
  1027.         } else {
  1028.             $this->setProperty('jcr:mixinTypes', [$mixinName], PropertyType::NAME);
  1029.             $this->setModified();
  1030.         }
  1031.     }
  1032.     /**
  1033.      * {@inheritDoc}
  1034.      *
  1035.      * @throws InvalidItemStateException
  1036.      *
  1037.      * @throws \InvalidArgumentException
  1038.      * @throws AccessDeniedException
  1039.      * @throws ItemNotFoundException
  1040.      * @throws PathNotFoundException
  1041.      * @throws NamespaceException
  1042.      * @throws ValueFormatException
  1043.      *
  1044.      * @api
  1045.      */
  1046.     public function removeMixin($mixinName)
  1047.     {
  1048.         $this->checkState();
  1049.         // check if node type is assigned
  1050.         if (! $this->hasProperty('jcr:mixinTypes')) {
  1051.             throw new NoSuchNodeTypeException("Node does not have type $mixinName");
  1052.         }
  1053.         $mixins $this->getPropertyValue('jcr:mixinTypes');
  1054.         $key array_search($mixinName$mixins);
  1055.         if (false === $key) {
  1056.             throw new NoSuchNodeTypeException("Node does not have type $mixinName");
  1057.         }
  1058.         unset($mixins[$key]);
  1059.         $this->setProperty('jcr:mixinTypes'$mixins); // might be empty array which is fine
  1060.     }
  1061.     /**
  1062.      * {@inheritDoc}
  1063.      *
  1064.      * @throws \InvalidArgumentException
  1065.      * @throws AccessDeniedException
  1066.      * @throws InvalidItemStateException
  1067.      * @throws ItemNotFoundException
  1068.      * @throws NamespaceException
  1069.      * @throws PathNotFoundException
  1070.      * @throws ValueFormatException
  1071.      *
  1072.      * @api
  1073.      */
  1074.     public function setMixins(array $mixinNames)
  1075.     {
  1076.         $toRemove = [];
  1077.         if ($this->hasProperty('jcr:mixinTypes')) {
  1078.             foreach ($this->getPropertyValue('jcr:mixinTypes') as $mixin) {
  1079.                 if (false !== $key array_search($mixin$mixinNames)) {
  1080.                     unset($mixinNames[$key]);
  1081.                 } else {
  1082.                     $toRemove[] = $mixin;
  1083.                 }
  1084.             }
  1085.         }
  1086.         if (! (count($toRemove) || count($mixinNames))) {
  1087.             return; // nothing to do
  1088.         }
  1089.         // make sure the new types actually exist before we add anything
  1090.         $ntm $this->session->getWorkspace()->getNodeTypeManager();
  1091.         foreach ($mixinNames as $mixinName) {
  1092.             $nodeType $ntm->getNodeType($mixinName);
  1093.             if (! $nodeType->isMixin()) {
  1094.                 throw new ConstraintViolationException("Trying to add a mixin '$mixinName' that is a primary type");
  1095.             }
  1096.         }
  1097.         foreach ($mixinNames as $type) {
  1098.             $this->addMixin($type);
  1099.         }
  1100.         foreach ($toRemove as $type) {
  1101.             $this->removeMixin($type);
  1102.         }
  1103.     }
  1104.     /**
  1105.      * {@inheritDoc}
  1106.      *
  1107.      * @throws InvalidItemStateException
  1108.      *
  1109.      * @api
  1110.      */
  1111.     public function canAddMixin($mixinName)
  1112.     {
  1113.         $this->checkState();
  1114.         throw new NotImplementedException('Write');
  1115.     }
  1116.     /**
  1117.      * {@inheritDoc}
  1118.      *
  1119.      * @api
  1120.      */
  1121.     public function getDefinition()
  1122.     {
  1123.         $this->checkState();
  1124.         if ('rep:root' === $this->primaryType) {
  1125.             throw new NotImplementedException('what is the definition of the root node?');
  1126.         }
  1127.         if (empty($this->definition)) {
  1128.             $this->definition $this->findItemDefinition(function (NodeTypeInterface $nt) {
  1129.                 return $nt->getChildNodeDefinitions();
  1130.             });
  1131.         }
  1132.         return $this->definition;
  1133.     }
  1134.     /**
  1135.      * {@inheritDoc}
  1136.      *
  1137.      * @api
  1138.      */
  1139.     public function update($srcWorkspace)
  1140.     {
  1141.         $this->checkState();
  1142.         if ($this->isNew()) {
  1143.             //no node in workspace
  1144.             return;
  1145.         }
  1146.         $this->getSession()->getTransport()->updateNode($this$srcWorkspace);
  1147.         $this->setDirty();
  1148.         $this->setChildrenDirty();
  1149.     }
  1150.     /**
  1151.      * {@inheritDoc}
  1152.      *
  1153.      * @throws InvalidItemStateException
  1154.      *
  1155.      * @api
  1156.      */
  1157.     public function getCorrespondingNodePath($workspaceName)
  1158.     {
  1159.         $this->checkState();
  1160.         return $this->getSession()
  1161.             ->getTransport()
  1162.             ->getNodePathForIdentifier($this->getIdentifier(), $workspaceName);
  1163.     }
  1164.     /**
  1165.      * {@inheritDoc}
  1166.      *
  1167.      * @api
  1168.      */
  1169.     public function getSharedSet()
  1170.     {
  1171.         $this->checkState();
  1172.         throw new NotImplementedException();
  1173.     }
  1174.     /**
  1175.      * {@inheritDoc}
  1176.      *
  1177.      * @throws InvalidItemStateException
  1178.      *
  1179.      * @api
  1180.      */
  1181.     public function removeSharedSet()
  1182.     {
  1183.         $this->checkState();
  1184.         $this->setModified();
  1185.         throw new NotImplementedException('Write');
  1186.     }
  1187.     /**
  1188.      * {@inheritDoc}
  1189.      *
  1190.      * @throws InvalidItemStateException
  1191.      *
  1192.      * @api
  1193.      */
  1194.     public function removeShare()
  1195.     {
  1196.         $this->checkState();
  1197.         $this->setModified();
  1198.         throw new NotImplementedException('Write');
  1199.     }
  1200.     /**
  1201.      * {@inheritDoc}
  1202.      *
  1203.      * @api
  1204.      */
  1205.     public function isCheckedOut()
  1206.     {
  1207.         $this->checkState();
  1208.         $workspace $this->session->getWorkspace();
  1209.         $versionManager $workspace->getVersionManager();
  1210.         return $versionManager->isCheckedOut($this->getPath());
  1211.     }
  1212.     /**
  1213.      * {@inheritDoc}
  1214.      *
  1215.      * @api
  1216.      */
  1217.     public function isLocked()
  1218.     {
  1219.         $this->checkState();
  1220.         throw new NotImplementedException();
  1221.     }
  1222.     /**
  1223.      * {@inheritDoc}
  1224.      *
  1225.      * @throws InvalidItemStateException
  1226.      *
  1227.      * @api
  1228.      */
  1229.     public function followLifecycleTransition($transition)
  1230.     {
  1231.         $this->checkState();
  1232.         $this->setModified();
  1233.         throw new NotImplementedException('Write');
  1234.     }
  1235.     /**
  1236.      * {@inheritDoc}
  1237.      *
  1238.      * @throws InvalidItemStateException
  1239.      *
  1240.      * @api
  1241.      */
  1242.     public function getAllowedLifecycleTransitions()
  1243.     {
  1244.         $this->checkState();
  1245.         throw new NotImplementedException('Write');
  1246.     }
  1247.     /**
  1248.      * Refresh this node
  1249.      *
  1250.      * {@inheritDoc}
  1251.      *
  1252.      * This is also called internally to refresh when the node is accessed in
  1253.      * state DIRTY.
  1254.      *
  1255.      * @see Item::checkState
  1256.      */
  1257.     protected function refresh($keepChanges$internal false)
  1258.     {
  1259.         if (! $internal && $this->isDeleted()) {
  1260.             throw new InvalidItemStateException('This item has been removed and can not be refreshed');
  1261.         }
  1262.         $deleted false;
  1263.         // Get properties and children from backend
  1264.         try {
  1265.             $json $this->objectManager->getTransport()->getNode(
  1266.                 is_null($this->oldPath)
  1267.                     ? $this->path
  1268.                     $this->oldPath
  1269.             );
  1270.         } catch (ItemNotFoundException $ex) {
  1271.             // The node was deleted in another session
  1272.             if (! $this->objectManager->purgeDisappearedNode($this->path$keepChanges)) {
  1273.                 throw new LogicException($this->path " should be purged and not kept");
  1274.             }
  1275.             $keepChanges false// delete never keeps changes
  1276.             if (! $internal) {
  1277.                 // this is not an internal update
  1278.                 $deleted true;
  1279.             }
  1280.             // continue with empty data, parseData will notify all cached
  1281.             // children and all properties that we are removed
  1282.             $json = [];
  1283.         }
  1284.         $this->parseData($jsontrue$keepChanges);
  1285.         if ($deleted) {
  1286.             $this->setDeleted();
  1287.         }
  1288.     }
  1289.     /**
  1290.      * Remove this node
  1291.      *
  1292.      * {@inheritDoc}
  1293.      *
  1294.      * A jackalope node needs to notify the parent node about this if it is
  1295.      * cached, in addition to \PHPCR\ItemInterface::remove()
  1296.      *
  1297.      * @uses Node::unsetChildNode()
  1298.      *
  1299.      * @api
  1300.      */
  1301.     public function remove()
  1302.     {
  1303.         $this->checkState();
  1304.         $parent $this->getParent();
  1305.         $parentNodeType $parent->getPrimaryNodeType();
  1306.         //will throw a ConstraintViolationException if this node can't be removed
  1307.         $parentNodeType->canRemoveNode($this->getName(), true);
  1308.         if ($parent) {
  1309.             $parent->unsetChildNode($this->nametrue);
  1310.         }
  1311.         // once we removed ourselves, $this->getParent() won't work anymore. do this last
  1312.         parent::remove();
  1313.     }
  1314.     /**
  1315.      * Removes the reference in the internal node storage
  1316.      *
  1317.      * @param string $name  the name of the child node to unset
  1318.      * @param bool   $check whether a state check should be done - set to false
  1319.      *      during internal update operations
  1320.      *
  1321.      * @throws ItemNotFoundException If there is no child with $name
  1322.      * @throws InvalidItemStateException
  1323.      *
  1324.      * @private
  1325.      */
  1326.     public function unsetChildNode($name$check)
  1327.     {
  1328.         if ($check) {
  1329.             $this->checkState();
  1330.         }
  1331.         $key array_search($name$this->nodes);
  1332.         if ($key === false) {
  1333.             if (! $check) {
  1334.                 // inside a refresh operation
  1335.                 return;
  1336.             }
  1337.             throw new ItemNotFoundException("Could not remove child node because it's already gone");
  1338.         }
  1339.         unset($this->nodes[$key]);
  1340.         if (null !== $this->originalNodesOrder) {
  1341.             $this->originalNodesOrder array_flip($this->originalNodesOrder);
  1342.             unset($this->originalNodesOrder[$name]);
  1343.             $this->originalNodesOrder array_flip($this->originalNodesOrder);
  1344.         }
  1345.     }
  1346.     /**
  1347.      * Adds child node to this node for internal reference
  1348.      *
  1349.      * @param NodeInterface $node  The name of the child node
  1350.      * @param boolean $check whether to check state
  1351.      * @param string  $name  is used in cases where $node->getName would not return the correct name (during move operation)
  1352.      *
  1353.      * @throws InvalidItemStateException
  1354.      * @throws RepositoryException
  1355.      *
  1356.      * @private
  1357.      */
  1358.     public function addChildNode(NodeInterface $node$check$name null)
  1359.     {
  1360.         if ($check) {
  1361.             $this->checkState();
  1362.         }
  1363.         if (is_null($name)) {
  1364.             $name $node->getName();
  1365.         }
  1366.         $nt $this->getPrimaryNodeType();
  1367.         //will throw a ConstraintViolationException if this node can't be added
  1368.         $nt->canAddChildNode($name$node->getPrimaryNodeType()->getName(), true);
  1369.         // TODO: same name siblings
  1370.         $this->nodes[] = $name;
  1371.         if (null !== $this->originalNodesOrder) {
  1372.             $this->originalNodesOrder[] = $name;
  1373.         }
  1374.     }
  1375.     /**
  1376.      * Removes the reference in the internal node storage
  1377.      *
  1378.      * @param string $name the name of the property to unset.
  1379.      *
  1380.      * @throws ItemNotFoundException If this node has no property with name $name
  1381.      * @throws InvalidItemStateException
  1382.      * @throws RepositoryException
  1383.      *
  1384.      * @private
  1385.      */
  1386.     public function unsetProperty($name)
  1387.     {
  1388.         $this->checkState();
  1389.         $this->setModified();
  1390.         if (!array_key_exists($name$this->properties)) {
  1391.             throw new ItemNotFoundException('Implementation Error: Could not remove property from node because it is already gone');
  1392.         }
  1393.         $this->deletedProperties[$name] = $this->properties[$name];
  1394.         unset($this->properties[$name]);
  1395.     }
  1396.     /**
  1397.      * In addition to calling parent method, tell all properties and clean deletedProperties
  1398.      */
  1399.     public function confirmSaved()
  1400.     {
  1401.         foreach ($this->properties as $property) {
  1402.             if ($property->isModified() || $property->isNew()) {
  1403.                 $property->confirmSaved();
  1404.             }
  1405.         }
  1406.         $this->deletedProperties = [];
  1407.         parent::confirmSaved();
  1408.     }
  1409.     /**
  1410.      * In addition to calling parent method, tell all properties
  1411.      */
  1412.     public function setPath($path$move false)
  1413.     {
  1414.         parent::setPath($path$move);
  1415.         foreach ($this->properties as $property) {
  1416.             $property->setPath($path.'/'.$property->getName(), $move);
  1417.         }
  1418.     }
  1419.     /**
  1420.      * Make sure $p is an absolute path
  1421.      *
  1422.      * If its a relative path, prepend the path to this node, otherwise return as is
  1423.      *
  1424.      * @param string $p the relative or absolute property or node path
  1425.      *
  1426.      * @return string the absolute path to this item, with relative paths resolved against the current node
  1427.      */
  1428.     protected function getChildPath($p)
  1429.     {
  1430.         if ('' == $p) {
  1431.             throw new InvalidArgumentException("Name can not be empty");
  1432.         }
  1433.         if ($p[0] == '/') {
  1434.             return $p;
  1435.         }
  1436.         //relative path, combine with base path for this node
  1437.         $path $this->path === '/' '/' $this->path.'/';
  1438.         return $path $p;
  1439.     }
  1440.     /**
  1441.      * Filter the list of names according to the filter expression / array
  1442.      *
  1443.      * @param string|array $filter according to getNodes|getProperties
  1444.      * @param array        $names  list of names to filter
  1445.      *
  1446.      * @return array the names in $names that match the filter
  1447.      */
  1448.     protected static function filterNames($filter$names)
  1449.     {
  1450.         if ($filter !== null) {
  1451.             $filtered = [];
  1452.             $filter = (array) $filter;
  1453.             foreach ($filter as $k => $f) {
  1454.                 $f trim($f);
  1455.                 $filter[$k] = strtr($f, [
  1456.                     '*'=>'.*'//wildcard
  1457.                     '.'  => '\\.'//escape regexp
  1458.                     '\\' => '\\\\',
  1459.                     '{'  => '\\{',
  1460.                     '}'  => '\\}',
  1461.                     '('  => '\\(',
  1462.                     ')'  => '\\)',
  1463.                     '+'  => '\\+',
  1464.                     '^'  => '\\^',
  1465.                     '$'  => '\\$'
  1466.                 ]);
  1467.             }
  1468.             foreach ($names as $name) {
  1469.                 foreach ($filter as $f) {
  1470.                     if (preg_match('/^'.$f.'$/'$name)) {
  1471.                         $filtered[] = $name;
  1472.                     }
  1473.                 }
  1474.             }
  1475.         } else {
  1476.             $filtered $names;
  1477.         }
  1478.         return $filtered;
  1479.     }
  1480.     /**
  1481.      * Provide Traversable interface: redirect to getNodes with no filter
  1482.      *
  1483.      * @return Iterator over all child nodes
  1484.      * @throws RepositoryException
  1485.      */
  1486.     #[\ReturnTypeWillChange]
  1487.     public function getIterator()
  1488.     {
  1489.         $this->checkState();
  1490.         return $this->getNodes();
  1491.     }
  1492.     /**
  1493.      * Implement really setting the property without any notification.
  1494.      *
  1495.      * Implement the setProperty, but also used from constructor or in refresh,
  1496.      * when the backend has a new property that is not yet loaded in memory.
  1497.      *
  1498.      * @param string  $name
  1499.      * @param mixed   $value
  1500.      * @param string  $type
  1501.      * @param boolean $internal whether we are setting this node through api or internally
  1502.      *
  1503.      * @return Property
  1504.      *
  1505.      * @throws InvalidArgumentException
  1506.      * @throws LockException
  1507.      * @throws ConstraintViolationException
  1508.      * @throws RepositoryException
  1509.      * @throws UnsupportedRepositoryOperationException
  1510.      * @throws ValueFormatException
  1511.      * @throws VersionException
  1512.      *
  1513.      * @see Node::setProperty
  1514.      * @see Node::refresh
  1515.      * @see Node::__construct
  1516.      */
  1517.     protected function _setProperty($name$value$type$internal)
  1518.     {
  1519.         if ($name === '' || false !== strpos($name'/')) {
  1520.             throw new InvalidArgumentException("The name '$name' is no valid property name");
  1521.         }
  1522.         if (!isset($this->properties[$name])) {
  1523.             $path $this->getChildPath($name);
  1524.             $property $this->factory->get(
  1525.                 Property::class,
  1526.                 [
  1527.                     ['type' => $type'value' => $value],
  1528.                     $path,
  1529.                     $this->session,
  1530.                     $this->objectManager,
  1531.                     ! $internal
  1532.                 ]
  1533.             );
  1534.             $this->properties[$name] = $property;
  1535.             if (! $internal) {
  1536.                 $this->setModified();
  1537.             }
  1538.         } else {
  1539.             if ($internal) {
  1540.                 $this->properties[$name]->_setValue($value$type);
  1541.                 if ($this->properties[$name]->isDirty()) {
  1542.                     $this->properties[$name]->setClean();
  1543.                 }
  1544.             } else {
  1545.                 $this->properties[$name]->setValue($value$type);
  1546.             }
  1547.         }
  1548.         return $this->properties[$name];
  1549.     }
  1550.     /**
  1551.      * Overwrite to set the properties dirty as well.
  1552.      *
  1553.      * @private
  1554.      */
  1555.     public function setDirty($keepChanges false$targetState false)
  1556.     {
  1557.         parent::setDirty($keepChanges$targetState);
  1558.         foreach ($this->properties as $property) {
  1559.             if ($keepChanges && self::STATE_NEW !== $property->getState()) {
  1560.                 // if we want to keep changes, we do not want to set new properties dirty.
  1561.                 $property->setDirty($keepChanges$targetState);
  1562.             }
  1563.         }
  1564.     }
  1565.     /**
  1566.      * Mark all cached children as dirty.
  1567.      *
  1568.      * @private
  1569.      */
  1570.     public function setChildrenDirty()
  1571.     {
  1572.         foreach ($this->objectManager->getCachedDescendants($this->getPath()) as $childNode) {
  1573.             $childNode->setDirty();
  1574.         }
  1575.     }
  1576.     /**
  1577.      * In addition to set this item deleted, set all properties to deleted.
  1578.      *
  1579.      * They will be automatically deleted by the backend, but the user might
  1580.      * still have a reference to one of the property objects.
  1581.      *
  1582.      * @private
  1583.      */
  1584.     public function setDeleted()
  1585.     {
  1586.         parent::setDeleted();
  1587.         foreach ($this->properties as $property) {
  1588.             $property->setDeleted(); // not all properties are tracked in objectmanager
  1589.         }
  1590.     }
  1591.     /**
  1592.      * {@inheritDoc}
  1593.      *
  1594.      * Additionally, notifies all properties of this node. Child nodes are not
  1595.      * notified, it is the job of the ObjectManager to know which nodes are
  1596.      * cached and notify them.
  1597.      */
  1598.     public function beginTransaction()
  1599.     {
  1600.         parent::beginTransaction();
  1601.         // Notify the children properties
  1602.         foreach ($this->properties as $prop) {
  1603.             $prop->beginTransaction();
  1604.         }
  1605.     }
  1606.     /**
  1607.      * {@inheritDoc}
  1608.      *
  1609.      * Additionally, notifies all properties of this node. Child nodes are not
  1610.      * notified, it is the job of the ObjectManager to know which nodes are
  1611.      * cached and notify them.
  1612.      */
  1613.     public function commitTransaction()
  1614.     {
  1615.         parent::commitTransaction();
  1616.         foreach ($this->properties as $prop) {
  1617.             $prop->commitTransaction();
  1618.         }
  1619.     }
  1620.     /**
  1621.      * {@inheritDoc}
  1622.      *
  1623.      * Additionally, notifies all properties of this node. Child nodes are not
  1624.      * notified, it is the job of the ObjectManager to know which nodes are
  1625.      * cached and notify them.
  1626.      */
  1627.     public function rollbackTransaction()
  1628.     {
  1629.         parent::rollbackTransaction();
  1630.         foreach ($this->properties as $prop) {
  1631.             $prop->rollbackTransaction();
  1632.         }
  1633.     }
  1634. }