/** * Resolves a shortcut node to the target. The return value can be * * * a NodeInterface instance if the target is a node or a node:// URI * * a string (in case the target is a plain text URI or an asset:// URI) * * NULL in case the shortcut cannot be resolved * * @param NodeInterface $node * @return NodeInterface|string|NULL */ public function resolveShortcutTarget(NodeInterface $node) { $infiniteLoopPrevention = 0; while ($node->getNodeType()->isOfType('Neos.Neos:Shortcut') && $infiniteLoopPrevention < 50) { $infiniteLoopPrevention++; switch ($node->getProperty('targetMode')) { case 'selectedTarget': $target = $node->getProperty('target'); if ($this->linkingService->hasSupportedScheme($target)) { $targetObject = $this->linkingService->convertUriToObject($target, $node); if ($targetObject instanceof NodeInterface) { $node = $targetObject; } elseif ($targetObject instanceof AssetInterface) { return $this->linkingService->resolveAssetUri($target); } } else { return $target; } break; case 'parentNode': $node = $node->getParent(); break; case 'firstChildNode': default: $childNodes = $node->getChildNodes('Neos.Neos:Document'); if ($childNodes !== array()) { $node = reset($childNodes); } else { return null; } } } return $node; }
/** * Shows the specified node and takes visibility and access restrictions into * account. * * @param NodeInterface $node * @return string View output for the specified node * @Flow\SkipCsrfProtection We need to skip CSRF protection here because this action could be called with unsafe requests from widgets or plugins that are rendered on the node - For those the CSRF token is validated on the sub-request, so it is safe to be skipped here * @Flow\IgnoreValidation("node") * @throws NodeNotFoundException */ public function showAction(NodeInterface $node = null) { if ($node === null) { throw new NodeNotFoundException('The requested node does not exist or isn\'t accessible to the current user', 1430218623); } $inBackend = $node->getContext()->isInBackend(); if ($node->getNodeType()->isOfType('Neos.Neos:Shortcut') && !$inBackend) { $this->handleShortcutNode($node); } $this->view->assign('value', $node); if ($inBackend) { $this->overrideViewVariablesFromInternalArguments(); /** @var UserInterfaceMode $renderingMode */ $renderingMode = $node->getContext()->getCurrentRenderingMode(); $this->response->setHeader('Cache-Control', 'no-cache'); if ($renderingMode !== null) { // Deprecated TypoScript context variable from version 2.0. $this->view->assign('editPreviewMode', $renderingMode->getTypoScriptPath()); } if (!$this->view->canRenderWithNodeAndPath()) { $this->view->setTypoScriptPath('rawContent'); } } if ($this->session->isStarted() && $inBackend) { $this->session->putData('lastVisitedNode', $node->getContextPath()); } }
/** * 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); } } }
/** * Schedules flushing of the routing cache entries for the given $node * Note that child nodes are flushed automatically because they are tagged with all parents. * * @param NodeInterface $node The node which has changed in some way * @return void */ public function registerNodeChange(NodeInterface $node) { if (in_array($node->getIdentifier(), $this->tagsToFlush)) { return; } if (!$node->getNodeType()->isOfType('Neos.Neos:Document')) { return; } $this->tagsToFlush[] = $node->getIdentifier(); }
/** * 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()); } }
/** * Sets the best possible uriPathSegment for the given Node. * Will use an already set uriPathSegment or alternatively the node name as base, * then checks if the uriPathSegment already exists on the same level and appends a counter until a unique path segment was found. * * @param NodeInterface $node * @return void */ public static function setUniqueUriPathSegment(NodeInterface $node) { if ($node->getNodeType()->isOfType('Neos.Neos:Document')) { $q = new FlowQuery(array($node)); $q = $q->context(array('invisibleContentShown' => true, 'removedContentShown' => true, 'inaccessibleContentShown' => true)); $possibleUriPathSegment = $initialUriPathSegment = !$node->hasProperty('uriPathSegment') ? $node->getName() : $node->getProperty('uriPathSegment'); $i = 1; while ($q->siblings('[instanceof Neos.Neos:Document][uriPathSegment="' . $possibleUriPathSegment . '"]')->count() > 0) { $possibleUriPathSegment = $initialUriPathSegment . '-' . $i++; } $node->setProperty('uriPathSegment', $possibleUriPathSegment); } }
/** * Matches if the selected node is of the given NodeType(s). If multiple types are specified, only one entry has to match * * Example: nodeIsOfType(['Neos.ContentRepository:NodeType1', 'Neos.ContentRepository:NodeType2']) matches if the selected node is of (sub) type *Neos.ContentRepository:NodeType1* or *Neos.ContentRepository:NodeType1* * * @param string|array $nodeTypes A single or an array of fully qualified NodeType name(s), e.g. "Neos.Neos:Document" * @return boolean TRUE if the selected node matches the $nodeTypes, otherwise FALSE */ public function nodeIsOfType($nodeTypes) { if ($this->node === null) { return true; } if (!is_array($nodeTypes)) { $nodeTypes = array($nodeTypes); } foreach ($nodeTypes as $nodeType) { if ($this->node->getNodeType()->isOfType($nodeType)) { return true; } } return false; }
/** * @param NodeInterface $node A node * @return array of document nodes */ public function render(NodeInterface $node) { $documentNodes = []; $flowQuery = new FlowQuery(array($node)); $nodes = array_reverse($flowQuery->parents('[instanceof Neos.Neos:Document]')->get()); /** @var NodeInterface $node */ foreach ($nodes as $documentNode) { $documentNodes[] = $documentNode; } if ($node->getNodeType()->isOfType('Neos.Neos:Document')) { $documentNodes[] = $node; } $this->templateVariableContainer->add('documentNodes', $documentNodes); $content = $this->renderChildren(); $this->templateVariableContainer->remove('documentNodes'); return $content; }
/** * Publishes the given node to the specified target workspace. If no workspace is specified, the base workspace * is assumed. * * If the given node is a Document or has ContentCollection child nodes, these nodes are published as well. * * @param NodeInterface $node * @param Workspace $targetWorkspace If not set the base workspace is assumed to be the publishing target * @return void * @api */ public function publishNode(NodeInterface $node, Workspace $targetWorkspace = null) { if ($targetWorkspace === null) { $targetWorkspace = $node->getWorkspace()->getBaseWorkspace(); } if (!$targetWorkspace instanceof Workspace) { return; } $nodes = array($node); $nodeType = $node->getNodeType(); if ($nodeType->isOfType('Neos.Neos:Document') || $nodeType->hasConfiguration('childNodes')) { foreach ($node->getChildNodes('Neos.Neos:ContentCollection') as $contentCollectionNode) { array_push($nodes, $contentCollectionNode); } } $sourceWorkspace = $node->getWorkspace(); $sourceWorkspace->publishNodes($nodes, $targetWorkspace); $this->emitNodePublished($node, $targetWorkspace); }
/** * Returns the plugin namespace that will be prefixed to plugin parameters in URIs. * By default this is <plugin_class_name> * * @return string */ protected function getPluginNamespace() { if ($this->getArgumentNamespace() !== null) { return $this->getArgumentNamespace(); } if ($this->node instanceof NodeInterface) { $nodeArgumentNamespace = $this->node->getProperty('argumentNamespace'); if ($nodeArgumentNamespace !== null) { return $nodeArgumentNamespace; } $nodeArgumentNamespace = $this->node->getNodeType()->getName(); $nodeArgumentNamespace = str_replace(':', '-', $nodeArgumentNamespace); $nodeArgumentNamespace = str_replace('.', '_', $nodeArgumentNamespace); $nodeArgumentNamespace = strtolower($nodeArgumentNamespace); return $nodeArgumentNamespace; } $argumentNamespace = str_replace(array(':', '.', '\\'), array('_', '_', '_'), $this->getPackage() . '_' . $this->getSubpackage() . '-' . $this->getController()); $argumentNamespace = strtolower($argumentNamespace); return $argumentNamespace; }
/** * {@inheritdoc} */ public function createRedirectsForPublishedNode(NodeInterface $node, Workspace $targetWorkspace) { $nodeType = $node->getNodeType(); if ($targetWorkspace->getName() !== 'live' || !$nodeType->isOfType('Neos.Neos:Document')) { return; } $context = $this->contextFactory->create(['workspaceName' => 'live', 'invisibleContentShown' => true, 'dimensions' => $node->getContext()->getDimensions()]); $targetNode = $context->getNodeByIdentifier($node->getIdentifier()); if ($targetNode === null) { // The page has been added return; } $targetNodeUriPath = $this->buildUriPathForNodeContextPath($targetNode->getContextPath()); if ($targetNodeUriPath === null) { throw new Exception('The target URI path of the node could not be resolved', 1451945358); } $hosts = $this->getHostnames($node->getContext()); // The page has been removed if ($node->isRemoved()) { $this->flushRoutingCacheForNode($targetNode); $statusCode = (int) $this->defaultStatusCode['gone']; $this->redirectStorage->addRedirect($targetNodeUriPath, '', $statusCode, $hosts); return; } // compare the "old" node URI to the new one $nodeUriPath = $this->buildUriPathForNodeContextPath($node->getContextPath()); // use the same regexp than the ContentContextBar Ember View $nodeUriPath = preg_replace('/@[A-Za-z0-9;&,\\-_=]+/', '', $nodeUriPath); if ($nodeUriPath === null || $nodeUriPath === $targetNodeUriPath) { // The page node path has not been changed return; } $this->flushRoutingCacheForNode($targetNode); $statusCode = (int) $this->defaultStatusCode['redirect']; $this->redirectStorage->addRedirect($targetNodeUriPath, $nodeUriPath, $statusCode, $hosts); $q = new FlowQuery([$node]); foreach ($q->children('[instanceof Neos.Neos:Document]') as $childrenNode) { $this->createRedirectsForPublishedNode($childrenNode, $targetWorkspace); } }
/** * returns a plugin node or one of it's view nodes * if an view has been configured for that specific * controller and action combination * * @param NodeInterface $currentNode * @param string $controllerObjectName * @param string $actionName * @return NodeInterface */ public function getPluginNodeByAction(NodeInterface $currentNode, $controllerObjectName, $actionName) { $viewDefinition = $this->getPluginViewDefinitionByAction($controllerObjectName, $actionName); if ($currentNode->getNodeType()->isOfType('Neos.Neos:PluginView')) { $masterPluginNode = $this->getPluginViewNodeByMasterPlugin($currentNode, $viewDefinition->getName()); } else { $masterPluginNode = $currentNode; } if ($viewDefinition !== null) { $viewNode = $this->getPluginViewNodeByMasterPlugin($currentNode, $viewDefinition->getName()); if ($viewNode instanceof Node) { return $viewNode; } } return $masterPluginNode; }
/** * Returns the closest aggregate node of the given node * * @param NodeInterface $node * @return NodeInterface */ public static function getClosestAggregateNode(NodeInterface $node) { while ($node !== null && !$node->getNodeType()->isAggregate()) { $node = $node->getParent(); } return $node; }
/** * @param NodeInterface $node * @return NodeInterface */ protected function getClosestDocumentNode(NodeInterface $node) { while ($node !== null && !$node->getNodeType()->isOfType('Neos.Neos:Document')) { $node = $node->getParent(); } return $node; }
/** * Tries to determine a label for the specified property * * @param string $propertyName * @param NodeInterface $changedNode * @return string */ protected function getPropertyLabel($propertyName, NodeInterface $changedNode) { $properties = $changedNode->getNodeType()->getProperties(); if (!isset($properties[$propertyName]) || !isset($properties[$propertyName]['ui']['label'])) { return $propertyName; } return $properties[$propertyName]['ui']['label']; }
/** * @param NodeInterface $node * @param boolean $expand * @param array $children * @param boolean $hasChildNodes * @param boolean $matched * @return array */ public function collectTreeNodeData(NodeInterface $node, $expand = true, array $children = array(), $hasChildNodes = false, $matched = false) { $isTimedPage = false; $now = new \DateTime(); $now = $now->getTimestamp(); $hiddenBeforeDateTime = $node->getHiddenBeforeDateTime(); $hiddenAfterDateTime = $node->getHiddenAfterDateTime(); if ($hiddenBeforeDateTime !== null && $hiddenBeforeDateTime->getTimestamp() > $now) { $isTimedPage = true; } if ($hiddenAfterDateTime !== null) { $isTimedPage = true; } $classes = array(); if ($isTimedPage === true && $node->isHidden() === false) { array_push($classes, 'neos-timedVisibility'); } if ($node->isHidden() === true) { array_push($classes, 'neos-hidden'); } if ($node->isHiddenInIndex() === true) { array_push($classes, 'neos-hiddenInIndex'); } if ($matched) { array_push($classes, 'neos-matched'); } $uriBuilder = $this->controllerContext->getUriBuilder(); $nodeType = $node->getNodeType(); $nodeTypeConfiguration = $nodeType->getFullConfiguration(); if ($node->getNodeType()->isOfType('Neos.Neos:Document')) { $uriForNode = $uriBuilder->reset()->setFormat('html')->setCreateAbsoluteUri(true)->uriFor('show', array('node' => $node), 'Frontend\\Node', 'Neos.Neos'); } else { $uriForNode = '#'; } $label = $node->getLabel(); $nodeTypeLabel = $node->getNodeType()->getLabel(); $treeNode = array('key' => $node->getContextPath(), 'title' => $label, 'fullTitle' => $node->getProperty('title'), 'nodeTypeLabel' => $nodeTypeLabel, 'tooltip' => '', 'href' => $uriForNode, 'isFolder' => $hasChildNodes, 'isLazy' => $hasChildNodes && !$expand, 'nodeType' => $nodeType->getName(), 'isAutoCreated' => $node->isAutoCreated(), 'expand' => $expand, 'addClass' => implode(' ', $classes), 'name' => $node->getName(), 'iconClass' => isset($nodeTypeConfiguration['ui']) && isset($nodeTypeConfiguration['ui']['icon']) ? $nodeTypeConfiguration['ui']['icon'] : '', 'isHidden' => $node->isHidden()); if ($hasChildNodes) { $treeNode['children'] = $children; } return $treeNode; }
/** * Checks if the given Node has any properties configured as 'inlineEditable' * * @param NodeInterface $node * @return boolean */ protected function hasInlineEditableProperties(NodeInterface $node) { return array_reduce(array_values($node->getNodeType()->getProperties()), function ($hasInlineEditableProperties, $propertyConfiguration) { return $hasInlineEditableProperties || isset($propertyConfiguration['ui']['inlineEditable']) && $propertyConfiguration['ui']['inlineEditable'] === true; }, false); }
/** * Get all properties reduced to simple type (no objects) representations in an array * * @param NodeInterface $node * @return array */ public function getPropertiesArray(NodeInterface $node) { $properties = []; foreach ($node->getNodeType()->getProperties() as $propertyName => $propertyConfiguration) { if ($propertyName[0] === '_' && $propertyName[1] === '_') { // skip fully-private properties continue; } $properties[$propertyName] = $this->getProperty($node, $propertyName); } return $properties; }