/** * 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); } if (!$node->getContext()->isLive() && !$this->privilegeManager->isPrivilegeTargetGranted('TYPO3.Neos:Backend.GeneralAccess')) { $this->redirect('index', 'Login', NULL, array('unauthorized' => TRUE)); } $inBackend = $node->getContext()->isInBackend(); if ($node->getNodeType()->isOfType('TYPO3.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()); } }
/** * 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 \TYPO3\TYPO3CR\Domain\Model\NodeInterface $node * @return NodeInterface|string|NULL */ public function resolveShortcutTarget(NodeInterface $node) { $infiniteLoopPrevention = 0; while ($node->getNodeType()->isOfType('TYPO3.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('TYPO3.Neos:Document'); if ($childNodes !== array()) { $node = reset($childNodes); } else { return null; } } } return $node; }
/** * Set the node title for the newly created Document node * * @param NodeInterface $node The newly created node * @param array $data incoming data from the creationDialog * @return void */ public function handle(NodeInterface $node, array $data) { if (isset($data['title']) && $node->getNodeType()->isOfType('TYPO3.Neos:Document')) { $node->setProperty('title', $data['title']); $node->setProperty('uriPathSegment', NodeUtility::renderValidNodeName($data['title'])); } }
/** * Hooks into `afterNodeCreate` event dispatched from CR, * when new node has been created. * * @param NodeInterface $node */ public function afterNodeCreate(NodeInterface $node) { if (!($nodeType = $node->getNodeType())) { return; } // array with [beforeNodes, childNodes, afterNodes] keys $config = $this->getAssistanceConfigForNodeType($nodeType->getName()); switch ($nodeType->getName()) { case 'TYPO3.Neos.NodeTypes:Image': $this->configureImage($node, $nodeType, $config); break; case 'M12.Foundation:GridRow1Col': case 'M12.Foundation:GridRow2Col': case 'M12.Foundation:GridRow3Col': case 'M12.Foundation:GridRow4Col': $this->configureGridRow($node, $nodeType); break; case 'M12.Foundation:GridColumn': break; // assistance text nodes are created from $this->configureGridRow() // assistance text nodes are created from $this->configureGridRow() case 'M12.Foundation:RevealModal': $this->configureLinkingButton($node, $nodeType, $config, 'htmlDataRevealId'); break; case 'M12.Foundation:Dropdown': case 'M12.Foundation:DropdownContent': $this->configureLinkingButton($node, $nodeType, $config, 'htmlDataDropdownId'); break; default: $this->configureCreateAssistanceChildNodes($node, $nodeType, $config); break; } }
/** * Helper method to retrieve the closest document for a node * * @param NodeInterface $node * @return NodeInterface */ public function getClosestDocument(NodeInterface $node) { if ($node->getNodeType()->isOfType('TYPO3.Neos:Document')) { return $node; } $flowQuery = new FlowQuery(array($node)); return $flowQuery->closest('[instanceof TYPO3.Neos:Document]')->get(0); }
/** * 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 = 'TYPO3.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 TRUE if the given node is of the node type this filter expects. * * @param \TYPO3\TYPO3CR\Domain\Model\NodeInterface $node * @return boolean */ public function matches(\TYPO3\TYPO3CR\Domain\Model\NodeInterface $node) { if ($this->withSubTypes === TRUE) { return $this->nodeTypeManager->getNodeType($node->getNodeType())->isOfType($this->nodeTypeName); } else { $nodeData = \TYPO3\Flow\Reflection\ObjectAccess::getProperty($node, 'nodeData', TRUE); $nodeType = \TYPO3\Flow\Reflection\ObjectAccess::getProperty($nodeData, 'nodeType', TRUE); return $nodeType === $this->nodeTypeName; } }
/** * Schedules flushing of the routing cache entry for the given $nodeData * Note: This is not done recursively because the nodePathChanged signal is triggered for any affected node data instance * * @param NodeInterface $node The affected node data instance * @return void */ public function registerNodePathChange(NodeInterface $node) { if (in_array($node->getIdentifier(), $this->tagsToFlush)) { return; } if (!$node->getNodeType()->isOfType('TYPO3.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 TYPO3CR'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 TYPO3.TYPO3CR 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('TYPO3.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 TYPO3.Neos:Document][uriPathSegment="' . $possibleUriPathSegment . '"]')->count() > 0) { $possibleUriPathSegment = $initialUriPathSegment . '-' . $i++; } $node->setProperty('uriPathSegment', $possibleUriPathSegment); } }
/** * @param NodeInterface $node * @return array */ protected function breadcrumbNodesForNode(NodeInterface $node) { $documentNodes = []; $flowQuery = new FlowQuery(array($node)); $nodes = array_reverse($flowQuery->parents('[instanceof TYPO3.Neos:Document]')->get()); /** @var NodeInterface $node */ foreach ($nodes as $documentNode) { $documentNodes[] = $documentNode; } if ($node->getNodeType()->isOfType('TYPO3.Neos:Document')) { $documentNodes[] = $node; } return $documentNodes; }
/** * Generates cache tags to be flushed for a node which is flushed on shutdown. * * Code duplicated from Neos' ContentCacheFlusher class * * @param NodeInterface|NodeData $node The node which has changed in some way * @return void */ protected function generateCacheTags($node) { $this->tagsToFlush[ContentCache::TAG_EVERYTHING] = 'which were tagged with "Everything".'; $nodeTypesToFlush = $this->getAllImplementedNodeTypes($node->getNodeType()); foreach ($nodeTypesToFlush as $nodeType) { /** @var NodeType $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()); while ($node->getDepth() > 1) { $node = $node->getParent(); if ($node === NULL) { break; } $this->tagsToFlush['DescendantOf_' . $node->getIdentifier()] = sprintf('which were tagged with "DescendantOf_%s" because node "%s" has changed.', $node->getIdentifier(), $node->getPath()); } if ($node instanceof NodeInterface && $node->getContext() instanceof ContentContext) { $firstActiveDomain = $node->getContext()->getCurrentSite()->getFirstActiveDomain(); if ($firstActiveDomain) { $this->domainsToFlush[] = $firstActiveDomain->getHostPattern(); } } }
/** * @param string|array $nodeTypes * @return boolean */ 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; }
/** * Matches if the selected node is of the given NodeType(s). If multiple types are specified, only one entry has to match * * Example: nodeIsOfType(['TYPO3.TYPO3CR:NodeType1', 'TYPO3.TYPO3CR:NodeType2']) matches if the selected node is of (sub) type *TYPO3.TYPO3CR:NodeType1* or *TYPO3.TYPO3CR:NodeType1* * * @param string|array $nodeTypes A single or an array of fully qualified NodeType name(s), e.g. "TYPO3.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; }
/** * Increase read counter of the node by one * * @param NodeInterface $node Node to increase the read counter for * * @throws BadRequestException * @return mixed[] */ public function trackAction(NodeInterface $node) { // we can only count pages that include the mixin if ($node->getNodeType()->isOfType('Futjikato.ReadCounter:CounterMixin')) { $node->setProperty('readcounter', $node->getProperty('readcounter') + 1); /** * Action changes data but is accessible via GET. this issues a error if we do not manually * persists the object in the persistence manager */ $this->persistenceManager->persistAll(); // by default the flow JSON view uses the 'value' variable $this->view->assign('value', array('readcounter' => $node->getProperty('readcounter'))); } else { throw new BadRequestException('Node does not contain Futjikato.ReadCounter:CounterMixin.'); } }
/** * @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 TYPO3.Neos:Document]')->get()); /** @var NodeInterface $node */ foreach ($nodes as $documentNode) { $documentNodes[] = $documentNode; } if ($node->getNodeType()->isOfType('TYPO3.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('TYPO3.Neos:Document') || $nodeType->hasConfiguration('childNodes')) { foreach ($node->getChildNodes('TYPO3.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; }
protected function getACLPropertiesForNode(NodeInterface $node) { $properties = ['nodeIdentifier' => $node->getIdentifier(), 'nodePath' => $node->getPath(), 'nodeLabel' => $node->getLabel(), 'nodeType' => $node->getNodeType()->getName(), 'nodeLevel' => $node->getDepth()]; return $properties; }
private function buildNodeProperties(NodeInterface $node) { $encodedProperties = []; foreach ($node->getNodeType()->getProperties() as $propertyName => $propertyConfiguration) { if (substr($propertyName, 0, 2) === '__') { // skip fully-private properties continue; } /** @var $contentContext ContentContext */ $contentContext = $node->getContext(); if ($propertyName === '_name' && $node === $contentContext->getCurrentSiteNode()) { // skip the node name of the site node continue; } // Serialize objects to JSON strings $dataType = isset($propertyConfiguration['type']) ? $propertyConfiguration['type'] : 'string'; $encodedProperties[$propertyName] = $this->buildNodeProperty($node, $propertyName, $dataType); } return $encodedProperties; }
/** * Schedule node removal into the current bulk request. * * @param NodeInterface $node * @return string */ public function removeNode(NodeInterface $node) { // TODO: handle deletion from the fulltext index as well $identifier = sha1($node->getContextPath()); $this->currentBulkRequest[] = array(array('delete' => array('_type' => NodeTypeMappingBuilder::convertNodeTypeNameToMappingName($node->getNodeType()), '_id' => $identifier))); $this->logger->log(sprintf('NodeIndexer: Removed node %s from index (node actually removed). Persistence ID: %s', $node->getContextPath(), $identifier), LOG_DEBUG, NULL, 'ElasticSearch (CR)'); }
/** * @param NodeInterface $node * @return boolean */ protected function hasInlineEditableProperties(NodeInterface $node) { foreach (array_values($node->getNodeType()->getProperties()) as $propertyConfiguration) { if (isset($propertyConfiguration['ui']['inlineEditable']) && $propertyConfiguration['ui']['inlineEditable'] === true) { return true; } } return false; }
/** * 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; }
/** * 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 NodeType $nodeType * @return boolean */ public function isNodeOfType(NodeInterface $node, NodeType $nodeType) { if ($node->getNodeType()->getName() === $nodeType->getName()) { return true; } $subNodeTypes = $this->nodeTypeManager->getSubNodeTypes($nodeType->getName()); return isset($subNodeTypes[$node->getNodeType()->getName()]); }
/** * Fetch the configured views for the given master plugin * * @param NodeInterface $node * @return string * * @Flow\IgnoreValidation("node") */ public function pluginViewsAction(NodeInterface $node = NULL) { $this->response->setHeader('Content-Type', 'application/json'); $views = array(); if ($node !== NULL) { /** @var $pluginViewDefinition \TYPO3\Neos\Domain\Model\PluginViewDefinition */ $pluginViewDefinitions = $this->pluginService->getPluginViewDefinitionsByPluginNodeType($node->getNodeType()); foreach ($pluginViewDefinitions as $pluginViewDefinition) { $label = $pluginViewDefinition->getLabel(); $views[$pluginViewDefinition->getName()] = array('label' => $label); $pluginViewNode = $this->pluginService->getPluginViewNodeByMasterPlugin($node, $pluginViewDefinition->getName()); if ($pluginViewNode === NULL) { continue; } $q = new FlowQuery(array($pluginViewNode)); $page = $q->closest('[instanceof TYPO3.Neos:Document]')->get(0); $uri = $this->uriBuilder->reset()->uriFor('show', array('node' => $page), 'Frontend\\Node', 'TYPO3.Neos'); $pageTitle = $page->getLabel(); $views[$pluginViewDefinition->getName()] = array('label' => sprintf('"%s"', $label, $pageTitle), 'pageNode' => array('title' => $pageTitle, 'path' => $page->getPath(), 'uri' => $uri)); } } return json_encode((object) $views); }
/** * 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); }
/** * Apply nodeCreationHandlers * * @param NodeInterface $node * @throws InvalidNodeCreationHandlerException * @return void */ protected function applyNodeCreationHandlers(NodeInterface $node) { $data = $this->getData() ?: []; $nodeType = $node->getNodeType(); if (isset($nodeType->getOptions()['nodeCreationHandlers'])) { $nodeCreationHandlers = $nodeType->getOptions()['nodeCreationHandlers']; if (is_array($nodeCreationHandlers)) { foreach ($nodeCreationHandlers as $nodeCreationHandlerConfiguration) { $nodeCreationHandler = new $nodeCreationHandlerConfiguration['nodeCreationHandler'](); if (!$nodeCreationHandler instanceof NodeCreationHandlerInterface) { throw new InvalidNodeCreationHandlerException(sprintf('Expected NodeCreationHandlerInterface but got "%s"', get_class($nodeCreationHandler)), 1364759956); } $nodeCreationHandler->handle($node, $data); } } } }
/** * Schedule node removal into the current bulk request. * * @param NodeInterface $node * @return string */ public function removeNode(NodeInterface $node) { if ($this->settings['indexAllWorkspaces'] === false) { if ($node->getContext()->getWorkspaceName() !== 'live') { return; } } // TODO: handle deletion from the fulltext index as well $identifier = sha1($node->getContextPath()); $this->currentBulkRequest[] = [['delete' => ['_type' => NodeTypeMappingBuilder::convertNodeTypeNameToMappingName($node->getNodeType()), '_id' => $identifier]]]; $this->logger->log(sprintf('NodeIndexer: Removed node %s from index (node actually removed). Persistence ID: %s', $node->getContextPath(), $identifier), LOG_DEBUG, null, 'ElasticSearch (CR)'); }
/** * @param NodeInterface $node * @return NodeInterface */ protected function getClosestDocumentNode(NodeInterface $node) { while ($node !== null && !$node->getNodeType()->isOfType('TYPO3.Neos:Document')) { $node = $node->getParent(); } return $node; }