"Fossies" - the Fresh Open Source Software Archive

Member "neos-development-collection-7.0.1/Neos.ContentRepository/Classes/Domain/Model/Node.php" (23 Feb 2021, 83209 Bytes) of package /linux/www/neos-development-collection-7.0.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "Node.php": 7.0.0_vs_7.0.1.

    1 <?php
    2 namespace Neos\ContentRepository\Domain\Model;
    3 
    4 /*
    5  * This file is part of the Neos.ContentRepository package.
    6  *
    7  * (c) Contributors of the Neos Project - www.neos.io
    8  *
    9  * This package is Open Source Software. For the full copyright and license
   10  * information, please view the LICENSE file which was distributed with this
   11  * source code.
   12  */
   13 
   14 use Neos\ContentRepository\DimensionSpace\DimensionSpace\DimensionSpacePoint;
   15 use Neos\ContentRepository\Domain\Projection\Content\TraversableNodeInterface;
   16 use Neos\ContentRepository\Domain\Projection\Content\TraversableNodes;
   17 use Neos\ContentRepository\Domain\ContentStream\ContentStreamIdentifier;
   18 use Neos\ContentRepository\Domain\NodeAggregate\NodeAggregateIdentifier;
   19 use Neos\ContentRepository\Domain\NodeAggregate\NodeName;
   20 use Neos\ContentRepository\Domain\ContentSubgraph\NodePath;
   21 use Neos\ContentRepository\Domain\NodeType\NodeTypeConstraints;
   22 use Neos\ContentRepository\Domain\NodeType\NodeTypeName;
   23 use Neos\ContentRepository\Domain\Projection\Content\PropertyCollectionInterface;
   24 use Neos\ContentRepository\Exception\NodeConfigurationException;
   25 use Neos\ContentRepository\Exception\NodeTypeNotFoundException;
   26 use Neos\ContentRepository\Exception\NodeMethodIsUnsupported;
   27 use Neos\EventSourcedContentRepository\Domain\Context\NodeAggregate\OriginDimensionSpacePoint;
   28 use Neos\EventSourcedContentRepository\Domain\ValueObject\PropertyName;
   29 use Neos\Flow\Annotations as Flow;
   30 use Neos\Cache\CacheAwareInterface;
   31 use Neos\Flow\Property\PropertyMapper;
   32 use Neos\Utility\ObjectAccess;
   33 use Neos\ContentRepository\Domain\Factory\NodeFactory;
   34 use Neos\ContentRepository\Domain\Repository\NodeDataRepository;
   35 use Neos\ContentRepository\Domain\Service\Context;
   36 use Neos\ContentRepository\Domain\Service\ContextFactoryInterface;
   37 use Neos\ContentRepository\Domain\Service\NodeServiceInterface;
   38 use Neos\ContentRepository\Domain\Utility\NodePaths;
   39 use Neos\ContentRepository\Exception\NodeConstraintException;
   40 use Neos\ContentRepository\Exception\NodeException;
   41 use Neos\ContentRepository\Exception\NodeExistsException;
   42 use Neos\ContentRepository\Utility;
   43 
   44 /**
   45  * This is the main API for storing and retrieving content in the system.
   46  *
   47  * @Flow\Scope("prototype")
   48  * @api
   49  */
   50 class Node implements NodeInterface, CacheAwareInterface, TraversableNodeInterface
   51 {
   52     /**
   53      * The NodeData entity this version is for.
   54      *
   55      * @var NodeData
   56      */
   57     protected $nodeData;
   58 
   59     /**
   60      * @var Context
   61      */
   62     protected $context;
   63 
   64     /**
   65      * Defines if the NodeData represented by this Node is already
   66      * in the same context or if it is currently just "shining through".
   67      *
   68      * @var boolean
   69      */
   70     protected $nodeDataIsMatchingContext = null;
   71 
   72     /**
   73      * @Flow\Inject
   74      * @var NodeDataRepository
   75      */
   76     protected $nodeDataRepository;
   77 
   78     /**
   79      * @Flow\Inject
   80      * @var NodeFactory
   81      */
   82     protected $nodeFactory;
   83 
   84     /**
   85      * @Flow\Inject
   86      * @var PropertyMapper
   87      */
   88     protected $propertyMapper;
   89 
   90     /**
   91      * @Flow\Inject
   92      * @var ContextFactoryInterface
   93      */
   94     protected $contextFactory;
   95 
   96     /**
   97      * @Flow\Inject
   98      * @var NodeServiceInterface
   99      */
  100     protected $nodeService;
  101 
  102     /**
  103      * @param NodeData $nodeData
  104      * @param Context $context
  105      * @Flow\Autowiring(false)
  106      */
  107     public function __construct(NodeData $nodeData, Context $context)
  108     {
  109         $this->nodeData = $nodeData;
  110         $this->context = $context;
  111     }
  112 
  113     /**
  114      * Returns the absolute path of this node with additional context information (such as the workspace name).
  115      *
  116      * Example: /sites/mysitecom/homepage/about@user-admin
  117      *
  118      * NOTE: This method will probably be removed at some point. Code should never rely on the exact format of the context path
  119      *       since that might change in the future.
  120      *
  121      * @return string Node path with context information
  122      */
  123     public function getContextPath()
  124     {
  125         return NodePaths::generateContextPath($this->getPath(), $this->context->getWorkspaceName(), $this->context->getDimensions());
  126     }
  127 
  128     /**
  129      * Set the name of the node to $newName, keeping its position as it is.
  130      *
  131      * @param string $newName
  132      * @return void
  133      * @throws NodeException if you try to set the name of the root node.
  134      * @throws \InvalidArgumentException if $newName is invalid
  135      * @throws NodeTypeNotFoundException
  136      * @api
  137      */
  138     public function setName($newName): void
  139     {
  140         if ($this->getName() === $newName) {
  141             return;
  142         }
  143 
  144         if (!is_string($newName) || preg_match(NodeInterface::MATCH_PATTERN_NAME, $newName) !== 1) {
  145             throw new \InvalidArgumentException('Invalid node name "' . $newName . '" (a node name must only contain lowercase characters, numbers and the "-" sign).', 1364290748);
  146         }
  147 
  148         if ($this->isRoot()) {
  149             throw new NodeException('The root node cannot be renamed.', 1346778388);
  150         }
  151 
  152         $this->setPath(NodePaths::addNodePathSegment($this->getParentPath(), $newName));
  153         $this->nodeDataRepository->persistEntities();
  154         $this->context->getFirstLevelNodeCache()->flush();
  155         $this->emitNodeUpdated($this);
  156     }
  157 
  158     /**
  159      * Sets the absolute path of this node.
  160      *
  161      * This method is only for internal use by the content repository or node methods. Changing
  162      * the path of a node manually may lead to unexpected behavior.
  163      *
  164      * To achieve a correct behavior when changing the path (moving the node) in a workspace, a shadow node data that will
  165      * hide the node data in the base workspace will be created. Thus queries do not need to worry about moved nodes.
  166      * Through a movedTo reference the shadow node data will be removed when publishing the moved node.
  167      *
  168      * @param string $path
  169      * @param boolean $checkForExistence Checks for existence at target path, internally used for recursions and shadow nodes.
  170      * @return void
  171      * @throws NodeException
  172      * @throws NodeTypeNotFoundException
  173      */
  174     protected function setPath(string $path, bool $checkForExistence = true): void
  175     {
  176         $originalPath = $this->nodeData->getPath();
  177         if ($originalPath === $path) {
  178             return;
  179         }
  180 
  181         $pathAvailable = $checkForExistence ? $this->isNodePathAvailable($path) : true;
  182         if (!$pathAvailable) {
  183             throw new NodeException(sprintf('Can not rename the node "%s" as a node already exists on path "%s"', $this->getPath(), $path), 1414436551);
  184         }
  185 
  186         $changedNodePathsCollection = $this->setPathInternal($path, !$checkForExistence);
  187         $this->nodeDataRepository->persistEntities();
  188         array_walk($changedNodePathsCollection, function ($changedNodePathInformation) {
  189             call_user_func_array([
  190                 $this,
  191                 'emitNodePathChanged'
  192             ], $changedNodePathInformation);
  193         });
  194     }
  195 
  196     /**
  197      * Checks if the given node path is available for this node, so either no node with this path exists or an existing node has the same identifier.
  198      *
  199      * @param string $path
  200      * @return boolean
  201      */
  202     protected function isNodePathAvailable(string $path): bool
  203     {
  204         $existingNodeDataArray = $this->nodeDataRepository->findByPathWithoutReduce($path, $this->context->getWorkspace());
  205 
  206         $nonMatchingNodeData = array_filter($existingNodeDataArray, function (NodeData $nodeData) {
  207             return ($nodeData->getIdentifier() !== $this->getIdentifier());
  208         });
  209 
  210         return ($nonMatchingNodeData === []);
  211     }
  212 
  213     /**
  214      * Moves a node and sub nodes to the new path.
  215      * This process is different depending on the fact if the node is an aggregate type or not.
  216      *
  217      * @param string $destinationPath the new node path
  218      * @param boolean $recursiveCall is this a recursive call
  219      * @return array NodeVariants and old and new paths
  220      * @throws NodeException
  221      * @throws NodeTypeNotFoundException
  222      */
  223     protected function setPathInternal(string $destinationPath, bool $recursiveCall): array
  224     {
  225         if ($this->getNodeType()->isAggregate()) {
  226             return $this->setPathInternalForAggregate($destinationPath, $recursiveCall);
  227         }
  228 
  229         $originalPath = $this->nodeData->getPath();
  230 
  231         /** @var Node $childNode */
  232         foreach ($this->getChildNodes() as $childNode) {
  233             $childNode->setPath(NodePaths::addNodePathSegment($destinationPath, $childNode->getName()), false);
  234         }
  235 
  236         $this->moveNodeToDestinationPath($this, $destinationPath);
  237 
  238         return [
  239             [$this, $originalPath, $this->getNodeData()->getPath(), $recursiveCall]
  240         ];
  241     }
  242 
  243     /**
  244      * Moves a node and sub nodes to the new path given with special logic for aggregate node types.
  245      *
  246      * @param string $destinationPath the new node path
  247      * @param boolean $recursiveCall is this a recursive call
  248      * @return array of arrays with NodeVariant and old and new path and if this was a recursive call
  249      */
  250     protected function setPathInternalForAggregate(string $destinationPath, bool $recursiveCall): array
  251     {
  252         $originalPath = $this->nodeData->getPath();
  253         $nodeDataVariantsAndChildren = $this->nodeDataRepository->findByPathWithoutReduce($originalPath, $this->context->getWorkspace(), true, true);
  254 
  255         $changedNodePathsCollection = array_map(function ($nodeData) use ($destinationPath, $originalPath, $recursiveCall) {
  256             return $this->moveNodeData($nodeData, $originalPath, $destinationPath, $recursiveCall);
  257         }, $nodeDataVariantsAndChildren);
  258 
  259         return array_filter($changedNodePathsCollection);
  260     }
  261 
  262     /**
  263      * Moves a NodeData object that is either a variant or child node to the given destination path.
  264      *
  265      * @param NodeData $nodeData
  266      * @param string $originalPath
  267      * @param string $destinationPath
  268      * @param boolean $recursiveCall
  269      * @return array|null
  270      * @throws NodeConfigurationException
  271      */
  272     protected function moveNodeData(NodeData $nodeData, string $originalPath, string $destinationPath, bool $recursiveCall)
  273     {
  274         $recursiveCall = $recursiveCall || ($this->nodeData !== $nodeData);
  275         $nodeVariant = null;
  276         // $nodeData at this point could contain *our own NodeData reference* ($this->nodeData), as we find all NodeData objects
  277         // (across all dimensions) with the same path.
  278         //
  279         // We need to ensure that our own Node object's nodeData reference ($this->nodeData) is also updated correctly if a new NodeData object
  280         // is returned; as we rely on the fact that $this->getPath() will return the new node path in all circumstances.
  281         //
  282         // However, $this->createNodeForVariant() only returns $this if the Context object is the same as $this->context; which is not
  283         // the case if $this->context contains dimension fallbacks such as "Language: EN, DE".
  284         //
  285         // The "if" statement below is actually a workaround to ensure that if the NodeData object is our own one, we update *ourselves* correctly,
  286         // and thus return the correct (new) Node Path when calling $this->getPath() afterwards.
  287         // FIXME: This is dangerous and probably the NodeFactory should take care of globally tracking usage of NodeData objects and replacing them in Node objects
  288 
  289         if ($this->nodeData === $nodeData) {
  290             $nodeVariant = $this;
  291         }
  292 
  293         if ($nodeVariant === null) {
  294             $nodeVariant = $this->createNodeForVariant($nodeData);
  295         }
  296 
  297         $moveVariantResult = $nodeVariant === null ? null : $this->moveVariantOrChild($originalPath, $destinationPath, $nodeVariant);
  298         if ($moveVariantResult !== null) {
  299             array_push($moveVariantResult, $recursiveCall);
  300         }
  301 
  302         return $moveVariantResult;
  303     }
  304 
  305     /**
  306      * Create a node for the given NodeData, given that it is a variant of the current node
  307      *
  308      * @param NodeData $nodeData
  309      * @return NodeInterface|null
  310      * @throws NodeConfigurationException
  311      */
  312     protected function createNodeForVariant(NodeData $nodeData): ?NodeInterface
  313     {
  314         $contextProperties = $this->context->getProperties();
  315         $contextProperties['dimensions'] = $nodeData->getDimensionValues();
  316         unset($contextProperties['targetDimensions']);
  317         $adjustedContext = $this->contextFactory->create($contextProperties);
  318 
  319         return $this->nodeFactory->createFromNodeData($nodeData, $adjustedContext);
  320     }
  321 
  322     /**
  323      * Moves the given variant or child node to the destination defined by the given path which is
  324      * the new path for the originally moved (parent|variant) node
  325      *
  326      * @param string $aggregateOriginalPath
  327      * @param string $aggregateDestinationPath
  328      * @param NodeInterface $nodeToMove
  329      * @return array NodeVariant and old and new path
  330      */
  331     protected function moveVariantOrChild(string $aggregateOriginalPath, string $aggregateDestinationPath, NodeInterface $nodeToMove = null): ?array
  332     {
  333         if ($nodeToMove === null) {
  334             return null;
  335         }
  336 
  337         $variantOriginalPath = $nodeToMove->getPath();
  338         $relativePathSegment = NodePaths::getRelativePathBetween($aggregateOriginalPath, $variantOriginalPath);
  339         $variantDestinationPath = NodePaths::addNodePathSegment($aggregateDestinationPath, $relativePathSegment);
  340         $this->moveNodeToDestinationPath($nodeToMove, $variantDestinationPath);
  341 
  342         return [$nodeToMove, $variantOriginalPath, $nodeToMove->getPath()];
  343     }
  344 
  345     /**
  346      * Moves the given node to the destination path by modifying the underlaying NodeData object.
  347      *
  348      * @param NodeInterface $node
  349      * @param string $destinationPath
  350      * @return void
  351      */
  352     protected function moveNodeToDestinationPath(NodeInterface $node, $destinationPath)
  353     {
  354         $nodeData = $node->getNodeData();
  355         $possibleShadowedNodeData = $nodeData->move($destinationPath, $this->context->getWorkspace());
  356         if ($node instanceof Node) {
  357             $node->setNodeData($possibleShadowedNodeData);
  358         }
  359     }
  360 
  361     /**
  362      * {@inheritdoc}
  363      */
  364     public function getOtherNodeVariants(): array
  365     {
  366         return array_filter(
  367             $this->context->getNodeVariantsByIdentifier($this->getIdentifier()),
  368             function ($node) {
  369                 return ($node instanceof NodeInterface && $node->getNodeData() !== $this->nodeData);
  370             }
  371         );
  372     }
  373 
  374     /**
  375      * @return \DateTimeInterface
  376      */
  377     public function getCreationDateTime(): \DateTimeInterface
  378     {
  379         return $this->nodeData->getCreationDateTime();
  380     }
  381 
  382     /**
  383      * @return \DateTimeInterface
  384      */
  385     public function getLastModificationDateTime(): \DateTimeInterface
  386     {
  387         return $this->nodeData->getLastModificationDateTime();
  388     }
  389 
  390     /**
  391      * @param \DateTimeInterface $lastModificationDateTime
  392      * @return void
  393      */
  394     public function setLastPublicationDateTime(\DateTimeInterface $lastModificationDateTime)
  395     {
  396         $this->nodeData->setLastPublicationDateTime($lastModificationDateTime);
  397     }
  398 
  399     /**
  400      * @return \DateTimeInterface|null Date of last publication or null if the node was not published yet
  401      */
  402     public function getLastPublicationDateTime(): ?\DateTimeInterface
  403     {
  404         return $this->nodeData->getLastPublicationDateTime();
  405     }
  406 
  407     /**
  408      * Returns the path of this node
  409      *
  410      * @return string
  411      * @deprecated with version 4.3, use TraversableNodeInterface::findNodePath() instead.
  412      */
  413     public function getPath()
  414     {
  415         return $this->nodeData->getPath();
  416     }
  417 
  418     /**
  419      * Returns the level at which this node is located.
  420      * Counting starts with 0 for "/", 1 for "/foo", 2 for "/foo/bar" etc.
  421      *
  422      * @return integer
  423      * @deprecated with version 4.3 - Use TraversableNodeInterface::findNodePath()->getDepth() instead
  424      */
  425     public function getDepth()
  426     {
  427         return $this->nodeData->getDepth();
  428     }
  429 
  430     /**
  431      * Returns the name of this node
  432      *
  433      * @return string
  434      * @deprecated with version 4.3, use TraversableNodeInterface::getNodeName() instead.
  435      */
  436     public function getName()
  437     {
  438         return $this->nodeData->getName();
  439     }
  440 
  441     /**
  442      * Returns the node label as generated by the configured node label generator
  443      *
  444      * @return string
  445      * @throws NodeTypeNotFoundException
  446      */
  447     public function getLabel(): string
  448     {
  449         return $this->getNodeType()->getNodeLabelGenerator()->getLabel($this);
  450     }
  451 
  452     /**
  453      * Sets the workspace of this node.
  454      *
  455      * This method is only for internal use by the content repository. Changing
  456      * the workspace of a node manually may lead to unexpected behavior.
  457      *
  458      * @param Workspace $workspace
  459      * @return void
  460      * @throws NodeException
  461      * @throws NodeTypeNotFoundException
  462      */
  463     public function setWorkspace(Workspace $workspace): void
  464     {
  465         if ($this->getWorkspace()->getName() === $workspace->getName()) {
  466             return;
  467         }
  468         if (!$this->isNodeDataMatchingContext()) {
  469             $this->materializeNodeData();
  470         }
  471         $this->nodeData->setWorkspace($workspace);
  472         $this->context->getFirstLevelNodeCache()->flush();
  473         $this->emitNodeUpdated($this);
  474     }
  475 
  476     /**
  477      * Returns the workspace this node is contained in
  478      *
  479      * @return Workspace
  480      */
  481     public function getWorkspace()
  482     {
  483         return $this->nodeData->getWorkspace();
  484     }
  485 
  486     /**
  487      * Returns the identifier of this node
  488      *
  489      * @return string the node's UUID (unique within the workspace)
  490      * @deprecated with version 4.3, use getNodeAggregateIdentifier() instead.
  491      */
  492     public function getIdentifier()
  493     {
  494         return $this->nodeData->getIdentifier();
  495     }
  496 
  497     /**
  498      * Sets the index of this node
  499      *
  500      * NOTE: This method is meant for internal use and must only be used by other nodes.
  501      *
  502      * @param integer $index The new index
  503      * @return void
  504      * @throws NodeException
  505      * @throws NodeTypeNotFoundException
  506      */
  507     public function setIndex($index): void
  508     {
  509         if ($this->getIndex() === $index) {
  510             return;
  511         }
  512         if (!$this->isNodeDataMatchingContext()) {
  513             $this->materializeNodeData();
  514         }
  515         $this->nodeData->setIndex($index);
  516         $this->context->getFirstLevelNodeCache()->flush();
  517         $this->emitNodeUpdated($this);
  518     }
  519 
  520     /**
  521      * Returns the index of this node which determines the order among siblings
  522      * with the same parent node.
  523      *
  524      * @return integer
  525      */
  526     public function getIndex()
  527     {
  528         return $this->nodeData->getIndex();
  529     }
  530 
  531     /**
  532      * Returns the parent node of this node
  533      *
  534      * @return NodeInterface|null The parent node or NULL if this is the root node
  535      * @deprecated with version 4.3, use TraversableNodeInterface::findParentNode() instead.
  536      *  Beware that findParentNode() is not fully equivalent to this method.
  537      *  It has a different root node handling:
  538      *    - findParentNode() throws an exception for the root node
  539      *    - getParent() returns <code>null</code> for the root node
  540      */
  541     public function getParent()
  542     {
  543         if ($this->isRoot()) {
  544             return null;
  545         }
  546 
  547         $parentPath = $this->getParentPath();
  548         $node = $this->context->getFirstLevelNodeCache()->getByPath($parentPath);
  549         if ($node !== false) {
  550             return $node;
  551         }
  552         $node = $this->nodeDataRepository->findOneByPathInContext($parentPath, $this->context);
  553         $this->context->getFirstLevelNodeCache()->setByPath($parentPath, $node);
  554 
  555         return $node;
  556     }
  557 
  558     /**
  559      * Returns the parent node path
  560      *
  561      * @return string Absolute node path of the parent node
  562      * @deprecated with version 4.3, use TraversableNodeInterface::findParentNode()->findNodePath() instead.
  563      */
  564     public function getParentPath(): string
  565     {
  566         return $this->nodeData->getParentPath();
  567     }
  568 
  569 
  570     /**
  571      * Whether or not the node is the root node (i.e. has no parent node)
  572      *
  573      * @return bool
  574      */
  575     public function isRoot(): bool
  576     {
  577         return $this->getPath() === '/';
  578     }
  579 
  580     /**
  581      * Moves this node before the given node
  582      *
  583      * @param NodeInterface $referenceNode
  584      * @param string $newName
  585      * @throws NodeConstraintException if a node constraint prevents moving the node
  586      * @throws NodeException if you try to move the root node
  587      * @throws NodeExistsException
  588      * @throws NodeTypeNotFoundException
  589      * @api
  590      */
  591     public function moveBefore(NodeInterface $referenceNode, string $newName = null): void
  592     {
  593         if ($referenceNode === $this) {
  594             return;
  595         }
  596 
  597         if ($this->isRoot()) {
  598             throw new NodeException('The root node cannot be moved.', 1285005924);
  599         }
  600 
  601         $name = $newName !== null ? $newName : $this->getName();
  602         $referenceParentNode = $referenceNode->getParent();
  603 
  604         if ($referenceParentNode !== $this->getParent() && $referenceParentNode->getNode($name) !== null) {
  605             throw new NodeExistsException(sprintf('Node with path "%s" already exists.', $name), 1292503468);
  606         }
  607 
  608         if (($referenceParentNode instanceof Node && !$referenceParentNode->willChildNodeBeAutoCreated($name)) && !$referenceParentNode->isNodeTypeAllowedAsChildNode($this->getNodeType())) {
  609             throw new NodeConstraintException(sprintf('Cannot move %s before %s', $this, $referenceNode), 1400782413);
  610         }
  611 
  612         $this->emitBeforeNodeMove($this, $referenceNode, NodeDataRepository::POSITION_BEFORE);
  613         if ($referenceNode->getParentPath() !== $this->getParentPath()) {
  614             $this->setPath(NodePaths::addNodePathSegment($referenceNode->getParentPath(), $name));
  615         } else {
  616             if (!$this->isNodeDataMatchingContext()) {
  617                 $this->materializeNodeData();
  618             }
  619         }
  620 
  621         $this->nodeDataRepository->setNewIndex($this->nodeData, NodeDataRepository::POSITION_BEFORE, $referenceNode);
  622         $this->context->getFirstLevelNodeCache()->flush();
  623         $this->emitAfterNodeMove($this, $referenceNode, NodeDataRepository::POSITION_BEFORE);
  624         $this->emitNodeUpdated($this);
  625     }
  626 
  627     /**
  628      * Moves this node after the given node
  629      *
  630      * @param NodeInterface $referenceNode
  631      * @param string $newName
  632      * @throws NodeConstraintException if a node constraint prevents moving the node
  633      * @throws NodeException
  634      * @throws NodeExistsException
  635      * @throws NodeTypeNotFoundException
  636      * @api
  637      */
  638     public function moveAfter(NodeInterface $referenceNode, string $newName = null): void
  639     {
  640         if ($referenceNode === $this) {
  641             return;
  642         }
  643 
  644         if ($this->isRoot()) {
  645             throw new NodeException('The root node cannot be moved.', 1316361483);
  646         }
  647 
  648         $name = $newName !== null ? $newName : $this->getName();
  649         $referenceParentNode = $referenceNode->getParent();
  650 
  651         if ($referenceParentNode !== $this->getParent() && $referenceParentNode->getNode($name) !== null) {
  652             throw new NodeExistsException(sprintf('Node with path "%s" already exists.', $name), 1292503469);
  653         }
  654 
  655         if (($referenceParentNode instanceof Node && !$referenceParentNode->willChildNodeBeAutoCreated($name)) && !$referenceParentNode->isNodeTypeAllowedAsChildNode($this->getNodeType())) {
  656             throw new NodeConstraintException(sprintf('Cannot move %s after %s', $this, $referenceNode), 1404648100);
  657         }
  658 
  659         $this->emitBeforeNodeMove($this, $referenceNode, NodeDataRepository::POSITION_AFTER);
  660         if ($referenceNode->getParentPath() !== $this->getParentPath()) {
  661             $this->setPath(NodePaths::addNodePathSegment($referenceNode->getParentPath(), $name));
  662         } else {
  663             if (!$this->isNodeDataMatchingContext()) {
  664                 $this->materializeNodeData();
  665             }
  666         }
  667 
  668         $this->nodeDataRepository->setNewIndex($this->nodeData, NodeDataRepository::POSITION_AFTER, $referenceNode);
  669         $this->context->getFirstLevelNodeCache()->flush();
  670         $this->emitAfterNodeMove($this, $referenceNode, NodeDataRepository::POSITION_AFTER);
  671         $this->emitNodeUpdated($this);
  672     }
  673 
  674     /**
  675      * Moves this node into the given node
  676      *
  677      * @param NodeInterface $referenceNode
  678      * @param string $newName
  679      * @throws NodeConstraintException
  680      * @throws NodeException
  681      * @throws NodeExistsException
  682      * @throws NodeTypeNotFoundException
  683      * @api
  684      */
  685     public function moveInto(NodeInterface $referenceNode, string $newName = null): void
  686     {
  687         if ($referenceNode === $this || $referenceNode === $this->getParent()) {
  688             return;
  689         }
  690 
  691         if ($this->isRoot()) {
  692             throw new NodeException('The root node cannot be moved.', 1346769001);
  693         }
  694 
  695         $name = $newName !== null ? $newName : $this->getName();
  696 
  697         if ($referenceNode !== $this->getParent() && $referenceNode->getNode($name) !== null) {
  698             throw new NodeExistsException(sprintf('Node with path "%s" already exists.', $name), 1292503470);
  699         }
  700 
  701         if (($referenceNode instanceof Node && !$referenceNode->willChildNodeBeAutoCreated($name)) && !$referenceNode->isNodeTypeAllowedAsChildNode($this->getNodeType())) {
  702             throw new NodeConstraintException(sprintf('Cannot move %s into %s', $this, $referenceNode), 1404648124);
  703         }
  704 
  705         $this->emitBeforeNodeMove($this, $referenceNode, NodeDataRepository::POSITION_LAST);
  706         $this->setPath(NodePaths::addNodePathSegment($referenceNode->getPath(), $name));
  707 
  708         $this->nodeDataRepository->setNewIndex($this->nodeData, NodeDataRepository::POSITION_LAST);
  709         $this->context->getFirstLevelNodeCache()->flush();
  710         $this->emitAfterNodeMove($this, $referenceNode, NodeDataRepository::POSITION_LAST);
  711         $this->emitNodeUpdated($this);
  712     }
  713 
  714     /**
  715      * Copies this node before the given node
  716      *
  717      * @param NodeInterface $referenceNode
  718      * @param string $nodeName
  719      * @return NodeInterface
  720      * @throws NodeConstraintException
  721      * @throws NodeException
  722      * @throws NodeExistsException
  723      * @throws NodeTypeNotFoundException
  724      * @api
  725      */
  726     public function copyBefore(NodeInterface $referenceNode, $nodeName): NodeInterface
  727     {
  728         if ($referenceNode->getParent()->getNode($nodeName) !== null) {
  729             throw new NodeExistsException(sprintf('Node with path "%s/%s" already exists.', $referenceNode->getParent()->getPath(), $nodeName), 1292503465);
  730         }
  731 
  732         if (!$referenceNode->getParent()->isNodeTypeAllowedAsChildNode($this->getNodeType())) {
  733             throw new NodeConstraintException(sprintf('Cannot copy %s before %s', $this, $referenceNode), 1402050232);
  734         }
  735 
  736         $this->emitBeforeNodeCopy($this, $referenceNode->getParent());
  737         $copiedNode = $this->createRecursiveCopy($referenceNode->getParent(), $nodeName, $this->getNodeType()->isAggregate());
  738         $copiedNode->moveBefore($referenceNode);
  739 
  740         $this->context->getFirstLevelNodeCache()->flush();
  741         $this->emitNodeAdded($copiedNode);
  742         $this->emitAfterNodeCopy($copiedNode, $referenceNode->getParent());
  743 
  744         return $copiedNode;
  745     }
  746 
  747     /**
  748      * Copies this node after the given node
  749      *
  750      * @param NodeInterface $referenceNode
  751      * @param string $nodeName
  752      * @return NodeInterface
  753      * @throws NodeConstraintException
  754      * @throws NodeException
  755      * @throws NodeExistsException
  756      * @throws NodeTypeNotFoundException
  757      * @api
  758      */
  759     public function copyAfter(NodeInterface $referenceNode, $nodeName): NodeInterface
  760     {
  761         if ($referenceNode->getParent()->getNode($nodeName) !== null) {
  762             throw new NodeExistsException(sprintf('Node with path "%s/%s" already exists.', $referenceNode->getParent()->getPath(), $nodeName), 1292503466);
  763         }
  764 
  765         if (!$referenceNode->getParent()->isNodeTypeAllowedAsChildNode($this->getNodeType())) {
  766             throw new NodeConstraintException(sprintf('Cannot copy %s after %s', $this, $referenceNode), 1404648170);
  767         }
  768 
  769         $this->emitBeforeNodeCopy($this, $referenceNode->getParent());
  770         $copiedNode = $this->createRecursiveCopy($referenceNode->getParent(), $nodeName, $this->getNodeType()->isAggregate());
  771         $copiedNode->moveAfter($referenceNode);
  772 
  773         $this->context->getFirstLevelNodeCache()->flush();
  774         $this->emitNodeAdded($copiedNode);
  775         $this->emitAfterNodeCopy($copiedNode, $referenceNode->getParent());
  776 
  777         return $copiedNode;
  778     }
  779 
  780     /**
  781      * Copies this node into the given node
  782      *
  783      * @param NodeInterface $referenceNode
  784      * @param string $nodeName
  785      * @return NodeInterface
  786      * @throws NodeConstraintException
  787      * @throws NodeException
  788      * @throws NodeExistsException
  789      * @throws NodeTypeNotFoundException
  790      * @api
  791      */
  792     public function copyInto(NodeInterface $referenceNode, $nodeName): NodeInterface
  793     {
  794         $this->emitBeforeNodeCopy($this, $referenceNode);
  795         $copiedNode = $this->copyIntoInternal($referenceNode, $nodeName, $this->getNodeType()->isAggregate());
  796         $this->emitAfterNodeCopy($copiedNode, $referenceNode);
  797 
  798         return $copiedNode;
  799     }
  800 
  801     /**
  802      * Internal method to do the actual copying.
  803      *
  804      * For behavior of the $detachedCopy parameter, see method Node::createRecursiveCopy().
  805      *
  806      * @param NodeInterface $referenceNode
  807      * @param string $nodeName
  808      * @param boolean $detachedCopy
  809      * @return NodeInterface
  810      * @throws NodeConstraintException
  811      * @throws NodeException
  812      * @throws NodeExistsException
  813      * @throws NodeTypeNotFoundException
  814      */
  815     protected function copyIntoInternal(NodeInterface $referenceNode, string $nodeName, bool $detachedCopy): NodeInterface
  816     {
  817         if ($referenceNode->getNode($nodeName) !== null) {
  818             throw new NodeExistsException('Node with path "' . $referenceNode->getPath() . '/' . $nodeName . '" already exists.', 1292503467);
  819         }
  820 
  821         // On copy we basically re-recreate an existing node on a new location. As we skip the constraints check on
  822         // node creation we should do the same while writing the node on the new location.
  823         if (($referenceNode instanceof Node && !$referenceNode->willChildNodeBeAutoCreated($nodeName)) && !$referenceNode->isNodeTypeAllowedAsChildNode($this->getNodeType())) {
  824             throw new NodeConstraintException(sprintf('Cannot copy "%s" into "%s" due to node type constraints.', $this->__toString(), $referenceNode->__toString()), 1404648177);
  825         }
  826 
  827         $copiedNode = $this->createRecursiveCopy($referenceNode, $nodeName, $detachedCopy);
  828 
  829         $this->context->getFirstLevelNodeCache()->flush();
  830 
  831         $this->emitNodeAdded($copiedNode);
  832 
  833         return $copiedNode;
  834     }
  835 
  836     /**
  837      * Sets the specified property.
  838      *
  839      * If the node has a content object attached, the property will be set there
  840      * if it is settable.
  841      *
  842      * @param string $propertyName Name of the property
  843      * @param mixed $value Value of the property
  844      * @return mixed
  845      * @throws NodeException
  846      * @throws NodeTypeNotFoundException
  847      * @throws \Neos\Flow\Property\Exception
  848      * @throws \Neos\Flow\Security\Exception
  849      * @api
  850      */
  851     public function setProperty($propertyName, $value): void
  852     {
  853         $this->materializeNodeDataAsNeeded();
  854         // Arrays could potentially contain entities and objects could be entities. In that case even if the object is the same it needs to be persisted in NodeData.
  855         if (!is_object($value) && !is_array($value) && $this->getProperty($propertyName) === $value) {
  856             return;
  857         }
  858         $oldValue = $this->hasProperty($propertyName) ? $this->getProperty($propertyName) : null;
  859         $this->emitBeforeNodePropertyChange($this, $propertyName, $oldValue, $value);
  860         $this->nodeData->setProperty($propertyName, $value);
  861 
  862         $this->context->getFirstLevelNodeCache()->flush();
  863         $this->emitNodePropertyChanged($this, $propertyName, $oldValue, $value);
  864         $this->emitNodeUpdated($this);
  865     }
  866 
  867     /**
  868      * If this node has a property with the given name.
  869      *
  870      * If the node has a content object attached, the property will be checked
  871      * there.
  872      *
  873      * @param string $propertyName
  874      * @return boolean
  875      * @api
  876      */
  877     public function hasProperty($propertyName): bool
  878     {
  879         return $this->nodeData->hasProperty($propertyName);
  880     }
  881 
  882     /**
  883      * Returns the specified property.
  884      *
  885      * If the node has a content object attached, the property will be fetched
  886      * there if it is gettable.
  887      *
  888      * @param string $propertyName Name of the property
  889      * @param boolean $returnNodesAsIdentifiers If enabled, references to nodes are returned as node identifiers instead of NodeInterface instances
  890      * @return mixed value of the property
  891      * @throws NodeException
  892      * @throws \Neos\Flow\Property\Exception
  893      * @throws \Neos\Flow\Security\Exception
  894      * @throws NodeTypeNotFoundException
  895      * @api
  896      */
  897     public function getProperty($propertyName, bool $returnNodesAsIdentifiers = false)
  898     {
  899         $value = $this->nodeData->getProperty($propertyName);
  900         $nodeType = $this->getNodeType();
  901         $expectedPropertyType = null;
  902 
  903         if ($nodeType !== null) {
  904             $expectedPropertyType = $nodeType->getPropertyType($propertyName);
  905         }
  906 
  907         if (
  908             isset($expectedPropertyType) &&
  909             $expectedPropertyType === 'Neos\Media\Domain\Model\ImageInterface' &&
  910             empty($value)
  911         ) {
  912             return null;
  913         }
  914 
  915         if (empty($value)) {
  916             return $value;
  917         }
  918 
  919         if (!$nodeType->hasConfiguration('properties.' . $propertyName)) {
  920             return $value;
  921         }
  922 
  923         if ($expectedPropertyType === 'references') {
  924             return ($returnNodesAsIdentifiers ? $value : $this->resolvePropertyReferences($value));
  925         }
  926 
  927         if ($expectedPropertyType === 'reference') {
  928             return ($returnNodesAsIdentifiers ? $value : $this->context->getNodeByIdentifier($value));
  929         }
  930 
  931         return $this->propertyMapper->convert($value, $expectedPropertyType);
  932     }
  933 
  934     /**
  935      * Maps the property value (an array of node identifiers) to the Node objects if needed.
  936      *
  937      * @param array $value
  938      * @return array
  939      */
  940     protected function resolvePropertyReferences(array $value = []): array
  941     {
  942         $nodes = array_map(function ($nodeIdentifier) {
  943             return $this->context->getNodeByIdentifier($nodeIdentifier);
  944         }, $value);
  945 
  946         return array_filter($nodes);
  947     }
  948 
  949     /**
  950      * Removes the specified property.
  951      *
  952      * If the node has a content object attached, the property will not be removed on
  953      * that object if it exists.
  954      *
  955      * @param string $propertyName Name of the property
  956      * @return void
  957      * @throws NodeException if the node does not contain the specified property
  958      * @throws NodeTypeNotFoundException
  959      */
  960     public function removeProperty($propertyName): void
  961     {
  962         if (!$this->hasProperty($propertyName)) {
  963             return;
  964         }
  965         $this->materializeNodeDataAsNeeded();
  966         $this->nodeData->removeProperty($propertyName);
  967 
  968         $this->context->getFirstLevelNodeCache()->flush();
  969         $this->emitNodeUpdated($this);
  970     }
  971 
  972     /**
  973      * Returns all properties of this node.
  974      *
  975      * If the node has a content object attached, the properties will be fetched
  976      * there.
  977      *
  978      * @param boolean $returnNodesAsIdentifiers If enabled, references to nodes are returned as node identifiers instead of NodeData objects
  979      * @return PropertyCollectionInterface Property values, indexed by their name
  980      * @throws NodeException
  981      * @throws NodeTypeNotFoundException
  982      * @throws \Neos\Flow\Property\Exception
  983      * @throws \Neos\Flow\Security\Exception
  984      * @api
  985      */
  986     public function getProperties(bool $returnNodesAsIdentifiers = false): PropertyCollectionInterface
  987     {
  988         $properties = [];
  989         foreach ($this->getPropertyNames() as $propertyName) {
  990             $properties[$propertyName] = $this->getProperty($propertyName, $returnNodesAsIdentifiers);
  991         }
  992 
  993         return new ArrayPropertyCollection($properties);
  994     }
  995 
  996     /**
  997      * Returns the names of all properties of this node.
  998      *
  999      * @return string[] Property names
 1000      * @api
 1001      */
 1002     public function getPropertyNames()
 1003     {
 1004         return $this->nodeData->getPropertyNames();
 1005     }
 1006 
 1007     /**
 1008      * Sets a content object for this node.
 1009      *
 1010      * @param object $contentObject The content object
 1011      * @return void
 1012      * @deprecated with version 4.3. Attaching entities to nodes never really worked. Instead you can reference objects as node properties via their identifier
 1013      * @throws NodeTypeNotFoundException
 1014      * @throws NodeException
 1015      */
 1016     public function setContentObject($contentObject): void
 1017     {
 1018         if ($this->getContentObject() === $contentObject) {
 1019             return;
 1020         }
 1021         $this->materializeNodeDataAsNeeded();
 1022         $this->nodeData->setContentObject($contentObject);
 1023 
 1024         $this->context->getFirstLevelNodeCache()->flush();
 1025         $this->emitNodeUpdated($this);
 1026     }
 1027 
 1028     /**
 1029      * Returns the content object of this node (if any).
 1030      *
 1031      * @return object
 1032      * @deprecated with version 4.3. Attaching entities to nodes never really worked. Instead you can reference objects as node properties via their identifier
 1033      */
 1034     public function getContentObject()
 1035     {
 1036         return $this->nodeData->getContentObject();
 1037     }
 1038 
 1039     /**
 1040      * Unsets the content object of this node.
 1041      *
 1042      * @return void
 1043      * @throws NodeTypeNotFoundException
 1044      * @throws NodeException
 1045      * @deprecated with version 4.3. Attaching entities to nodes never really worked. Instead you can reference objects as node properties via their identifier
 1046      */
 1047     public function unsetContentObject(): void
 1048     {
 1049         if ($this->getContentObject() === null) {
 1050             return;
 1051         }
 1052         $this->materializeNodeDataAsNeeded();
 1053         $this->nodeData->unsetContentObject();
 1054 
 1055         $this->context->getFirstLevelNodeCache()->flush();
 1056         $this->emitNodeUpdated($this);
 1057     }
 1058 
 1059     /**
 1060      * Sets the node type of this node.
 1061      *
 1062      * @param NodeType $nodeType
 1063      * @return void
 1064      * @throws NodeException
 1065      * @throws NodeTypeNotFoundException
 1066      * @api
 1067      */
 1068     public function setNodeType(NodeType $nodeType): void
 1069     {
 1070         if ($this->getNodeType() === $nodeType) {
 1071             return;
 1072         }
 1073         if (!$this->isNodeDataMatchingContext()) {
 1074             $this->materializeNodeData();
 1075         }
 1076         $this->nodeData->setNodeType($nodeType);
 1077 
 1078         $this->context->getFirstLevelNodeCache()->flush();
 1079         $this->emitNodeUpdated($this);
 1080     }
 1081 
 1082     /**
 1083      * Returns the node type of this node.
 1084      *
 1085      * @return NodeType
 1086      * @api
 1087      * @throws NodeTypeNotFoundException
 1088      */
 1089     public function getNodeType(): NodeType
 1090     {
 1091         return $this->nodeData->getNodeType();
 1092     }
 1093 
 1094     /**
 1095      * Creates, adds and returns a child node of this node. Also sets default
 1096      * properties and creates default subnodes.
 1097      *
 1098      * @param string $name Name of the new node
 1099      * @param NodeType $nodeType Node type of the new node (optional)
 1100      * @param string $identifier The identifier of the node, unique within the workspace, optional(!)
 1101      * @return NodeInterface
 1102      * @throws NodeConfigurationException
 1103      * @throws NodeConstraintException
 1104      * @throws NodeException
 1105      * @throws NodeExistsException
 1106      * @throws NodeTypeNotFoundException
 1107      * @api
 1108      */
 1109     public function createNode($name, NodeType $nodeType = null, $identifier = null): NodeInterface
 1110     {
 1111         $this->emitBeforeNodeCreate($this, $name, $nodeType, $identifier);
 1112         $newNode = $this->createSingleNode($name, $nodeType, $identifier);
 1113         if ($nodeType !== null) {
 1114             foreach ($nodeType->getDefaultValuesForProperties() as $propertyName => $propertyValue) {
 1115                 if (substr($propertyName, 0, 1) === '_') {
 1116                     ObjectAccess::setProperty($newNode, substr($propertyName, 1), $propertyValue);
 1117                 } else {
 1118                     $newNode->setProperty($propertyName, $propertyValue);
 1119                 }
 1120             }
 1121 
 1122             foreach ($nodeType->getAutoCreatedChildNodes() as $childNodeName => $childNodeType) {
 1123                 $childNodeIdentifier = Utility::buildAutoCreatedChildNodeIdentifier($childNodeName, $newNode->getIdentifier());
 1124                 $alreadyPresentChildNode = $newNode->getNode($childNodeName);
 1125                 if ($alreadyPresentChildNode === null) {
 1126                     $newNode->createNode($childNodeName, $childNodeType, $childNodeIdentifier);
 1127                 }
 1128             }
 1129         }
 1130 
 1131         $this->context->getFirstLevelNodeCache()->flush();
 1132         $this->emitNodeAdded($newNode);
 1133         $this->emitAfterNodeCreate($newNode);
 1134 
 1135         return $newNode;
 1136     }
 1137 
 1138     /**
 1139      * Creates, adds and returns a child node of this node, without setting default
 1140      * properties or creating subnodes. Only used internally.
 1141      *
 1142      * For internal use only!
 1143      * TODO: New SiteImportService uses createNode() and DQL. When we drop the LegagcySiteImportService we can change this to protected.
 1144      *
 1145      * @param string $name Name of the new node
 1146      * @param NodeType $nodeType Node type of the new node (optional)
 1147      * @param string $identifier The identifier of the node, unique within the workspace, optional(!)
 1148      * @return NodeInterface
 1149      * @throws NodeConfigurationException
 1150      * @throws NodeConstraintException
 1151      * @throws NodeException
 1152      * @throws NodeExistsException
 1153      * @throws NodeTypeNotFoundException
 1154      */
 1155     public function createSingleNode($name, NodeType $nodeType = null, $identifier = null): NodeInterface
 1156     {
 1157         if ($nodeType !== null && !$this->willChildNodeBeAutoCreated($name) && !$this->isNodeTypeAllowedAsChildNode($nodeType)) {
 1158             throw new NodeConstraintException('Cannot create new node "' . $name . '" of Type "' . $nodeType->getName() . '" in ' . $this->__toString(), 1400782413);
 1159         }
 1160 
 1161         $dimensions = $this->context->getTargetDimensionValues();
 1162 
 1163         $nodeData = $this->nodeData->createSingleNodeData($name, $nodeType, $identifier, $this->context->getWorkspace(), $dimensions);
 1164         $node = $this->nodeFactory->createFromNodeData($nodeData, $this->context);
 1165 
 1166         $this->context->getFirstLevelNodeCache()->flush();
 1167         $this->emitNodeAdded($node);
 1168 
 1169         return $node;
 1170     }
 1171 
 1172     /**
 1173      * Checks if the given Node $name is configured as auto-created childNode in the NodeType configuration.
 1174      *
 1175      * @param string $name The node name to check.
 1176      * @return boolean true if the given nodeName is configured as auto-created child node.
 1177      * @throws NodeTypeNotFoundException
 1178      */
 1179     protected function willChildNodeBeAutoCreated(string $name): bool
 1180     {
 1181         $autoCreatedChildNodes = $this->getNodeType()->getAutoCreatedChildNodes();
 1182 
 1183         return isset($autoCreatedChildNodes[$name]);
 1184     }
 1185 
 1186     /**
 1187      * Creates and persists a node from the given $nodeTemplate as child node
 1188      *
 1189      * @param NodeTemplate $nodeTemplate
 1190      * @param string $nodeName name of the new node. If not specified the name of the nodeTemplate will be used.
 1191      * @return NodeInterface the freshly generated node
 1192      * @api
 1193      * @throws NodeConfigurationException
 1194      */
 1195     public function createNodeFromTemplate(NodeTemplate $nodeTemplate, $nodeName = null): NodeInterface
 1196     {
 1197         $nodeData = $this->nodeData->createNodeDataFromTemplate($nodeTemplate, $nodeName, $this->context->getWorkspace(), $this->context->getDimensions());
 1198         $node = $this->nodeFactory->createFromNodeData($nodeData, $this->context);
 1199 
 1200         $this->context->getFirstLevelNodeCache()->flush();
 1201         $this->emitNodeAdded($node);
 1202 
 1203         return $node;
 1204     }
 1205 
 1206     /**
 1207      * Returns a node specified by the given relative path.
 1208      *
 1209      * @param string $path Path specifying the node, relative to this node
 1210      * @return NodeInterface|null The specified node or NULL if no such node exists
 1211      * @deprecated with version 4.3 - use TraversableNodeInterface::findNamedChildNode() instead
 1212      */
 1213     public function getNode($path): ?NodeInterface
 1214     {
 1215         $absolutePath = $this->nodeService->normalizePath($path, $this->getPath());
 1216         $node = $this->context->getFirstLevelNodeCache()->getByPath($absolutePath);
 1217         if ($node !== false) {
 1218             return $node;
 1219         }
 1220         $node = $this->nodeDataRepository->findOneByPathInContext($absolutePath, $this->context);
 1221         $this->context->getFirstLevelNodeCache()->setByPath($absolutePath, $node);
 1222         return $node;
 1223     }
 1224 
 1225     /**
 1226      * Returns the primary child node of this node.
 1227      *
 1228      * Which node acts as a primary child node will in the future depend on the
 1229      * node type. For now it is just the first child node.
 1230      *
 1231      * @return NodeInterface|null The primary child node or NULL if no such node exists
 1232      * @deprecated with version 4.3. use TraversableNodeInterface::findChildNodes() instead, the first result is considered the "primary child node"
 1233      */
 1234     public function getPrimaryChildNode(): ?NodeInterface
 1235     {
 1236         return $this->nodeDataRepository->findFirstByParentAndNodeTypeInContext($this->getPath(), null, $this->context);
 1237     }
 1238 
 1239     /**
 1240      * Returns all direct child nodes of this node.
 1241      * If a node type is specified, only nodes of that type are returned.
 1242      *
 1243      * @param string $nodeTypeFilter If specified, only nodes with that node type are considered
 1244      * @param integer $limit An optional limit for the number of nodes to find. Added or removed nodes can still change the number nodes!
 1245      * @param integer $offset An optional offset for the query
 1246      * @return array<\Neos\ContentRepository\Domain\Model\NodeInterface> An array of nodes or an empty array if no child nodes matched
 1247      * @deprecated with version 4.3, use TraversableNodeInterface::findChildNodes() instead.
 1248      */
 1249     public function getChildNodes($nodeTypeFilter = null, $limit = null, $offset = null): array
 1250     {
 1251         $nodes = $this->context->getFirstLevelNodeCache()->getChildNodesByPathAndNodeTypeFilter($this->getPath(), $nodeTypeFilter);
 1252         if ($nodes === false) {
 1253             $nodes = $this->nodeDataRepository->findByParentAndNodeTypeInContext($this->getPath(), $nodeTypeFilter, $this->context, false);
 1254             $this->context->getFirstLevelNodeCache()->setChildNodesByPathAndNodeTypeFilter($this->getPath(), $nodeTypeFilter, $nodes);
 1255         }
 1256 
 1257         if ($offset !== null || $limit !== null) {
 1258             $offset = ($offset === null) ? 0 : $offset;
 1259 
 1260             return array_slice($nodes, $offset, $limit);
 1261         }
 1262 
 1263         return $nodes;
 1264     }
 1265 
 1266     /**
 1267      * Returns the number of child nodes a similar getChildNodes() call would return.
 1268      *
 1269      * @param string $nodeTypeFilter If specified, only nodes with that node type are considered
 1270      * @return integer The number of child nodes
 1271      * @api
 1272      */
 1273     public function getNumberOfChildNodes($nodeTypeFilter = null): int
 1274     {
 1275         return $this->nodeData->getNumberOfChildNodes($nodeTypeFilter, $this->context->getWorkspace(), $this->context->getDimensions());
 1276     }
 1277 
 1278     /**
 1279      * Checks if this node has any child nodes.
 1280      *
 1281      * @param string $nodeTypeFilter If specified, only nodes with that node type are considered
 1282      * @return boolean true if this node has child nodes, otherwise false
 1283      * @deprecated with version 4.3, use TraversableNodeInterface::findChildNodes() instead and count the result
 1284      */
 1285     public function hasChildNodes($nodeTypeFilter = null): bool
 1286     {
 1287         return ($this->getNumberOfChildNodes($nodeTypeFilter) > 0);
 1288     }
 1289 
 1290     /**
 1291      * Removes this node and all its child nodes. This is an alias for setRemoved(true)
 1292      *
 1293      * @return void
 1294      * @api
 1295      * @throws NodeTypeNotFoundException
 1296      * @throws NodeException
 1297      */
 1298     public function remove(): void
 1299     {
 1300         $this->setRemoved(true);
 1301     }
 1302 
 1303     /**
 1304      * Enables using the remove method when only setters are available
 1305      *
 1306      * @param boolean $removed If true, this node and it's child nodes will be removed. If it is false only this node will be restored.
 1307      * @return void
 1308      * @throws NodeException
 1309      * @throws NodeTypeNotFoundException
 1310      * @api
 1311      */
 1312     public function setRemoved($removed): void
 1313     {
 1314         if (!$this->isNodeDataMatchingContext()) {
 1315             $this->materializeNodeData();
 1316         }
 1317 
 1318         if ((boolean)$removed === true) {
 1319             /** @var $childNode Node */
 1320             foreach ($this->getChildNodes() as $childNode) {
 1321                 $childNode->setRemoved(true);
 1322             }
 1323 
 1324             $this->nodeData->setRemoved(true);
 1325             $this->emitNodeRemoved($this);
 1326         } else {
 1327             $this->nodeData->setRemoved(false);
 1328             $this->emitNodeUpdated($this);
 1329         }
 1330 
 1331         $this->context->getFirstLevelNodeCache()->flush();
 1332     }
 1333 
 1334     /**
 1335      * If this node is a removed node.
 1336      *
 1337      * @return boolean
 1338      */
 1339     public function isRemoved(): bool
 1340     {
 1341         return $this->nodeData->isRemoved();
 1342     }
 1343 
 1344     /**
 1345      * Sets the "hidden" flag for this node.
 1346      *
 1347      * @param boolean $hidden If true, this Node will be hidden
 1348      * @return void
 1349      * @api
 1350      * @throws NodeTypeNotFoundException
 1351      * @throws NodeException
 1352      */
 1353     public function setHidden($hidden): void
 1354     {
 1355         if ($this->isHidden() === $hidden) {
 1356             return;
 1357         }
 1358         $this->materializeNodeDataAsNeeded();
 1359         $this->nodeData->setHidden($hidden);
 1360 
 1361         $this->context->getFirstLevelNodeCache()->flush();
 1362         $this->emitNodeUpdated($this);
 1363     }
 1364 
 1365     /**
 1366      * Returns the current state of the hidden flag
 1367      *
 1368      * @return boolean
 1369      * @api
 1370      */
 1371     public function isHidden(): bool
 1372     {
 1373         return $this->nodeData->isHidden();
 1374     }
 1375 
 1376     /**
 1377      * Sets the date and time when this node becomes potentially visible.
 1378      *
 1379      * @param \DateTimeInterface $dateTime Date before this node should be hidden
 1380      * @return void
 1381      * @api
 1382      * @throws NodeTypeNotFoundException
 1383      * @throws NodeException
 1384      */
 1385     public function setHiddenBeforeDateTime(\DateTimeInterface $dateTime = null): void
 1386     {
 1387         if ($this->getHiddenBeforeDateTime() instanceof \DateTime && $dateTime instanceof \DateTime && $this->getHiddenBeforeDateTime()->format(\DateTime::W3C) === $dateTime->format(\DateTime::W3C)) {
 1388             return;
 1389         }
 1390         $this->materializeNodeDataAsNeeded();
 1391         $this->nodeData->setHiddenBeforeDateTime($dateTime);
 1392 
 1393         $this->context->getFirstLevelNodeCache()->flush();
 1394         $this->emitNodeUpdated($this);
 1395     }
 1396 
 1397     /**
 1398      * Returns the date and time before which this node will be automatically hidden.
 1399      *
 1400      * @return \DateTimeInterface Date before this node will be hidden
 1401      */
 1402     public function getHiddenBeforeDateTime(): ?\DateTimeInterface
 1403     {
 1404         return $this->nodeData->getHiddenBeforeDateTime();
 1405     }
 1406 
 1407     /**
 1408      * Sets the date and time when this node should be automatically hidden
 1409      *
 1410      * @param \DateTimeInterface $dateTime Date after which this node should be hidden
 1411      * @return void
 1412      * @api
 1413      * @throws NodeTypeNotFoundException
 1414      * @throws NodeException
 1415      */
 1416     public function setHiddenAfterDateTime(\DateTimeInterface $dateTime = null): void
 1417     {
 1418         if ($this->getHiddenAfterDateTime() instanceof \DateTimeInterface && $dateTime instanceof \DateTimeInterface && $this->getHiddenAfterDateTime()->format(\DateTime::W3C) === $dateTime->format(\DateTime::W3C)) {
 1419             return;
 1420         }
 1421         $this->materializeNodeDataAsNeeded();
 1422         $this->nodeData->setHiddenAfterDateTime($dateTime);
 1423 
 1424         $this->context->getFirstLevelNodeCache()->flush();
 1425         $this->emitNodeUpdated($this);
 1426     }
 1427 
 1428     /**
 1429      * Returns the date and time after which this node will be automatically hidden.
 1430      *
 1431      * @return \DateTimeInterface Date after which this node will be hidden
 1432      */
 1433     public function getHiddenAfterDateTime(): ?\DateTimeInterface
 1434     {
 1435         return $this->nodeData->getHiddenAfterDateTime();
 1436     }
 1437 
 1438     /**
 1439      * Sets if this node should be hidden in indexes, such as a site navigation.
 1440      *
 1441      * @param boolean $hidden true if it should be hidden, otherwise false
 1442      * @return void
 1443      * @api
 1444      * @throws NodeTypeNotFoundException
 1445      * @throws NodeException
 1446      */
 1447     public function setHiddenInIndex($hidden): void
 1448     {
 1449         if ($this->isHiddenInIndex() === $hidden) {
 1450             return;
 1451         }
 1452         $this->materializeNodeDataAsNeeded();
 1453         $this->nodeData->setHiddenInIndex($hidden);
 1454 
 1455         $this->context->getFirstLevelNodeCache()->flush();
 1456         $this->emitNodeUpdated($this);
 1457     }
 1458 
 1459     /**
 1460      * If this node should be hidden in indexes
 1461      *
 1462      * @return boolean
 1463      * @api
 1464      */
 1465     public function isHiddenInIndex(): bool
 1466     {
 1467         return $this->nodeData->isHiddenInIndex();
 1468     }
 1469 
 1470     /**
 1471      * Sets the roles which are required to access this node
 1472      *
 1473      * @param array $accessRoles
 1474      * @return void
 1475      * @api
 1476      * @throws NodeTypeNotFoundException
 1477      * @throws NodeException
 1478      * @deprecated with version 4.3. Use a Policy to restrict access to nodes
 1479      */
 1480     public function setAccessRoles(array $accessRoles): void
 1481     {
 1482         if ($this->getAccessRoles() === $accessRoles) {
 1483             return;
 1484         }
 1485         if (!$this->isNodeDataMatchingContext()) {
 1486             $this->materializeNodeData();
 1487         }
 1488         $this->nodeData->setAccessRoles($accessRoles);
 1489 
 1490         $this->context->getFirstLevelNodeCache()->flush();
 1491         $this->emitNodeUpdated($this);
 1492     }
 1493 
 1494     /**
 1495      * Returns the names of defined access roles
 1496      *
 1497      * @return array
 1498      * @deprecated with version 4.3. Use a Policy to restrict access to nodes
 1499      */
 1500     public function getAccessRoles(): array
 1501     {
 1502         return $this->nodeData->getAccessRoles();
 1503     }
 1504 
 1505     /**
 1506      * Tells if a node, in general,  has access restrictions, independent of the
 1507      * current security context.
 1508      *
 1509      * @return boolean
 1510      * @deprecated with version 4.3. Use a Policy to restrict access to nodes
 1511      */
 1512     public function hasAccessRestrictions(): bool
 1513     {
 1514         return $this->nodeData->hasAccessRestrictions();
 1515     }
 1516 
 1517     /**
 1518      * Tells if this node is "visible".
 1519      *
 1520      * For this the "hidden" flag and the "hiddenBeforeDateTime" and "hiddenAfterDateTime" dates are
 1521      * taken into account.
 1522      *
 1523      * @return boolean
 1524      */
 1525     public function isVisible(): bool
 1526     {
 1527         if ($this->nodeData->isVisible() === false) {
 1528             return false;
 1529         }
 1530         $currentDateTime = $this->context->getCurrentDateTime();
 1531         if ($this->getHiddenBeforeDateTime() !== null && $this->getHiddenBeforeDateTime() > $currentDateTime) {
 1532             return false;
 1533         }
 1534         if ($this->getHiddenAfterDateTime() !== null && $this->getHiddenAfterDateTime() < $currentDateTime) {
 1535             return false;
 1536         }
 1537 
 1538         return true;
 1539     }
 1540 
 1541     /**
 1542      * Tells if this node may be accessed according to the current security context.
 1543      *
 1544      * @return boolean
 1545      * @deprecated with version 4.3. Use a Policy to restrict access to nodes
 1546      */
 1547     public function isAccessible(): bool
 1548     {
 1549         return $this->nodeData->isAccessible();
 1550     }
 1551 
 1552     /**
 1553      * Returns the context this node operates in.
 1554      *
 1555      * @return Context
 1556      * @internal This method is not meant to be called in userland code
 1557      */
 1558     public function getContext(): Context
 1559     {
 1560         return $this->context;
 1561     }
 1562 
 1563     /**
 1564      * Materialize the node data either shallow or with child nodes depending
 1565      * on how we materialize (workspace or dimensions).
 1566      * A workspace materialize doesn't necessarily need the child nodes materialized as well
 1567      * unless we do structural changes in which case "materializeNodeData" should be used directly.
 1568      * For dimensional materialization we always want child nodes though.
 1569      *
 1570      * @return void
 1571      * @throws NodeTypeNotFoundException
 1572      * @throws NodeException
 1573      */
 1574     protected function materializeNodeDataAsNeeded(): void
 1575     {
 1576         $dimensionsMatching = $this->dimensionsAreMatchingTargetDimensionValues();
 1577         $workspaceMatching = $this->workspaceIsMatchingContext();
 1578 
 1579         // If we need to materialize across dimensions we should always take child nodes into consideration
 1580         if (!$dimensionsMatching) {
 1581             $this->materializeNodeData();
 1582             return;
 1583         }
 1584 
 1585         if (!$workspaceMatching) {
 1586             $this->shallowMaterializeNodeData();
 1587         }
 1588     }
 1589 
 1590     /**
 1591      * Materializes the original node data (of a different workspace) into the current
 1592      * workspace. And unlike the shallow counterpart does that for all auto-created
 1593      * child nodes as well.
 1594      *
 1595      * @return void
 1596      * @throws NodeException
 1597      * @throws NodeTypeNotFoundException
 1598      * @see shallowMaterializeNodeData
 1599      */
 1600     protected function materializeNodeData(): void
 1601     {
 1602         $this->shallowMaterializeNodeData();
 1603         $nodeType = $this->getNodeType();
 1604         foreach ($nodeType->getAutoCreatedChildNodes() as $childNodeName => $childNodeConfiguration) {
 1605             $childNode = $this->getNode($childNodeName);
 1606             if ($childNode instanceof Node) {
 1607                 $childNode->materializeNodeData();
 1608             }
 1609         }
 1610     }
 1611 
 1612     /**
 1613      * Materializes the original node data (of a different workspace) into the current
 1614      * workspace.
 1615      *
 1616      * @return void
 1617      */
 1618     protected function shallowMaterializeNodeData(): void
 1619     {
 1620         if ($this->isNodeDataMatchingContext()) {
 1621             return;
 1622         }
 1623 
 1624         $dimensions = $this->context->getTargetDimensionValues();
 1625 
 1626         $newNodeData = new NodeData($this->nodeData->getPath(), $this->context->getWorkspace(), $this->nodeData->getIdentifier(), $dimensions);
 1627         $this->nodeDataRepository->add($newNodeData);
 1628 
 1629         $newNodeData->similarize($this->nodeData);
 1630 
 1631         $this->nodeData = $newNodeData;
 1632         $this->nodeDataIsMatchingContext = true;
 1633     }
 1634 
 1635     /**
 1636      * Create a recursive copy of this node below $referenceNode with $nodeName.
 1637      *
 1638      * $detachedCopy only has an influence if we are copying from one dimension to the other, possibly creating a new
 1639      * node variant:
 1640      *
 1641      * - If $detachedCopy is true, the whole (recursive) copy is done without connecting original and copied node,
 1642      *   so NOT CREATING a new node variant.
 1643      * - If $detachedCopy is false, and the node does not yet have a variant in the target dimension, we are CREATING
 1644      *   a new node variant.
 1645      *
 1646      * As a caller of this method, $detachedCopy should be true if $this->getNodeType()->isAggregate() is true, and false
 1647      * otherwise.
 1648      *
 1649      * @param NodeInterface $referenceNode
 1650      * @param string $nodeName
 1651      * @param boolean $detachedCopy
 1652      * @return NodeInterface
 1653      * @throws NodeConstraintException
 1654      * @throws NodeException
 1655      * @throws NodeExistsException
 1656      * @throws NodeTypeNotFoundException
 1657      */
 1658     protected function createRecursiveCopy(NodeInterface $referenceNode, string $nodeName, bool $detachedCopy): NodeInterface
 1659     {
 1660         $identifier = null;
 1661 
 1662         $referenceNodeDimensions = $referenceNode->getDimensions();
 1663         $referenceNodeDimensionsHash = Utility::sortDimensionValueArrayAndReturnDimensionsHash($referenceNodeDimensions);
 1664         $thisDimensions = $this->getDimensions();
 1665         $thisNodeDimensionsHash = Utility::sortDimensionValueArrayAndReturnDimensionsHash($thisDimensions);
 1666 
 1667         // We are only allowed to re-use the node's identifier if the copy-target's context (from $referenceNode)
 1668         // does NOT contain this identifier.
 1669         // We need to check this also taking removed and invisible nodes into account.
 1670         //
 1671         // Without changing the context, removedContentShown is typically FALSE, leading to the FOLLOWING BUG:
 1672         //
 1673         // PREREQUISITES:
 1674         // - a language dimension with two values, without fallbacks ("de" and "en")
 1675         // - create a page in DE with content nodes "text1" and "text2"
 1676         // - translate this page to EN and let it copy all content. "text1" also exists on EN now, and has the same identifier as in DE.
 1677         // - publish everything.
 1678         //
 1679         // REPRODUCING THE BUG: (comment out the removedContentShown line below)
 1680         // - select "text1" in "DE" and copy it
 1681         // - switch to EN
 1682         // - REMOVE the node "text1" in EN
 1683         // - PASTE the node from the clipboard AFTER text2 (in EN).
 1684         // - (this triggers the code we have here.)
 1685         //
 1686         // EXPECTED BEHAVIOR
 1687         // - the pasted node is shown
 1688         //
 1689         // ACTUAL BEHAVIOR
 1690         // - the pasted node is not shown, but is still in the database.
 1691         // - it can happen that the node *is* shown, if it is inserted above the removed node. Still, we have an invariant violation nevertheless.
 1692         // - this can also trigger problems when **publishing** the not-rendered-anymore-node (UniqueConstraint errors in the database) - this is
 1693         //   how we actually found the error.
 1694         $contextPropertiesWithAllNodesShown = $referenceNode->getContext()->getProperties();
 1695         $contextPropertiesWithAllNodesShown['invisibleContentShown'] = true;
 1696         $contextPropertiesWithAllNodesShown['removedContentShown'] = true;
 1697         $contextPropertiesWithAllNodesShown['inaccessibleContentShown'] = true;
 1698         $referenceNodeContextWithAllNodesShown = $this->contextFactory->create($contextPropertiesWithAllNodesShown);
 1699         if ($detachedCopy === false && $referenceNodeDimensionsHash !== $thisNodeDimensionsHash && $referenceNodeContextWithAllNodesShown->getNodeByIdentifier($this->getIdentifier()) === null) {
 1700             // If the target dimensions are different than this one, and there is no node shadowing this one in the target dimension yet, we use the same
 1701             // node identifier, effectively creating a new node variant.
 1702             $identifier = $this->getIdentifier();
 1703         }
 1704 
 1705         $copiedNode = $referenceNode->createSingleNode($nodeName, null, $identifier);
 1706 
 1707         if ($copiedNode instanceof Node) {
 1708             $copiedNode->similarize($this, true);
 1709         }
 1710         /** @var $childNode Node */
 1711         foreach ($this->getChildNodes() as $childNode) {
 1712             // Prevent recursive copy when copying into itself
 1713             if ($childNode->getIdentifier() !== $copiedNode->getIdentifier()) {
 1714                 $childNode->copyIntoInternal($copiedNode, $childNode->getName(), $detachedCopy);
 1715             }
 1716         }
 1717 
 1718         return $copiedNode;
 1719     }
 1720 
 1721     /**
 1722      * The NodeData matches the context if the workspace matches exactly.
 1723      * Needs to be adjusted for further context dimensions.
 1724      *
 1725      * @return boolean
 1726      */
 1727     protected function isNodeDataMatchingContext(): bool
 1728     {
 1729         if ($this->nodeDataIsMatchingContext === null) {
 1730             $workspacesMatch = $this->workspaceIsMatchingContext();
 1731             $this->nodeDataIsMatchingContext = $workspacesMatch && $this->dimensionsAreMatchingTargetDimensionValues();
 1732         }
 1733 
 1734         return $this->nodeDataIsMatchingContext;
 1735     }
 1736 
 1737     /**
 1738      * @return bool
 1739      */
 1740     protected function workspaceIsMatchingContext(): bool
 1741     {
 1742         return ($this->nodeData->getWorkspace() !== null && $this->context->getWorkspace() !== null && $this->nodeData->getWorkspace()->getName() === $this->context->getWorkspace()->getName());
 1743     }
 1744 
 1745     /**
 1746      * For internal use in createRecursiveCopy.
 1747      *
 1748      * @param NodeInterface $sourceNode
 1749      * @param boolean $isCopy
 1750      * @return void
 1751      */
 1752     public function similarize(NodeInterface $sourceNode, $isCopy = false): void
 1753     {
 1754         $this->nodeData->similarize($sourceNode->getNodeData(), $isCopy);
 1755     }
 1756 
 1757     /**
 1758      * @return NodeData
 1759      * @internal This is not meant to be used in userland code
 1760      */
 1761     public function getNodeData(): NodeData
 1762     {
 1763         return $this->nodeData;
 1764     }
 1765 
 1766     /**
 1767      * Returns a string which distinctly identifies this object and thus can be used as an identifier for cache entries
 1768      * related to this object.
 1769      *
 1770      * @return string
 1771      */
 1772     public function getCacheEntryIdentifier(): string
 1773     {
 1774         return $this->getContextPath();
 1775     }
 1776 
 1777     /**
 1778      * Return the assigned content dimensions of the node.
 1779      *
 1780      * @return array
 1781      */
 1782     public function getDimensions(): array
 1783     {
 1784         return $this->nodeData->getDimensionValues();
 1785     }
 1786 
 1787     /**
 1788      * Given a context a new node is returned that is like this node, but
 1789      * lives in the new context.
 1790      *
 1791      * @param Context $context
 1792      * @return NodeInterface
 1793      * @throws NodeConfigurationException
 1794      * @throws NodeTypeNotFoundException
 1795      */
 1796     public function createVariantForContext($context): NodeInterface
 1797     {
 1798         $autoCreatedChildNodes = [];
 1799         $nodeType = $this->getNodeType();
 1800         foreach ($nodeType->getAutoCreatedChildNodes() as $childNodeName => $childNodeConfiguration) {
 1801             $childNode = $this->getNode($childNodeName);
 1802             if ($childNode !== null) {
 1803                 $autoCreatedChildNodes[$childNodeName] = $childNode;
 1804             }
 1805         }
 1806 
 1807         $nodeData = new NodeData($this->nodeData->getPath(), $context->getWorkspace(), $this->nodeData->getIdentifier(), $context->getTargetDimensionValues());
 1808         $nodeData->similarize($this->nodeData);
 1809 
 1810         if ($this->context !== $context) {
 1811             $node = $this->nodeFactory->createFromNodeData($nodeData, $context);
 1812         } else {
 1813             $this->setNodeData($nodeData);
 1814             $node = $this;
 1815         }
 1816 
 1817         $this->context->getFirstLevelNodeCache()->flush();
 1818         $this->emitNodeAdded($node);
 1819 
 1820         /**
 1821          * @var $autoCreatedChildNode NodeInterface
 1822          */
 1823         foreach ($autoCreatedChildNodes as $autoCreatedChildNode) {
 1824             $existingChildNode = $node->getNode($autoCreatedChildNode->getName());
 1825             if ($existingChildNode === null || !$existingChildNode->dimensionsAreMatchingTargetDimensionValues()) {
 1826                 // only if needed, see https://github.com/neos/neos-development-collection/issues/782
 1827                 $autoCreatedChildNode->createVariantForContext($context);
 1828             }
 1829         }
 1830 
 1831         return $node;
 1832     }
 1833 
 1834     /**
 1835      * Internal method
 1836      *
 1837      * The dimension value of this node has to match the current target dimension value (must be higher in priority or equal)
 1838      *
 1839      * @return boolean
 1840      */
 1841     public function dimensionsAreMatchingTargetDimensionValues(): bool
 1842     {
 1843         $dimensions = $this->getDimensions();
 1844         $contextDimensions = $this->context->getDimensions();
 1845         foreach ($this->context->getTargetDimensions() as $dimensionName => $targetDimensionValue) {
 1846             if (!isset($dimensions[$dimensionName])) {
 1847                 if ($targetDimensionValue === null) {
 1848                     continue;
 1849                 } else {
 1850                     return false;
 1851                 }
 1852             } elseif ($targetDimensionValue === null && $dimensions[$dimensionName] === []) {
 1853                 continue;
 1854             } elseif (!in_array($targetDimensionValue, $dimensions[$dimensionName], true)) {
 1855                 $contextDimensionValues = $contextDimensions[$dimensionName];
 1856                 $targetPositionInContext = array_search($targetDimensionValue, $contextDimensionValues, true);
 1857                 $nodePositionInContext = min(array_map(function ($value) use ($contextDimensionValues) {
 1858                     return array_search($value, $contextDimensionValues, true);
 1859                 }, $dimensions[$dimensionName]));
 1860 
 1861                 $val = $targetPositionInContext !== false && $nodePositionInContext !== false && $targetPositionInContext >= $nodePositionInContext;
 1862                 if ($val === false) {
 1863                     return false;
 1864                 }
 1865             }
 1866         }
 1867 
 1868         return true;
 1869     }
 1870 
 1871     /**
 1872      * Set the associated NodeData in regards to the Context.
 1873      *
 1874      * NOTE: This is internal only and should not be used outside of the ContentRepository.
 1875      *
 1876      * @param NodeData $nodeData
 1877      * @return void
 1878      * @internal This method is not meant to be called from userland
 1879      */
 1880     public function setNodeData(NodeData $nodeData): void
 1881     {
 1882         $this->nodeData = $nodeData;
 1883         $this->nodeDataIsMatchingContext = null;
 1884     }
 1885 
 1886     /**
 1887      * Checks if the given $nodeType would be allowed as a child node of this node according to the configured constraints.
 1888      *
 1889      * @param NodeType $nodeType
 1890      * @return boolean true if the passed $nodeType is allowed as child node
 1891      * @throws NodeTypeNotFoundException
 1892      */
 1893     public function isNodeTypeAllowedAsChildNode(NodeType $nodeType): bool
 1894     {
 1895         if ($this->isAutoCreated()) {
 1896             return $this->getParent()->getNodeType()->allowsGrandchildNodeType($this->getName(), $nodeType);
 1897         } else {
 1898             return $this->getNodeType()->allowsChildNodeType($nodeType);
 1899         }
 1900     }
 1901 
 1902     /**
 1903      * Determine if this node is configured as auto-created childNode of the parent node. If that is the case, it
 1904      * should not be deleted.
 1905      *
 1906      * @return boolean true if this node is auto-created by the parent.
 1907      * @deprecated with version 4.3. This information should not be required usually. Otherwise it can be determined via:
 1908      * if (array_key_exists((string)$node->getNodeName(), $parent->getNodeType()->getAutoCreatedChildNodes()))
 1909      */
 1910     public function isAutoCreated(): bool
 1911     {
 1912         return $this->isTethered();
 1913     }
 1914 
 1915     /**
 1916      * Whether or not this node is tethered to its parent, fka auto created child node
 1917      *
 1918      * @return bool
 1919      */
 1920     public function isTethered(): bool
 1921     {
 1922         $parent = $this->getParent();
 1923         if ($parent === null) {
 1924             return false;
 1925         }
 1926         if (array_key_exists($this->getName(), $parent->getNodeType()->getAutoCreatedChildNodes())) {
 1927             return true;
 1928         }
 1929 
 1930         return false;
 1931     }
 1932 
 1933     /**
 1934      * Set the status of the associated NodeData in regards to the Context.
 1935      *
 1936      * NOTE: This is internal only and should not be used outside of the ContentRepository.
 1937      *
 1938      * @param boolean $status
 1939      * @return void
 1940      */
 1941     public function setNodeDataIsMatchingContext(bool $status = null): void
 1942     {
 1943         $this->nodeDataIsMatchingContext = $status;
 1944     }
 1945 
 1946     /**
 1947      * @return ContentStreamIdentifier
 1948      * @throws NodeMethodIsUnsupported
 1949      */
 1950     public function getContentStreamIdentifier(): ContentStreamIdentifier
 1951     {
 1952         throw new NodeMethodIsUnsupported('getContentStreamIdentifier is unsupported in the legacy Node API.', 1542893545);
 1953     }
 1954 
 1955     /**
 1956      * @return NodeAggregateIdentifier
 1957      * @throws \Exception
 1958      */
 1959     public function getNodeAggregateIdentifier(): NodeAggregateIdentifier
 1960     {
 1961         return NodeAggregateIdentifier::fromString($this->getIdentifier());
 1962     }
 1963 
 1964     /**
 1965      * @return NodeTypeName
 1966      * @throws NodeTypeNotFoundException
 1967      */
 1968     public function getNodeTypeName(): NodeTypeName
 1969     {
 1970         return NodeTypeName::fromString($this->getNodeType()->getName());
 1971     }
 1972 
 1973     /**
 1974      * @return NodeName|null
 1975      */
 1976     public function getNodeName(): ?NodeName
 1977     {
 1978         return NodeName::fromString($this->getName());
 1979     }
 1980 
 1981     /**
 1982      * @return DimensionSpacePoint
 1983      * @throws NodeMethodIsUnsupported
 1984      */
 1985     public function getDimensionSpacePoint(): DimensionSpacePoint
 1986     {
 1987         throw new NodeMethodIsUnsupported('getDimensionSpacePoint is unsupported in the legacy Node API.', 1542893558);
 1988     }
 1989 
 1990     /**
 1991      * @return OriginDimensionSpacePoint
 1992      * @throws NodeMethodIsUnsupported
 1993      */
 1994     public function getOriginDimensionSpacePoint(): OriginDimensionSpacePoint
 1995     {
 1996         throw new NodeMethodIsUnsupported('getOriginDimensionSpacePoint is unsupported in the legacy Node API.', 1542893562);
 1997     }
 1998 
 1999     /**
 2000      * @return TraversableNodeInterface
 2001      * @throws NodeException if no parent node was found (= this is the root node)
 2002      */
 2003     public function findParentNode(): TraversableNodeInterface
 2004     {
 2005         /** @var TraversableNodeInterface $parentNode It's safe to return the old NodeInterface as TraversableNodeInterface; as the base implementation "Node" (this class) implements both interfaces at the same time. */
 2006         $parentNode = $this->getParent();
 2007         if ($parentNode === null) {
 2008             throw new NodeException('Parent node not found', 1542983610);
 2009         }
 2010         return $parentNode;
 2011     }
 2012 
 2013     public function findNodePath(): NodePath
 2014     {
 2015         return NodePath::fromString($this->getPath());
 2016     }
 2017 
 2018     /**
 2019      * @param NodeName $nodeName
 2020      * @return TraversableNodeInterface
 2021      * @throws NodeException
 2022      */
 2023     public function findNamedChildNode(NodeName $nodeName): TraversableNodeInterface
 2024     {
 2025         /** @var TraversableNodeInterface $childNode It's safe to return the old NodeInterface as TraversableNodeInterface; as the base implementation "Node" (this class) implements both interfaces at the same time. */
 2026         $childNode = $this->getNode((string)$nodeName);
 2027         if ($childNode === null) {
 2028             throw new NodeException(sprintf('Child node named "%s" not found', $nodeName), 1543406006);
 2029         }
 2030         return $childNode;
 2031     }
 2032 
 2033     /**
 2034      * Returns all direct child nodes of this node.
 2035      * If a node type is specified, only nodes of that type are returned.
 2036      *
 2037      * @param NodeTypeConstraints $nodeTypeConstraints If specified, only nodes with that node type are considered
 2038      * @param int $limit An optional limit for the number of nodes to find. Added or removed nodes can still change the number nodes!
 2039      * @param int $offset An optional offset for the query
 2040      * @return TraversableNodes
 2041      * @api
 2042      */
 2043     public function findChildNodes(NodeTypeConstraints $nodeTypeConstraints = null, int $limit = null, int $offset = null): TraversableNodes
 2044     {
 2045         /** @noinspection PhpDeprecationInspection */
 2046         $filter = $nodeTypeConstraints !== null ? $nodeTypeConstraints->asLegacyNodeTypeFilterString() : null;
 2047         // It's safe to return the old NodeInterface as TraversableNodeInterface; as the base implementation "Node" (this class) implements both interfaces at the same time.
 2048         return TraversableNodes::fromArray($this->getChildNodes($filter, $limit, $offset));
 2049     }
 2050 
 2051     /**
 2052      * Returns the number of direct child nodes of this node from its subgraph.
 2053      *
 2054      * @param NodeTypeConstraints|null $nodeTypeConstraints
 2055      * @return int
 2056      */
 2057     public function countChildNodes(NodeTypeConstraints $nodeTypeConstraints = null): int
 2058     {
 2059         return count($this->getChildNodes($nodeTypeConstraints));
 2060     }
 2061 
 2062     /**
 2063      * Retrieves and returns all nodes referenced by this node from its subgraph.
 2064      *
 2065      * @return TraversableNodes
 2066      * @throws NodeException
 2067      * @throws NodeTypeNotFoundException
 2068      * @throws \Neos\Flow\Property\Exception
 2069      * @throws \Neos\Flow\Security\Exception
 2070      */
 2071     public function findReferencedNodes(): TraversableNodes
 2072     {
 2073         $referencedNodes = [];
 2074         foreach ($this->getNodeType()->getProperties() as $propertyName => $property) {
 2075             $propertyType = $this->getNodeType()->getPropertyType($propertyName);
 2076             if ($propertyType === 'reference' && $this->getProperty($propertyName) instanceof TraversableNodeInterface) {
 2077                 $referencedNodes[] = $this->getProperty($propertyName);
 2078             } elseif ($propertyName === 'references' && !empty($this->getProperty($propertyName))) {
 2079                 foreach ($this->getProperty($propertyName) as $node) {
 2080                     if ($node instanceof TraversableNodeInterface) {
 2081                         $referencedNodes[] = $node;
 2082                     }
 2083                 }
 2084             }
 2085         }
 2086 
 2087         return TraversableNodes::fromArray($referencedNodes);
 2088     }
 2089 
 2090     /**
 2091      * Retrieves and returns nodes referenced by this node by name from its subgraph.
 2092      *
 2093      * @param PropertyName $edgeName
 2094      * @return TraversableNodes
 2095      * @throws NodeException
 2096      * @throws NodeTypeNotFoundException
 2097      * @throws \Neos\Flow\Property\Exception
 2098      * @throws \Neos\Flow\Security\Exception
 2099      */
 2100     public function findNamedReferencedNodes(PropertyName $edgeName): TraversableNodes
 2101     {
 2102         $referencedNodes = [];
 2103         $propertyName = (string) $edgeName;
 2104         $propertyType = $this->getNodeType()->getPropertyType($propertyName);
 2105         if ($propertyType === 'reference' && $this->getProperty($propertyName) instanceof TraversableNodeInterface) {
 2106             $referencedNodes = [$this->getProperty($propertyName)];
 2107         } elseif ($propertyName === 'references' && !empty($this->getProperty($propertyName))) {
 2108             $referencedNodes = $this->getProperty($propertyName);
 2109         }
 2110 
 2111         return TraversableNodes::fromArray($referencedNodes);
 2112     }
 2113 
 2114     /**
 2115      * Retrieves and returns nodes referencing this node from its subgraph.
 2116      *
 2117      * @return TraversableNodes
 2118      * @throws NodeMethodIsUnsupported
 2119      */
 2120     public function findReferencingNodes(): TraversableNodes
 2121     {
 2122         throw new NodeMethodIsUnsupported('findReferencingNodes is unsupported in the legacy Node API.', 1542893575);
 2123     }
 2124 
 2125 
 2126     /**
 2127      * Retrieves and returns nodes referencing this node by name from its subgraph.
 2128      *
 2129      * @param PropertyName $edgeName
 2130      * @return TraversableNodes
 2131      * @throws NodeMethodIsUnsupported
 2132      */
 2133     public function findNamedReferencingNodes(PropertyName $edgeName): TraversableNodes
 2134     {
 2135         throw new NodeMethodIsUnsupported('findNamedReferencingNodes is unsupported in the legacy Node API.', 1542893577);
 2136     }
 2137 
 2138     /**
 2139      * @Flow\Signal
 2140      * @param NodeInterface $movedNode
 2141      * @param NodeInterface $referenceNode
 2142      * @param integer $movePosition
 2143      * @return void
 2144      */
 2145     protected function emitBeforeNodeMove(NodeInterface $movedNode, NodeInterface $referenceNode, int $movePosition): void
 2146     {
 2147     }
 2148 
 2149     /**
 2150      * @Flow\Signal
 2151      * @param NodeInterface $movedNode
 2152      * @param NodeInterface $referenceNode
 2153      * @param integer $movePosition
 2154      * @return void
 2155      */
 2156     protected function emitAfterNodeMove(NodeInterface $movedNode, NodeInterface $referenceNode, int $movePosition): void
 2157     {
 2158     }
 2159 
 2160     /**
 2161      * @Flow\Signal
 2162      * @param NodeInterface $sourceNode
 2163      * @param NodeInterface $targetParentNode
 2164      * @return void
 2165      */
 2166     protected function emitBeforeNodeCopy(NodeInterface $sourceNode, NodeInterface $targetParentNode): void
 2167     {
 2168     }
 2169 
 2170     /**
 2171      * @Flow\Signal
 2172      * @param NodeInterface $copiedNode
 2173      * @param NodeInterface $targetParentNode
 2174      * @return void
 2175      */
 2176     protected function emitAfterNodeCopy(NodeInterface $copiedNode, NodeInterface $targetParentNode): void
 2177     {
 2178     }
 2179 
 2180     /**
 2181      * Signals that the node path has been changed.
 2182      *
 2183      * @Flow\Signal
 2184      * @param NodeInterface $node
 2185      * @param string $oldPath
 2186      * @param string $newPath
 2187      * @param boolean $recursion true if the node path change was caused because a parent node path was changed
 2188      */
 2189     protected function emitNodePathChanged(NodeInterface $node, $oldPath, $newPath, $recursion)
 2190     {
 2191     }
 2192 
 2193     /**
 2194      * Signals that a node will be created.
 2195      *
 2196      * @Flow\Signal
 2197      * @param NodeInterface $node
 2198      * @param string $name
 2199      * @param string $nodeType
 2200      * @param string $identifier
 2201      * @return void
 2202      */
 2203     protected function emitBeforeNodeCreate(NodeInterface $node, $name, $nodeType, $identifier)
 2204     {
 2205     }
 2206 
 2207     /**
 2208      * Signals that a node was created.
 2209      *
 2210      * @Flow\Signal
 2211      * @param NodeInterface $node
 2212      * @return void
 2213      */
 2214     protected function emitAfterNodeCreate(NodeInterface $node)
 2215     {
 2216     }
 2217 
 2218     /**
 2219      * Signals that a node was added.
 2220      *
 2221      * @Flow\Signal
 2222      * @param NodeInterface $node
 2223      * @return void
 2224      */
 2225     protected function emitNodeAdded(NodeInterface $node)
 2226     {
 2227     }
 2228 
 2229     /**
 2230      * Signals that a node was updated.
 2231      *
 2232      * @Flow\Signal
 2233      * @param NodeInterface $node
 2234      * @return void
 2235      */
 2236     protected function emitNodeUpdated(NodeInterface $node)
 2237     {
 2238     }
 2239 
 2240     /**
 2241      * Signals that a node was removed.
 2242      *
 2243      * @Flow\Signal
 2244      * @param NodeInterface $node
 2245      * @return void
 2246      */
 2247     protected function emitNodeRemoved(NodeInterface $node)
 2248     {
 2249     }
 2250 
 2251     /**
 2252      * Signals that the property of a node will be changed.
 2253      *
 2254      * @Flow\Signal
 2255      * @param NodeInterface $node
 2256      * @param string $propertyName name of the property that has been changed/added
 2257      * @param mixed $oldValue the property value before it was changed or NULL if the property is new
 2258      * @param mixed $newValue the new property value
 2259      * @return void
 2260      */
 2261     protected function emitBeforeNodePropertyChange(NodeInterface $node, $propertyName, $oldValue, $newValue)
 2262     {
 2263     }
 2264 
 2265     /**
 2266      * Signals that the property of a node was changed.
 2267      *
 2268      * @Flow\Signal
 2269      * @param NodeInterface $node
 2270      * @param string $propertyName name of the property that has been changed/added
 2271      * @param mixed $oldValue the property value before it was changed or NULL if the property is new
 2272      * @param mixed $newValue the new property value
 2273      * @return void
 2274      */
 2275     protected function emitNodePropertyChanged(NodeInterface $node, $propertyName, $oldValue, $newValue)
 2276     {
 2277     }
 2278 
 2279     /**
 2280      * For debugging purposes, the node can be converted to a string.
 2281      *
 2282      * @return string
 2283      * @throws NodeTypeNotFoundException
 2284      */
 2285     public function __toString(): string
 2286     {
 2287         return 'Node ' . $this->getContextPath() . '[' . $this->getNodeType()->getName() . ']';
 2288     }
 2289 
 2290     public function equals(TraversableNodeInterface $other): bool
 2291     {
 2292         if ($other instanceof NodeInterface) {
 2293             return $this->getContextPath() === $other->getContextPath();
 2294         }
 2295 
 2296         // if $other is not a Legacy NodeInterface, they are always different.
 2297         return false;
 2298     }
 2299 }