예제 #1
0
 /**
  * Filter a node by the current context.
  * Will either return the node or NULL if it is not permitted in current context.
  *
  * @param NodeInterface $node
  * @param Context $context
  * @return \TYPO3\TYPO3CR\Domain\Model\Node|NULL
  */
 protected function filterNodeByContext(NodeInterface $node, Context $context)
 {
     if (!$context->isRemovedContentShown() && $node->isRemoved()) {
         return NULL;
     }
     if (!$context->isInvisibleContentShown() && !$node->isVisible()) {
         return NULL;
     }
     if (!$context->isInaccessibleContentShown() && !$node->isAccessible()) {
         return NULL;
     }
     return $node;
 }
 /**
  * Move the given node instance to the target workspace
  *
  * If no target node variant (having the same dimension values) exists in the target workspace, the node that
  * is published will be used as a new node variant in the target workspace.
  *
  * @param NodeInterface $node The node to publish
  * @param Workspace $targetWorkspace The workspace to publish to
  * @return void
  */
 protected function moveNodeVariantToTargetWorkspace(NodeInterface $node, Workspace $targetWorkspace)
 {
     $nodeData = $node->getNodeData();
     $movedShadowNodeData = $this->nodeDataRepository->findOneByMovedTo($nodeData);
     if ($movedShadowNodeData instanceof NodeData && $movedShadowNodeData->isRemoved()) {
         $this->nodeDataRepository->remove($movedShadowNodeData);
     }
     if ($targetWorkspace->getBaseWorkspace() === null && $node->isRemoved()) {
         $this->nodeDataRepository->remove($nodeData);
     } else {
         $nodeData->setWorkspace($targetWorkspace);
         $nodeData->setLastPublicationDateTime($this->now);
         $this->nodeService->cleanUpProperties($node);
     }
     $node->setNodeDataIsMatchingContext(null);
 }
    /**
     * 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)');
    }
 /**
  * Collects CSS class names used for styling editable elements in the Neos backend.
  *
  * @param NodeInterface $node
  * @return array
  */
 protected function collectEditingClassNames(NodeInterface $node)
 {
     $classNames = [];
     if ($node->getNodeType()->isOfType('TYPO3.Neos:ContentCollection')) {
         // This is needed since the backend relies on this class (should not be necessary)
         $classNames[] = 'neos-contentcollection';
     } else {
         $classNames[] = 'neos-contentelement';
     }
     if ($node->isRemoved()) {
         $classNames[] = 'neos-contentelement-removed';
     }
     if ($node->isHidden()) {
         $classNames[] = 'neos-contentelement-hidden';
     }
     if ($this->isInlineEditable($node) === false) {
         $classNames[] = 'neos-not-inline-editable';
     }
     return $classNames;
 }
 /**
  * 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)
 {
     $indexer = function (NodeInterface $node, $targetWorkspaceName = null) {
         $contextPath = $node->getContextPath();
         if ($this->settings['indexAllWorkspaces'] === false) {
             // we are only supposed to index the live workspace.
             // We need to check the workspace at two occasions; checking the
             // $targetWorkspaceName and the workspace name of the node's context as fallback
             if ($targetWorkspaceName !== null && $targetWorkspaceName !== 'live') {
                 return;
             }
             if ($targetWorkspaceName === null && $node->getContext()->getWorkspaceName() !== 'live') {
                 return;
             }
         }
         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->logger->log(sprintf('NodeIndexer: Removing node %s from index (if node type changed from %s). ID: %s', $contextPath, $node->getNodeType()->getName(), $contextPathHash), LOG_DEBUG, null, 'ElasticSearch (CR)');
         $this->getIndex()->request('DELETE', '/_query', [], json_encode(['query' => ['bool' => ['must' => ['ids' => ['values' => [$contextPathHash]]], 'must_not' => ['term' => ['_type' => NodeTypeMappingBuilder::convertNodeTypeNameToMappingName($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 = [];
         $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;
             $documentData['__dimensionCombinationHash'] = md5(json_encode($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[] = [['update' => ['_type' => $document->getType()->getName(), '_id' => $document->getId()]], ['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' => ['newData' => $documentData], 'upsert' => $documentData, 'lang' => 'groovy']];
             } else {
                 // non-fulltext-root documents can be indexed as-they-are
                 $this->currentBulkRequest[] = [['index' => ['_type' => $document->getType()->getName(), '_id' => $document->getId()]], $documentData];
             }
             $this->updateFulltext($node, $fulltextIndexOfNode, $targetWorkspaceName);
         }
         $this->logger->log(sprintf('NodeIndexer: Added / updated node %s. ID: %s Context: %s', $contextPath, $contextPathHash, json_encode($node->getContext()->getProperties())), LOG_DEBUG, null, 'ElasticSearch (CR)');
     };
     $dimensionCombinations = $this->contentDimensionCombinator->getAllAllowedCombinations();
     $workspaceName = $targetWorkspaceName ?: 'live';
     $nodeIdentifier = $node->getIdentifier();
     if ($dimensionCombinations !== []) {
         foreach ($dimensionCombinations as $combination) {
             $context = $this->contextFactory->create(['workspaceName' => $workspaceName, 'dimensions' => $combination]);
             $node = $context->getNodeByIdentifier($nodeIdentifier);
             if ($node !== null) {
                 $indexer($node, $targetWorkspaceName);
             }
         }
     } else {
         $context = $this->contextFactory->create(['workspaceName' => $workspaceName]);
         $node = $context->getNodeByIdentifier($nodeIdentifier);
         if ($node !== null) {
             $indexer($node, $targetWorkspaceName);
         }
     }
 }
 /**
  * Filter a node by the current context.
  * Will either return the node or NULL if it is not permitted in current context.
  *
  * @param NodeInterface $node
  * @param Context $context
  * @return \TYPO3\TYPO3CR\Domain\Model\NodeInterface|NULL
  */
 protected function filterNodeByContext(NodeInterface $node, Context $context)
 {
     $this->securityContext->withoutAuthorizationChecks(function () use(&$node, $context) {
         if (!$context->isRemovedContentShown() && $node->isRemoved()) {
             $node = null;
             return;
         }
         if (!$context->isInvisibleContentShown() && !$node->isVisible()) {
             $node = null;
             return;
         }
         if (!$context->isInaccessibleContentShown() && !$node->isAccessible()) {
             $node = null;
         }
     });
     return $node;
 }
 /**
  * Remove all properties not configured in the current Node Type.
  * This will not do anything on Nodes marked as removed as those could be queued up for deletion
  * which contradicts updates (that would be necessary to remove the properties).
  *
  * @param NodeInterface $node
  * @return void
  */
 public function cleanUpProperties(NodeInterface $node)
 {
     if ($node->isRemoved() === false) {
         $nodeData = $node->getNodeData();
         $nodeTypeProperties = $node->getNodeType()->getProperties();
         foreach ($node->getProperties() as $name => $value) {
             if (!isset($nodeTypeProperties[$name])) {
                 $nodeData->removeProperty($name);
             }
         }
     }
 }
예제 #8
0
 /**
  * Replace the node data of a node instance with a given target node data
  *
  * The node data of the node that is published will be removed and the existing node data inside the target
  * workspace is updated to the changes and will be injected into the node instance. If the node was marked as
  * removed, both node data are removed.
  *
  * @param NodeInterface $node The node instance with node data to be published
  * @param NodeData $targetNodeData The existing node data in the target workspace
  * @return void
  */
 protected function replaceNodeData(NodeInterface $node, NodeData $targetNodeData)
 {
     $sourceNodeData = $node->getNodeData();
     if ($node->isRemoved() === TRUE) {
         $this->nodeDataRepository->remove($targetNodeData);
     } else {
         $targetNodeData->similarize($node->getNodeData());
         $targetNodeData->setPath($node->getPath(), FALSE);
         $node->setNodeData($targetNodeData);
     }
     $this->nodeDataRepository->remove($sourceNodeData);
 }
 /**
  * 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();
 }