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

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.      * @return NodeInterface the node that was added
  338.      *
  339.      * @api
  340.      */
  341.     public function addNode($relPath$primaryNodeTypeName null)
  342.     {
  343.         $relPath = (string)$relPath;
  344.         $this->checkState();
  345.         $ntm $this->session->getWorkspace()->getNodeTypeManager();
  346.         // are we not the immediate parent?
  347.         if (strpos($relPath'/') !== false) {
  348.             // forward to real parent
  349.             $relPath PathHelper::absolutizePath($relPath$this->getPath(), true);
  350.             $parentPath PathHelper::getParentPath($relPath);
  351.             $newName PathHelper::getNodeName($relPath);
  352.             try {
  353.                 $parentNode $this->objectManager->getNodeByPath($parentPath);
  354.             } catch (ItemNotFoundException $e) {
  355.                 //we have to throw a different exception if there is a property
  356.                 // with that name than if there is nothing at the path at all.
  357.                 // lets see if the property exists
  358.                 if ($this->session->propertyExists($parentPath)) {
  359.                     throw new ConstraintViolationException("Node '{$this->path}': Not allowed to add a node below property at $parentPath");
  360.                 }
  361.                 throw new PathNotFoundException($e->getMessage(), $e->getCode(), $e);
  362.             }
  363.             return $parentNode->addNode($newName$primaryNodeTypeName);
  364.         }
  365.         if (null === $primaryNodeTypeName) {
  366.             if ($this->primaryType === 'rep:root') {
  367.                 $primaryNodeTypeName 'nt:unstructured';
  368.             } else {
  369.                 $type $ntm->getNodeType($this->primaryType);
  370.                 $nodeDefinitions $type->getChildNodeDefinitions();
  371.                 foreach ($nodeDefinitions as $def) {
  372.                     if (!is_null($def->getDefaultPrimaryType())) {
  373.                         $primaryNodeTypeName $def->getDefaultPrimaryTypeName();
  374.                         break;
  375.                     }
  376.                 }
  377.             }
  378.             if (is_null($primaryNodeTypeName)) {
  379.                 throw new ConstraintViolationException("No matching child node definition found for `$relPath' in type `{$this->primaryType}' for node '{$this->path}'. Please specify the type explicitly.");
  380.             }
  381.         }
  382.         // create child node
  383.         //sanity check: no index allowed. TODO: we should verify this is a valid node name
  384.         if (false !== strpos($relPath']')) {
  385.             throw new RepositoryException("The node '{$this->path}' does not allow an index in name of newly created node: $relPath");
  386.         }
  387.         if (in_array($relPath$this->nodestrue)) {
  388.             throw new ItemExistsException("The node '{$this->path}' already has a child named '$relPath''."); //TODO: same-name siblings if nodetype allows for them
  389.         }
  390.         $data = ['jcr:primaryType' => $primaryNodeTypeName];
  391.         $path $this->getChildPath($relPath);
  392.         $node $this->factory->get(Node::class, [$data$path$this->session$this->objectManagertrue]);
  393.         $this->addChildNode($nodefalse); // no need to check the state, we just checked when entering this method
  394.         $this->objectManager->addNode($path$node);
  395.         if (is_array($this->originalNodesOrder)) {
  396.             // new nodes are added at the end
  397.             $this->originalNodesOrder[] = $relPath;
  398.         }
  399.         //by definition, adding a node sets the parent to modified
  400.         $this->setModified();
  401.         return $node;
  402.     }
  403.     /**
  404.      * {@inheritDoc}
  405.      *
  406.      * @return NodeInterface the newly created node
  407.      *
  408.      * @throws InvalidArgumentException
  409.      * @throws ItemExistsException
  410.      * @throws PathNotFoundException
  411.      * @throws RepositoryException
  412.      * @api
  413.      */
  414.     public function addNodeAutoNamed($nameHint null$primaryNodeTypeName null)
  415.     {
  416.         $name NodeHelper::generateAutoNodeName(
  417.             $this->nodes,
  418.             $this->session->getWorkspace()->getNamespaceRegistry()->getNamespaces(),
  419.             'jcr',
  420.             $nameHint
  421.         );
  422.         return $this->addNode($name$primaryNodeTypeName);
  423.     }
  424.     /**
  425.      * Jackalope implements this feature and updates the position of the
  426.      * existing child at srcChildRelPath to be in the list immediately before
  427.      * destChildRelPath.
  428.      *
  429.      * {@inheritDoc}
  430.      *
  431.      * Jackalope has no implementation-specific ordering restriction so no
  432.      * \PHPCR\ConstraintViolationException is expected. VersionException and
  433.      * LockException are not tested immediately but thrown on save.
  434.      *
  435.      * @api
  436.      */
  437.     public function orderBefore($srcChildRelPath$destChildRelPath)
  438.     {
  439.         if ($srcChildRelPath === $destChildRelPath) {
  440.             //nothing to move
  441.             return;
  442.         }
  443.         if (null === $this->originalNodesOrder) {
  444.             $this->originalNodesOrder $this->nodes;
  445.         }
  446.         $this->nodes NodeHelper::orderBeforeArray($srcChildRelPath$destChildRelPath$this->nodes);
  447.         $this->setModified();
  448.     }
  449.     /**
  450.      * {@inheritDoc}
  451.      *
  452.      * @throws PathNotFoundException
  453.      *
  454.      * @api
  455.      * @throws AccessDeniedException
  456.      * @throws ItemNotFoundException
  457.      * @throws \InvalidArgumentException
  458.      */
  459.     public function rename($newName)
  460.     {
  461.         $names = (array) $this->getParent()->getNodeNames();
  462.         $pos array_search($this->name$names);
  463.         $next = isset($names[$pos 1]) ? $names[$pos 1] : null;
  464.         $newPath $this->parentPath '/' $newName;
  465.         if (substr($newPath02) === '//') {
  466.             $newPath substr($newPath1);
  467.         }
  468.         $this->session->move($this->path$newPath);
  469.         if ($next) {
  470.             $this->getParent()->orderBefore($newName$next);
  471.         }
  472.     }
  473.     /**
  474.      * Determine whether the children of this node need to be reordered
  475.      *
  476.      * @return boolean
  477.      *
  478.      * @private
  479.      */
  480.     public function needsChildReordering()
  481.     {
  482.         return (bool) $this->originalNodesOrder;
  483.     }
  484.     /**
  485.      * Returns the orderBefore commands to be applied to the childnodes
  486.      * to get from the original order to the new one
  487.      *
  488.      * @return array of arrays with 2 fields: name of node to order before second name
  489.      *
  490.      * @throws AccessDeniedException
  491.      * @throws ItemNotFoundException
  492.      *
  493.      * @private
  494.      */
  495.     public function getOrderCommands()
  496.     {
  497.         if (! $this->originalNodesOrder) {
  498.             return [];
  499.         }
  500.         $reorders NodeHelper::calculateOrderBefore($this->originalNodesOrder$this->nodes);
  501.         $this->originalNodesOrder null;
  502.         return $reorders;
  503.     }
  504.     /**
  505.      * {@inheritDoc}
  506.      *
  507.      * @param boolean $validate When false, node types are not asked to validate
  508.      *                          whether operation is allowed
  509.      *
  510.      * @return \PHPCR\PropertyInterface The new resp. updated Property object
  511.      *
  512.      * @throws InvalidItemStateException
  513.      * @throws NamespaceException
  514.      * @throws \InvalidArgumentException
  515.      * @throws AccessDeniedException
  516.      * @throws ItemNotFoundException
  517.      *
  518.      * @api
  519.      */
  520.     public function setProperty($name$value$type PropertyType::UNDEFINED$validate true)
  521.     {
  522.         $this->checkState();
  523.         // abort early when the node value is not changed
  524.         // for multivalue, === is only true when array keys and values match. this is exactly what we need.
  525.         try {
  526.             if (array_key_exists($name$this->properties) && $this->properties[$name]->getValue() === $value) {
  527.                 return $this->properties[$name];
  528.             }
  529.         } catch (RepositoryException $e) {
  530.             // if anything goes wrong trying to get the property value, move on and don't return early
  531.         }
  532.         if ($validate && 'jcr:uuid' === $name && !$this->isNodeType('mix:referenceable')) {
  533.             throw new ConstraintViolationException('You can only change the uuid of newly created nodes that have "referenceable" mixin.');
  534.         }
  535.         if ($validate) {
  536.             if (is_array($value)) {
  537.                 foreach ($value as $key => $v) {
  538.                     if (null === $v) {
  539.                         unset($value[$key]);
  540.                     }
  541.                 }
  542.             }
  543.             $types $this->getMixinNodeTypes();
  544.             array_push($types$this->getPrimaryNodeType());
  545.             if (null !== $value) {
  546.                 $exception null;
  547.                 foreach ($types as $nt) {
  548.                     /** @var $nt NodeType */
  549.                     try {
  550.                         $nt->canSetProperty($name$valuetrue);
  551.                         $exception null;
  552.                         break; // exit loop, we found a valid definition
  553.                     } catch (RepositoryException $e) {
  554.                         if (null === $exception) {
  555.                             $exception $e;
  556.                         }
  557.                     }
  558.                 }
  559.                 if (null !== $exception) {
  560.                     $types 'Primary type '.$this->primaryType;
  561.                     if (isset($this->properties['jcr:mixinTypes'])) {
  562.                         $types .= ', mixins '.implode(','$this->getPropertyValue('jcr:mixinTypes'PropertyType::STRING));
  563.                     }
  564.                     $msg sprintf('Can not set property %s on node %s. Node types do not allow for this: %s'$name$this->path$types);
  565.                     throw new ConstraintViolationException($msg0$exception);
  566.                 }
  567.             } else {
  568.                 // $value is null for property removal
  569.                 // if any type forbids, throw exception
  570.                 foreach ($types as $nt) {
  571.                     /** @var $nt \Jackalope\NodeType\NodeType */
  572.                     $nt->canRemoveProperty($nametrue);
  573.                 }
  574.             }
  575.         }
  576.         //try to get a namespace for the set property
  577.         if (strpos($name':') !== false) {
  578.             list($prefix) = explode(':'$name);
  579.             //Check if the namespace exists. If not, throw an NamespaceException
  580.             $this->session->getNamespaceURI($prefix);
  581.         }
  582.         if (is_null($value)) {
  583.             if (isset($this->properties[$name])) {
  584.                 $this->properties[$name]->remove();
  585.             }
  586.             return null;
  587.         }
  588.         // if the property is the UUID, then register the UUID against the path
  589.         // of this node.
  590.         if ('jcr:uuid' === $name) {
  591.             $this->objectManager->registerUuid($value$this->getPath());
  592.         }
  593.         return $this->_setProperty($name$value$typefalse);
  594.     }
  595.     /**
  596.      * {@inheritDoc}
  597.      *
  598.      * @return NodeInterface the node at relPath
  599.      *
  600.      * @throws InvalidItemStateException
  601.      *
  602.      * @api
  603.      */
  604.     public function getNode($relPath)
  605.     {
  606.         $this->checkState();
  607.         $relPath = (string) $relPath;
  608.         if ('' === $relPath || '/' === $relPath[0]) {
  609.             throw new PathNotFoundException("$relPath is not a relative path");
  610.         }
  611.         try {
  612.             $node $this->objectManager->getNodeByPath(PathHelper::absolutizePath($relPath$this->path));
  613.         } catch (ItemNotFoundException $e) {
  614.             throw new PathNotFoundException($e->getMessage(), $e->getCode(), $e);
  615.         }
  616.         return $node;
  617.     }
  618.     /**
  619.      * {@inheritDoc}
  620.      *
  621.      * @return \Iterator<string, NodeInterface> over all (matching) child Nodes implementing <b>SeekableIterator</b>
  622.      *                                          and <b>Countable</b>. Keys are the Node names.
  623.      *
  624.      * @api
  625.      */
  626.     public function getNodes($nameFilter null$typeFilter null)
  627.     {
  628.         $this->checkState();
  629.         $names self::filterNames($nameFilter$this->nodes);
  630.         $result = [];
  631.         if (count($names)) {
  632.             $paths = [];
  633.             foreach ($names as $name) {
  634.                 $paths[] = PathHelper::absolutizePath($name$this->path);
  635.             }
  636.             $nodes $this->objectManager->getNodesByPath($pathsNode::class, $typeFilter);
  637.             // OPTIMIZE if we lazy-load in ObjectManager we should not do this loop
  638.             foreach ($nodes as $node) {
  639.                 $result[$node->getName()] = $node;
  640.             }
  641.         }
  642.         return new ArrayIterator($result);
  643.     }
  644.     /**
  645.      * {@inheritDoc}
  646.      *
  647.      * @return \Iterator<string> over all child node names
  648.      *
  649.      * @api
  650.      */
  651.     public function getNodeNames($nameFilter null$typeFilter null)
  652.     {
  653.         $this->checkState();
  654.         if (null !== $typeFilter) {
  655.             return $this->objectManager->filterChildNodeNamesByType($this$nameFilter$typeFilter);
  656.         }
  657.         $names self::filterNames($nameFilter$this->nodes);
  658.         return new ArrayIterator($names);
  659.     }
  660.     /**
  661.      * {@inheritDoc}
  662.      *
  663.      * @return \PHPCR\PropertyInterface the property at relPath
  664.      *
  665.      * @throws InvalidItemStateException
  666.      *
  667.      * @api
  668.      */
  669.     public function getProperty($relPath)
  670.     {
  671.         $this->checkState();
  672.         if (false === strpos($relPath'/')) {
  673.             if (!isset($this->properties[$relPath])) {
  674.                 throw new PathNotFoundException("Property $relPath in ".$this->path);
  675.             }
  676.             if ($this->properties[$relPath]->isDeleted()) {
  677.                 throw new PathNotFoundException("Property '$relPath' of " $this->path ' is deleted');
  678.             }
  679.             return $this->properties[$relPath];
  680.         }
  681.         return $this->session->getProperty($this->getChildPath($relPath));
  682.     }
  683.     /**
  684.      * This method is only meant for the transport to be able to still build a
  685.      * store request for afterwards deleted nodes to support the operationslog.
  686.      *
  687.      * @return Property[] with just the jcr:primaryType property in it
  688.      *
  689.      * @see \Jackalope\Transport\WritingInterface::storeNodes
  690.      *
  691.      * @throws InvalidItemStateException
  692.      * @throws RepositoryException
  693.      *
  694.      * @private
  695.      */
  696.     public function getPropertiesForStoreDeletedNode()
  697.     {
  698.         if (! $this->isDeleted()) {
  699.             throw new InvalidItemStateException('You are not supposed to call this on a not deleted node');
  700.         }
  701.         $myProperty $this->properties['jcr:primaryType'];
  702.         $myProperty->setClean();
  703.         $path $this->getChildPath('jcr:primaryType');
  704.         $property $this->factory->get(
  705.             'Property',
  706.             [['type' => $myProperty->getType(), 'value' => $myProperty->getValue()],
  707.                 $path,
  708.                 $this->session,
  709.                 $this->objectManager
  710.             ]
  711.         );
  712.         $myProperty->setDeleted();
  713.         return ['jcr:primaryType' => $property];
  714.     }
  715.     /**
  716.      * {@inheritDoc}
  717.      *
  718.      * @return mixed the value of the property with $name
  719.      *
  720.      * @throws InvalidItemStateException
  721.      * @throws \InvalidArgumentException
  722.      *
  723.      * @api
  724.      */
  725.     public function getPropertyValue($name$type null)
  726.     {
  727.         $this->checkState();
  728.         $val $this->getProperty($name)->getValue();
  729.         if (null !== $type) {
  730.             $val $this->valueConverter->convertType($val$type);
  731.         }
  732.         return $val;
  733.     }
  734.     /**
  735.      * {@inheritDoc}
  736.      *
  737.      * @return mixed the value of the property at $relPath or $defaultValue
  738.      *
  739.      * @throws \InvalidArgumentException
  740.      *
  741.      * @throws InvalidItemStateException
  742.      * @throws PathNotFoundException
  743.      * @throws ValueFormatException
  744.      *
  745.      * @api
  746.      */
  747.     public function getPropertyValueWithDefault($relPath$defaultValue)
  748.     {
  749.         if ($this->hasProperty($relPath)) {
  750.             return $this->getPropertyValue($relPath);
  751.         }
  752.         return $defaultValue;
  753.     }
  754.     /**
  755.      * {@inheritDoc}
  756.      *
  757.      * @return \Iterator<string, \PHPCR\PropertyInterface> implementing <b>SeekableIterator</b> and
  758.      *                                                     <b>Countable</b>. Keys are the property names.
  759.      *
  760.      * @api
  761.      */
  762.     public function getProperties($nameFilter null)
  763.     {
  764.         $this->checkState();
  765.         //OPTIMIZE: lazy iterator?
  766.         $names self::filterNames($nameFilterarray_keys($this->properties));
  767.         $result = [];
  768.         foreach ($names as $name) {
  769.             //we know for sure the properties exist, as they come from the
  770.             // array keys of the array we are accessing
  771.             $result[$name] = $this->properties[$name];
  772.         }
  773.         return new ArrayIterator($result);
  774.     }
  775.     /**
  776.      * {@inheritDoc}
  777.      *
  778.      * @return array<string, mixed> keys are the property names, values the corresponding
  779.      *                              property value (or array of values in case of multi-valued properties)
  780.      *                              If $dereference is false, reference properties are uuid strings and
  781.      *                              path properties path strings instead of the referenced node instances
  782.      *
  783.      * @throws \InvalidArgumentException
  784.      * @throws InvalidItemStateException
  785.      * @throws ValueFormatException
  786.      * @throws ItemNotFoundException
  787.      *
  788.      * @api
  789.      */
  790.     public function getPropertiesValues($nameFilter null$dereference true)
  791.     {
  792.         $this->checkState();
  793.         // OPTIMIZE: do not create properties in constructor, go over array here
  794.         $names self::filterNames($nameFilterarray_keys($this->properties));
  795.         $result = [];
  796.         foreach ($names as $name) {
  797.             //we know for sure the properties exist, as they come from the
  798.             // array keys of the array we are accessing
  799.             $type $this->properties[$name]->getType();
  800.             if (! $dereference &&
  801.                     (PropertyType::REFERENCE === $type
  802.                     || PropertyType::WEAKREFERENCE === $type
  803.                     || PropertyType::PATH === $type)
  804.             ) {
  805.                 $result[$name] = $this->properties[$name]->getString();
  806.             } else {
  807.                 // OPTIMIZE: collect the paths and call objectmanager->getNodesByPath once
  808.                 $result[$name] = $this->properties[$name]->getValue();
  809.             }
  810.         }
  811.         return $result;
  812.     }
  813.     /**
  814.      * {@inheritDoc}
  815.      *
  816.      * @return ItemInterface the primary child item
  817.      *
  818.      * @api
  819.      */
  820.     public function getPrimaryItem()
  821.     {
  822.         try {
  823.             $primary_item null;
  824.             $item_name $this->getPrimaryNodeType()->getPrimaryItemName();
  825.             if ($item_name !== null) {
  826.                 $primary_item $this->session->getItem($this->path '/' $item_name);
  827.             }
  828.         } catch (Exception $ex) {
  829.             throw new RepositoryException("An error occured while reading the primary item of the node '{$this->path}': " $ex->getMessage());
  830.         }
  831.         if ($primary_item === null) {
  832.             throw new ItemNotFoundException("No primary item found for node '{$this->path}'");
  833.         }
  834.         return $primary_item;
  835.     }
  836.     /**
  837.      * @return string a universally unique id.
  838.      */
  839.     protected function generateUuid()
  840.     {
  841.         return UUIDHelper::generateUUID();
  842.     }
  843.     /**
  844.      * {@inheritDoc}
  845.      *
  846.      * @return string the identifier of this node
  847.      *
  848.      * @throws \InvalidArgumentException
  849.      *
  850.      * @throws AccessDeniedException
  851.      * @throws InvalidItemStateException
  852.      * @throws ItemNotFoundException
  853.      * @throws LockException
  854.      * @throws NamespaceException
  855.      * @throws ConstraintViolationException
  856.      * @throws ValueFormatException
  857.      * @throws VersionException
  858.      * @throws PathNotFoundException
  859.      *
  860.      * @api
  861.      */
  862.     public function getIdentifier()
  863.     {
  864.         $this->checkState();
  865.         if ($this->isNodeType('mix:referenceable')) {
  866.             if (empty($this->properties['jcr:uuid'])) {
  867.                 $this->setProperty('jcr:uuid'$this->generateUuid());
  868.             }
  869.             return $this->getPropertyValue('jcr:uuid');
  870.         }
  871.         return $this->getPath();
  872.     }
  873.     /**
  874.      * {@inheritDoc}
  875.      *
  876.      * @return int the index of this node within the ordered set of its
  877.      *             same-name sibling nodes
  878.      *
  879.      * @api
  880.      */
  881.     public function getIndex()
  882.     {
  883.         $this->checkState();
  884.         return $this->index;
  885.     }
  886.     /**
  887.      * {@inheritDoc}
  888.      *
  889.      * @return \Iterator<string, \PHPCR\PropertyInterface> implementing <b>SeekableIterator</b> and
  890.      *                                                     <b>Countable</b>. Keys are the property names.
  891.      *
  892.      * @api
  893.      */
  894.     public function getReferences($name null)
  895.     {
  896.         $this->checkState();
  897.         return $this->objectManager->getReferences($this->path$name);
  898.     }
  899.     /**
  900.      * {@inheritDoc}
  901.      *
  902.      * @return \Iterator<string, \PHPCR\PropertyInterface> implementing <b>SeekableIterator</b> and
  903.      *                                                     <b>Countable</b>. Keys are the property names.
  904.      * @api
  905.      */
  906.     public function getWeakReferences($name null)
  907.     {
  908.         $this->checkState();
  909.         return $this->objectManager->getWeakReferences($this->path$name);
  910.     }
  911.     /**
  912.      * {@inheritDoc}
  913.      *
  914.      * @return bool true if a node exists at relPath; false otherwise
  915.      *
  916.      * @api
  917.      */
  918.     public function hasNode($relPath)
  919.     {
  920.         $this->checkState();
  921.         if (false === strpos($relPath'/')) {
  922.             return array_search($relPath$this->nodes) !== false;
  923.         }
  924.         if (! strlen($relPath) || $relPath[0] === '/') {
  925.             throw new InvalidArgumentException("'$relPath' is not a relative path");
  926.         }
  927.         return $this->session->nodeExists($this->getChildPath($relPath));
  928.     }
  929.     /**
  930.      * {@inheritDoc}
  931.      *
  932.      * @return bool true if a property exists at relPath; false otherwise
  933.      *
  934.      * @api
  935.      */
  936.     public function hasProperty($relPath)
  937.     {
  938.         $this->checkState();
  939.         if (false === strpos($relPath'/')) {
  940.             return isset($this->properties[$relPath]);
  941.         }
  942.         if (! strlen($relPath) || $relPath[0] === '/') {
  943.             throw new InvalidArgumentException("'$relPath' is not a relative path");
  944.         }
  945.         return $this->session->propertyExists($this->getChildPath($relPath));
  946.     }
  947.     /**
  948.      * {@inheritDoc}
  949.      *
  950.      * @return bool true if this node has one or more child nodes; false
  951.      *              otherwise
  952.      *
  953.      * @api
  954.      */
  955.     public function hasNodes()
  956.     {
  957.         $this->checkState();
  958.         return count($this->nodes) !== 0;
  959.     }
  960.     /**
  961.      * {@inheritDoc}
  962.      *
  963.      * @return bool true if this node has one or more properties; false
  964.      *              otherwise
  965.      *
  966.      * @api
  967.      */
  968.     public function hasProperties()
  969.     {
  970.         $this->checkState();
  971.         return count($this->properties) !== 0;
  972.     }
  973.     /**
  974.      * {@inheritDoc}
  975.      *
  976.      * @return NodeTypeInterface a NodeType object
  977.      *
  978.      * @api
  979.      */
  980.     public function getPrimaryNodeType()
  981.     {
  982.         $this->checkState();
  983.         $ntm $this->session->getWorkspace()->getNodeTypeManager();
  984.         return $ntm->getNodeType($this->primaryType);
  985.     }
  986.     /**
  987.      * {@inheritDoc}
  988.      *
  989.      * @return NodeTypeInterface[] an array of mixin node types
  990.      *
  991.      * @api
  992.      */
  993.     public function getMixinNodeTypes()
  994.     {
  995.         $this->checkState();
  996.         if (!isset($this->properties['jcr:mixinTypes'])) {
  997.             return [];
  998.         }
  999.         $res = [];
  1000.         $ntm $this->session->getWorkspace()->getNodeTypeManager();
  1001.         foreach ($this->properties['jcr:mixinTypes']->getValue() as $type) {
  1002.             $res[] = $ntm->getNodeType($type);
  1003.         }
  1004.         return $res;
  1005.     }
  1006.     /**
  1007.      * {@inheritDoc}
  1008.      *
  1009.      * @return bool true if this node is of the specified primary node type
  1010.      *               or mixin type, or a subtype thereof. Returns false otherwise.
  1011.      * @api
  1012.      */
  1013.     public function isNodeType($nodeTypeName)
  1014.     {
  1015.         $this->checkState();
  1016.         // is it the primary type?
  1017.         if ($this->primaryType === $nodeTypeName) {
  1018.             return true;
  1019.         }
  1020.         // is it one of the mixin types?
  1021.         if (isset($this->properties['jcr:mixinTypes'])) {
  1022.             if (in_array($nodeTypeName$this->properties["jcr:mixinTypes"]->getValue())) {
  1023.                 return true;
  1024.             }
  1025.         }
  1026.         $ntm $this->session->getWorkspace()->getNodeTypeManager();
  1027.         // is the primary type a subtype of the type?
  1028.         if ($ntm->getNodeType($this->primaryType)->isNodeType($nodeTypeName)) {
  1029.             return true;
  1030.         }
  1031.         // if there are no mixin types, then we now know this node is not of that type
  1032.         if (! isset($this->properties["jcr:mixinTypes"])) {
  1033.             return false;
  1034.         }
  1035.         // is it an ancestor of any of the mixin types?
  1036.         foreach ($this->properties['jcr:mixinTypes'] as $mixin) {
  1037.             if ($ntm->getNodeType($mixin)->isNodeType($nodeTypeName)) {
  1038.                 return true;
  1039.             }
  1040.         }
  1041.         return false;
  1042.     }
  1043.     /**
  1044.      * Changes the primary node type of this node to nodeTypeName.
  1045.      *
  1046.      * {@inheritDoc}
  1047.      *
  1048.      * Jackalope only validates type conflicts on save.
  1049.      *
  1050.      * @throws InvalidItemStateException
  1051.      *
  1052.      * @api
  1053.      */
  1054.     public function setPrimaryType($nodeTypeName)
  1055.     {
  1056.         $this->checkState();
  1057.         throw new NotImplementedException('Write');
  1058.     }
  1059.     /**
  1060.      * {@inheritDoc}
  1061.      *
  1062.      * Jackalope validates type conflicts only on save, not immediately.
  1063.      * It is possible to add mixin types after the first save.
  1064.      *
  1065.      * @api
  1066.      */
  1067.     public function addMixin($mixinName)
  1068.     {
  1069.         // Check if mixinName exists as a mixin type
  1070.         $typemgr $this->session->getWorkspace()->getNodeTypeManager();
  1071.         $nodeType $typemgr->getNodeType($mixinName);
  1072.         if (! $nodeType->isMixin()) {
  1073.             throw new ConstraintViolationException("Trying to add a mixin '$mixinName' that is a primary type");
  1074.         }
  1075.         $this->checkState();
  1076.         // TODO handle LockException & VersionException cases
  1077.         if ($this->hasProperty('jcr:mixinTypes')) {
  1078.             if (!in_array($mixinName$this->properties['jcr:mixinTypes']->getValue())) {
  1079.                 $this->properties['jcr:mixinTypes']->addValue($mixinName);
  1080.                 $this->setModified();
  1081.             }
  1082.         } else {
  1083.             $this->setProperty('jcr:mixinTypes', [$mixinName], PropertyType::NAME);
  1084.             $this->setModified();
  1085.         }
  1086.     }
  1087.     /**
  1088.      * {@inheritDoc}
  1089.      *
  1090.      * @throws InvalidItemStateException
  1091.      *
  1092.      * @throws \InvalidArgumentException
  1093.      * @throws AccessDeniedException
  1094.      * @throws ItemNotFoundException
  1095.      * @throws PathNotFoundException
  1096.      * @throws NamespaceException
  1097.      * @throws ValueFormatException
  1098.      *
  1099.      * @api
  1100.      */
  1101.     public function removeMixin($mixinName)
  1102.     {
  1103.         $this->checkState();
  1104.         // check if node type is assigned
  1105.         if (! $this->hasProperty('jcr:mixinTypes')) {
  1106.             throw new NoSuchNodeTypeException("Node does not have type $mixinName");
  1107.         }
  1108.         $mixins $this->getPropertyValue('jcr:mixinTypes');
  1109.         $key array_search($mixinName$mixins);
  1110.         if (false === $key) {
  1111.             throw new NoSuchNodeTypeException("Node does not have type $mixinName");
  1112.         }
  1113.         unset($mixins[$key]);
  1114.         $this->setProperty('jcr:mixinTypes'$mixins); // might be empty array which is fine
  1115.     }
  1116.     /**
  1117.      * {@inheritDoc}
  1118.      *
  1119.      * @throws \InvalidArgumentException
  1120.      * @throws AccessDeniedException
  1121.      * @throws InvalidItemStateException
  1122.      * @throws ItemNotFoundException
  1123.      * @throws NamespaceException
  1124.      * @throws PathNotFoundException
  1125.      * @throws ValueFormatException
  1126.      *
  1127.      * @api
  1128.      */
  1129.     public function setMixins(array $mixinNames)
  1130.     {
  1131.         $toRemove = [];
  1132.         if ($this->hasProperty('jcr:mixinTypes')) {
  1133.             foreach ($this->getPropertyValue('jcr:mixinTypes') as $mixin) {
  1134.                 if (false !== $key array_search($mixin$mixinNames)) {
  1135.                     unset($mixinNames[$key]);
  1136.                 } else {
  1137.                     $toRemove[] = $mixin;
  1138.                 }
  1139.             }
  1140.         }
  1141.         if (! (count($toRemove) || count($mixinNames))) {
  1142.             return; // nothing to do
  1143.         }
  1144.         // make sure the new types actually exist before we add anything
  1145.         $ntm $this->session->getWorkspace()->getNodeTypeManager();
  1146.         foreach ($mixinNames as $mixinName) {
  1147.             $nodeType $ntm->getNodeType($mixinName);
  1148.             if (! $nodeType->isMixin()) {
  1149.                 throw new ConstraintViolationException("Trying to add a mixin '$mixinName' that is a primary type");
  1150.             }
  1151.         }
  1152.         foreach ($mixinNames as $type) {
  1153.             $this->addMixin($type);
  1154.         }
  1155.         foreach ($toRemove as $type) {
  1156.             $this->removeMixin($type);
  1157.         }
  1158.     }
  1159.     /**
  1160.      * {@inheritDoc}
  1161.      *
  1162.      * @return bool true if the specified mixin node type, mixinName, can be
  1163.      *              added to this node; false otherwise
  1164.      *
  1165.      * @throws InvalidItemStateException
  1166.      *
  1167.      * @api
  1168.      */
  1169.     public function canAddMixin($mixinName)
  1170.     {
  1171.         $this->checkState();
  1172.         throw new NotImplementedException('Write');
  1173.     }
  1174.     /**
  1175.      * {@inheritDoc}
  1176.      *
  1177.      * @return NodeDefinitionInterface a NodeDefinition object
  1178.      *
  1179.      * @api
  1180.      */
  1181.     public function getDefinition()
  1182.     {
  1183.         $this->checkState();
  1184.         if ('rep:root' === $this->primaryType) {
  1185.             throw new NotImplementedException('what is the definition of the root node?');
  1186.         }
  1187.         if (empty($this->definition)) {
  1188.             $this->definition $this->findItemDefinition(function (NodeTypeInterface $nt) {
  1189.                 return $nt->getChildNodeDefinitions();
  1190.             });
  1191.         }
  1192.         return $this->definition;
  1193.     }
  1194.     /**
  1195.      * {@inheritDoc}
  1196.      *
  1197.      * @api
  1198.      */
  1199.     public function update($srcWorkspace)
  1200.     {
  1201.         $this->checkState();
  1202.         if ($this->isNew()) {
  1203.             //no node in workspace
  1204.             return;
  1205.         }
  1206.         $this->getSession()->getTransport()->updateNode($this$srcWorkspace);
  1207.         $this->setDirty();
  1208.         $this->setChildrenDirty();
  1209.     }
  1210.     /**
  1211.      * {@inheritDoc}
  1212.      *
  1213.      * @return string the absolute path to the corresponding node
  1214.      *
  1215.      * @throws InvalidItemStateException
  1216.      *
  1217.      * @api
  1218.      */
  1219.     public function getCorrespondingNodePath($workspaceName)
  1220.     {
  1221.         $this->checkState();
  1222.         return $this->getSession()
  1223.             ->getTransport()
  1224.             ->getNodePathForIdentifier($this->getIdentifier(), $workspaceName);
  1225.     }
  1226.     /**
  1227.      * {@inheritDoc}
  1228.      *
  1229.      * @return \Iterator<string, NodeInterface> implementing <b>SeekableIterator</b> and
  1230.      *                                          <b>Countable</b>. Keys are the Node names.
  1231.      * @api
  1232.      */
  1233.     public function getSharedSet()
  1234.     {
  1235.         $this->checkState();
  1236.         throw new NotImplementedException();
  1237.     }
  1238.     /**
  1239.      * {@inheritDoc}
  1240.      *
  1241.      * @return void
  1242.      *
  1243.      * @throws InvalidItemStateException
  1244.      *
  1245.      * @api
  1246.      */
  1247.     public function removeSharedSet()
  1248.     {
  1249.         $this->checkState();
  1250.         $this->setModified();
  1251.         throw new NotImplementedException('Write');
  1252.     }
  1253.     /**
  1254.      * {@inheritDoc}
  1255.      *
  1256.      * @throws InvalidItemStateException
  1257.      *
  1258.      * @api
  1259.      */
  1260.     public function removeShare()
  1261.     {
  1262.         $this->checkState();
  1263.         $this->setModified();
  1264.         throw new NotImplementedException('Write');
  1265.     }
  1266.     /**
  1267.      * {@inheritDoc}
  1268.      *
  1269.      * @return bool
  1270.      *
  1271.      * @api
  1272.      */
  1273.     public function isCheckedOut()
  1274.     {
  1275.         $this->checkState();
  1276.         $workspace $this->session->getWorkspace();
  1277.         $versionManager $workspace->getVersionManager();
  1278.         return $versionManager->isCheckedOut($this->getPath());
  1279.     }
  1280.     /**
  1281.      * {@inheritDoc}
  1282.      *
  1283.      * @return bool
  1284.      *
  1285.      * @api
  1286.      */
  1287.     public function isLocked()
  1288.     {
  1289.         $this->checkState();
  1290.         throw new NotImplementedException();
  1291.     }
  1292.     /**
  1293.      * {@inheritDoc}
  1294.      *
  1295.      * @throws InvalidItemStateException
  1296.      *
  1297.      * @api
  1298.      */
  1299.     public function followLifecycleTransition($transition)
  1300.     {
  1301.         $this->checkState();
  1302.         $this->setModified();
  1303.         throw new NotImplementedException('Write');
  1304.     }
  1305.     /**
  1306.      * {@inheritDoc}
  1307.      *
  1308.      * @return string[]
  1309.      *
  1310.      * @throws InvalidItemStateException
  1311.      *
  1312.      * @api
  1313.      */
  1314.     public function getAllowedLifecycleTransitions()
  1315.     {
  1316.         $this->checkState();
  1317.         throw new NotImplementedException('Write');
  1318.     }
  1319.     /**
  1320.      * Refresh this node
  1321.      *
  1322.      * {@inheritDoc}
  1323.      *
  1324.      * This is also called internally to refresh when the node is accessed in
  1325.      * state DIRTY.
  1326.      *
  1327.      * @see Item::checkState
  1328.      */
  1329.     protected function refresh($keepChanges$internal false)
  1330.     {
  1331.         if (! $internal && $this->isDeleted()) {
  1332.             throw new InvalidItemStateException('This item has been removed and can not be refreshed');
  1333.         }
  1334.         $deleted false;
  1335.         // Get properties and children from backend
  1336.         try {
  1337.             $json $this->objectManager->getTransport()->getNode(
  1338.                 is_null($this->oldPath)
  1339.                     ? $this->path
  1340.                     $this->oldPath
  1341.             );
  1342.         } catch (ItemNotFoundException $ex) {
  1343.             // The node was deleted in another session
  1344.             if (! $this->objectManager->purgeDisappearedNode($this->path$keepChanges)) {
  1345.                 throw new LogicException($this->path " should be purged and not kept");
  1346.             }
  1347.             $keepChanges false// delete never keeps changes
  1348.             if (! $internal) {
  1349.                 // this is not an internal update
  1350.                 $deleted true;
  1351.             }
  1352.             // continue with empty data, parseData will notify all cached
  1353.             // children and all properties that we are removed
  1354.             $json = [];
  1355.         }
  1356.         $this->parseData($jsontrue$keepChanges);
  1357.         if ($deleted) {
  1358.             $this->setDeleted();
  1359.         }
  1360.     }
  1361.     /**
  1362.      * Remove this node
  1363.      *
  1364.      * {@inheritDoc}
  1365.      *
  1366.      * A jackalope node needs to notify the parent node about this if it is
  1367.      * cached, in addition to \PHPCR\ItemInterface::remove()
  1368.      *
  1369.      * @uses Node::unsetChildNode()
  1370.      *
  1371.      * @api
  1372.      */
  1373.     public function remove()
  1374.     {
  1375.         $this->checkState();
  1376.         $parent $this->getParent();
  1377.         $parentNodeType $parent->getPrimaryNodeType();
  1378.         //will throw a ConstraintViolationException if this node can't be removed
  1379.         $parentNodeType->canRemoveNode($this->getName(), true);
  1380.         if ($parent) {
  1381.             $parent->unsetChildNode($this->nametrue);
  1382.         }
  1383.         // once we removed ourselves, $this->getParent() won't work anymore. do this last
  1384.         parent::remove();
  1385.     }
  1386.     /**
  1387.      * Removes the reference in the internal node storage
  1388.      *
  1389.      * @param string $name  the name of the child node to unset
  1390.      * @param bool   $check whether a state check should be done - set to false
  1391.      *      during internal update operations
  1392.      *
  1393.      * @throws ItemNotFoundException If there is no child with $name
  1394.      * @throws InvalidItemStateException
  1395.      *
  1396.      * @private
  1397.      */
  1398.     public function unsetChildNode($name$check)
  1399.     {
  1400.         if ($check) {
  1401.             $this->checkState();
  1402.         }
  1403.         $key array_search($name$this->nodes);
  1404.         if ($key === false) {
  1405.             if (! $check) {
  1406.                 // inside a refresh operation
  1407.                 return;
  1408.             }
  1409.             throw new ItemNotFoundException("Could not remove child node because it's already gone");
  1410.         }
  1411.         unset($this->nodes[$key]);
  1412.         if (null !== $this->originalNodesOrder) {
  1413.             $this->originalNodesOrder array_flip($this->originalNodesOrder);
  1414.             unset($this->originalNodesOrder[$name]);
  1415.             $this->originalNodesOrder array_flip($this->originalNodesOrder);
  1416.         }
  1417.     }
  1418.     /**
  1419.      * Adds child node to this node for internal reference
  1420.      *
  1421.      * @param NodeInterface $node  The name of the child node
  1422.      * @param boolean $check whether to check state
  1423.      * @param string  $name  is used in cases where $node->getName would not return the correct name (during move operation)
  1424.      *
  1425.      * @throws InvalidItemStateException
  1426.      * @throws RepositoryException
  1427.      *
  1428.      * @private
  1429.      */
  1430.     public function addChildNode(NodeInterface $node$check$name null)
  1431.     {
  1432.         if ($check) {
  1433.             $this->checkState();
  1434.         }
  1435.         if (is_null($name)) {
  1436.             $name $node->getName();
  1437.         }
  1438.         $nt $this->getPrimaryNodeType();
  1439.         //will throw a ConstraintViolationException if this node can't be added
  1440.         $nt->canAddChildNode($name$node->getPrimaryNodeType()->getName(), true);
  1441.         // TODO: same name siblings
  1442.         $this->nodes[] = $name;
  1443.         if (null !== $this->originalNodesOrder) {
  1444.             $this->originalNodesOrder[] = $name;
  1445.         }
  1446.     }
  1447.     /**
  1448.      * Removes the reference in the internal node storage
  1449.      *
  1450.      * @param string $name the name of the property to unset.
  1451.      *
  1452.      * @throws ItemNotFoundException If this node has no property with name $name
  1453.      * @throws InvalidItemStateException
  1454.      * @throws RepositoryException
  1455.      *
  1456.      * @private
  1457.      */
  1458.     public function unsetProperty($name)
  1459.     {
  1460.         $this->checkState();
  1461.         $this->setModified();
  1462.         if (!array_key_exists($name$this->properties)) {
  1463.             throw new ItemNotFoundException('Implementation Error: Could not remove property from node because it is already gone');
  1464.         }
  1465.         $this->deletedProperties[$name] = $this->properties[$name];
  1466.         unset($this->properties[$name]);
  1467.     }
  1468.     /**
  1469.      * In addition to calling parent method, tell all properties and clean deletedProperties
  1470.      */
  1471.     public function confirmSaved()
  1472.     {
  1473.         foreach ($this->properties as $property) {
  1474.             if ($property->isModified() || $property->isNew()) {
  1475.                 $property->confirmSaved();
  1476.             }
  1477.         }
  1478.         $this->deletedProperties = [];
  1479.         parent::confirmSaved();
  1480.     }
  1481.     /**
  1482.      * In addition to calling parent method, tell all properties
  1483.      */
  1484.     public function setPath($path$move false)
  1485.     {
  1486.         parent::setPath($path$move);
  1487.         foreach ($this->properties as $property) {
  1488.             $property->setPath($path.'/'.$property->getName(), $move);
  1489.         }
  1490.     }
  1491.     /**
  1492.      * Make sure $p is an absolute path
  1493.      *
  1494.      * If its a relative path, prepend the path to this node, otherwise return as is
  1495.      *
  1496.      * @param string $p the relative or absolute property or node path
  1497.      *
  1498.      * @return string the absolute path to this item, with relative paths resolved against the current node
  1499.      */
  1500.     protected function getChildPath($p)
  1501.     {
  1502.         if ('' == $p) {
  1503.             throw new InvalidArgumentException("Name can not be empty");
  1504.         }
  1505.         if ($p[0] == '/') {
  1506.             return $p;
  1507.         }
  1508.         //relative path, combine with base path for this node
  1509.         $path $this->path === '/' '/' $this->path.'/';
  1510.         return $path $p;
  1511.     }
  1512.     /**
  1513.      * Filter the list of names according to the filter expression / array
  1514.      *
  1515.      * @param string|array $filter according to getNodes|getProperties
  1516.      * @param array        $names  list of names to filter
  1517.      *
  1518.      * @return array the names in $names that match the filter
  1519.      */
  1520.     protected static function filterNames($filter$names)
  1521.     {
  1522.         if ($filter !== null) {
  1523.             $filtered = [];
  1524.             $filter = (array) $filter;
  1525.             foreach ($filter as $k => $f) {
  1526.                 $f trim($f);
  1527.                 $filter[$k] = strtr($f, [
  1528.                     '*'=>'.*'//wildcard
  1529.                     '.'  => '\\.'//escape regexp
  1530.                     '\\' => '\\\\',
  1531.                     '{'  => '\\{',
  1532.                     '}'  => '\\}',
  1533.                     '('  => '\\(',
  1534.                     ')'  => '\\)',
  1535.                     '+'  => '\\+',
  1536.                     '^'  => '\\^',
  1537.                     '$'  => '\\$'
  1538.                 ]);
  1539.             }
  1540.             foreach ($names as $name) {
  1541.                 foreach ($filter as $f) {
  1542.                     if (preg_match('/^'.$f.'$/'$name)) {
  1543.                         $filtered[] = $name;
  1544.                     }
  1545.                 }
  1546.             }
  1547.         } else {
  1548.             $filtered $names;
  1549.         }
  1550.         return $filtered;
  1551.     }
  1552.     /**
  1553.      * Provide Traversable interface: redirect to getNodes with no filter
  1554.      *
  1555.      * @return Iterator over all child nodes
  1556.      * @throws RepositoryException
  1557.      */
  1558.     #[\ReturnTypeWillChange]
  1559.     public function getIterator()
  1560.     {
  1561.         $this->checkState();
  1562.         return $this->getNodes();
  1563.     }
  1564.     /**
  1565.      * Implement really setting the property without any notification.
  1566.      *
  1567.      * Implement the setProperty, but also used from constructor or in refresh,
  1568.      * when the backend has a new property that is not yet loaded in memory.
  1569.      *
  1570.      * @param string  $name
  1571.      * @param mixed   $value
  1572.      * @param string  $type
  1573.      * @param boolean $internal whether we are setting this node through api or internally
  1574.      *
  1575.      * @return Property
  1576.      *
  1577.      * @throws InvalidArgumentException
  1578.      * @throws LockException
  1579.      * @throws ConstraintViolationException
  1580.      * @throws RepositoryException
  1581.      * @throws UnsupportedRepositoryOperationException
  1582.      * @throws ValueFormatException
  1583.      * @throws VersionException
  1584.      *
  1585.      * @see Node::setProperty
  1586.      * @see Node::refresh
  1587.      * @see Node::__construct
  1588.      */
  1589.     protected function _setProperty($name$value$type$internal)
  1590.     {
  1591.         if ($name === '' || false !== strpos($name'/')) {
  1592.             throw new InvalidArgumentException("The name '$name' is no valid property name");
  1593.         }
  1594.         if (!isset($this->properties[$name])) {
  1595.             $path $this->getChildPath($name);
  1596.             $property $this->factory->get(
  1597.                 Property::class,
  1598.                 [
  1599.                     ['type' => $type'value' => $value],
  1600.                     $path,
  1601.                     $this->session,
  1602.                     $this->objectManager,
  1603.                     ! $internal
  1604.                 ]
  1605.             );
  1606.             $this->properties[$name] = $property;
  1607.             if (! $internal) {
  1608.                 $this->setModified();
  1609.             }
  1610.         } else {
  1611.             if ($internal) {
  1612.                 $this->properties[$name]->_setValue($value$type);
  1613.                 if ($this->properties[$name]->isDirty()) {
  1614.                     $this->properties[$name]->setClean();
  1615.                 }
  1616.             } else {
  1617.                 $this->properties[$name]->setValue($value$type);
  1618.             }
  1619.         }
  1620.         return $this->properties[$name];
  1621.     }
  1622.     /**
  1623.      * Overwrite to set the properties dirty as well.
  1624.      *
  1625.      * @private
  1626.      */
  1627.     public function setDirty($keepChanges false$targetState false)
  1628.     {
  1629.         parent::setDirty($keepChanges$targetState);
  1630.         foreach ($this->properties as $property) {
  1631.             if ($keepChanges && self::STATE_NEW !== $property->getState()) {
  1632.                 // if we want to keep changes, we do not want to set new properties dirty.
  1633.                 $property->setDirty($keepChanges$targetState);
  1634.             }
  1635.         }
  1636.     }
  1637.     /**
  1638.      * Mark all cached children as dirty.
  1639.      *
  1640.      * @private
  1641.      */
  1642.     public function setChildrenDirty()
  1643.     {
  1644.         foreach ($this->objectManager->getCachedDescendants($this->getPath()) as $childNode) {
  1645.             $childNode->setDirty();
  1646.         }
  1647.     }
  1648.     /**
  1649.      * In addition to set this item deleted, set all properties to deleted.
  1650.      *
  1651.      * They will be automatically deleted by the backend, but the user might
  1652.      * still have a reference to one of the property objects.
  1653.      *
  1654.      * @private
  1655.      */
  1656.     public function setDeleted()
  1657.     {
  1658.         parent::setDeleted();
  1659.         foreach ($this->properties as $property) {
  1660.             $property->setDeleted(); // not all properties are tracked in objectmanager
  1661.         }
  1662.     }
  1663.     /**
  1664.      * {@inheritDoc}
  1665.      *
  1666.      * Additionally, notifies all properties of this node. Child nodes are not
  1667.      * notified, it is the job of the ObjectManager to know which nodes are
  1668.      * cached and notify them.
  1669.      */
  1670.     public function beginTransaction()
  1671.     {
  1672.         parent::beginTransaction();
  1673.         // Notify the children properties
  1674.         foreach ($this->properties as $prop) {
  1675.             $prop->beginTransaction();
  1676.         }
  1677.     }
  1678.     /**
  1679.      * {@inheritDoc}
  1680.      *
  1681.      * Additionally, notifies all properties of this node. Child nodes are not
  1682.      * notified, it is the job of the ObjectManager to know which nodes are
  1683.      * cached and notify them.
  1684.      */
  1685.     public function commitTransaction()
  1686.     {
  1687.         parent::commitTransaction();
  1688.         foreach ($this->properties as $prop) {
  1689.             $prop->commitTransaction();
  1690.         }
  1691.     }
  1692.     /**
  1693.      * {@inheritDoc}
  1694.      *
  1695.      * Additionally, notifies all properties of this node. Child nodes are not
  1696.      * notified, it is the job of the ObjectManager to know which nodes are
  1697.      * cached and notify them.
  1698.      */
  1699.     public function rollbackTransaction()
  1700.     {
  1701.         parent::rollbackTransaction();
  1702.         foreach ($this->properties as $prop) {
  1703.             $prop->rollbackTransaction();
  1704.         }
  1705.     }
  1706. }