/** * Get the root node * * @return NodeInterface */ public function getRoot() { if (!$this->root) { $this->root = $this->active->getContext()->getCurrentSiteNode(); } return $this->root; }
/** * 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()); } }
/** * @param NodeInterface $contextNode * @return QueryBuilder */ public function query(NodeInterface $contextNode) { $this->where[] = "(__parentPath LIKE '%#" . $contextNode->getPath() . "#%' OR __path LIKE '" . $contextNode->getPath() . "')"; $this->where[] = "(__workspace LIKE '%#" . $contextNode->getContext()->getWorkspace()->getName() . "#%')"; $this->where[] = "(__dimensionshash LIKE '%#" . md5(json_encode($contextNode->getContext()->getDimensions())) . "#%')"; $this->contextNode = $contextNode; return $this; }
/** * Generates a URI path segment for a given node taking it's language dimension into account * * @param NodeInterface $node Optional node to determine language dimension * @param string $text Optional text * @return string */ public function generateUriPathSegment(NodeInterface $node = null, $text = null) { if ($node) { $text = $text ?: $node->getLabel() ?: $node->getName(); $dimensions = $node->getContext()->getDimensions(); if (array_key_exists('language', $dimensions) && $dimensions['language'] !== array()) { $locale = new Locale($dimensions['language'][0]); $language = $locale->getLanguage(); } } elseif (strlen($text) === 0) { throw new Exception('Given text was empty.', 1457591815); } $text = $this->transliterationService->transliterate($text, isset($language) ? $language : null); return Transliterator::urlize($text); }
/** * Wrap the $content identified by $node with the needed markup for the backend. * * @param NodeInterface $node * @param string $property * @param string $content * @return string */ public function wrapContentProperty(NodeInterface $node, $property, $content) { /** @var $contentContext ContentContext */ $contentContext = $node->getContext(); if ($contentContext->getWorkspaceName() === 'live' || !$this->privilegeManager->isPrivilegeTargetGranted('TYPO3.Neos:Backend.GeneralAccess')) { return $content; } if (!$this->nodeAuthorizationService->isGrantedToEditNode($node)) { return $content; } $attributes = array(); $attributes['class'] = 'neos-inline-editable'; $attributes['property'] = 'typo3:' . $property; $attributes['data-neos-node-type'] = $node->getNodeType()->getName(); return $this->htmlAugmenter->addAttributes($content, $attributes, 'span'); }
/** * {@inheritdoc} * * @param FlowQuery $flowQuery the FlowQuery object * @param array $arguments the arguments for this operation * @return mixed|null if the operation is final, the return value */ public function evaluate(FlowQuery $flowQuery, array $arguments) { $imagePropertyName = $arguments[0]; if ($this->contextNode->hasProperty($imagePropertyName)) { $image = $this->contextNode->getProperty($imagePropertyName); if ($image instanceof ImageVariant) { $image = $image->getOriginalAsset(); } if ($image instanceof Image) { $identifier = $image->getIdentifier(); $nodeData = $this->metaDataRepository->findOneByAssetIdentifier($identifier, $this->contextNode->getContext()->getWorkspace()); if ($nodeData instanceof NodeData) { return $this->nodeFactory->createFromNodeData($nodeData, $this->contextNode->getContext()); } } } return null; }
/** * Matches if the currently-selected preset in the passed $dimensionName is one of $presets. * * Example: isInDimensionPreset('language', 'de') checks whether the currently-selected language * preset (in the Neos backend) is "de". * * Implementation Note: We deliberately work on the Dimension Preset Name, and not on the * dimension values itself; as the preset is user-visible and the actual dimension-values * for a preset are just implementation details. * * @param string $dimensionName * @param string|array $presets * @return boolean */ public function isInDimensionPreset($dimensionName, $presets) { if ($this->node === NULL) { return TRUE; } $dimensionValues = $this->node->getContext()->getDimensions(); if (!isset($dimensionValues[$dimensionName])) { return FALSE; } $preset = $this->contentDimensionPresetSource->findPresetByDimensionValues($dimensionName, $dimensionValues[$dimensionName]); if ($preset === NULL) { return FALSE; } $presetIdentifier = $preset['identifier']; if (!is_array($presets)) { $presets = array($presets); } return in_array($presetIdentifier, $presets); }
/** * Creates missing child nodes for the given node. * * @param NodeInterface $node * @return void */ public function createChildNodes(NodeInterface $node) { $nodeType = $node->getNodeType(); foreach ($nodeType->getAutoCreatedChildNodes() as $childNodeName => $childNodeType) { try { $node->createNode($childNodeName, $childNodeType); } catch (NodeExistsException $exception) { // If you have a node that has been marked as removed, but is needed again // the old node is recovered $childNodePath = NodePaths::addNodePathSegment($node->getPath(), $childNodeName); $contextProperties = $node->getContext()->getProperties(); $contextProperties['removedContentShown'] = true; $context = $this->contextFactory->create($contextProperties); $childNode = $context->getNode($childNodePath); if ($childNode->isRemoved()) { $childNode->setRemoved(false); } } } }
/** * Returns the rendered content of this plugin * * @return string The rendered content as a string * @throws StopActionException */ public function evaluate() { $currentContext = $this->tsRuntime->getCurrentContext(); $this->pluginViewNode = $currentContext['node']; /** @var $parentResponse Response */ $parentResponse = $this->tsRuntime->getControllerContext()->getResponse(); $pluginResponse = new Response($parentResponse); $pluginRequest = $this->buildPluginRequest(); if ($pluginRequest->getControllerObjectName() === '') { $message = 'Master View not selected'; if ($this->pluginViewNode->getProperty('plugin')) { $message = 'Plugin View not selected'; } if ($this->pluginViewNode->getProperty('view')) { $message = 'Master View or Plugin View not found'; } return $this->pluginViewNode->getContext()->getWorkspaceName() !== 'live' || $this->objectManager->getContext()->isDevelopment() ? '<p>' . $message . '</p>' : '<!-- ' . $message . '-->'; } $this->dispatcher->dispatch($pluginRequest, $pluginResponse); return $pluginResponse->getContent(); }
/** * Shows the specified node and takes visibility and access restrictions into * account. * * @param \TYPO3\TYPO3CR\Domain\Model\NodeInterface $node * @return string View output for the specified node */ public function showAction(\TYPO3\TYPO3CR\Domain\Model\NodeInterface $node) { if ($node->getContext()->getWorkspace()->getName() !== 'live') { // TODO: Introduce check if workspace is visible or accessible to the user try { $this->accessDecisionManager->decideOnResource('TYPO3_TYPO3_Backend_BackendController'); $this->nodeRepository->getContext()->setInvisibleContentShown(TRUE); $this->nodeRepository->getContext()->setRemovedContentShown(TRUE); } catch (\TYPO3\FLOW3\Security\Exception\AccessDeniedException $exception) { $this->throwStatus(403); } } if ($this->isWireframeModeEnabled($node)) { $this->forward('showWireframe', NULL, NULL, array('node' => $node->getPath())); } if (!$node->isAccessible()) { try { $this->authenticationManager->authenticate(); } catch (\Exception $exception) { } } if (!$node->isAccessible() && !$this->nodeRepository->getContext()->isInaccessibleContentShown()) { $this->throwStatus(403); } if (!$node->isVisible() && !$this->nodeRepository->getContext()->isInvisibleContentShown()) { $this->throwStatus(404); } if ($node->getContentType()->isOfType('TYPO3.Phoenix.ContentTypes:Shortcut')) { while ($node->getContentType()->isOfType('TYPO3.Phoenix.ContentTypes:Shortcut')) { $childNodes = $node->getChildNodes('TYPO3.Phoenix.ContentTypes:Page,TYPO3.Phoenix.ContentTypes:Shortcut'); $node = current($childNodes); } $this->redirect('show', NULL, NULL, array('node' => $node)); } $this->nodeRepository->getContext()->setCurrentNode($node); $this->view->assign('value', $node); $this->response->setHeader('Cache-Control', 'public, s-maxage=600', FALSE); }
/** * Set the "context node" this operation was working on. * * @param NodeInterface $node * @return void */ public function setNode(NodeInterface $node) { $this->nodeIdentifier = $node->getIdentifier(); $this->workspaceName = $node->getContext()->getWorkspaceName(); $this->dimension = $node->getContext()->getDimensions(); $context = $node->getContext(); if ($context instanceof ContentContext && $context->getCurrentSite() !== null) { $siteIdentifier = $this->persistenceManager->getIdentifierByObject($context->getCurrentSite()); } else { $siteIdentifier = null; } $this->data = Arrays::arrayMergeRecursiveOverrule($this->data, array('nodeContextPath' => $node->getContextPath(), 'nodeLabel' => $node->getLabel(), 'nodeType' => $node->getNodeType()->getName(), 'site' => $siteIdentifier)); $node = self::getClosestAggregateNode($node); if ($node !== null) { $this->documentNodeIdentifier = $node->getIdentifier(); $this->data = Arrays::arrayMergeRecursiveOverrule($this->data, array('documentNodeContextPath' => $node->getContextPath(), 'documentNodeLabel' => $node->getLabel(), 'documentNodeType' => $node->getNodeType()->getName())); } }
/** * Returns a merged TypoScript object tree in the context of the given nodes * * @param \TYPO3\TYPO3CR\Domain\Model\NodeInterface $startNode Node marking the starting point * @return array The merged object tree as of the given node * @throws \TYPO3\Neos\Domain\Exception */ public function getMergedTypoScriptObjectTree(NodeInterface $startNode) { $contentContext = $startNode->getContext(); $siteResourcesPackageKey = $contentContext->getCurrentSite()->getSiteResourcesPackageKey(); $siteRootTypoScriptPathAndFilename = sprintf($this->siteRootTypoScriptPattern, $siteResourcesPackageKey); $siteRootTypoScriptCode = $this->readExternalTypoScriptFile($siteRootTypoScriptPathAndFilename); if ($siteRootTypoScriptCode === '') { $siteRootTypoScriptPathAndFilename = sprintf($this->legacySiteRootTypoScriptPattern, $siteResourcesPackageKey); $siteRootTypoScriptCode = $this->readExternalTypoScriptFile($siteRootTypoScriptPathAndFilename); } $mergedTypoScriptCode = ''; $mergedTypoScriptCode .= $this->generateNodeTypeDefinitions(); $mergedTypoScriptCode .= $this->getTypoScriptIncludes($this->prepareAutoIncludeTypoScript()); $mergedTypoScriptCode .= $this->getTypoScriptIncludes($this->prependTypoScriptIncludes); $mergedTypoScriptCode .= $siteRootTypoScriptCode; $mergedTypoScriptCode .= $this->getTypoScriptIncludes($this->appendTypoScriptIncludes); return $this->typoScriptParser->parse($mergedTypoScriptCode, $siteRootTypoScriptPathAndFilename); }
/** * Create a recursive copy of this node below $referenceNode with $nodeName. * * $detachedCopy only has an influence if we are copying from one dimension to the other, possibly creating a new * node variant: * * - If $detachedCopy is TRUE, the whole (recursive) copy is done without connecting original and copied node, * so NOT CREATING a new node variant. * - If $detachedCopy is FALSE, and the node does not yet have a variant in the target dimension, we are CREATING * a new node variant. * * As a caller of this method, $detachedCopy should be TRUE if $this->getNodeType()->isAggregate() is TRUE, and FALSE * otherwise. * * @param NodeInterface $referenceNode * @param boolean $detachedCopy * @param string $nodeName * @return NodeInterface */ protected function createRecursiveCopy(NodeInterface $referenceNode, $nodeName, $detachedCopy) { $identifier = null; $referenceNodeDimensions = $referenceNode->getDimensions(); $referenceNodeDimensionsHash = Utility::sortDimensionValueArrayAndReturnDimensionsHash($referenceNodeDimensions); $thisDimensions = $this->getDimensions(); $thisNodeDimensionsHash = Utility::sortDimensionValueArrayAndReturnDimensionsHash($thisDimensions); if ($detachedCopy === false && $referenceNodeDimensionsHash !== $thisNodeDimensionsHash && $referenceNode->getContext()->getNodeByIdentifier($this->getIdentifier()) === null) { // 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 // node identifier, effectively creating a new node variant. $identifier = $this->getIdentifier(); } $copiedNode = $referenceNode->createSingleNode($nodeName, null, $identifier); $copiedNode->similarize($this, true); /** @var $childNode Node */ foreach ($this->getChildNodes() as $childNode) { // Prevent recursive copy when copying into itself if ($childNode->getIdentifier() !== $copiedNode->getIdentifier()) { $childNode->copyIntoInternal($copiedNode, $childNode->getName(), $detachedCopy); } } return $copiedNode; }
/** * Sets the starting point for this query. Search result should only contain nodes that * match the context of the given node and have it as parent node in their rootline. * * @param NodeInterface $contextNode * @return QueryBuilderInterface * @api */ public function query(NodeInterface $contextNode) { // on indexing, the __parentPath is tokenized to contain ALL parent path parts, // e.g. /foo, /foo/bar/, /foo/bar/baz; to speed up matching.. That's why we use a simple "term" filter here. // http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-term-filter.html $this->queryFilter('term', ['__parentPath' => $contextNode->getPath()]); // // http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-terms-filter.html $this->queryFilter('terms', ['__workspace' => array_unique(['live', $contextNode->getContext()->getWorkspace()->getName()])]); // match exact dimension values for each dimension, this works because the indexing flattens the node variants for all dimension preset combinations $dimensionCombinations = $contextNode->getContext()->getDimensions(); if (is_array($dimensionCombinations)) { $this->queryFilter('term', ['__dimensionCombinationHash' => md5(json_encode($dimensionCombinations))]); } $this->contextNode = $contextNode; return $this; }
/** * @param NodeInterface $node * @param boolean $renderCurrentDocumentMetadata * @return boolean */ protected function needsMetadata(NodeInterface $node, $renderCurrentDocumentMetadata) { /** @var $contentContext ContentContext */ $contentContext = $node->getContext(); return $contentContext->isInBackend() === true && ($renderCurrentDocumentMetadata === true || $this->nodeAuthorizationService->isGrantedToEditNode($node) === true); }
/** * 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)'); }
/** * Retrieves the given node's corresponding node in the base workspace (that is, which would be overwritten if the * given node would be published) * * @param NodeInterface $modifiedNode * @return NodeInterface */ protected function getOriginalNode(NodeInterface $modifiedNode) { $baseWorkspaceName = $modifiedNode->getWorkspace()->getBaseWorkspace()->getName(); $contextProperties = $modifiedNode->getContext()->getProperties(); $contextProperties['workspaceName'] = $baseWorkspaceName; $contentContext = $this->contextFactory->create($contextProperties); return $contentContext->getNodeByIdentifier($modifiedNode->getIdentifier()); }
/** * Fetch all master plugins that are available in the current * workspace. * * @param NodeInterface $node * @return string JSON encoded array of node path => label */ public function masterPluginsAction(NodeInterface $node) { $this->response->setHeader('Content-Type', 'application/json'); $pluginNodes = $this->pluginService->getPluginNodesWithViewDefinitions($node->getContext()); $masterPlugins = array(); if (is_array($pluginNodes)) { /** @var $pluginNode NodeInterface */ foreach ($pluginNodes as $pluginNode) { if ($pluginNode->isRemoved()) { continue; } $q = new FlowQuery(array($pluginNode)); $page = $q->closest('[instanceof TYPO3.Neos:Document]')->get(0); if ($page === NULL) { continue; } $masterPlugins[$pluginNode->getPath()] = sprintf('"%s" on page "%s"', $pluginNode->getNodeType()->getLabel(), $page->getLabel()); } } return json_encode((object) $masterPlugins); }
/** * Resolves the request path, also known as route path, identifying the given node. * * A path is built, based on the uri path segment properties of the parents of and the given node itself. * If content dimensions are configured, the first path segment will the identifiers of the dimension * values according to the current context. * * @param NodeInterface $siteNode The site node, used as a starting point while traversing the tree * @param NodeInterface $node The node where the generated path should lead to * @return string The relative route path, possibly prefixed with a segment for identifying the current content dimension values */ protected function resolveRoutePathForNode(NodeInterface $siteNode, NodeInterface $node) { $workspaceName = $node->getContext()->getWorkspaceName(); $nodeContextPath = $node->getContextPath(); $nodeContextPathSuffix = $workspaceName !== 'live' ? substr($nodeContextPath, strpos($nodeContextPath, '@')) : ''; $currentNodeIsSiteNode = $siteNode === $node; $dimensionsUriSegment = $this->getUriSegmentForDimensions($node->getContext()->getDimensions(), $currentNodeIsSiteNode); $requestPath = $this->getRequestPathByNode($siteNode, $node); return trim($dimensionsUriSegment . $requestPath, '/') . $nodeContextPathSuffix; }
/** * Creates a mock sub node of the given parent node * * @param NodeInterface $mockParentNode * @param string $nodeName * @return NodeInterface */ protected function buildSubNode($mockParentNode, $nodeName, $nodeTypeName = 'TYPO3.Neos:Document') { $mockNode = $this->buildNode($mockParentNode->getContext(), $nodeName, $nodeTypeName); $mockNode->mockParentNode = $mockParentNode; $mockParentNode->mockChildNodes[$nodeName] = $mockNode; $mockNode->expects($this->any())->method('getChildNodes')->will($this->returnCallback(function ($nodeTypeFilter) use($mockNode) { return $mockNode->mockChildNodes; })); return $mockNode; }
/** * 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)->setCreateAbsoluteUri($absolute)->setArguments($arguments)->setAddQueryString($addQueryString)->setArgumentsToBeExcludedFromQueryString($argumentsToBeExcludedFromQueryString)->setFormat($format ?: $request->getFormat())->uriFor('show', array('node' => $resolvedNode), 'Frontend\\Node', 'TYPO3.Neos'); return $uri; }
/** * returns a specific view node of an master plugin * or NULL if it does not exist * * @param NodeInterface $node * @param string $viewName * @return NodeInterface */ public function getPluginViewNodeByMasterPlugin(NodeInterface $node, $viewName) { /** @var $context ContentContext */ $context = $node->getContext(); foreach ($this->getNodes('TYPO3.Neos:PluginView', $context) as $pluginViewNode) { /** @var \TYPO3\TYPO3CR\Domain\Model\NodeInterface $pluginViewNode */ if ($pluginViewNode->isRemoved()) { continue; } if ($pluginViewNode->getProperty('plugin') === $node->getPath() && $pluginViewNode->getProperty('view') === $viewName) { return $pluginViewNode; } } return null; }
/** * Adds node properties to the given $attributes collection and returns the extended array * * @param NodeInterface $node * @param array $attributes * @return array the merged attributes */ public function addNodePropertyAttributes(NodeInterface $node, array $attributes) { 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'; $dasherizedPropertyName = $this->dasherize($propertyName); $attributes['data-node-' . $dasherizedPropertyName] = $this->getNodeProperty($node, $propertyName, $dataType); if ($dataType !== 'string') { $prefixedDataType = $dataType === 'jsonEncoded' ? 'typo3:jsonEncoded' : 'xsd:' . $dataType; $attributes['data-nodedatatype-' . $dasherizedPropertyName] = $prefixedDataType; } } return $attributes; }
/** * 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('user-' . $currentAccount->getAccountIdentifier()); /** @var Workspace $personalWorkspace */ if ($this->publishingService->getUnpublishedNodesCount($personalWorkspace) > 0) { $message = $this->translator->translateById('workspaces.cantEditBecauseWorkspaceContainsChanges', [], null, null, 'Modules', 'TYPO3.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', 'TYPO3.Neos', ['node' => $context->getNode($targetNode->getPath())]); }
/** * index this node, and add it to the current bulk request. * * @param NodeInterface $node * @param string $targetWorkspaceName In case this is triggered during publishing, a workspace name will be passed in * @return void * @throws \TYPO3\TYPO3CR\Search\Exception\IndexingException */ public function indexNode(NodeInterface $node, $targetWorkspaceName = NULL) { $contextPath = $node->getContextPath(); if ($targetWorkspaceName !== NULL) { $contextPath = str_replace($node->getContext()->getWorkspace()->getName(), $targetWorkspaceName, $contextPath); } $contextPathHash = sha1($contextPath); $nodeType = $node->getNodeType(); $mappingType = $this->getIndex()->findType(NodeTypeMappingBuilder::convertNodeTypeNameToMappingName($nodeType)); // Remove document with the same contextPathHash but different NodeType, required after NodeType change $this->getIndex()->request('DELETE', '/_query', array(), json_encode(['query' => ['bool' => ['must' => ['ids' => ['values' => [$contextPathHash]]], 'must_not' => ['term' => ['_type' => str_replace('.', '/', $node->getNodeType()->getName())]]]]])); if ($node->isRemoved()) { // TODO: handle deletion from the fulltext index as well $mappingType->deleteDocumentById($contextPathHash); $this->logger->log(sprintf('NodeIndexer: Removed node %s from index (node flagged as removed). ID: %s', $contextPath, $contextPathHash), LOG_DEBUG, NULL, 'ElasticSearch (CR)'); return; } $logger = $this->logger; $fulltextIndexOfNode = array(); $nodePropertiesToBeStoredInIndex = $this->extractPropertiesAndFulltext($node, $fulltextIndexOfNode, function ($propertyName) use($logger, $contextPathHash) { $logger->log(sprintf('NodeIndexer (%s) - Property "%s" not indexed because no configuration found.', $contextPathHash, $propertyName), LOG_DEBUG, NULL, 'ElasticSearch (CR)'); }); $document = new ElasticSearchDocument($mappingType, $nodePropertiesToBeStoredInIndex, $contextPathHash); $documentData = $document->getData(); if ($targetWorkspaceName !== NULL) { $documentData['__workspace'] = $targetWorkspaceName; } $dimensionCombinations = $node->getContext()->getDimensions(); if (is_array($dimensionCombinations)) { $documentData['__dimensionCombinations'] = $dimensionCombinations; } if ($this->isFulltextEnabled($node)) { if ($this->isFulltextRoot($node)) { // for fulltext root documents, we need to preserve the "__fulltext" field. That's why we use the // "update" API instead of the "index" API, with a custom script internally; as we // shall not delete the "__fulltext" part of the document if it has any. $this->currentBulkRequest[] = array(array('update' => array('_type' => $document->getType()->getName(), '_id' => $document->getId())), array('script' => ' fulltext = (ctx._source.containsKey("__fulltext") ? ctx._source.__fulltext : new LinkedHashMap()); fulltextParts = (ctx._source.containsKey("__fulltextParts") ? ctx._source.__fulltextParts : new LinkedHashMap()); ctx._source = newData; ctx._source.__fulltext = fulltext; ctx._source.__fulltextParts = fulltextParts ', 'params' => array('newData' => $documentData), 'upsert' => $documentData, 'lang' => 'groovy')); } else { // non-fulltext-root documents can be indexed as-they-are $this->currentBulkRequest[] = array(array('index' => array('_type' => $document->getType()->getName(), '_id' => $document->getId())), $documentData); } $this->updateFulltext($node, $fulltextIndexOfNode, $targetWorkspaceName); } $this->logger->log(sprintf('NodeIndexer: Added / updated node %s. ID: %s', $contextPath, $contextPathHash), LOG_DEBUG, NULL, 'ElasticSearch (CR)'); }
/** * Fetch all master plugins that are available in the current * workspace. * * @param NodeInterface $node * @return string JSON encoded array of node path => label */ public function masterPluginsAction(NodeInterface $node) { $this->response->setHeader('Content-Type', 'application/json'); $pluginNodes = $this->pluginService->getPluginNodesWithViewDefinitions($node->getContext()); $masterPlugins = array(); if (is_array($pluginNodes)) { /** @var $pluginNode NodeInterface */ foreach ($pluginNodes as $pluginNode) { if ($pluginNode->isRemoved()) { continue; } $q = new FlowQuery(array($pluginNode)); $page = $q->closest('[instanceof TYPO3.Neos:Document]')->get(0); if ($page === null) { continue; } $translationHelper = new TranslationHelper(); $masterPlugins[$pluginNode->getPath()] = $translationHelper->translate('masterPlugins.nodeTypeOnPageLabel', null, ['nodeTypeName' => $translationHelper->translate($pluginNode->getNodeType()->getLabel()), 'pageLabel' => $page->getLabel()], 'Main', 'TYPO3.Neos'); } } return json_encode((object) $masterPlugins); }
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; }
/** * Adopts a node from a (possibly) different context to this context * * Checks if a node variant matching the exact dimensions already exists for this context and * return it if found. Otherwise a new node variant for this context is created. * * In case the node already exists in the context but does not match the target dimensions a * new, more specific node is created and returned. * * @param NodeInterface $node The node with a different context. If the context of the given node is the same as this context the operation will have no effect. * @param boolean $recursive If TRUE also adopt all descendant nodes which are non-aggregate * @return NodeInterface A new or existing node that matches this context */ public function adoptNode(NodeInterface $node, $recursive = false) { if ($node->getContext() === $this && $node->dimensionsAreMatchingTargetDimensionValues()) { return $node; } $this->emitBeforeAdoptNode($node, $this, $recursive); $existingNode = $this->getNodeByIdentifier($node->getIdentifier()); if ($existingNode !== null) { if ($existingNode->dimensionsAreMatchingTargetDimensionValues()) { $adoptedNode = $existingNode; } else { $adoptedNode = $existingNode->createVariantForContext($this); } } else { $adoptedNode = $node->createVariantForContext($this); } $this->firstLevelNodeCache->setByIdentifier($adoptedNode->getIdentifier(), $adoptedNode); if ($recursive) { $childNodes = $node->getChildNodes(); /** @var NodeInterface $childNode */ foreach ($childNodes as $childNode) { if (!$childNode->getNodeType()->isAggregate()) { $this->adoptNode($childNode, true); } } } $this->emitAfterAdoptNode($node, $this, $recursive); return $adoptedNode; }
/** * Node Level relative to site root node. * 0 = Site root node * * @param NodeInterface $node * @return integer */ protected function getNodeLevelInSite(NodeInterface $node) { $siteNode = $this->currentNode->getContext()->getCurrentSiteNode(); return $node->getDepth() - $siteNode->getDepth(); }
/** * 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 \TYPO3\TYPO3CR\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); }