/** * Add the current node and all parent identifiers to be used for cache entry tagging * * @Flow\Before("method(TYPO3\Flow\Mvc\Routing\RouterCachingService->extractUuids())") * @param \TYPO3\Flow\Aop\JoinPointInterface $joinPoint The current join point * @return void */ public function addCurrentNodeIdentifier(JoinPointInterface $joinPoint) { $values = $joinPoint->getMethodArgument('values'); if (!isset($values['node']) || strpos($values['node'], '@') === false) { return; } // Build context explicitly without authorization checks because the security context isn't available yet // anyway and any Entity Privilege targeted on Workspace would fail at this point: $this->securityContext->withoutAuthorizationChecks(function () use($joinPoint, $values) { $contextPathPieces = NodePaths::explodeContextPath($values['node']); $context = $this->contextFactory->create(['workspaceName' => $contextPathPieces['workspaceName'], 'dimensions' => $contextPathPieces['dimensions'], 'invisibleContentShown' => true]); $node = $context->getNode($contextPathPieces['nodePath']); if (!$node instanceof NodeInterface) { return; } $values['node-identifier'] = $node->getIdentifier(); $node = $node->getParent(); $values['node-parent-identifier'] = array(); while ($node !== null) { $values['node-parent-identifier'][] = $node->getIdentifier(); $node = $node->getParent(); } $joinPoint->setMethodArgument('values', $values); }); }
/** * Normalizes the given node path to a reference path and returns an absolute path. * * @param string $path The non-normalized path * @param string $referencePath a reference path in case the given path is relative. * @param string $siteNodePath Reference path to a site node. Relative paths starting with "~" will be based on the siteNodePath. * @return string The normalized absolute path * @throws \InvalidArgumentException if the node path was invalid. */ public function normalizePath($path, $referencePath = NULL, $siteNodePath = NULL) { if (strpos($path, '~') === 0) { $path = NodePaths::addNodePathSegment($siteNodePath, substr($path, 1)); } return parent::normalizePath($path, $referencePath); }
/** * Generates a Context that exactly fits the given NodeData Workspace, Dimensions & Site. * * @param NodeData $nodeData * @return ContentContext */ protected function createContextMatchingNodeData(NodeData $nodeData) { $nodePath = NodePaths::getRelativePathBetween(SiteService::SITES_ROOT_PATH, $nodeData->getPath()); list($siteNodeName) = explode('/', $nodePath); $site = $this->siteRepository->findOneByNodeName($siteNodeName); $contextProperties = ['workspaceName' => $nodeData->getWorkspace()->getName(), 'invisibleContentShown' => true, 'inaccessibleContentShown' => true, 'removedContentShown' => true, 'dimensions' => $nodeData->getDimensionValues(), 'currentSite' => $site]; if ($domain = $site->getFirstActiveDomain()) { $contextProperties['currentDomain'] = $domain; } return $this->_contextFactory->create($contextProperties); }
/** * Change the property on the given node. * * @param NodeData $node * @return NodeData */ public function execute(NodeData $node) { $node = $this->nodeFactory->createFromNodeData($node, $this->contextFactory->create(array('workspaceName' => $node->getWorkspace()->getName(), 'invisibleContentShown' => true, 'inaccessibleContentShown' => true))); if (!$node) { return; } $column0 = $node->getPrimaryChildNode(); if (!$column0) { return; } foreach ($column0->getChildNodes() as $childNode) { $node = $childNode->copyAfter($node, NodePaths::generateRandomNodeName()); } return $node; }
/** * Remove given site all nodes for that site and all domains associated. * * @param Site $site * @return void */ public function pruneSite(Site $site) { $siteNodePath = NodePaths::addNodePathSegment(static::SITES_ROOT_PATH, $site->getNodeName()); $this->nodeDataRepository->removeAllInPath($siteNodePath); $siteNodes = $this->nodeDataRepository->findByPath($siteNodePath); foreach ($siteNodes as $siteNode) { $this->nodeDataRepository->remove($siteNode); } $domainsForSite = $this->domainRepository->findBySite($site); foreach ($domainsForSite as $domain) { $this->domainRepository->remove($domain); } $this->siteRepository->remove($site); $this->emitSitePruned($site); }
/** * Converts a given context path to a node object * * @param string $contextPath * @return NodeInterface */ public function getNodeFromContextPath($contextPath, Site $site = null, Domain $domain = null) { $nodePathAndContext = NodePaths::explodeContextPath($contextPath); $nodePath = $nodePathAndContext['nodePath']; $workspaceName = $nodePathAndContext['workspaceName']; $dimensions = $nodePathAndContext['dimensions']; $contextProperties = $this->prepareContextProperties($workspaceName, $dimensions); if ($site === null) { list(, , $siteNodeName) = explode('/', $nodePath); $site = $this->siteRepository->findOneByNodeName($siteNodeName); } if ($domain === null) { $domain = $this->domainRepository->findOneBySite($site); } $contextProperties['currentSite'] = $site; $contextProperties['currentDomain'] = $domain; $context = $this->contextFactory->create($contextProperties); $workspace = $context->getWorkspace(false); if (!$workspace) { return new \TYPO3\Flow\Error\Error(sprintf('Could not convert the given source to Node object because the workspace "%s" as specified in the context node path does not exist.', $workspaceName), 1451392329); } return $context->getNode($nodePath); }
/** * Update a site * * @param Site $site A site to update * @param string $newSiteNodeName A new site node name * @return void * @Flow\Validate(argumentName="$site", type="UniqueEntity") * @Flow\Validate(argumentName="$newSiteNodeName", type="NotEmpty") * @Flow\Validate(argumentName="$newSiteNodeName", type="StringLength", options={ "minimum"=1, "maximum"=250 }) * @Flow\Validate(argumentName="$newSiteNodeName", type="TYPO3.Neos:NodeName") */ public function updateSiteAction(Site $site, $newSiteNodeName) { if ($site->getNodeName() !== $newSiteNodeName) { $oldSiteNodePath = NodePaths::addNodePathSegment(SiteService::SITES_ROOT_PATH, $site->getNodeName()); $newSiteNodePath = NodePaths::addNodePathSegment(SiteService::SITES_ROOT_PATH, $newSiteNodeName); /** @var $workspace Workspace */ foreach ($this->workspaceRepository->findAll() as $workspace) { $siteNode = $this->nodeDataRepository->findOneByPath($oldSiteNodePath, $workspace); if ($siteNode !== null) { $siteNode->setPath($newSiteNodePath); } } $site->setNodeName($newSiteNodeName); $this->nodeDataRepository->persistEntities(); } $this->siteRepository->update($site); $this->addFlashMessage('The site "%s" has been updated.', 'Update', null, array(htmlspecialchars($site->getName())), 1412371798); $this->unsetLastVisitedNodeAndRedirect('index'); }
/** * Generate missing URI path segments * * This generates URI path segment properties for all document nodes which don't have * a path segment set yet. * * @param string $workspaceName * @param boolean $dryRun * @return void */ public function generateUriPathSegments($workspaceName, $dryRun) { $baseContext = $this->createContext($workspaceName, []); $baseContextSitesNode = $baseContext->getNode(SiteService::SITES_ROOT_PATH); if (!$baseContextSitesNode) { $this->output->outputLine('<error>Could not find "' . SiteService::SITES_ROOT_PATH . '" root node</error>'); return; } $baseContextSiteNodes = $baseContextSitesNode->getChildNodes(); if ($baseContextSiteNodes === []) { $this->output->outputLine('<error>Could not find any site nodes in "' . SiteService::SITES_ROOT_PATH . '" root node</error>'); return; } foreach ($this->dimensionCombinator->getAllAllowedCombinations() as $dimensionCombination) { $flowQuery = new FlowQuery($baseContextSiteNodes); $siteNodes = $flowQuery->context(['dimensions' => $dimensionCombination, 'targetDimensions' => []])->get(); if (count($siteNodes) > 0) { $this->output->outputLine('Checking for nodes with missing URI path segment in dimension "%s"', array(trim(NodePaths::generateContextPath('', '', $dimensionCombination), '@;'))); foreach ($siteNodes as $siteNode) { $this->generateUriPathSegmentsForNode($siteNode, $dryRun); } } } $this->persistenceManager->persistAll(); }
/** * Moves this node into the given node * * @param NodeInterface $referenceNode * @return void * @throws NodeExistsException * @throws NodeException * @throws NodeConstraintException * @api */ public function moveInto(NodeInterface $referenceNode) { if ($referenceNode === $this || $referenceNode === $this->getParent()) { return; } if ($this->getPath() === '/') { throw new NodeException('The root node cannot be moved.', 1346769001); } if ($referenceNode !== $this->getParent() && $referenceNode->getNode($this->getName()) !== null) { throw new NodeExistsException('Node with path "' . $this->getName() . '" already exists.', 1292503470); } if (!$referenceNode->willChildNodeBeAutoCreated($this->getName()) && !$referenceNode->isNodeTypeAllowedAsChildNode($this->getNodeType())) { throw new NodeConstraintException('Cannot move ' . $this->__toString() . ' into ' . $referenceNode->__toString(), 1404648124); } $this->emitBeforeNodeMove($this, $referenceNode, NodeDataRepository::POSITION_LAST); $this->setPath(NodePaths::addNodePathSegment($referenceNode->getPath(), $this->getName())); $this->nodeDataRepository->persistEntities(); $this->nodeDataRepository->setNewIndex($this->nodeData, NodeDataRepository::POSITION_LAST); $this->context->getFirstLevelNodeCache()->flush(); $this->emitAfterNodeMove($this, $referenceNode, NodeDataRepository::POSITION_LAST); $this->emitNodeUpdated($this); }
/** * Find all node data in a path matching the given workspace hierarchy * * Internal method, used by Node::setPath * * @param string $path * @param Workspace $workspace * @param boolean $includeRemovedNodes Should removed nodes be included in the result (defaults to FALSE) * @param boolean $recursive * @return array<NodeData> Node data reduced by workspace but with all existing content dimension variants, includes removed nodes */ public function findByPathWithoutReduce($path, Workspace $workspace, $includeRemovedNodes = false, $recursive = false) { $path = strtolower($path); $workspaces = array(); while ($workspace !== null) { $workspaces[] = $workspace; $workspace = $workspace->getBaseWorkspace(); } $queryBuilder = $this->createQueryBuilder($workspaces); $this->addPathConstraintToQueryBuilder($queryBuilder, $path, $recursive); $query = $queryBuilder->getQuery(); $foundNodes = $query->getResult(); // Consider materialized, but not yet persisted nodes foreach ($this->addedNodes as $addedNode) { if (($addedNode->getPath() === $path || $recursive && NodePaths::isSubPathOf($path, $addedNode->getPath())) && in_array($addedNode->getWorkspace(), $workspaces)) { $foundNodes[] = $addedNode; } } $foundNodes = $this->reduceNodeVariantsByWorkspaces($foundNodes, $workspaces); if ($includeRemovedNodes === false) { $foundNodes = $this->filterRemovedNodes($foundNodes, false); } return $foundNodes; }
/** * If the node is not found, we *first* want to figure out whether the node exists in other dimensions or is really non-existent * * @param $identifier * @param ContentContext $context * @return void */ protected function addExistingNodeVariantInformationToResponse($identifier, ContentContext $context) { $nodeVariants = $context->getNodeVariantsByIdentifier($identifier); if (count($nodeVariants) > 0) { $this->response->setHeader('X-Neos-Node-Exists-In-Other-Dimensions', true); // If the node exists in another dimension, we want to know how many nodes in the rootline are also missing for the target // dimension. This is needed in the UI to tell the user if nodes will be materialized recursively upwards in the rootline. // To find the node path for the given identifier, we just use the first result. This is a safe assumption at least for // "Document" nodes (aggregate=TRUE), because they are always moved in-sync. $node = reset($nodeVariants); /** @var NodeInterface $node */ if ($node->getNodeType()->isAggregate()) { $pathSegmentsToSites = NodePaths::getPathDepth(SiteService::SITES_ROOT_PATH); $pathSegmentsToNodeVariant = NodePaths::getPathDepth($node->getPath()); // Segments between the sites root "/sites" and the node variant (minimum 1) $pathSegments = $pathSegmentsToNodeVariant - $pathSegmentsToSites; // Nodes between (and including) the site root node and the node variant (minimum 1) $siteNodePath = NodePaths::addNodePathSegment(SiteService::SITES_ROOT_PATH, $context->getCurrentSite()->getNodeName()); $nodes = $context->getNodesOnPath($siteNodePath, $node->getPath()); $missingNodesOnRootline = $pathSegments - count($nodes); if ($missingNodesOnRootline > 0) { $this->response->setHeader('X-Neos-Nodes-Missing-On-Rootline', $missingNodesOnRootline); } } } }
/** * Creates, adds and returns a child node of this node, without setting default * properties or creating subnodes. * * @param string $name Name of the new node * @param \TYPO3\TYPO3CR\Domain\Model\NodeType $nodeType Node type of the new node (optional) * @param string $identifier The identifier of the node, unique within the workspace, optional(!) * @param \TYPO3\TYPO3CR\Domain\Model\Workspace $workspace * @param array $dimensions An array of dimension name to dimension values * @throws NodeExistsException if a node with this path already exists. * @throws \InvalidArgumentException if the node name is not accepted. * @return \TYPO3\TYPO3CR\Domain\Model\NodeData */ public function createSingleNodeData($name, NodeType $nodeType = null, $identifier = null, Workspace $workspace = null, array $dimensions = null) { if (!is_string($name) || preg_match(NodeInterface::MATCH_PATTERN_NAME, $name) !== 1) { throw new \InvalidArgumentException('Invalid node name "' . $name . '" (a node name must only contain lowercase characters, numbers and the "-" sign).', 1292428697); } $nodeWorkspace = $workspace ?: $this->workspace; $newPath = NodePaths::addNodePathSegment($this->path, $name); if ($this->nodeDataRepository->findOneByPath($newPath, $nodeWorkspace, $dimensions, null) !== null) { throw new NodeExistsException(sprintf('Node with path "' . $newPath . '" already exists in workspace %s and given dimensions %s.', $nodeWorkspace->getName(), var_export($dimensions, true)), 1292503471); } $newNodeData = new NodeData($newPath, $nodeWorkspace, $identifier, $dimensions); if ($nodeType !== null) { $newNodeData->setNodeType($nodeType); } $this->nodeDataRepository->setNewIndex($newNodeData, NodeDataRepository::POSITION_LAST); return $newNodeData; }
/** * @param string $path an absolute or relative node path which possibly contains context information, for example "/sites/somesite/the/node/path@some-workspace" * @return string the same path without context information */ protected function removeContextFromPath($path) { if ($path === '' || NodePaths::isContextPath($path) === FALSE) { return $path; } try { $nodePathAndContext = NodePaths::explodeContextPath($path); return $nodePathAndContext['nodePath']; } catch (\InvalidArgumentException $exception) { } return NULL; }
/** * Converts the specified $source into a Node. * * If $source is a UUID it is expected to refer to the identifier of a NodeData record of the "live" workspace * * Otherwise $source has to be a valid node path: * * The node path must be an absolute context node path and can be specified as a string or as an array item with the * key "__contextNodePath". The latter case is for updating existing nodes. * * This conversion method does not support / allow creation of new nodes because new nodes should be created through * the createNode() method of an existing reference node. * * Also note that the context's "current node" is not affected by this object converter, you will need to set it to * whatever node your "current" node is, if any. * * All elements in the source array which start with two underscores (like __contextNodePath) are specially treated * by this converter. * * All elements in the source array which start with a *single underscore (like _hidden) are *directly* set on the Node * object. * * All other elements, not being prefixed with underscore, are properties of the node. * * @param string|array $source Either a string or array containing the absolute context node path which identifies the node. For example "/sites/mysitecom/homepage/about@user-admin" * @param string $targetType not used * @param array $subProperties not used * @param PropertyMappingConfigurationInterface $configuration * @return mixed An object or \TYPO3\Flow\Error\Error if the input format is not supported or could not be converted for other reasons * @throws NodeException */ public function convertFrom($source, $targetType = null, array $subProperties = array(), PropertyMappingConfigurationInterface $configuration = null) { if (is_string($source)) { $source = array('__contextNodePath' => $source); } if (!is_array($source) || !isset($source['__contextNodePath'])) { return new Error('Could not convert ' . gettype($source) . ' to Node object, a valid absolute context node path as a string or array is expected.', 1302879936); } try { $nodePathAndContext = NodePaths::explodeContextPath($source['__contextNodePath']); $nodePath = $nodePathAndContext['nodePath']; $workspaceName = $nodePathAndContext['workspaceName']; $dimensions = $nodePathAndContext['dimensions']; } catch (\InvalidArgumentException $exception) { return new Error('Could not convert array to Node object because the node path was invalid.', 1285162903); } $context = $this->contextFactory->create($this->prepareContextProperties($workspaceName, $configuration, $dimensions)); $workspace = $context->getWorkspace(false); if (!$workspace) { return new Error(sprintf('Could not convert the given source to Node object because the workspace "%s" as specified in the context node path does not exist.', $workspaceName), 1383577859); } $node = $context->getNode($nodePath); if (!$node) { return new Error(sprintf('Could not convert array to Node object because the node "%s" does not exist.', $nodePath), 1370502328); } if (isset($source['_nodeType']) && $source['_nodeType'] !== $node->getNodeType()->getName()) { if ($context->getWorkspace()->getName() === 'live') { throw new NodeException('Could not convert the node type in live workspace', 1429989736); } $oldNodeType = $node->getNodeType(); $targetNodeType = $this->nodeTypeManager->getNodeType($source['_nodeType']); $node->setNodeType($targetNodeType); $this->nodeService->setDefaultValues($node); $this->nodeService->cleanUpAutoCreatedChildNodes($node, $oldNodeType); $this->nodeService->createChildNodes($node); } unset($source['_nodeType']); $this->setNodeProperties($node, $node->getNodeType(), $source, $context, $configuration); return $node; }
/** * Imports one or multiple sites from the XML file at $pathAndFilename * * @param string $pathAndFilename * @return Site The imported site * @throws UnknownPackageException|InvalidPackageStateException|NeosException */ public function importFromFile($pathAndFilename) { /** @var Site $importedSite */ $site = null; $xmlReader = new \XMLReader(); $xmlReader->open($pathAndFilename, null, LIBXML_PARSEHUGE); if ($this->workspaceRepository->findOneByName('live') === null) { $this->workspaceRepository->add(new Workspace('live')); $this->persistenceManager->persistAll(); } while ($xmlReader->read()) { if ($xmlReader->nodeType != \XMLReader::ELEMENT || $xmlReader->name !== 'site') { continue; } $isLegacyFormat = $xmlReader->getAttribute('nodeName') !== null && $xmlReader->getAttribute('state') === null && $xmlReader->getAttribute('siteResourcesPackageKey') === null; if ($isLegacyFormat) { $site = $this->legacySiteImportService->importSitesFromFile($pathAndFilename); $this->emitSiteImported($site); return $site; } $site = $this->getSiteByNodeName($xmlReader->getAttribute('siteNodeName')); $site->setName($xmlReader->getAttribute('name')); $site->setState((int) $xmlReader->getAttribute('state')); $siteResourcesPackageKey = $xmlReader->getAttribute('siteResourcesPackageKey'); if (!$this->packageManager->isPackageAvailable($siteResourcesPackageKey)) { throw new UnknownPackageException(sprintf('Package "%s" specified in the XML as site resources package does not exist.', $siteResourcesPackageKey), 1303891443); } if (!$this->packageManager->isPackageActive($siteResourcesPackageKey)) { throw new InvalidPackageStateException(sprintf('Package "%s" specified in the XML as site resources package is not active.', $siteResourcesPackageKey), 1303898135); } $site->setSiteResourcesPackageKey($siteResourcesPackageKey); $rootNode = $this->contextFactory->create()->getRootNode(); // We fetch the workspace to be sure it's known to the persistence manager and persist all // so the workspace and site node are persisted before we import any nodes to it. $rootNode->getContext()->getWorkspace(); $this->persistenceManager->persistAll(); $sitesNode = $rootNode->getNode(SiteService::SITES_ROOT_PATH); if ($sitesNode === null) { $sitesNode = $rootNode->createNode(NodePaths::getNodeNameFromPath(SiteService::SITES_ROOT_PATH)); } $this->nodeImportService->import($xmlReader, $sitesNode->getPath(), dirname($pathAndFilename) . '/Resources'); } if ($site === null) { throw new NeosException(sprintf('The XML file did not contain a valid site node.'), 1418999522); } $this->emitSiteImported($site); return $site; }
/** * Generate missing URI path segments * * This generates URI path segment properties for all document nodes which don't have * a path segment set yet. * * @param string $workspaceName * @param boolean $dryRun * @return void */ public function generateUriPathSegments($workspaceName, $dryRun) { $baseContext = $this->createContext($workspaceName, []); $baseContextSiteNodes = $baseContext->getNode('/sites')->getChildNodes(); if ($baseContextSiteNodes === []) { return; } foreach ($this->dimensionCombinator->getAllAllowedCombinations() as $dimensionCombination) { $flowQuery = new FlowQuery($baseContextSiteNodes); $siteNodes = $flowQuery->context(['dimensions' => $dimensionCombination, 'targetDimensions' => []])->get(); if (count($siteNodes) > 0) { $this->output->outputLine('Searching for nodes with missing URI path segment in dimension "%s"', array(trim(NodePaths::generateContextPath('', '', $dimensionCombination), '@;'))); foreach ($siteNodes as $siteNode) { $this->generateUriPathSegmentsForNode($siteNode, $dryRun); } } } }
/** * Generate possible node name. When an idealNodeName is given then this is put into a valid format for a node name, * otherwise a random node name in the form "node-alphanumeric" is generated. * * @param string $idealNodeName * @return string */ protected function generatePossibleNodeName($idealNodeName = null) { if ($idealNodeName !== null) { $possibleNodeName = \TYPO3\TYPO3CR\Utility::renderValidNodeName($idealNodeName); } else { $possibleNodeName = NodePaths::generateRandomNodeName(); } return $possibleNodeName; }
/** * Imports one or multiple sites from the XML file at $pathAndFilename * * @param string $pathAndFilename * @return Site The imported site or NULL if not successful * @throws UnknownPackageException * @throws InvalidPackageStateException */ public function importSitesFromFile($pathAndFilename) { $contentContext = $this->createContext(); $sitesXmlString = $this->loadSitesXml($pathAndFilename); if (defined('LIBXML_PARSEHUGE')) { $options = LIBXML_PARSEHUGE; } else { $options = 0; } $site = null; $sitesXml = new \SimpleXMLElement($sitesXmlString, $options); foreach ($sitesXml->site as $siteXml) { $site = $this->getSiteByNodeName((string) $siteXml['nodeName']); if ((string) $siteXml->properties->name !== '') { $site->setName((string) $siteXml->properties->name); } $site->setState((int) $siteXml->properties->state); $siteResourcesPackageKey = (string) $siteXml->properties->siteResourcesPackageKey; if (!$this->packageManager->isPackageAvailable($siteResourcesPackageKey)) { throw new UnknownPackageException(sprintf('Package "%s" specified in the XML as site resources package does not exist.', $siteResourcesPackageKey), 1303891443); } if (!$this->packageManager->isPackageActive($siteResourcesPackageKey)) { throw new InvalidPackageStateException(sprintf('Package "%s" specified in the XML as site resources package is not active.', $siteResourcesPackageKey), 1303898135); } $site->setSiteResourcesPackageKey($siteResourcesPackageKey); $rootNode = $contentContext->getRootNode(); $sitesNode = $rootNode->getNode(SiteService::SITES_ROOT_PATH); if ($sitesNode === null) { $sitesNode = $rootNode->createSingleNode(NodePaths::getNodeNameFromPath(SiteService::SITES_ROOT_PATH)); } // We fetch the workspace to be sure it's known to the persistence manager and persist all // so the workspace and site node are persisted before we import any nodes to it. $rootNode->getContext()->getWorkspace(); $this->persistenceManager->persistAll(); if ($siteXml['type'] === null) { $this->upgradeLegacySiteXml($siteXml, $site); } $this->importNode($siteXml, $sitesNode); } return $site; }
/** * @param string $path an absolute or relative node path which possibly contains context information, for example "/sites/somesite/the/node/path@some-workspace" * @return string the same path without context information */ protected function removeContextFromPath($path) { if ($path === '' || NodePaths::isContextPath($path) === false) { return $path; } try { $nodePathAndContext = NodePaths::explodeContextPath($path); // This is a workaround as we potentially prepend the context path with "/" in buildContextFromRequestPath to create a valid context path, // the code in this class expects an empty nodePath though for the site node, so we remove it again at this point. return $nodePathAndContext['nodePath'] === '/' ? '' : $nodePathAndContext['nodePath']; } catch (\InvalidArgumentException $exception) { } return null; }
/** * Move $node before, into or after $targetNode * * @param NodeInterface $node * @param NodeInterface $targetNode * @param string $position where the node should be added (allowed: before, into, after) * @return NodeInterface The same node given as first argument * @throws NodeException */ public function move(NodeInterface $node, NodeInterface $targetNode, $position) { if (!in_array($position, array('before', 'into', 'after'), true)) { throw new NodeException('The position should be one of the following: "before", "into", "after".', 1296132542); } $designatedParentNode = $this->getDesignatedParentNode($targetNode, $position); // If we stay inside the same parent we basically just reorder, no rename needed or wanted. if ($designatedParentNode !== $node->getParent()) { $designatedNodePath = NodePaths::addNodePathSegment($designatedParentNode->getPath(), $node->getName()); if ($this->nodeService->nodePathAvailableForNode($designatedNodePath, $node) === false) { $nodeName = $this->nodeService->generateUniqueNodeName($designatedParentNode->getPath(), $node->getName()); if ($nodeName !== $node->getName()) { // FIXME: This can be removed if $node->move* supports additionally changing the name of the node. $node->setName($nodeName); } } } switch ($position) { case 'before': $node->moveBefore($targetNode); break; case 'into': $node->moveInto($targetNode); break; case 'after': $node->moveAfter($targetNode); } return $node; }
/** * Create a new site * * This command allows to create a blank site with just a single empty document in the default dimension. * The name of the site, the packageKey must be specified. * * If no ``nodeType`` option is specified the command will use `TYPO3.Neos.NodeTypes:Page` as fallback. The node type * must already exists and have the superType ``TYPO3.Neos:Document``. * * If no ``nodeName` option is specified the command will create a unique node-name from the name of the site. * If a node name is given it has to be unique for the setup. * * If the flag ``activate` is set to false new site will not be activated. * * @param string $name The name of the site * @param string $packageKey The site package * @param string $nodeType The node type to use for the site node. (Default = TYPO3.Neos.NodeTypes:Page) * @param string $nodeName The name of the site node. If no nodeName is given it will be determined from the siteName. * @param boolean $inactive The new site is not activated immediately (default = false). * @return void */ public function createCommand($name, $packageKey, $nodeType = 'TYPO3.Neos.NodeTypes:Page', $nodeName = null, $inactive = false) { if ($nodeName === null) { $nodeName = $this->nodeService->generateUniqueNodeName(SiteService::SITES_ROOT_PATH, $name); } if ($this->siteRepository->findOneByNodeName($nodeName)) { $this->outputLine('<error>A site with siteNodeName "%s" already exists</error>', [$nodeName]); $this->quit(1); } if ($this->packageManager->isPackageAvailable($packageKey) === false) { $this->outputLine('<error>Could not find package "%s"</error>', [$packageKey]); $this->quit(1); } $siteNodeType = $this->nodeTypeManager->getNodeType($nodeType); if ($siteNodeType === null || $siteNodeType->getName() === 'TYPO3.Neos:FallbackNode') { $this->outputLine('<error>The given node type "%s" was not found</error>', [$nodeType]); $this->quit(1); } if ($siteNodeType->isOfType('TYPO3.Neos:Document') === false) { $this->outputLine('<error>The given node type "%s" is not based on the superType "%s"</error>', [$nodeType, 'TYPO3.Neos:Document']); $this->quit(1); } $rootNode = $this->nodeContextFactory->create()->getRootNode(); // We fetch the workspace to be sure it's known to the persistence manager and persist all // so the workspace and site node are persisted before we import any nodes to it. $rootNode->getContext()->getWorkspace(); $this->persistenceManager->persistAll(); $sitesNode = $rootNode->getNode(SiteService::SITES_ROOT_PATH); if ($sitesNode === null) { $sitesNode = $rootNode->createNode(NodePaths::getNodeNameFromPath(SiteService::SITES_ROOT_PATH)); } $siteNode = $sitesNode->createNode($nodeName, $siteNodeType); $siteNode->setProperty('title', $name); $site = new Site($nodeName); $site->setSiteResourcesPackageKey($packageKey); $site->setState($inactive ? Site::STATE_OFFLINE : Site::STATE_ONLINE); $site->setName($name); $this->siteRepository->add($site); $this->outputLine('Successfully created site "%s" with siteNode "%s", type "%s", packageKey "%s" and state "%s"', [$name, $nodeName, $nodeType, $packageKey, $inactive ? 'offline' : 'online']); }
/** * Get the name of this node template. * * If a name has been set using setName(), it is returned. If not, but the * template has a (non-empty) title property, this property is used to * generate a valid name. As a last resort a random name is returned (in * the form "name-XXXXX"). * * @return string * @api */ public function getName() { if ($this->name !== null) { return $this->name; } return NodePaths::generateRandomNodeName(); }
/** * Renders the URI to a given node instance or -path. * * @param ControllerContext $controllerContext * @param mixed $node A node object or a string node path, if a relative path is provided the baseNode argument is required * @param NodeInterface $baseNode * @param string $format Format to use for the URL, for example "html" or "json" * @param boolean $absolute If set, an absolute URI is rendered * @param array $arguments Additional arguments to be passed to the UriBuilder (for example pagination parameters) * @param string $section * @param boolean $addQueryString If set, the current query parameters will be kept in the URI * @param array $argumentsToBeExcludedFromQueryString arguments to be removed from the URI. Only active if $addQueryString = TRUE * @param boolean $resolveShortcuts INTERNAL Parameter - if FALSE, shortcuts are not redirected to their target. Only needed on rare backend occasions when we want to link to the shortcut itself. * @return string The rendered URI * @throws \InvalidArgumentException if the given node/baseNode is not valid * @throws NeosException if no URI could be resolved for the given node */ public function createNodeUri(ControllerContext $controllerContext, $node = null, NodeInterface $baseNode = null, $format = null, $absolute = false, array $arguments = array(), $section = '', $addQueryString = false, array $argumentsToBeExcludedFromQueryString = array(), $resolveShortcuts = true) { $this->lastLinkedNode = null; if (!($node instanceof NodeInterface || is_string($node) || $baseNode instanceof NodeInterface)) { throw new \InvalidArgumentException('Expected an instance of NodeInterface or a string for the node argument, or alternatively a baseNode argument.', 1373101025); } if (is_string($node)) { $nodeString = $node; if ($nodeString === '') { throw new NeosException(sprintf('Empty strings can not be resolved to nodes.', $nodeString), 1415709942); } preg_match(NodeInterface::MATCH_PATTERN_CONTEXTPATH, $nodeString, $matches); if (isset($matches['WorkspaceName']) && $matches['WorkspaceName'] !== '') { $node = $this->propertyMapper->convert($nodeString, 'TYPO3\\TYPO3CR\\Domain\\Model\\NodeInterface'); } else { if ($baseNode === null) { throw new NeosException('The baseNode argument is required for linking to nodes with a relative path.', 1407879905); } /** @var ContentContext $contentContext */ $contentContext = $baseNode->getContext(); $normalizedPath = $this->nodeService->normalizePath($nodeString, $baseNode->getPath(), $contentContext->getCurrentSiteNode()->getPath()); $node = $contentContext->getNode($normalizedPath); } if (!$node instanceof NodeInterface) { throw new NeosException(sprintf('The string "%s" could not be resolved to an existing node.', $nodeString), 1415709674); } } elseif (!$node instanceof NodeInterface) { $node = $baseNode; } if (!$node instanceof NodeInterface) { throw new NeosException(sprintf('Node must be an instance of NodeInterface or string, given "%s".', gettype($node)), 1414772029); } $this->lastLinkedNode = $node; if ($resolveShortcuts === true) { $resolvedNode = $this->nodeShortcutResolver->resolveShortcutTarget($node); } else { // this case is only relevant in extremely rare occasions in the Neos Backend, when we want to generate // a link towards the *shortcut itself*, and not to its target. $resolvedNode = $node; } if (is_string($resolvedNode)) { return $resolvedNode; } if (!$resolvedNode instanceof NodeInterface) { throw new NeosException(sprintf('Could not resolve shortcut target for node "%s"', $node->getPath()), 1414771137); } /** @var ActionRequest $request */ $request = $controllerContext->getRequest()->getMainRequest(); $uriBuilder = clone $controllerContext->getUriBuilder(); $uriBuilder->setRequest($request); $uri = $uriBuilder->reset()->setSection($section)->setArguments($arguments)->setAddQueryString($addQueryString)->setArgumentsToBeExcludedFromQueryString($argumentsToBeExcludedFromQueryString)->setFormat($format ?: $request->getFormat())->uriFor('show', array('node' => $resolvedNode), 'Frontend\\Node', 'TYPO3.Neos'); $siteNode = $resolvedNode->getContext()->getCurrentSiteNode(); if (NodePaths::isSubPathOf($siteNode->getPath(), $resolvedNode->getPath())) { /** @var Site $site */ $site = $resolvedNode->getContext()->getCurrentSite(); } else { $nodePath = NodePaths::getRelativePathBetween(SiteService::SITES_ROOT_PATH, $resolvedNode->getPath()); list($siteNodeName) = explode('/', $nodePath); $site = $this->siteRepository->findOneByNodeName($siteNodeName); } if ($site->hasActiveDomains()) { $requestUriHost = $request->getHttpRequest()->getBaseUri()->getHost(); $activeHostPatterns = $site->getActiveDomains()->map(function ($domain) { return $domain->getHostPattern(); })->toArray(); if (!in_array($requestUriHost, $activeHostPatterns, true)) { $uri = $this->createSiteUri($controllerContext, $site) . '/' . ltrim($uri, '/'); } elseif ($absolute === true) { $uri = $request->getHttpRequest()->getBaseUri() . ltrim($uri, '/'); } } elseif ($absolute === true) { $uri = $request->getHttpRequest()->getBaseUri() . ltrim($uri, '/'); } return $uri; }
/** * Returns the node of the current site. * * @return NodeInterface */ public function getCurrentSiteNode() { if ($this->currentSite !== null && $this->currentSiteNode === null) { $siteNodePath = NodePaths::addNodePathSegment(SiteService::SITES_ROOT_PATH, $this->currentSite->getNodeName()); $this->currentSiteNode = $this->getNode($siteNodePath); if (!$this->currentSiteNode instanceof NodeInterface) { $this->systemLogger->log(sprintf('Warning: %s::getCurrentSiteNode() couldn\'t load the site node for path "%s" in workspace "%s". This is probably due to a missing baseworkspace for the workspace of the current user.', __CLASS__, $siteNodePath, $this->workspaceName), LOG_WARNING); } } return $this->currentSiteNode; }
/** * Normalizes the given node path to a reference path and returns an absolute path. * * You should usually use \TYPO3\TYPO3CR\Domain\Service\NodeService::normalizePath() because functionality could be overloaded, * this is here only for low level operations. * * * @see \TYPO3\TYPO3CR\Domain\Service\NodeService::normalizePath() * @param $path * @param string $referencePath * @return string */ public static function normalizePath($path, $referencePath = NULL) { if ($path === '.') { return $referencePath; } if (!is_string($path)) { throw new \InvalidArgumentException(sprintf('An invalid node path was specified: is of type %s but a string is expected.', gettype($path)), 1357832901); } if (strpos($path, '//') !== FALSE) { throw new \InvalidArgumentException('Paths must not contain two consecutive slashes.', 1291371910); } if ($path[0] === '/') { $absolutePath = $path; } else { $absolutePath = NodePaths::addNodePathSegment($referencePath, $path); } $normalizedPath = NodePaths::replaceRelativePathElements($absolutePath); return strtolower($normalizedPath); }