/**
  * Builds the array of Menu items for this variant menu
  */
 protected function buildItems()
 {
     $menuItems = [];
     $targetDimensionsToMatch = [];
     $allDimensionPresets = $this->configurationContentDimensionPresetSource->getAllPresets();
     $includeAllPresets = $this->getIncludeAllPresets();
     $pinnedDimensionValues = $this->getPresets();
     $pinnedDimensionName = $this->getDimension();
     if ($pinnedDimensionName !== null) {
         $targetDimensionsToMatch = $this->currentNode->getContext()->getTargetDimensions();
         unset($targetDimensionsToMatch[$pinnedDimensionName]);
     }
     foreach ($this->contentDimensionCombinator->getAllAllowedCombinations() as $allowedCombination) {
         $targetDimensions = $this->calculateTargetDimensionsForCombination($allowedCombination);
         if ($pinnedDimensionName !== null && is_array($pinnedDimensionValues)) {
             if (!in_array($targetDimensions[$pinnedDimensionName], $pinnedDimensionValues)) {
                 continue;
             }
         }
         // skip variants not matching the current target dimensions (except the dimension this menu covers)
         if ($targetDimensionsToMatch !== []) {
             foreach ($targetDimensionsToMatch as $dimensionName => $dimensionValue) {
                 if ($targetDimensions[$dimensionName] !== $dimensionValue) {
                     continue 2;
                 }
             }
         }
         $nodeInDimensions = $this->getNodeInDimensions($allowedCombination, $targetDimensions);
         // no match, so we look further...
         if ($nodeInDimensions === null && $includeAllPresets) {
             $nodeInDimensions = $this->findAcceptableNode($allowedCombination, $allDimensionPresets);
         }
         if ($nodeInDimensions !== null && $this->isNodeHidden($nodeInDimensions)) {
             $nodeInDimensions = null;
         }
         // determine metadata for target dimensions of node
         array_walk($targetDimensions, function (&$dimensionValue, $dimensionName, $allDimensionPresets) use($pinnedDimensionName) {
             $dimensionValue = ['value' => $dimensionValue, 'label' => $allDimensionPresets[$dimensionName]['presets'][$dimensionValue]['label'], 'isPinnedDimension' => $pinnedDimensionName === null || $dimensionName == $pinnedDimensionName ? true : false];
         }, $allDimensionPresets);
         if ($pinnedDimensionName === null) {
             $itemLabel = $nodeInDimensions->getLabel();
         } else {
             $itemLabel = $targetDimensions[$pinnedDimensionName]['label'];
         }
         $menuItems[] = ['node' => $nodeInDimensions, 'state' => $this->calculateItemState($nodeInDimensions), 'label' => $itemLabel, 'dimensions' => $allowedCombination, 'targetDimensions' => $targetDimensions];
     }
     // sort/limit according to configured "presets" if needed
     if ($pinnedDimensionName !== null && is_array($pinnedDimensionValues)) {
         $sortedMenuItems = [];
         foreach ($pinnedDimensionValues as $pinnedDimensionValue) {
             foreach ($menuItems as $menuItemKey => $menuItem) {
                 if ($menuItem['targetDimensions'][$pinnedDimensionName]['value'] === $pinnedDimensionValue) {
                     $sortedMenuItems[$menuItemKey] = $menuItem;
                 }
             }
         }
         return $sortedMenuItems;
     }
     return $menuItems;
 }
 /**
  * @param string $workspaceName
  * @param integer $limit
  * @param callable $callback
  * @return integer
  */
 protected function indexWorkspace($workspaceName, $limit = null, callable $callback = null)
 {
     $count = 0;
     $combinations = $this->contentDimensionCombinator->getAllAllowedCombinations();
     if ($combinations === []) {
         $count += $this->indexWorkspaceWithDimensions($workspaceName, [], $limit, $callback);
     } else {
         foreach ($combinations as $combination) {
             $count += $this->indexWorkspaceWithDimensions($workspaceName, $combination, $limit, $callback);
         }
     }
     return $count;
 }
 /**
  * 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();
 }
 /**
  * Performs checks for disallowed child nodes according to the node's auto-create configuration and constraints
  * and removes them if found.
  *
  * @param string $workspaceName
  * @param boolean $dryRun Simulate?
  * @return void
  */
 protected function removeDisallowedChildNodes($workspaceName, $dryRun)
 {
     $this->output->outputLine('Checking for disallowed child nodes ...');
     /** @var \Doctrine\ORM\QueryBuilder $queryBuilder */
     $queryBuilder = $this->entityManager->createQueryBuilder();
     /** @var \TYPO3\TYPO3CR\Domain\Model\Workspace $workspace */
     $workspace = $this->workspaceRepository->findByIdentifier($workspaceName);
     $nodes = array();
     $nodeExceptionCount = 0;
     $removeDisallowedChildNodes = function (NodeInterface $node) use(&$removeDisallowedChildNodes, &$nodes, &$nodeExceptionCount, $queryBuilder) {
         try {
             foreach ($node->getChildNodes() as $childNode) {
                 /** @var $childNode NodeInterface */
                 if (!$childNode->isAutoCreated() && !$node->isNodeTypeAllowedAsChildNode($childNode->getNodeType())) {
                     $nodes[] = $childNode;
                     $parent = $node->isAutoCreated() ? $node->getParent() : $node;
                     $this->output->outputLine('Found disallowed node named "%s" (%s) in "%s", child of node "%s" (%s)', array($childNode->getName(), $childNode->getNodeType()->getName(), $childNode->getPath(), $parent->getName(), $parent->getNodeType()->getName()));
                 } else {
                     $removeDisallowedChildNodes($childNode);
                 }
             }
         } catch (\Exception $e) {
             $nodeExceptionCount++;
         }
     };
     // TODO: Performance could be improved by a search for all child node data instead of looping over all contexts
     foreach ($this->contentDimensionCombinator->getAllAllowedCombinations() as $dimensionConfiguration) {
         $context = $this->createContext($workspace->getName(), $dimensionConfiguration);
         $removeDisallowedChildNodes($context->getRootNode());
     }
     $disallowedChildNodesCount = count($nodes);
     if ($disallowedChildNodesCount > 0) {
         $this->output->outputLine();
         if (!$dryRun) {
             $self = $this;
             $this->askBeforeExecutingTask('Do you want to remove all disallowed child nodes?', function () use($self, $nodes, $disallowedChildNodesCount, $workspaceName) {
                 foreach ($nodes as $node) {
                     $self->removeNodeAndChildNodesInWorkspaceByPath($node->getPath(), $workspaceName);
                 }
                 $self->output->outputLine('Removed %s disallowed node%s.', array($disallowedChildNodesCount, $disallowedChildNodesCount > 1 ? 's' : ''));
             });
         } else {
             $this->output->outputLine('Found %s disallowed node%s to be removed.', array($disallowedChildNodesCount, $disallowedChildNodesCount > 1 ? 's' : ''));
         }
         if ($nodeExceptionCount > 0) {
             $this->output->outputLine();
             $this->output->outputLine('%s error%s occurred during child node traversing.', array($nodeExceptionCount, $nodeExceptionCount > 1 ? 's' : ''));
         }
         $this->output->outputLine();
     }
 }
 /**
  * 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);
             }
         }
     }
 }
 /**
  * Remove nodes with invalid dimension values
  *
  * This removes nodes which have dimension values not fitting to the current dimension configuration
  *
  * @param string $workspaceName Name of the workspace to consider
  * @param boolean $dryRun Simulate?
  * @return void
  */
 public function removeNodesWithInvalidDimensions($workspaceName, $dryRun)
 {
     $this->output->outputLine('Checking for nodes with invalid dimensions ...');
     $allowedDimensionCombinations = $this->contentDimensionCombinator->getAllAllowedCombinations();
     $nodesArray = $this->collectNodesWithInvalidDimensions($workspaceName, $allowedDimensionCombinations);
     if ($nodesArray === []) {
         return;
     }
     if (!$dryRun) {
         $self = $this;
         $this->output->outputLine();
         $this->output->outputLine('Nodes with invalid dimension values found.' . PHP_EOL . 'You might solve this by migrating them to your current dimension configuration or by removing them.');
         $this->askBeforeExecutingTask(sprintf('Do you want to remove %s node%s with invalid dimensions now?', count($nodesArray), count($nodesArray) > 1 ? 's' : ''), function () use($self, $nodesArray, $workspaceName) {
             foreach ($nodesArray as $nodeArray) {
                 $self->removeNode($nodeArray['identifier'], $nodeArray['dimensionsHash']);
             }
             $self->output->outputLine('Removed %s node%s with invalid dimension values.', array(count($nodesArray), count($nodesArray) > 1 ? 's' : ''));
         });
     } else {
         $this->output->outputLine('Found %s node%s with invalid dimension values to be removed.', array(count($nodesArray), count($nodesArray) > 1 ? 's' : ''));
     }
     $this->output->outputLine();
 }
 /**
  * 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);
         }
     }
 }
 /**
  * @param string $workspaceName
  * @return void
  */
 protected function indexWorkspace($workspaceName)
 {
     $combinations = $this->contentDimensionCombinator->getAllAllowedCombinations();
     if ($combinations === array()) {
         $this->indexWorkspaceWithDimensions($workspaceName);
     } else {
         foreach ($combinations as $combination) {
             $this->indexWorkspaceWithDimensions($workspaceName, $combination);
         }
     }
 }