/** * Generates a Context that exactly fits the given NodeData Workspace, Dimensions & Site. * * @param NodeData $nodeData * @return ContentContext */ protected function createContextMatchingNodeData(NodeData $nodeData) { $nodePath = NodePaths::getRelativePathBetween(SiteService::SITES_ROOT_PATH, $nodeData->getPath()); list($siteNodeName) = explode('/', $nodePath); $site = $this->siteRepository->findOneByNodeName($siteNodeName); $contextProperties = ['workspaceName' => $nodeData->getWorkspace()->getName(), 'invisibleContentShown' => true, 'inaccessibleContentShown' => true, 'removedContentShown' => true, 'dimensions' => $nodeData->getDimensionValues(), 'currentSite' => $site]; if ($domain = $site->getFirstActiveDomain()) { $contextProperties['currentDomain'] = $domain; } return $this->_contextFactory->create($contextProperties); }
/** * Sets the absolute path of this node. * * This method is only for internal use by the content repository or node methods. Changing * the path of a node manually may lead to unexpected behavior. * * To achieve a correct behavior when changing the path (moving the node) in a workspace, a shadow node data that will * hide the node data in the base workspace will be created. Thus queries do not need to worry about moved nodes. * Through a movedTo reference the shadow node data will be removed when publishing the moved node. * * @param string $path * @param boolean $checkForExistence Checks for existence at target path, internally used for recursions and shadow nodes. * @throws NodeException * @internal This method is purely internal and Node objects can call this on other Node objects */ public function setPath($path, $checkForExistence = true) { $originalPath = $this->nodeData->getPath(); if ($originalPath === $path) { return; } if ($checkForExistence) { $existingNodeDataArray = $this->nodeDataRepository->findByPathWithoutReduce($path, $this->context->getWorkspace()); /** @var NodeData $existingNodeData */ foreach ($existingNodeDataArray as $existingNodeData) { if ($existingNodeData->getIdentifier() !== $this->getIdentifier()) { throw new NodeException(sprintf('Can not rename the node "%s" as a node already exists on path "%s"', $this->getPath(), $path), 1414436551); } } } $changedNodePathsCollection = array(); if ($this->getNodeType()->isAggregate()) { $nodeDataVariantsAndChildren = $this->nodeDataRepository->findByPathWithoutReduce($originalPath, $this->context->getWorkspace(), true, true); /** @var NodeData $nodeData */ foreach ($nodeDataVariantsAndChildren as $nodeData) { $nodeVariant = $this->createNodeForVariant($nodeData); if ($nodeVariant !== null) { $relativePathSegment = NodePaths::getRelativePathBetween($originalPath, $nodeVariant->getPath()); $newNodeVariantPath = NodePaths::addNodePathSegment($path, $relativePathSegment); $possibleShadowedNodeData = $nodeData->move($newNodeVariantPath, $this->context->getWorkspace()); $nodeVariant->setNodeData($possibleShadowedNodeData); $changedNodePathsCollection[] = array($nodeVariant, $originalPath, $nodeVariant->getNodeData()->getPath(), !$checkForExistence); } } } else { /** @var Node $childNode */ foreach ($this->getChildNodes() as $childNode) { $childNode->setPath(NodePaths::addNodePathSegment($path, $childNode->getName()), false); } $possibleShadowedNodeData = $this->nodeData->move($path, $this->context->getWorkspace()); $this->setNodeData($possibleShadowedNodeData); $changedNodePathsCollection[] = array($this, $originalPath, $this->getNodeData()->getPath(), $checkForExistence); } $this->nodeDataRepository->persistEntities(); foreach ($changedNodePathsCollection as $nodePathChangedArguments) { call_user_func_array(array($this, 'emitNodePathChanged'), $nodePathChangedArguments); } }
/** * Finds all nodes of the specified workspace lying on the path specified by * (and including) the given starting point and end point and (optionally) a node type filter. * * If some node does not exist in the specified workspace, this function will * try to find a corresponding node in one of the base workspaces (if any). * * @param string $pathStartingPoint Absolute path specifying the starting point * @param string $pathEndPoint Absolute path specifying the end point * @param Workspace $workspace The containing workspace * @param array $dimensions Array of dimensions to array of dimension values * @param boolean $includeRemovedNodes Should removed nodes be included in the result (defaults to FALSE) * @param string $nodeTypeFilter Optional filter for the node type of the nodes, supports complex expressions (e.g. "TYPO3.Neos:Page", "!TYPO3.Neos:Page,TYPO3.Neos:Text" or NULL) * @throws \InvalidArgumentException * @return array<\TYPO3\TYPO3CR\Domain\Model\NodeData> The nodes found on the given path * @todo findOnPath should probably not return child nodes of removed nodes unless removed nodes are included. */ public function findOnPath($pathStartingPoint, $pathEndPoint, Workspace $workspace, array $dimensions = null, $includeRemovedNodes = false, $nodeTypeFilter = null) { $pathStartingPoint = strtolower($pathStartingPoint); $pathEndPoint = strtolower($pathEndPoint); if (NodePaths::isSubPathOf($pathStartingPoint, $pathEndPoint) === false) { throw new \InvalidArgumentException('Invalid paths: path of starting point must be first part of end point path.', 1284391181); } $workspaces = array(); while ($workspace !== null) { $workspaces[] = $workspace; $workspace = $workspace->getBaseWorkspace(); } $queryBuilder = $this->createQueryBuilder($workspaces); if ($dimensions !== null) { $this->addDimensionJoinConstraintsToQueryBuilder($queryBuilder, $dimensions); } else { $dimensions = array(); } if ($nodeTypeFilter !== null) { $this->addNodeTypeFilterConstraintsToQueryBuilder($queryBuilder, $nodeTypeFilter); } $pathConstraints = array(); $constraintPath = $pathStartingPoint; $pathConstraints[] = md5($constraintPath); $pathSegments = explode('/', NodePaths::getRelativePathBetween($pathStartingPoint, $pathEndPoint)); foreach ($pathSegments as $pathSegment) { $constraintPath = NodePaths::addNodePathSegment($constraintPath, $pathSegment); $pathConstraints[] = md5($constraintPath); } if (count($pathConstraints) > 0) { $queryBuilder->andWhere('n.pathHash IN (:paths)')->setParameter('paths', $pathConstraints); } $query = $queryBuilder->getQuery(); $foundNodes = $query->getResult(); $foundNodes = $this->reduceNodeVariantsByWorkspacesAndDimensions($foundNodes, $workspaces, $dimensions); if ($includeRemovedNodes === false) { $foundNodes = $this->filterRemovedNodes($foundNodes, false); } $nodesByDepth = array(); /** @var NodeData $node */ foreach ($foundNodes as $node) { $nodesByDepth[$node->getDepth()] = $node; } ksort($nodesByDepth); return array_values($nodesByDepth); }
/** * Moves the given variant or child node to the destination defined by the given path which is * the new path for the originally moved (parent|variant) node * * @param string $aggregateOriginalPath * @param string $aggregateDestinationPath * @param NodeInterface $nodeToMove * @return array NodeVariant and old and new path */ protected function moveVariantOrChild($aggregateOriginalPath, $aggregateDestinationPath, NodeInterface $nodeToMove = null) { if ($nodeToMove === null) { return null; } $variantOriginalPath = $nodeToMove->getPath(); $relativePathSegment = NodePaths::getRelativePathBetween($aggregateOriginalPath, $variantOriginalPath); $variantDestinationPath = NodePaths::addNodePathSegment($aggregateDestinationPath, $relativePathSegment); $this->moveNodeToDestinationPath($nodeToMove, $variantDestinationPath); return [$nodeToMove, $variantOriginalPath, $nodeToMove->getPath()]; }
/** * Sets the absolute path of this node. * * This method is only for internal use by the content repository or node methods. Changing * the path of a node manually may lead to unexpected behavior. * * To achieve a correct behavior when changing the path (moving the node) in a workspace, a shadow node data that will * hide the node data in the base workspace will be created. Thus queries do not need to worry about moved nodes. * Through a movedTo reference the shadow node data will be removed when publishing the moved node. * * @param string $path * @param boolean $checkForExistence Checks for existence at target path, internally used for recursions and shadow nodes. * @throws NodeException * @internal This method is purely internal and Node objects can call this on other Node objects */ public function setPath($path, $checkForExistence = true) { $originalPath = $this->nodeData->getPath(); if ($originalPath === $path) { return; } if ($checkForExistence) { $existingNodeDataArray = $this->nodeDataRepository->findByPathWithoutReduce($path, $this->context->getWorkspace()); /** @var NodeData $existingNodeData */ foreach ($existingNodeDataArray as $existingNodeData) { if ($existingNodeData->getIdentifier() !== $this->getIdentifier()) { throw new NodeException(sprintf('Can not rename the node "%s" as a node already exists on path "%s"', $this->getPath(), $path), 1414436551); } } } $changedNodePathsCollection = array(); if ($this->getNodeType()->isAggregate()) { $nodeDataVariantsAndChildren = $this->nodeDataRepository->findByPathWithoutReduce($originalPath, $this->context->getWorkspace(), true, true); /** @var NodeData $nodeData */ foreach ($nodeDataVariantsAndChildren as $nodeData) { // $nodeDataVariants at this point also contains *our own NodeData reference* ($this->nodeData), as we find all NodeData objects // (across all dimensions) with the same path. // // We need to ensure that our own Node object's nodeData reference ($this->nodeData) is also updated correctly if a new NodeData object // is returned; as we rely on the fact that $this->getPath() will return the new node path in all circumstances. // // However, $this->createNodeForVariant() only returns $this if the Context object is the same as $this->context; which is not // the case if $this->context contains dimension fallbacks such as "Language: EN, DE". // // The "if" statement below is actually a workaround to ensure that if the NodeData object is our own one, we update *ourselves* correctly, // and thus return the correct (new) Node Path when calling $this->getPath() afterwards. if ($this->nodeData === $nodeData) { $nodeVariant = $this; } else { $nodeVariant = $this->createNodeForVariant($nodeData); } if ($nodeVariant !== null) { $relativePathSegment = NodePaths::getRelativePathBetween($originalPath, $nodeVariant->getPath()); $newNodeVariantPath = NodePaths::addNodePathSegment($path, $relativePathSegment); $possibleShadowedNodeData = $nodeData->move($newNodeVariantPath, $this->context->getWorkspace()); $nodeVariant->setNodeData($possibleShadowedNodeData); $changedNodePathsCollection[] = array($nodeVariant, $originalPath, $nodeVariant->getNodeData()->getPath(), !$checkForExistence); } } } else { /** @var Node $childNode */ foreach ($this->getChildNodes() as $childNode) { $childNode->setPath(NodePaths::addNodePathSegment($path, $childNode->getName()), false); } $possibleShadowedNodeData = $this->nodeData->move($path, $this->context->getWorkspace()); $this->setNodeData($possibleShadowedNodeData); $changedNodePathsCollection[] = array($this, $originalPath, $this->getNodeData()->getPath(), $checkForExistence); } $this->nodeDataRepository->persistEntities(); foreach ($changedNodePathsCollection as $nodePathChangedArguments) { call_user_func_array(array($this, 'emitNodePathChanged'), $nodePathChangedArguments); } }
/** * Renders the URI to a given node instance or -path. * * @param ControllerContext $controllerContext * @param mixed $node A node object or a string node path, if a relative path is provided the baseNode argument is required * @param NodeInterface $baseNode * @param string $format Format to use for the URL, for example "html" or "json" * @param boolean $absolute If set, an absolute URI is rendered * @param array $arguments Additional arguments to be passed to the UriBuilder (for example pagination parameters) * @param string $section * @param boolean $addQueryString If set, the current query parameters will be kept in the URI * @param array $argumentsToBeExcludedFromQueryString arguments to be removed from the URI. Only active if $addQueryString = TRUE * @param boolean $resolveShortcuts INTERNAL Parameter - if FALSE, shortcuts are not redirected to their target. Only needed on rare backend occasions when we want to link to the shortcut itself. * @return string The rendered URI * @throws \InvalidArgumentException if the given node/baseNode is not valid * @throws NeosException if no URI could be resolved for the given node */ public function createNodeUri(ControllerContext $controllerContext, $node = null, NodeInterface $baseNode = null, $format = null, $absolute = false, array $arguments = array(), $section = '', $addQueryString = false, array $argumentsToBeExcludedFromQueryString = array(), $resolveShortcuts = true) { $this->lastLinkedNode = null; if (!($node instanceof NodeInterface || is_string($node) || $baseNode instanceof NodeInterface)) { throw new \InvalidArgumentException('Expected an instance of NodeInterface or a string for the node argument, or alternatively a baseNode argument.', 1373101025); } if (is_string($node)) { $nodeString = $node; if ($nodeString === '') { throw new NeosException(sprintf('Empty strings can not be resolved to nodes.', $nodeString), 1415709942); } preg_match(NodeInterface::MATCH_PATTERN_CONTEXTPATH, $nodeString, $matches); if (isset($matches['WorkspaceName']) && $matches['WorkspaceName'] !== '') { $node = $this->propertyMapper->convert($nodeString, 'TYPO3\\TYPO3CR\\Domain\\Model\\NodeInterface'); } else { if ($baseNode === null) { throw new NeosException('The baseNode argument is required for linking to nodes with a relative path.', 1407879905); } /** @var ContentContext $contentContext */ $contentContext = $baseNode->getContext(); $normalizedPath = $this->nodeService->normalizePath($nodeString, $baseNode->getPath(), $contentContext->getCurrentSiteNode()->getPath()); $node = $contentContext->getNode($normalizedPath); } if (!$node instanceof NodeInterface) { throw new NeosException(sprintf('The string "%s" could not be resolved to an existing node.', $nodeString), 1415709674); } } elseif (!$node instanceof NodeInterface) { $node = $baseNode; } if (!$node instanceof NodeInterface) { throw new NeosException(sprintf('Node must be an instance of NodeInterface or string, given "%s".', gettype($node)), 1414772029); } $this->lastLinkedNode = $node; if ($resolveShortcuts === true) { $resolvedNode = $this->nodeShortcutResolver->resolveShortcutTarget($node); } else { // this case is only relevant in extremely rare occasions in the Neos Backend, when we want to generate // a link towards the *shortcut itself*, and not to its target. $resolvedNode = $node; } if (is_string($resolvedNode)) { return $resolvedNode; } if (!$resolvedNode instanceof NodeInterface) { throw new NeosException(sprintf('Could not resolve shortcut target for node "%s"', $node->getPath()), 1414771137); } /** @var ActionRequest $request */ $request = $controllerContext->getRequest()->getMainRequest(); $uriBuilder = clone $controllerContext->getUriBuilder(); $uriBuilder->setRequest($request); $uri = $uriBuilder->reset()->setSection($section)->setArguments($arguments)->setAddQueryString($addQueryString)->setArgumentsToBeExcludedFromQueryString($argumentsToBeExcludedFromQueryString)->setFormat($format ?: $request->getFormat())->uriFor('show', array('node' => $resolvedNode), 'Frontend\\Node', 'TYPO3.Neos'); $siteNode = $resolvedNode->getContext()->getCurrentSiteNode(); if (NodePaths::isSubPathOf($siteNode->getPath(), $resolvedNode->getPath())) { /** @var Site $site */ $site = $resolvedNode->getContext()->getCurrentSite(); } else { $nodePath = NodePaths::getRelativePathBetween(SiteService::SITES_ROOT_PATH, $resolvedNode->getPath()); list($siteNodeName) = explode('/', $nodePath); $site = $this->siteRepository->findOneByNodeName($siteNodeName); } if ($site->hasActiveDomains()) { $requestUriHost = $request->getHttpRequest()->getBaseUri()->getHost(); $activeHostPatterns = $site->getActiveDomains()->map(function ($domain) { return $domain->getHostPattern(); })->toArray(); if (!in_array($requestUriHost, $activeHostPatterns, true)) { $uri = $this->createSiteUri($controllerContext, $site) . '/' . ltrim($uri, '/'); } elseif ($absolute === true) { $uri = $request->getHttpRequest()->getBaseUri() . ltrim($uri, '/'); } } elseif ($absolute === true) { $uri = $request->getHttpRequest()->getBaseUri() . ltrim($uri, '/'); } return $uri; }