/** * @param string|array $workspaceNames * @return boolean */ public function isInWorkspace($workspaceNames) { if ($this->node === NULL) { return TRUE; } return in_array($this->node->getWorkspace()->getName(), $workspaceNames); }
/** * Matches if the selected node belongs to one of the given $workspaceNames * * Example: isInWorkspace(['live', 'user-admin']) matches if the selected node is in one of the workspaces "user-admin" or "live" * * @param array $workspaceNames An array of workspace names, e.g. ["live", "user-admin"] * @return boolean TRUE if the selected node matches the $workspaceNames, otherwise FALSE */ public function isInWorkspace($workspaceNames) { if ($this->node === null) { return true; } return in_array($this->node->getWorkspace()->getName(), $workspaceNames); }
/** * 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); }
/** * Publishes the given node to the specified targetWorkspace * * @param \TYPO3\TYPO3CR\Domain\Model\NodeInterface $node * @param string $targetWorkspaceName * @return void * @ExtDirect */ public function publishNodeAction(\TYPO3\TYPO3CR\Domain\Model\NodeInterface $node, $targetWorkspaceName) { /** * TODO: The publishing pushes the same node twice, which causes the node to be published * already when it's processed the second time. This obviously leads to a problem for the * Workspace object which will (in the second time) try to publish a node in the live workspace * to the baseWorkspace of the live workspace (which does not exist). */ if ($targetWorkspaceName === $node->getWorkspace()->getName()) { $this->view->assign('value', array('success' => TRUE)); return; } $this->workspacesService->publishNode($node, $targetWorkspaceName); $this->view->assign('value', array('success' => TRUE)); }
/** * Publishes the given node to the target workspace. * * The specified workspace must be a base workspace of this workspace. * * @param NodeInterface $node The node to publish * @param Workspace $targetWorkspace The workspace to publish to * @return void * @api */ public function publishNode(NodeInterface $node, Workspace $targetWorkspace) { if ($this->baseWorkspace === null) { return; } if ($node->getWorkspace() !== $this) { return; } $this->verifyPublishingTargetWorkspace($targetWorkspace); $this->emitBeforeNodePublishing($node, $targetWorkspace); if ($node->getPath() === '/') { return; } $targetNodeData = $this->findNodeDataInTargetWorkspace($node, $targetWorkspace); $matchingNodeVariantExistsInTargetWorkspace = $targetNodeData !== null && $targetNodeData->getDimensionValues() === $node->getDimensions(); if ($matchingNodeVariantExistsInTargetWorkspace) { $this->replaceNodeData($node, $targetNodeData); } else { $this->moveNodeVariantToTargetWorkspace($node, $targetWorkspace); } $this->emitAfterNodePublishing($node, $targetWorkspace); }
/** * * * @param NodeInterface $node * @param array $fulltextIndexOfNode * @param string $targetWorkspaceName * @return void */ protected function updateFulltext(NodeInterface $node, array $fulltextIndexOfNode, $targetWorkspaceName = NULL) { if ($targetWorkspaceName !== NULL && $targetWorkspaceName !== 'live' || $node->getWorkspace()->getName() !== 'live' || count($fulltextIndexOfNode) === 0) { return; } $closestFulltextNode = $node; while (!$this->isFulltextRoot($closestFulltextNode)) { $closestFulltextNode = $closestFulltextNode->getParent(); if ($closestFulltextNode === NULL) { // root of hierarchy, no fulltext root found anymore, abort silently... $this->logger->log('No fulltext root found for ' . $node->getPath(), LOG_WARNING); return; } } $closestFulltextNodeContextPath = str_replace($closestFulltextNode->getContext()->getWorkspace()->getName(), 'live', $closestFulltextNode->getContextPath()); $closestFulltextNodeContextPathHash = sha1($closestFulltextNodeContextPath); $this->currentBulkRequest[] = array(array('update' => array('_type' => NodeTypeMappingBuilder::convertNodeTypeNameToMappingName($closestFulltextNode->getNodeType()->getName()), '_id' => $closestFulltextNodeContextPathHash)), array('script' => ' if (!ctx._source.containsKey("__fulltextParts")) { ctx._source.__fulltextParts = new LinkedHashMap(); } ctx._source.__fulltextParts[identifier] = fulltext; ctx._source.__fulltext = new LinkedHashMap(); Iterator<LinkedHashMap.Entry<String, LinkedHashMap>> fulltextByNode = ctx._source.__fulltextParts.entrySet().iterator(); for (fulltextByNode; fulltextByNode.hasNext();) { Iterator<LinkedHashMap.Entry<String, String>> elementIterator = fulltextByNode.next().getValue().entrySet().iterator(); for (elementIterator; elementIterator.hasNext();) { Map.Entry<String, String> element = elementIterator.next(); String value; if (ctx._source.__fulltext.containsKey(element.key)) { value = ctx._source.__fulltext[element.key] + " " + element.value.trim(); } else { value = element.value.trim(); } ctx._source.__fulltext[element.key] = value; } } ', 'params' => array('identifier' => $node->getIdentifier(), 'fulltext' => $fulltextIndexOfNode), 'upsert' => array('__fulltext' => $fulltextIndexOfNode, '__fulltextParts' => array($node->getIdentifier() => $fulltextIndexOfNode)), 'lang' => 'groovy')); }
/** * Publishes the given node to the target workspace. * * The specified workspace must be a base workspace of this workspace. * * @param NodeInterface $node The node to publish * @param Workspace $targetWorkspace The workspace to publish to * @return void * @api */ public function publishNode(NodeInterface $node, Workspace $targetWorkspace) { if ($this->baseWorkspace === null) { return; } if ($node->getWorkspace() !== $this) { return; } // Might happen if a node which has been published during an earlier call of publishNode() is attempted to // be published again: if ($node->getWorkspace() === $targetWorkspace) { return; } $this->verifyPublishingTargetWorkspace($targetWorkspace); $this->emitBeforeNodePublishing($node, $targetWorkspace); if ($node->getPath() === '/') { return; } $targetNodeData = $this->findNodeDataInTargetWorkspace($node, $targetWorkspace); $matchingNodeVariantExistsInTargetWorkspace = $targetNodeData !== null && $targetNodeData->getDimensionValues() === $node->getDimensions(); if ($matchingNodeVariantExistsInTargetWorkspace) { $this->replaceNodeData($node, $targetNodeData); } else { $this->moveNodeVariantToTargetWorkspace($node, $targetWorkspace); } $this->emitAfterNodePublishing($node, $targetWorkspace); }
/** * Collects metadata attributes used to allow editing of the node in the Neos backend. * * @param array $attributes * @param NodeInterface $node * @return array */ protected function addGenericEditingMetadata(array $attributes, NodeInterface $node) { $attributes['typeof'] = 'typo3:' . $node->getNodeType()->getName(); $attributes['about'] = $node->getContextPath(); $attributes['data-node-_identifier'] = $node->getIdentifier(); $attributes['data-node-__workspace-name'] = $node->getWorkspace()->getName(); $attributes['data-node-__label'] = $node->getLabel(); if ($node->getNodeType()->isOfType('TYPO3.Neos:ContentCollection')) { $attributes['rel'] = 'typo3:content-collection'; } // these properties are needed together with the current NodeType to evaluate Node Type Constraints // TODO: this can probably be greatly cleaned up once we do not use CreateJS or VIE anymore. if ($node->getParent()) { $attributes['data-node-__parent-node-type'] = $node->getParent()->getNodeType()->getName(); } if ($node->isAutoCreated()) { $attributes['data-node-_name'] = $node->getName(); $attributes['data-node-_is-autocreated'] = 'true'; } if ($node->getParent() && $node->getParent()->isAutoCreated()) { $attributes['data-node-_parent-is-autocreated'] = 'true'; // we shall only add these properties if the parent is actually auto-created; as the Node-Type-Switcher in the UI relies on that. $attributes['data-node-__parent-node-name'] = $node->getParent()->getName(); $attributes['data-node-__grandparent-node-type'] = $node->getParent()->getParent()->getNodeType()->getName(); } return $attributes; }
/** * Discards the given node. * * @param NodeInterface $node * @return void * @throws \TYPO3\TYPO3CR\Exception\WorkspaceException * @api */ public function discardNode(NodeInterface $node) { if ($node->getWorkspace()->getBaseWorkspace() === NULL) { throw new WorkspaceException('Nodes in a in a workspace without a base workspace cannot be discarded.', 1395841899); } $possibleShadowNodeData = $this->nodeDataRepository->findOneByMovedTo($node->getNodeData()); if ($possibleShadowNodeData !== NULL) { $this->nodeDataRepository->remove($possibleShadowNodeData); } if ($node->getPath() !== '/') { $this->nodeDataRepository->remove($node); $this->emitNodeDiscarded($node); } }
/** * Updates the specified node. Returns the following data: * - the (possibly changed) workspace name of the node * - the URI of the closest folder node. If $node is a folder node (f.e. a Page), the own URI is returned. * This is important to handle renamings of nodes correctly. * * Note: We do not call $nodeRepository->update() here, as TYPO3CR has a stateful API for now. * * @param \TYPO3\TYPO3CR\Domain\Model\NodeInterface $node * @return void * @ExtDirect */ public function updateAction(\TYPO3\TYPO3CR\Domain\Model\NodeInterface $node) { $closestFolderNode = $node; while (!$closestFolderNode->getContentType()->isOfType('TYPO3.TYPO3CR:Folder')) { $closestFolderNode = $closestFolderNode->getParent(); } $nextUri = $this->uriBuilder->reset()->setFormat('html')->setCreateAbsoluteUri(TRUE)->uriFor('show', array('node' => $closestFolderNode), 'Frontend\\Node', 'TYPO3.TYPO3', ''); $this->view->assign('value', array('data' => array('workspaceNameOfNode' => $node->getWorkspace()->getName(), 'nextUri' => $nextUri), 'success' => TRUE)); }
/** * 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); }
/** * Checks if the given node path can be used for the given node. * * @param string $nodePath * @param NodeInterface $node * @return boolean */ public function nodePathAvailableForNode($nodePath, NodeInterface $node) { /** @var NodeData $existingNodeData */ $existingNodeDataObjects = $this->nodeDataRepository->findByPathWithoutReduce($nodePath, $node->getWorkspace(), true); foreach ($existingNodeDataObjects as $existingNodeData) { if ($existingNodeData->getMovedTo() !== null && $existingNodeData->getMovedTo() === $node->getNodeData()) { return true; } } return !$this->nodePathExistsInAnyContext($nodePath); }
/** * 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()); }
/** * @param \TYPO3\TYPO3CR\Domain\Model\NodeInterface $node * @param $targetWorkspaceName * @return void */ public function publishNode(\TYPO3\TYPO3CR\Domain\Model\NodeInterface $node, $targetWorkspaceName = 'live') { $nodes = array($node); $contentType = $node->getContentType(); if ($contentType->isOfType('TYPO3.Phoenix.ContentTypes:Page') || $contentType->hasStructure()) { foreach ($node->getChildNodes('TYPO3.Phoenix.ContentTypes:Section') as $sectionNode) { array_push($nodes, $sectionNode); } } $sourceWorkspace = $node->getWorkspace(); $sourceWorkspace->publishNodes($nodes, $targetWorkspaceName); }
/** * Wrap the $content identified by $node with the needed markup for * the backend. * $parameters can be used to further pass parameters to the content element. * * @param \TYPO3\TYPO3CR\Domain\Model\NodeInterface $node * @param string $typoscriptPath * @param string $content * @param boolean $isPage * @return string */ public function wrapContentObject(\TYPO3\TYPO3CR\Domain\Model\NodeInterface $node, $typoscriptPath, $content, $isPage = FALSE) { $contentType = $node->getContentType(); $tagBuilder = new \TYPO3\Fluid\Core\ViewHelper\TagBuilder('div'); $tagBuilder->forceClosingTag(TRUE); if (!$node->isRemoved()) { $tagBuilder->setContent($content); } if (!$isPage) { $cssClasses = array('t3-contentelement'); $cssClasses[] = str_replace(array(':', '.'), '-', strtolower($contentType->getName())); if ($node->isHidden()) { $cssClasses[] = 't3-contentelement-hidden'; } if ($node->isRemoved()) { $cssClasses[] = 't3-contentelement-removed'; } $tagBuilder->addAttribute('class', implode(' ', $cssClasses)); $tagBuilder->addAttribute('id', 'c' . $node->getIdentifier()); } try { $this->accessDecisionManager->decideOnResource('TYPO3_TYPO3_Backend_BackendController'); } catch (\TYPO3\FLOW3\Security\Exception\AccessDeniedException $e) { return $tagBuilder->render(); } $tagBuilder->addAttribute('typeof', 'typo3:' . $contentType->getName()); $tagBuilder->addAttribute('about', $node->getContextPath()); $this->addScriptTag($tagBuilder, '__workspacename', $node->getWorkspace()->getName()); $this->addScriptTag($tagBuilder, '_removed', $node->isRemoved() ? 'true' : 'false', 'boolean'); $this->addScriptTag($tagBuilder, '_typoscriptPath', $typoscriptPath); foreach ($contentType->getProperties() as $propertyName => $propertyConfiguration) { $dataType = isset($propertyConfiguration['type']) ? $propertyConfiguration['type'] : 'string'; if ($propertyName[0] === '_') { $propertyValue = \TYPO3\FLOW3\Reflection\ObjectAccess::getProperty($node, substr($propertyName, 1)); } else { $propertyValue = $node->getProperty($propertyName); } // Serialize boolean values to String if (isset($propertyConfiguration['type']) && $propertyConfiguration['type'] === 'boolean') { $propertyValue = $propertyValue ? 'true' : 'false'; } // Serialize date values to String if ($propertyValue !== NULL && isset($propertyConfiguration['type']) && $propertyConfiguration['type'] === 'date') { $propertyValue = $propertyValue->format('Y-m-d'); } // Serialize objects to JSON strings if (is_object($propertyValue) && $propertyValue !== NULL && isset($propertyConfiguration['type']) && $this->objectManager->isRegistered($propertyConfiguration['type'])) { $gettableProperties = \TYPO3\FLOW3\Reflection\ObjectAccess::getGettableProperties($propertyValue); $convertedProperties = array(); foreach ($gettableProperties as $key => $value) { if (is_object($value)) { $entityIdentifier = $this->persistenceManager->getIdentifierByObject($value); if ($entityIdentifier !== NULL) { $value = $entityIdentifier; } } $convertedProperties[$key] = $value; } $propertyValue = json_encode($convertedProperties); $dataType = 'jsonEncoded'; } $this->addScriptTag($tagBuilder, $propertyName, $propertyValue, $dataType); } if (!$isPage) { // add CSS classes $this->addScriptTag($tagBuilder, '__contenttype', $contentType->getName()); } else { $tagBuilder->addAttribute('id', 't3-page-metainformation'); $tagBuilder->addAttribute('data-__sitename', $this->nodeRepository->getContext()->getCurrentSite()->getName()); $tagBuilder->addAttribute('data-__siteroot', sprintf('/sites/%s@%s', $this->nodeRepository->getContext()->getCurrentSite()->getNodeName(), $this->nodeRepository->getContext()->getWorkspace()->getName())); } return $tagBuilder->render(); }