/** * Matches if the selected node is a *descendant* of the given node specified by $nodePathOrIdentifier * * Example: isDescendantNodeOf('/sites/some/path') matches for the nodes "/sites/some/path", "/sites/some/path/subnode" but not for "/sites/some/other" * * @param string $nodePathOrIdentifier The identifier or absolute path of the node to match * @return boolean TRUE if the given node matches otherwise false */ public function isDescendantNodeOf($nodePathOrIdentifier) { $nodePath = $this->resolveNodePath($nodePathOrIdentifier); if (is_bool($nodePath)) { return $nodePath; } return substr($this->node->getPath() . '/', 0, strlen($nodePath)) === $nodePath; }
/** * Register a node change for a later cache flush. This method is triggered by a signal sent via ContentRepository's Node * model or the Neos Publishing Service. * * @param NodeInterface $node The node which has changed in some way * @return void */ public function registerNodeChange(NodeInterface $node) { $this->tagsToFlush[ContentCache::TAG_EVERYTHING] = 'which were tagged with "Everything".'; $nodeTypesToFlush = $this->getAllImplementedNodeTypes($node->getNodeType()); foreach ($nodeTypesToFlush as $nodeType) { $nodeTypeName = $nodeType->getName(); $this->tagsToFlush['NodeType_' . $nodeTypeName] = sprintf('which were tagged with "NodeType_%s" because node "%s" has changed and was of type "%s".', $nodeTypeName, $node->getPath(), $node->getNodeType()->getName()); } $this->tagsToFlush['Node_' . $node->getIdentifier()] = sprintf('which were tagged with "Node_%s" because node "%s" has changed.', $node->getIdentifier(), $node->getPath()); $originalNode = $node; while ($node->getDepth() > 1) { $node = $node->getParent(); // Workaround for issue #56566 in Neos.ContentRepository if ($node === null) { break; } $tagName = 'DescendantOf_' . $node->getIdentifier(); $this->tagsToFlush[$tagName] = sprintf('which were tagged with "%s" because node "%s" has changed.', $tagName, $originalNode->getPath()); } }
/** * Get the rootline from the current node up to the site node. * * @return array */ protected function getCurrentNodeRootline() { if ($this->currentNodeRootline === null) { $nodeRootline = $this->currentNode->getContext()->getNodesOnPath($this->currentNode->getContext()->getCurrentSiteNode()->getPath(), $this->currentNode->getPath()); $this->currentNodeRootline = array(); foreach ($nodeRootline as $rootlineElement) { $this->currentNodeRootline[$this->getNodeLevelInSite($rootlineElement)] = $rootlineElement; } } return $this->currentNodeRootline; }
/** * Search all properties for given $term * * TODO: Implement a better search when Flow offer the possibility * * @param string|array $term search term * @param array $searchNodeTypes * @param Context $context * @param NodeInterface $startingPoint * @return array <\Neos\ContentRepository\Domain\Model\NodeInterface> */ public function findByProperties($term, array $searchNodeTypes, Context $context, NodeInterface $startingPoint = null) { if (empty($term)) { throw new \InvalidArgumentException('"term" cannot be empty: provide a term to search for.', 1421329285); } $searchResult = array(); $nodeTypeFilter = implode(',', $searchNodeTypes); $nodeDataRecords = $this->nodeDataRepository->findByProperties($term, $nodeTypeFilter, $context->getWorkspace(), $context->getDimensions(), $startingPoint ? $startingPoint->getPath() : null); foreach ($nodeDataRecords as $nodeData) { $node = $this->nodeFactory->createFromNodeData($nodeData, $context); if ($node !== null) { $searchResult[$node->getPath()] = $node; } } return $searchResult; }
/** * Check if the given node is already a collection, find collection by nodePath otherwise, throw exception * if no content collection could be found * * @param NodeInterface $node * @param string $nodePath * @return NodeInterface * @throws Exception */ public function nearestContentCollection(NodeInterface $node, $nodePath) { $contentCollectionType = 'Neos.Neos:ContentCollection'; if ($node->getNodeType()->isOfType($contentCollectionType)) { return $node; } else { if ((string) $nodePath === '') { throw new Exception(sprintf('No content collection of type %s could be found in the current node and no node path was provided. You might want to configure the nodePath property with a relative path to the content collection.', $contentCollectionType), 1409300545); } $subNode = $node->getNode($nodePath); if ($subNode !== null && $subNode->getNodeType()->isOfType($contentCollectionType)) { return $subNode; } else { throw new Exception(sprintf('No content collection of type %s could be found in the current node (%s) or at the path "%s". You might want to adjust your node type configuration and create the missing child node through the "flow node:repair --node-type %s" command.', $contentCollectionType, $node->getPath(), $nodePath, (string) $node->getNodeType()), 1389352984); } } }
/** * Returns an array with the data needed by for example the Hallo and Aloha * link plugins to represent the passed Node instance. * * @param NodeInterface $node * @return array */ protected function processNodeForEditorPlugins(NodeInterface $node) { return array('id' => $node->getPath(), 'name' => $node->getLabel(), 'url' => $this->uriBuilder->uriFor('show', array('node' => $node), 'Frontend\\Node', 'Neos.Neos'), 'type' => 'neos/internal-link'); }
/** * @param array $nextNodes the remaining nodes * @param NodeInterface $until * @return array */ protected function getNodesUntil($nextNodes, NodeInterface $until) { $count = count($nextNodes) - 1; for ($i = $count; $i >= 0; $i--) { if ($nextNodes[$i]->getPath() === $until->getPath()) { unset($nextNodes[$i]); return array_values($nextNodes); } else { unset($nextNodes[$i]); } } return array_values($nextNodes); }
/** * @param NodeInterface $rootNode * @param array<\Neos\ContentRepository\Domain\Model\NodeData> $nodes * @return array */ public function collectParentNodeData(NodeInterface $rootNode, array $nodes) { $nodeCollection = array(); $addNode = function ($node, $matched) use($rootNode, &$nodeCollection) { /** @var NodeInterface $node */ $path = str_replace('/', '.children.', substr($node->getPath(), strlen($rootNode->getPath()) + 1)); if ($path !== '') { $nodeCollection = Arrays::setValueByPath($nodeCollection, $path . '.node', $node); if ($matched === true) { $nodeCollection = Arrays::setValueByPath($nodeCollection, $path . '.matched', true); } } }; $findParent = function ($node) use(&$findParent, &$addNode) { /** @var NodeInterface $node */ $parent = $node->getParent(); if ($parent !== null) { $addNode($parent, false); $findParent($parent); } }; foreach ($nodes as $node) { $addNode($node, true); $findParent($node); } $treeNodes = array(); $self = $this; $collectTreeNodeData = function (&$treeNodes, $node) use(&$collectTreeNodeData, $self) { $children = array(); if (isset($node['children'])) { foreach ($node['children'] as $childNode) { $collectTreeNodeData($children, $childNode); } } $treeNodes[] = $self->collectTreeNodeData($node['node'], true, $children, $children !== array(), isset($node['matched'])); }; foreach ($nodeCollection as $firstLevelNode) { $collectTreeNodeData($treeNodes, $firstLevelNode); } return $treeNodes; }
/** * Method which does the actual work of discarding, includes a protection against endless recursions and * multiple discarding of the same node. * * @param NodeInterface $node The node to discard * @param array &$alreadyDiscardedNodeIdentifiers List of node identifiers which already have been discarded during one discardNode() run * @return void * @throws \Neos\ContentRepository\Exception\WorkspaceException */ protected function doDiscardNode(NodeInterface $node, array &$alreadyDiscardedNodeIdentifiers = []) { if ($node->getWorkspace()->getBaseWorkspace() === null) { throw new WorkspaceException('Nodes in a in a workspace without a base workspace cannot be discarded.', 1395841899); } if ($node->getPath() === '/') { return; } if (array_search($node->getIdentifier(), $alreadyDiscardedNodeIdentifiers) !== false) { return; } $alreadyDiscardedNodeIdentifiers[] = $node->getIdentifier(); $possibleShadowNodeData = $this->nodeDataRepository->findOneByMovedTo($node->getNodeData()); if ($possibleShadowNodeData instanceof NodeData) { if ($possibleShadowNodeData->getMovedTo() !== null) { $parentBasePath = $node->getPath(); $affectedChildNodeDataInSameWorkspace = $this->nodeDataRepository->findByParentAndNodeType($parentBasePath, null, $node->getWorkspace(), null, false, true); foreach ($affectedChildNodeDataInSameWorkspace as $affectedChildNodeData) { /** @var NodeData $affectedChildNodeData */ $affectedChildNode = $this->nodeFactory->createFromNodeData($affectedChildNodeData, $node->getContext()); $this->doDiscardNode($affectedChildNode, $alreadyDiscardedNodeIdentifiers); } } $this->nodeDataRepository->remove($possibleShadowNodeData); } $this->nodeDataRepository->remove($node); $this->emitNodeDiscarded($node); }
/** * Renders a request path based on the "uriPathSegment" properties of the nodes leading to the given node. * * @param NodeInterface $node The node where the generated path should lead to * @return string A relative request path * @throws Exception\MissingNodePropertyException if the given node doesn't have a "uriPathSegment" property set */ protected function getRequestPathByNode(NodeInterface $node) { if ($node->getParentPath() === SiteService::SITES_ROOT_PATH) { return ''; } $requestPathSegments = []; while ($node instanceof NodeInterface && $node->getParentPath() !== SiteService::SITES_ROOT_PATH) { if (!$node->hasProperty('uriPathSegment')) { throw new Exception\MissingNodePropertyException(sprintf('Missing "uriPathSegment" property for node "%s". Nodes can be migrated with the "flow node:repair" command.', $node->getPath()), 1415020326); } $pathSegment = $node->getProperty('uriPathSegment'); $requestPathSegments[] = $pathSegment; $node = $node->getParent(); } return implode('/', array_reverse($requestPathSegments)); }
/** * Rebase the current users personal workspace onto the given $targetWorkspace and then * redirects to the $targetNode in the content module. * * @param NodeInterface $targetNode * @param Workspace $targetWorkspace * @return void */ public function rebaseAndRedirectAction(NodeInterface $targetNode, Workspace $targetWorkspace) { $currentAccount = $this->securityContext->getAccount(); $personalWorkspace = $this->workspaceRepository->findOneByName(UserUtility::getPersonalWorkspaceNameForUsername($currentAccount->getAccountIdentifier())); /** @var Workspace $personalWorkspace */ if ($personalWorkspace !== $targetWorkspace) { if ($this->publishingService->getUnpublishedNodesCount($personalWorkspace) > 0) { $message = $this->translator->translateById('workspaces.cantEditBecauseWorkspaceContainsChanges', [], null, null, 'Modules', 'Neos.Neos'); $this->addFlashMessage($message, '', Message::SEVERITY_WARNING, [], 1437833387); $this->redirect('show', null, null, ['workspace' => $targetWorkspace]); } $personalWorkspace->setBaseWorkspace($targetWorkspace); $this->workspaceRepository->update($personalWorkspace); } $contextProperties = $targetNode->getContext()->getProperties(); $contextProperties['workspaceName'] = $personalWorkspace->getName(); $context = $this->contextFactory->create($contextProperties); $mainRequest = $this->controllerContext->getRequest()->getMainRequest(); /** @var ActionRequest $mainRequest */ $this->uriBuilder->setRequest($mainRequest); $this->redirect('show', 'Frontend\\Node', 'Neos.Neos', ['node' => $context->getNode($targetNode->getPath())]); }
/** * Replace the node data of a node instance with a given target node data * * The node data of the node that is published will be removed and the existing node data inside the target * workspace is updated to the changes and will be injected into the node instance. If the node was marked as * removed, both node data are removed. * * @param NodeInterface $node The node instance with node data to be published * @param NodeData $targetNodeData The existing node data in the target workspace * @return void */ protected function replaceNodeData(NodeInterface $node, NodeData $targetNodeData) { $sourceNodeData = $node->getNodeData(); $nodeWasMoved = $this->handleShadowNodeData($sourceNodeData, $targetNodeData->getWorkspace(), $targetNodeData); // Technically this shouldn't be needed but due to doctrines behavior we need it. if ($sourceNodeData->isRemoved() && $targetNodeData->getWorkspace()->getBaseWorkspace() === null) { $this->nodeDataRepository->remove($targetNodeData); $this->nodeDataRepository->remove($sourceNodeData); return; } $targetNodeData->similarize($sourceNodeData); $targetNodeData->setLastPublicationDateTime($this->now); if ($nodeWasMoved) { // TODO: This seems wrong and introduces a publish order between nodes. We should always set the path. $targetNodeData->setPath($node->getPath(), false); } $node->setNodeData($targetNodeData); $this->nodeService->cleanUpProperties($node); $targetNodeData->setRemoved($sourceNodeData->isRemoved()); $this->nodeDataRepository->remove($sourceNodeData); }
/** * Internal method to do the actual copying. * * For behavior of the $detachedCopy parameter, see method Node::createRecursiveCopy(). * * @param NodeInterface $referenceNode * @param $nodeName * @param boolean $detachedCopy * @return NodeInterface * @throws NodeConstraintException * @throws NodeExistsException */ protected function copyIntoInternal(NodeInterface $referenceNode, $nodeName, $detachedCopy) { if ($referenceNode->getNode($nodeName) !== null) { throw new NodeExistsException('Node with path "' . $referenceNode->getPath() . '/' . $nodeName . '" already exists.', 1292503467); } // On copy we basically re-recreate an existing node on a new location. As we skip the constraints check on // node creation we should do the same while writing the node on the new location. if (!$referenceNode->willChildNodeBeAutoCreated($nodeName) && !$referenceNode->isNodeTypeAllowedAsChildNode($this->getNodeType())) { throw new NodeConstraintException(sprintf('Cannot copy "%s" into "%s" due to node type constraints.', $this->__toString(), $referenceNode->__toString()), 1404648177); } $copiedNode = $this->createRecursiveCopy($referenceNode, $nodeName, $detachedCopy); $this->context->getFirstLevelNodeCache()->flush(); $this->emitNodeAdded($copiedNode); return $copiedNode; }
/** * 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, NodeInterface::class); } 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', 'Neos.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; }
/** * Adds the given node to the cache for the given identifier. The node * will also be added with is's path. * * @param string $identifier * @param NodeInterface $node * @return void */ public function setByIdentifier($identifier, NodeInterface $node = null) { $this->nodesByIdentifier[$identifier] = $node; if ($node !== null) { $this->nodesByPath[$node->getPath()] = $node; } }