From c9fa2b06834b3e420fbff4c646a8893b0c384ea6 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Fri, 9 Jun 2023 17:54:38 +0200 Subject: [PATCH 01/35] WIP: Initial refactioring for neos 9 The backend mule is basically working again --- Classes/Command/TaxonomyCommandController.php | 491 +++++++------ Classes/Controller/ModuleController.php | 658 +++++++++--------- Classes/Hooks/ContentRepositoryHooks.php | 104 +-- Classes/Package.php | 30 +- Classes/Service/DimensionService.php | 175 ----- Classes/Service/TaxonomyService.php | 184 ++--- Configuration/NodeTypes.Root.yaml | 2 +- Configuration/NodeTypes.Taxonomy.yaml | 2 +- Configuration/NodeTypes.Vocabulary.yaml | 2 +- Configuration/Settings.yaml | 2 +- .../Fusion/Backend/Form/Taxonomy.fusion | 12 +- .../Fusion/Backend/Form/Vocabulary.fusion | 20 +- .../Fusion/Backend/Views/Taxonomy.Edit.fusion | 8 +- .../Fusion/Backend/Views/Taxonomy.List.fusion | 35 +- .../Fusion/Backend/Views/Taxonomy.New.fusion | 4 +- .../Backend/Views/Vocabulary.Edit.fusion | 5 +- .../Backend/Views/Vocabulary.List.fusion | 30 +- .../Backend/Views/Vocabulary.New.fusion | 3 +- composer.json | 8 +- 19 files changed, 804 insertions(+), 971 deletions(-) delete mode 100644 Classes/Service/DimensionService.php diff --git a/Classes/Command/TaxonomyCommandController.php b/Classes/Command/TaxonomyCommandController.php index 201cbed..ecc39ed 100644 --- a/Classes/Command/TaxonomyCommandController.php +++ b/Classes/Command/TaxonomyCommandController.php @@ -3,13 +3,8 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Cli\CommandController; -use Neos\ContentRepository\Domain\Model\NodeInterface; -use Neos\ContentRepository\Domain\Service\ImportExport\NodeExportService; -use Neos\ContentRepository\Domain\Service\ImportExport\NodeImportService; -use Neos\ContentRepository\Domain\Repository\NodeDataRepository; use Neos\Eel\FlowQuery\FlowQuery; use Neos\Flow\Persistence\PersistenceManagerInterface; -use Sitegeist\Taxonomy\Service\DimensionService; use Sitegeist\Taxonomy\Service\TaxonomyService; /** @@ -18,48 +13,48 @@ class TaxonomyCommandController extends CommandController { - /** - * @var array - * @Flow\InjectConfiguration - */ - protected $configuration; - - /** - * @Flow\Inject - * @var NodeImportService - */ - protected $nodeImportService; - - /** - * @var NodeExportService - * @Flow\Inject - */ - protected $nodeExportService; - - /** - * @var NodeDataRepository - * @Flow\Inject - */ - protected $nodeDataRepository; - +// /** +// * @var array +// * @Flow\InjectConfiguration +// */ +// protected $configuration; +// +// /** +// * @Flow\Inject +// * @var NodeImportService +// */ +// protected $nodeImportService; +// +// /** +// * @var NodeExportService +// * @Flow\Inject +// */ +// protected $nodeExportService; +// +// /** +// * @var NodeDataRepository +// * @Flow\Inject +// */ +// protected $nodeDataRepository; +// /** * @var TaxonomyService * @Flow\Inject */ protected $taxonomyService; - - /** - * @var DimensionService - * @Flow\Inject - */ - protected $dimensionService; - - /** - * @var PersistenceManagerInterface - * @Flow\Inject - */ - protected $persistenceManager; - +// +// /** +// * @var DimensionService +// * @Flow\Inject +// */ +// protected $dimensionService; +// +// /** +// * @var PersistenceManagerInterface +// * @Flow\Inject +// */ +// protected $persistenceManager; +// /** * List taxonomy vocabularies * @@ -68,218 +63,214 @@ class TaxonomyCommandController extends CommandController */ public function listCommand() { - $taxonomyRoot = $this->taxonomyService->getRoot(); - - /** - * @var NodeInterface[] $vocabularyNodes - */ - $vocabularyNodes = (new FlowQuery([$taxonomyRoot])) - ->children('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ' ]') - ->get(); - - /** - * @var NodeInterface $vocabularyNode - */ - foreach ($vocabularyNodes as $vocabularyNode) { - $this->outputLine($vocabularyNode->getName()); - } - } - - /** - * Import taxonomy content - * - * @param string $filename relative path and filename to the XML file to read. - * @param string $vocabularyNode vocabularay nodename(path) to import (globbing is supported) - * @return void - */ - public function importCommand($filename, $vocabulary = null) - { - $xmlReader = new \XMLReader(); - $xmlReader->open($filename, null, LIBXML_PARSEHUGE); - - $taxonomyRoot = $this->taxonomyService->getRoot(); - - while ($xmlReader->read()) { - if ($xmlReader->nodeType != \XMLReader::ELEMENT || $xmlReader->name !== 'vocabulary') { - continue; - } - - $vocabularyName = (string) $xmlReader->getAttribute('name'); - if (is_string($vocabulary) && fnmatch($vocabulary, $vocabularyName) == false) { - continue; - } - - $this->nodeImportService->import($xmlReader, $taxonomyRoot->getPath()); - $this->outputLine('Imported vocabulary %s from file %s', [$vocabularyName, $filename]); - } - } - - /** - * Export taxonomy content - * - * @param string $filename filename for the xml that is written. - * @param string $vocabularyNode vocabularay nodename(path) to export (globbing is supported) - * @return void - */ - public function exportCommand($filename, $vocabulary = null) - { - $xmlWriter = new \XMLWriter(); - $xmlWriter->openUri($filename); - $xmlWriter->setIndent(true); - - $xmlWriter->startDocument('1.0', 'UTF-8'); - $xmlWriter->startElement('root'); - - $taxonomyRoot = $this->taxonomyService->getRoot(); - - /** - * @var NodeInterface[] $vocabularyNodes - */ - $vocabularyNodes = (new FlowQuery([$taxonomyRoot])) - ->children('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ' ]') - ->get(); - - /** - * @var NodeInterface $vocabularyNode - */ - foreach ($vocabularyNodes as $vocabularyNode) { - $vocabularyName = $vocabularyNode->getName(); - if (is_string($vocabulary) && fnmatch($vocabulary, $vocabularyName) == false) { - continue; - } - $xmlWriter->startElement('vocabulary'); - $xmlWriter->writeAttribute('name', $vocabularyName); - $this->nodeExportService->export($vocabularyNode->getPath(), 'live', $xmlWriter, false, false); - $this->outputLine('Exported vocabulary %s to file %s', [$vocabularyName, $filename]); - $xmlWriter->endElement(); - } - - $xmlWriter->endElement(); - $xmlWriter->endDocument(); - - $xmlWriter->flush(); - } - - /** - * Prune taxonomy content - * - * @param string $vocabularyNode vocabularay nodename(path) to prune (globbing is supported) - * @return void - */ - public function pruneCommand($vocabulary) - { - $taxonomyRoot = $this->taxonomyService->getRoot(); + $subgraph = $this->taxonomyService->findSubgraph(); + $taxonomyRoot = $this->taxonomyService->getRoot($subgraph); + $vocabularies = $this->taxonomyService->getVocabularies($subgraph); /** - * @var NodeInterface[] $vocabularyNodes + * @var Node $vocabularyNode */ - $vocabularyNodes = (new FlowQuery([$taxonomyRoot])) - ->children('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ' ]') - ->get(); - - /** - * @var NodeInterface $vocabularyNode - */ - foreach ($vocabularyNodes as $vocabularyNode) { - $vocabularyName = $vocabularyNode->getName(); - if (is_string($vocabulary) && fnmatch($vocabulary, $vocabularyName) == false) { - continue; - } - $this->nodeDataRepository->removeAllInPath($vocabularyNode->getPath()); - $dimensionNodes = $this->nodeDataRepository->findByPath($vocabularyNode->getPath()); - foreach ($dimensionNodes as $node) { - $this->nodeDataRepository->remove($node); - } - - $this->outputLine('Pruned vocabulary %s', [$vocabularyName]); + foreach ($vocabularies->getIterator() as $vocabulary) { + $this->outputLine($vocabulary->getLabel()); } } - /** - * Reset a taxonimy dimension and create fresh variants from the base dimension - * - * @param string $dimensionName - * @param string $dimensionValue - * @return void - */ - public function pruneDimensionCommand($dimensionName, $dimensionValue) - { - $taxonomyRoot = $this->taxonomyService->getRoot(); - $targetSubgraph = $this->dimensionService->getDimensionSubgraphByTargetValues([$dimensionName => $dimensionValue]); - - if (!$targetSubgraph) { - $this->outputLine('Target subgraph not found'); - $this->quit(1); - } - - $targetContextValues = $this->dimensionService->getDimensionValuesForSubgraph($targetSubgraph); - $flowQuery = new FlowQuery([$taxonomyRoot]); - $taxonomyRootInTargetContest = $flowQuery->context($targetContextValues)->get(0); - - if (!$taxonomyRootInTargetContest) { - $this->outputLine('Not root in target context found'); - $this->quit(1); - } - - if ($taxonomyRootInTargetContest == $taxonomyRoot) { - $this->outputLine('The root is the default context and cannot be pruned'); - $this->quit(1); - } - - $this->outputLine('Removing content all below ' . $taxonomyRootInTargetContest->getContextPath()); - $flowQuery = new FlowQuery([$taxonomyRootInTargetContest]); - $allNodes = $flowQuery->find('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . '],[instanceof ' . $this->taxonomyService->getTaxonomyNodeType() . ']')->get(); - foreach ($allNodes as $node) { - $this->outputLine(' - remove: ' . $node->getContextPath()); - $node->remove(); - } - $this->outputLine('Done'); - } - - /** - * Make sure all values from default are present in the target dimension aswell - * - * @param string $dimensionName - * @param string $dimensionValue - * @return void - */ - public function populateDimensionCommand($dimensionName, $dimensionValue) - { - $taxonomyRoot = $this->taxonomyService->getRoot(); - $targetSubgraph = $this->dimensionService->getDimensionSubgraphByTargetValues([$dimensionName => $dimensionValue]); - - if (!$targetSubgraph) { - $this->outputLine('Target subgraph not found'); - $this->quit(1); - } - - $targetContextValues = $this->dimensionService->getDimensionValuesForSubgraph($targetSubgraph); - $flowQuery = new FlowQuery([$taxonomyRoot]); - $taxonomyRootInTargetContest = $flowQuery->context($targetContextValues)->get(0); - - if (!$taxonomyRootInTargetContest) { - $this->outputLine('Not root in target context found'); - $this->quit(1); - } - - if ($taxonomyRootInTargetContest == $taxonomyRoot) { - $this->outputLine('The root is the default context and cannot be recreated'); - $this->quit(1); - } - - if ($taxonomyRootInTargetContest == $taxonomyRoot) { - $this->outputLine('The root is the default context and cannot be recreated'); - $this->quit(1); - } - - $this->outputLine('Populating taxonomy content from default below' . $taxonomyRootInTargetContest->getContextPath()); - $targetContext = $taxonomyRootInTargetContest->getContext(); - $flowQuery = new FlowQuery([$taxonomyRoot]); - $allNodes = $flowQuery->find('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . '],[instanceof ' . $this->taxonomyService->getTaxonomyNodeType() . ']')->get(); - foreach ($allNodes as $node) { - $this->outputLine(' - adopt: ' . $node->getContextPath()); - $targetContext->adoptNode($node); - } - $this->outputLine('Done'); - } +// +// /** +// * Import taxonomy content +// * +// * @param string $filename relative path and filename to the XML file to read. +// * @param string $vocabularyNode vocabularay nodename(path) to import (globbing is supported) +// * @return void +// */ +// public function importCommand($filename, $vocabulary = null) +// { +// $xmlReader = new \XMLReader(); +// $xmlReader->open($filename, null, LIBXML_PARSEHUGE); +// +// $taxonomyRoot = $this->taxonomyService->getRoot(); +// +// while ($xmlReader->read()) { +// if ($xmlReader->nodeType != \XMLReader::ELEMENT || $xmlReader->name !== 'vocabulary') { +// continue; +// } +// +// $vocabularyName = (string) $xmlReader->getAttribute('name'); +// if (is_string($vocabulary) && fnmatch($vocabulary, $vocabularyName) == false) { +// continue; +// } +// +// $this->nodeImportService->import($xmlReader, $taxonomyRoot->getPath()); +// $this->outputLine('Imported vocabulary %s from file %s', [$vocabularyName, $filename]); +// } +// } +// +// /** +// * Export taxonomy content +// * +// * @param string $filename filename for the xml that is written. +// * @param string $vocabularyNode vocabularay nodename(path) to export (globbing is supported) +// * @return void +// */ +// public function exportCommand($filename, $vocabulary = null) +// { +// $xmlWriter = new \XMLWriter(); +// $xmlWriter->openUri($filename); +// $xmlWriter->setIndent(true); +// +// $xmlWriter->startDocument('1.0', 'UTF-8'); +// $xmlWriter->startElement('root'); +// +// $taxonomyRoot = $this->taxonomyService->getRoot(); +// +// /** +// * @var NodeInterface[] $vocabularyNodes +// */ +// $vocabularyNodes = (new FlowQuery([$taxonomyRoot])) +// ->children('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ' ]') +// ->get(); +// +// /** +// * @var NodeInterface $vocabularyNode +// */ +// foreach ($vocabularyNodes as $vocabularyNode) { +// $vocabularyName = $vocabularyNode->getName(); +// if (is_string($vocabulary) && fnmatch($vocabulary, $vocabularyName) == false) { +// continue; +// } +// $xmlWriter->startElement('vocabulary'); +// $xmlWriter->writeAttribute('name', $vocabularyName); +// $this->nodeExportService->export($vocabularyNode->getPath(), 'live', $xmlWriter, false, false); +// $this->outputLine('Exported vocabulary %s to file %s', [$vocabularyName, $filename]); +// $xmlWriter->endElement(); +// } +// +// $xmlWriter->endElement(); +// $xmlWriter->endDocument(); +// +// $xmlWriter->flush(); +// } +// +// /** +// * Prune taxonomy content +// * +// * @param string $vocabularyNode vocabularay nodename(path) to prune (globbing is supported) +// * @return void +// */ +// public function pruneCommand($vocabulary) +// { +// $taxonomyRoot = $this->taxonomyService->getRoot(); +// +// /** +// * @var NodeInterface[] $vocabularyNodes +// */ +// $vocabularyNodes = (new FlowQuery([$taxonomyRoot])) +// ->children('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ' ]') +// ->get(); +// +// /** +// * @var NodeInterface $vocabularyNode +// */ +// foreach ($vocabularyNodes as $vocabularyNode) { +// $vocabularyName = $vocabularyNode->getName(); +// if (is_string($vocabulary) && fnmatch($vocabulary, $vocabularyName) == false) { +// continue; +// } +// $this->nodeDataRepository->removeAllInPath($vocabularyNode->getPath()); +// $dimensionNodes = $this->nodeDataRepository->findByPath($vocabularyNode->getPath()); +// foreach ($dimensionNodes as $node) { +// $this->nodeDataRepository->remove($node); +// } +// +// $this->outputLine('Pruned vocabulary %s', [$vocabularyName]); +// } +// } +// +// /** +// * Reset a taxonimy dimension and create fresh variants from the base dimension +// * +// * @param string $dimensionName +// * @param string $dimensionValue +// * @return void +// */ +// public function pruneDimensionCommand($dimensionName, $dimensionValue) +// { +// $taxonomyRoot = $this->taxonomyService->getRoot(); +// $targetSubgraph = $this->dimensionService->getDimensionSubgraphByTargetValues([$dimensionName => $dimensionValue]); +// +// if (!$targetSubgraph) { +// $this->outputLine('Target subgraph not found'); +// $this->quit(1); +// } +// +// $targetContextValues = $this->dimensionService->getDimensionValuesForSubgraph($targetSubgraph); +// $flowQuery = new FlowQuery([$taxonomyRoot]); +// $taxonomyRootInTargetContest = $flowQuery->context($targetContextValues)->get(0); +// +// if (!$taxonomyRootInTargetContest) { +// $this->outputLine('Not root in target context found'); +// $this->quit(1); +// } +// +// if ($taxonomyRootInTargetContest == $taxonomyRoot) { +// $this->outputLine('The root is the default context and cannot be pruned'); +// $this->quit(1); +// } +// +// $this->outputLine('Removing content all below ' . $taxonomyRootInTargetContest->getContextPath()); +// $flowQuery = new FlowQuery([$taxonomyRootInTargetContest]); +// $allNodes = $flowQuery->find('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . '],[instanceof ' . $this->taxonomyService->getTaxonomyNodeType() . ']')->get(); +// foreach ($allNodes as $node) { +// $this->outputLine(' - remove: ' . $node->getContextPath()); +// $node->remove(); +// } +// $this->outputLine('Done'); +// } +// +// /** +// * Make sure all values from default are present in the target dimension aswell +// * +// * @param string $dimensionName +// * @param string $dimensionValue +// * @return void +// */ +// public function populateDimensionCommand($dimensionName, $dimensionValue) +// { +// $taxonomyRoot = $this->taxonomyService->getRoot(); +// $targetSubgraph = $this->dimensionService->getDimensionSubgraphByTargetValues([$dimensionName => $dimensionValue]); +// +// if (!$targetSubgraph) { +// $this->outputLine('Target subgraph not found'); +// $this->quit(1); +// } +// +// $targetContextValues = $this->dimensionService->getDimensionValuesForSubgraph($targetSubgraph); +// $flowQuery = new FlowQuery([$taxonomyRoot]); +// $taxonomyRootInTargetContest = $flowQuery->context($targetContextValues)->get(0); +// +// if (!$taxonomyRootInTargetContest) { +// $this->outputLine('Not root in target context found'); +// $this->quit(1); +// } +// +// if ($taxonomyRootInTargetContest == $taxonomyRoot) { +// $this->outputLine('The root is the default context and cannot be recreated'); +// $this->quit(1); +// } +// +// if ($taxonomyRootInTargetContest == $taxonomyRoot) { +// $this->outputLine('The root is the default context and cannot be recreated'); +// $this->quit(1); +// } +// +// $this->outputLine('Populating taxonomy content from default below' . $taxonomyRootInTargetContest->getContextPath()); +// $targetContext = $taxonomyRootInTargetContest->getContext(); +// $flowQuery = new FlowQuery([$taxonomyRoot]); +// $allNodes = $flowQuery->find('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . '],[instanceof ' . $this->taxonomyService->getTaxonomyNodeType() . ']')->get(); +// foreach ($allNodes as $node) { +// $this->outputLine(' - adopt: ' . $node->getContextPath()); +// $targetContext->adoptNode($node); +// } +// $this->outputLine('Done'); +// } } diff --git a/Classes/Controller/ModuleController.php b/Classes/Controller/ModuleController.php index cbaa8bb..6eb17a5 100644 --- a/Classes/Controller/ModuleController.php +++ b/Classes/Controller/ModuleController.php @@ -12,22 +12,36 @@ * source code. */ +use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; +use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; +use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetNodeProperties; +use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite; +use Neos\ContentRepository\Core\Feature\NodeRemoval\Command\RemoveNodeAggregate; +use Neos\ContentRepository\Core\Feature\NodeVariation\Command\CreateNodeVariant; +use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\NodeType\NodeTypeNames; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSubtreeFilter; +use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTypeConstraints; +use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy; +use Neos\ContentRepository\Core\SharedModel\User\UserId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Error\Messages\Error; use Neos\Error\Messages\Message; use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\View\ViewInterface; use Neos\Flow\Mvc\Controller\ActionController; use Neos\Fusion\View\FusionView; -use Sitegeist\Taxonomy\Service\DimensionService; +use Neos\Neos\FrontendRouting\NodeAddress; +use Neos\Neos\FrontendRouting\NodeAddressFactory; use Sitegeist\Taxonomy\Service\TaxonomyService; -use Neos\ContentRepository\Domain\Service\ContextFactoryInterface; use Neos\Eel\FlowQuery\FlowQuery; -use Neos\ContentRepository\Domain\Model\NodeTemplate; -use Neos\ContentRepository\Domain\Service\NodeTypeManager; use Neos\Flow\Persistence\PersistenceManagerInterface; -use Neos\ContentRepository\Domain\Model\NodeInterface; -use Neos\ContentRepository\Domain\Service\NodeServiceInterface; -use Neos\ContentRepository\Utility as CrUtitlity; use Neos\Utility\Arrays; /** @@ -46,23 +60,6 @@ class ModuleController extends ActionController */ protected $view; - /** - * @Flow\Inject - * @var ContextFactoryInterface - */ - protected $contextFactory; - - /** - * @Flow\Inject - * @var NodeTypeManager - */ - protected $nodeTypeManager; - - /** - * @Flow\Inject - * @var NodeServiceInterface - */ - protected $nodeService; /** * @Flow\Inject @@ -77,27 +74,31 @@ class ModuleController extends ActionController protected $additionalFusionIncludePathes; /** - * @var string - * @Flow\InjectConfiguration(package="Neos.ContentRepository", path="contentDimensions") + * @var TaxonomyService + * @Flow\Inject */ - protected $contentDimensions; + protected $taxonomyService; /** - * @var DimensionService - * @Flow\Inject + * @var NodeInterface */ - protected $dimensionService; + protected $defaultRoot; /** - * @var TaxonomyService - * @Flow\Inject + * @var ContentRepository */ - protected $taxonomyService; + protected $contentRepository; /** - * @var NodeInterface + * @var NodeAddressFactory */ - protected $defaultRoot; + protected $nodeAddressFactory; + + public function initializeObject() + { + $this->contentRepository = $this->taxonomyService->getContentRepository(); + $this->nodeAddressFactory = NodeAddressFactory::create($this->contentRepository); + } /** * Initialize the view @@ -112,411 +113,408 @@ public function initializeView(ViewInterface $view) $fusionPathes = Arrays::arrayMergeRecursiveOverrule($fusionPathes, $this->additionalFusionIncludePathes); } $this->view->setFusionPathPatterns($fusionPathes); - $this->view->assign('contentDimensionOptions', $this->getContentDimensionOptions()); } /** * Show an overview of available vocabularies - * - * @param NodeInterface $root - * @return void */ - public function indexAction(NodeInterface $root = null) + public function indexAction(string $rootNodeAddress = null): void { - if (!$root) { - $root = $this->taxonomyService->getRoot(); - } + if (is_null($rootNodeAddress)) { + $subgraph = $this->taxonomyService->findSubgraph(); + $rootNode = $this->taxonomyService->getRoot($subgraph); + } else { + $rootNode = $this->getNodeByNodeAddress($rootNodeAddress); + $subgraph = $this->getSubgraphForNode($rootNode); + } - $flowQuery = new FlowQuery([$root]); - $vocabularyNodes = $flowQuery->children('[instanceof Sitegeist.Taxonomy:Vocabulary]')->get(); + $vocabularies = $this->taxonomyService->getVocabularies($subgraph); - // fetch name and base node of vocabulary - $vocabularies = []; - foreach ($vocabularyNodes as $vocabulary) { - $vocabularies[] = [ - 'node' => $vocabulary, - 'defaultNode' => $this->getNodeInDefaultDimensions($vocabulary) - ]; - } - usort($vocabularies, function (array $vocabularyA, array $vocabularyB) { - return strcmp( - $vocabularyA['node']->getProperty('title') ?: '', - $vocabularyB['node']->getProperty('title') ?: '' - ); - }); - - $this->view->assign('taxonomyRoot', $root); + $this->view->assign('rootNode', $rootNode); + $this->view->assign('rootNodeAddress', $rootNode ? $this->nodeAddressFactory->createFromNode($rootNode)->serializeForUri() : null); $this->view->assign('vocabularies', $vocabularies); } - /** - * Switch to a modified content context and redirect to the given action - * - * @param string $targetAction the target action to redirect to - * @param string $targetProperty the property in the target action that will accept the node - * @param NodeInterface $contextNode the node to adjust the context for - * @param array $dimensions array with dimensionName, presetName combinations - * @return void - */ - public function changeContextAction($targetAction, $targetProperty, NodeInterface $contextNode, $dimensions = []) - { - $contextProperties = $contextNode->getContext()->getProperties(); - - $newContextProperties = []; - foreach ($dimensions as $dimensionName => $presetName) { - $newContextProperties['dimensions'][$dimensionName] = $this->getContentDimensionValues( - $dimensionName, - $presetName - ); - $newContextProperties['targetDimensions'][$dimensionName] = $presetName; - } - $modifiedContext = $this->contextFactory->create(array_merge($contextProperties, $newContextProperties)); - - $nodeInModifiedContext = $modifiedContext->getNodeByIdentifier($contextNode->getIdentifier()); - - $this->redirect($targetAction, null, null, [$targetProperty => $nodeInModifiedContext]); - } - - /** - * Prepare all available content dimensions for use in a select box - * - * @return array the list of available content dimensions and their presets - */ - protected function getContentDimensionOptions() - { - $result = []; - - if (is_array($this->contentDimensions) === false || count($this->contentDimensions) === 0) { - return $result; - } - - foreach ($this->contentDimensions as $dimensionName => $dimensionConfiguration) { - $dimensionOption = []; - $dimensionOption['label'] = array_key_exists('label', $dimensionConfiguration) ? - $dimensionConfiguration['label'] : $dimensionName; - $dimensionOption['presets'] = []; - - foreach ($dimensionConfiguration['presets'] as $presetKey => $presetConfiguration) { - $dimensionOption['presets'][$presetKey] = array_key_exists('label', $presetConfiguration) ? - $presetConfiguration['label'] : $presetKey; - } - - $result[$dimensionName] = $dimensionOption; - } - - return $result; - } - - /** - * Get the content dimension values for a given content dimension and preset - * - * @param $dimensionName - * @param $presetName - * @return array the values assiged to the preset identified by $dimensionName and $presetName - */ - protected function getContentDimensionValues($dimensionName, $presetName) - { - return $this->contentDimensions[$dimensionName]['presets'][$presetName]['values']; - } - - /** - * @param NodeInterface $node - * @return NodeInterface|null - */ - protected function getNodeInDefaultDimensions(NodeInterface $node) : ?NodeInterface - { - if (!$this->defaultRoot) { - $this->defaultRoot = $this->taxonomyService->getRoot(); - } - - $flowQuery = new FlowQuery([$this->defaultRoot]); - $defaultNode = $flowQuery->find('#' . $node->getIdentifier())->get(0); - if ($defaultNode && $defaultNode !== $node) { - return $defaultNode; - } else { - return null; - } - } - - /** - * @param NodeInterface $node - * @param array $parents - * @return array - */ - public function fetchChildTaxonomies(NodeInterface $node, array $parents = []) : array - { - $flowQuery = new FlowQuery([$node]); - $childTaxonomies = $flowQuery->children('[instanceof ' . $this->taxonomyService->getTaxonomyNodeType() . ']')->get(); - $result = []; - foreach ($childTaxonomies as $childTaxonomy) { - $result[] = [ - 'node' => $childTaxonomy, - 'defaultNode' => $this->getNodeInDefaultDimensions($childTaxonomy), - 'children' => $this->fetchChildTaxonomies($childTaxonomy, array_merge($parents, [$childTaxonomy])), - 'parents' => $parents - ]; - } - return $result; - } +// /** +// * Switch to a modified content context and redirect to the given action +// * +// * @param string $targetAction the target action to redirect to +// * @param string $targetProperty the property in the target action that will accept the node +// * @param NodeInterface $contextNode the node to adjust the context for +// * @param array $dimensions array with dimensionName, presetName combinations +// * @return void +// */ +// public function changeContextAction($targetAction, $targetProperty, NodeInterface $contextNode, $dimensions = []) +// { +// $contextProperties = $contextNode->getContext()->getProperties(); +// +// $newContextProperties = []; +// foreach ($dimensions as $dimensionName => $presetName) { +// $newContextProperties['dimensions'][$dimensionName] = $this->getContentDimensionValues( +// $dimensionName, +// $presetName +// ); +// $newContextProperties['targetDimensions'][$dimensionName] = $presetName; +// } +// $modifiedContext = $this->contextFactory->create(array_merge($contextProperties, $newContextProperties)); +// +// $nodeInModifiedContext = $modifiedContext->getNodeByIdentifier($contextNode->getIdentifier()); +// +// $this->redirect($targetAction, null, null, [$targetProperty => $nodeInModifiedContext]); +// } +// +// /** +// * @param NodeInterface $node +// * @return NodeInterface|null +// */ +// protected function getNodeInDefaultDimensions(NodeInterface $node) : ?NodeInterface +// { +// if (!$this->defaultRoot) { +// $this->defaultRoot = $this->taxonomyService->getRoot(); +// } +// +// $flowQuery = new FlowQuery([$this->defaultRoot]); +// $defaultNode = $flowQuery->find('#' . $node->getIdentifier())->get(0); +// if ($defaultNode && $defaultNode !== $node) { +// return $defaultNode; +// } else { +// return null; +// } +// } /** * Show the given vocabulary - * - * @param NodeInterface $vocabulary - * @return void */ - public function vocabularyAction(NodeInterface $vocabulary) + public function vocabularyAction(string $vocabularyNodeAddress) { - $flowQuery = new FlowQuery([$vocabulary]); - $root = $flowQuery->closest('[instanceof ' . $this->taxonomyService->getRootNodeType() . ']')->get(0); - - $this->view->assign('taxonomyRoot', $root); - $this->view->assign('vocabulary', $vocabulary); - $this->view->assign('defaultVocabulary', $this->getNodeInDefaultDimensions($vocabulary)); - $taxonomies = $this->fetchChildTaxonomies($vocabulary); - usort($taxonomies, function (array $taxonomyA, array $taxonomyB) { - return strcmp( - $taxonomyA['node']->getProperty('title') ?: '', - $taxonomyB['node']->getProperty('title') ?: '' - ); - }); - $this->view->assign('taxonomies', $taxonomies); + $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); + $subgraph = $this->getSubgraphForNode($vocabularyNode); + + $rootNode = $this->taxonomyService->getRoot($subgraph); + $this->view->assign('rootNode', $rootNode); + $this->view->assign('vocabularyNode', $vocabularyNode); + + $vocabularySubtree = $subgraph->findSubtree( + $vocabularyNode->nodeAggregateId, + FindSubtreeFilter::create( + $this->taxonomyService->getTaxonomyNodeType() + ) + ); + + $this->view->assign('vocabularySubtree', $vocabularySubtree); } /** * Display a form that allows to create a new vocabulary - * - * @param NodeInterface $taxonomyRoot - * @return void */ - public function newVocabularyAction(NodeInterface $taxonomyRoot) + public function newVocabularyAction(string $rootNodeAddress = null): void { - $this->view->assign('taxonomyRoot', $taxonomyRoot); - + $node = $this->getNodeByNodeAddress($rootNodeAddress); + $this->view->assign('rootNode', $node); } /** * Create a new vocabulary * - * @param NodeInterface $taxonomyRoot + * @param string $rootNodeAddress root node address * @param array $properties - * @return void */ - public function createVocabularyAction(NodeInterface $taxonomyRoot, array $properties) + public function createVocabularyAction(string $rootNodeAddress, array $properties) { - $vocabularyNodeType = $this->nodeTypeManager->getNodeType($this->taxonomyService->getVocabularyNodeType()); - $vocabularyProperties = $vocabularyNodeType->getProperties(); - - $nodeTemplate = new NodeTemplate(); - $nodeTemplate->setNodeType($vocabularyNodeType); - $nodeTemplate->setName(CrUtitlity::renderValidNodeName($properties['title'])); - foreach($properties as $name => $value) { - if (array_key_exists($name, $vocabularyProperties)) { - $nodeTemplate->setProperty($name, $value); + $contentRepository = $this->taxonomyService->getContentRepository(); + + $rootNode = $this->getNodeByNodeAddress($rootNodeAddress); + $subgraph = $this->getSubgraphForNode($rootNode); + $liveWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()); + $generalizations = $contentRepository->getVariationGraph()->getRootGeneralizations(); + $nodeAddress = $this->nodeAddressFactory->createFromUriString($rootNodeAddress); + $nodeAggregateId = NodeAggregateId::create(); + $originDimensionSpacePoint = OriginDimensionSpacePoint::fromDimensionSpacePoint($nodeAddress->dimensionSpacePoint); + + // create node + $commandResult = $contentRepository->handle( + new CreateNodeAggregateWithNode( + $liveWorkspace->currentContentStreamId, + $nodeAggregateId, + $this->taxonomyService->getVocabularyNodeTypeName(), + $originDimensionSpacePoint, + $rootNode->nodeAggregateId, + null, + null, + PropertyValuesToWrite::fromArray($properties) + ) + ); + $commandResult->block(); + + // create required generalizations + foreach ($generalizations as $dimensionSpacePoint) { + $originDimensionSpacePoint2 = OriginDimensionSpacePoint::fromDimensionSpacePoint($dimensionSpacePoint); + if ($originDimensionSpacePoint->equals($originDimensionSpacePoint2)) { + continue; } + + $commandResult = $contentRepository->handle( + new CreateNodeVariant( + $liveWorkspace->currentContentStreamId, + $nodeAggregateId, + $originDimensionSpacePoint, + $originDimensionSpacePoint2 + ) + ); + $commandResult->block(); } - $vocabulary = $taxonomyRoot->createNodeFromTemplate($nodeTemplate); + $newVocabularyNode = $subgraph->findNodeById($nodeAggregateId); $this->addFlashMessage( - sprintf('Created vocabulary %s at path %s', $properties['title'], $vocabulary->getLabel()) + sprintf('Created vocabulary %s', $newVocabularyNode->getLabel()), + 'Create Vocabulary' ); - $this->redirect('index', null, null, ['root' => $taxonomyRoot]); + + $this->redirect('index'); } /** * Show a form that allows to modify the given vocabulary - * - * @param NodeInterface $vocabulary - * @return void */ - public function editVocabularyAction(NodeInterface $vocabulary) + public function editVocabularyAction(string $vocabularyNodeAddress) { - $taxonomyRoot = $this->taxonomyService->getRoot($vocabulary->getContext()); - $this->view->assign('taxonomyRoot', $taxonomyRoot); - $this->view->assign('vocabulary', $vocabulary); - $this->view->assign('defaultVocabulary', $this->getNodeInDefaultDimensions($vocabulary)); + $contentRepository = $this->taxonomyService->getContentRepository(); + $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); + + $subgraph = $contentRepository->getContentGraph()->getSubgraph( + $vocabularyNode->subgraphIdentity->contentStreamId, + $vocabularyNode->subgraphIdentity->dimensionSpacePoint, + $vocabularyNode->subgraphIdentity->visibilityConstraints, + ); + + $rootNode = $this->taxonomyService->getRoot($subgraph); + + $this->view->assign('rootNode', $rootNode); + $this->view->assign('vocabularyNode', $vocabularyNode); } /** * Apply changes to the given vocabulary - * - * @param NodeInterface $vocabulary - * @param array $properties - * @return void */ - public function updateVocabularyAction(NodeInterface $vocabulary, array $properties) + public function updateVocabularyAction(string $vocabularyNodeAddress, array $properties) { - $taxonomyRoot = $this->taxonomyService->getRoot($vocabulary->getContext()); - $vocabularyProperties = $vocabulary->getNodeType()->getProperties(); - foreach($properties as $name => $value) { - if (array_key_exists($name, $vocabularyProperties)) { - $previous = $vocabulary->getProperty($name); - if ($previous !== $value) { - $vocabulary->setProperty($name, $value); - } - } - } + $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); + $subgraph = $this->getSubgraphForNode($vocabularyNode); + $rootNode = $this->taxonomyService->getRoot($subgraph); + + $commandResult = $this->contentRepository->handle( + new SetNodeProperties( + $vocabularyNode->subgraphIdentity->contentStreamId, + $vocabularyNode->nodeAggregateId, + $vocabularyNode->originDimensionSpacePoint, + PropertyValuesToWrite::fromArray($properties) + ) + ); + $commandResult->block(); + $updatedVocabularyNode = $subgraph->findNodeById($vocabularyNode->nodeAggregateId); $this->addFlashMessage( - sprintf('Updated vocabulary %s', $vocabulary->getLabel()) + sprintf('Updated vocabulary %s', $updatedVocabularyNode->getLabel()) ); - $this->redirect('index', null, null, ['root' => $taxonomyRoot]); + $this->redirect('index', null, null, ['rootNodeAddress' => $this->nodeAddressFactory->createFromNode($rootNode)]); } /** * Delete the given vocabulary - * - * @param NodeInterface $vocabulary - * @return void - * @throws \Exception */ - public function deleteVocabularyAction(NodeInterface $vocabulary) + public function deleteVocabularyAction(string $vocabularyNodeAddress) { - if ($vocabulary->isAutoCreated()) { - throw new \Exception('cannot delete autocrated vocabularies'); - } else { - $path = $vocabulary->getPath(); - $vocabulary->remove(); - $this->addFlashMessage( - sprintf('Deleted vocabulary %s', $path) - ); - } - $taxonomyRoot = $this->taxonomyService->getRoot($vocabulary->getContext()); - $this->redirect('index', null, null, ['root' => $taxonomyRoot]); + $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); + $subgraph = $this->getSubgraphForNode($vocabularyNode); + $rootNode = $this->taxonomyService->getRoot($subgraph); + $liveWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName(\Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName::forLive()); + + $commandResult = $this->contentRepository->handle( + new RemoveNodeAggregate( + $liveWorkspace->currentContentStreamId, + $vocabularyNode->nodeAggregateId, + $vocabularyNode->originDimensionSpacePoint->toDimensionSpacePoint(), + NodeVariantSelectionStrategy::STRATEGY_ALL_VARIANTS + ) + ); + $commandResult->block(); + + $this->addFlashMessage( + sprintf('Deleted vocabulary %s', $vocabularyNode->getLabel()) + ); + + $this->redirect('index', null, null, ['rootNodeAddress' => $this->nodeAddressFactory->createFromNode($rootNode)]); + } /** * Show a form to create a new taxonomy - * - * @param NodeInterface $parent - * @return void */ - public function newTaxonomyAction(NodeInterface $parent) + public function newTaxonomyAction(string $parentNodeAddress) { - $flowQuery = new FlowQuery([$parent]); - $vocabulary = $flowQuery->closest('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ']')->get(0); - $this->view->assign('vocabulary', $vocabulary); - $this->view->assign('parent', $parent); + $parentNode = $this->getNodeByNodeAddress($parentNodeAddress); + $subgraph = $this->getSubgraphForNode($parentNode); + $rootNode = $this->taxonomyService->getRoot($subgraph); + $vocabularyNode = null; + + if ($parentNode->nodeType->isOfType($this->taxonomyService->getTaxonomyNodeType())) { + $vocabularyNode = $this->taxonomyService->findVocabularyForNode($parentNode); + } elseif ($parentNode->nodeType->isOfType($this->taxonomyService->getVocabularyNodeType())) { + $vocabularyNode = $parentNode; + } + + $this->view->assign('rootNode', $rootNode); + $this->view->assign('vocabularyNode', $vocabularyNode); + $this->view->assign('parentNode', $parentNode); } /** * Create a new taxonomy - * - * @param NodeInterface $parent - * @param array $properties - * @return void */ - public function createTaxonomyAction(NodeInterface $parent, array $properties) + public function createTaxonomyAction(string $parentNodeAddress, array $properties): void { - $taxonomyNodeType = $this->nodeTypeManager->getNodeType($this->taxonomyService->getTaxonomyNodeType()); - $taxomonyProperties = $taxonomyNodeType->getProperties(); - - $nodeTemplate = new NodeTemplate(); - $nodeTemplate->setNodeType($taxonomyNodeType); - $nodeTemplate->setName(CrUtitlity::renderValidNodeName($properties['title'])); + $parentNode = $this->getNodeByNodeAddress($parentNodeAddress); + $vocabularyNode = $this->taxonomyService->findVocabularyForNode($parentNode); + $subgraph = $this->getSubgraphForNode($parentNode); + $liveWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()); + $generalizations = $this->contentRepository->getVariationGraph()->getRootGeneralizations(); + $nodeAddress = $this->nodeAddressFactory->createFromUriString($parentNodeAddress); + $nodeAggregateId = NodeAggregateId::create(); + $originDimensionSpacePoint = OriginDimensionSpacePoint::fromDimensionSpacePoint($nodeAddress->dimensionSpacePoint); + + // create node + $commandResult = $this->contentRepository->handle( + new CreateNodeAggregateWithNode( + $liveWorkspace->currentContentStreamId, + $nodeAggregateId, + $this->taxonomyService->getTaxonomyNodeTypeName(), + $originDimensionSpacePoint, + $parentNode->nodeAggregateId, + null, + null, + PropertyValuesToWrite::fromArray($properties) + ) + ); + $commandResult->block(); - foreach($properties as $name => $value) { - if (array_key_exists($name, $taxomonyProperties)) { - $nodeTemplate->setProperty($name, $value); + // create required generalizations + foreach ($generalizations as $dimensionSpacePoint) { + $originDimensionSpacePoint2 = OriginDimensionSpacePoint::fromDimensionSpacePoint($dimensionSpacePoint); + if ($originDimensionSpacePoint->equals($originDimensionSpacePoint2)) { + continue; } + + $commandResult = $this->contentRepository->handle( + new CreateNodeVariant( + $liveWorkspace->currentContentStreamId, + $nodeAggregateId, + $originDimensionSpacePoint, + $originDimensionSpacePoint2 + ) + ); + $commandResult->block(); } - $taxonomy = $parent->createNodeFromTemplate($nodeTemplate); + $newTaxonomyNode = $subgraph->findNodeById($nodeAggregateId); $this->addFlashMessage( - sprintf('Created taxonomy %s at path %s', $taxonomy->getLabel(), $taxonomy->getPath()) + sprintf('Created taxonomy %s', $newTaxonomyNode->getLabel()), + 'Create taxomony' ); - $flowQuery = new FlowQuery([$taxonomy]); - $vocabulary = $flowQuery - ->closest('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ']') - ->get(0); - $this->redirect( 'vocabulary', null, null, - ['vocabulary' => $vocabulary->getContextPath()] + ['vocabularyNodeAddress' => $this->nodeAddressFactory->createFromNode($vocabularyNode)] ); } /** * Display a form that allows to modify the given taxonomy - * - * @param NodeInterface $taxonomy - * @return void */ - public function editTaxonomyAction(NodeInterface $taxonomy) + public function editTaxonomyAction(string $taxonomyNodeAddress) { - $flowQuery = new FlowQuery([$taxonomy]); - $vocabulary = $flowQuery - ->closest('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ']') - ->get(0); + $taxonomyNode = $this->getNodeByNodeAddress($taxonomyNodeAddress); + $vocabularyNode = $this->taxonomyService->findVocabularyForNode($taxonomyNode); - $this->view->assign('vocabulary', $vocabulary); - $this->view->assign('defaultVocabulary', $this->getNodeInDefaultDimensions($vocabulary)); - - $this->view->assign('taxonomy', $taxonomy); - $this->view->assign('defaultTaxonomy', $this->getNodeInDefaultDimensions($taxonomy)); + $this->view->assign('vocabularyNode', $vocabularyNode); + $this->view->assign('taxonomyNode', $taxonomyNode); } /** * Apply changes to the given taxonomy - * - * @param NodeInterface $taxonomy - * @param array $properties - * @return void */ - public function updateTaxonomyAction(NodeInterface $taxonomy, array $properties) + public function updateTaxonomyAction(string $taxonomyNodeAddress, array $properties) { - $taxonomyProperties = $taxonomy->getNodeType()->getProperties(); - foreach($properties as $name => $value) { - if (array_key_exists($name, $taxonomyProperties)) { - $previous = $taxonomy->getProperty($name); - if ($previous !== $value) { - $taxonomy->setProperty($name, $value); - } - } - } + $taxonomyNode = $this->getNodeByNodeAddress($taxonomyNodeAddress); + $vocabularyNode = $this->taxonomyService->findVocabularyForNode($taxonomyNode); + $subgraph = $this->getSubgraphForNode($taxonomyNode); + + $commandResult = $this->contentRepository->handle( + new SetNodeProperties( + $taxonomyNode->subgraphIdentity->contentStreamId, + $taxonomyNode->nodeAggregateId, + $taxonomyNode->originDimensionSpacePoint, + PropertyValuesToWrite::fromArray($properties) + ) + ); + $commandResult->block(); + $updatedTaxonomyNode = $subgraph->findNodeById($vocabularyNode->nodeAggregateId); $this->addFlashMessage( - sprintf('Updated taxonomy %s', $taxonomy->getPath()) + sprintf('Updated taxonomy %s', $updatedTaxonomyNode->getLabel()) ); - $flowQuery = new FlowQuery([$taxonomy]); - $vocabulary = $flowQuery - ->closest('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ']') - ->get(0); - - $this->redirect('vocabulary', null, null, ['vocabulary' => $vocabulary->getContextPath()]); + $this->redirect('vocabulary', null, null, ['vocabularyNodeAddress' => $this->nodeAddressFactory->createFromNode($vocabularyNode)]); } /** * Delete the given taxonomy - * - * @param NodeInterface $taxonomy - * @return void */ - public function deleteTaxonomyAction(NodeInterface $taxonomy) + public function deleteTaxonomyAction(string $taxonomyNodeAddress) { - if ($taxonomy->isAutoCreated()) { - throw new \Exception('cannot delete autocrated taxonomies'); - } - - $flowQuery = new FlowQuery([$taxonomy]); - $vocabulary = $flowQuery - ->closest('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ']') - ->get(0); - - $taxonomy->remove(); + $taxonomyNode = $this->getNodeByNodeAddress($taxonomyNodeAddress); + $vocabularyNode = $this->taxonomyService->findVocabularyForNode($taxonomyNode); + $liveWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()); + + $commandResult = $this->contentRepository->handle( + new RemoveNodeAggregate( + $liveWorkspace->currentContentStreamId, + $taxonomyNode->nodeAggregateId, + $taxonomyNode->originDimensionSpacePoint->toDimensionSpacePoint(), + NodeVariantSelectionStrategy::STRATEGY_ALL_VARIANTS + ) + ); + $commandResult->block(); $this->addFlashMessage( - sprintf('Deleted taxonomy %s', $taxonomy->getPath()) + sprintf('Deleted taxonomy %s', $taxonomyNode->getLabel()) ); - $this->redirect('vocabulary', null, null, ['vocabulary' => $vocabulary]); + $this->redirect('vocabulary', null, null, ['vocabularyNodeAddress' => $this->nodeAddressFactory->createFromNode($vocabularyNode)]); } + protected function getNodeByNodeAddress(?string $serializedNodeAddress): ?Node + { + $contentRepository = $this->taxonomyService->getContentRepository(); + $nodeAddress = NodeAddressFactory::create($contentRepository)->createFromUriString($serializedNodeAddress); + $subgraph = $contentRepository->getContentGraph()->getSubgraph( + $nodeAddress->contentStreamId, + $nodeAddress->dimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() + ); + return $subgraph->findNodeById($nodeAddress->nodeAggregateId); + } + protected function getSubgraphForNode(Node $node): ContentSubgraphInterface + { + $contentRepository = $this->taxonomyService->getContentRepository(); + return $contentRepository->getContentGraph()->getSubgraph( + $node->subgraphIdentity->contentStreamId, + $node->subgraphIdentity->dimensionSpacePoint, + $node->subgraphIdentity->visibilityConstraints, + ); + } } diff --git a/Classes/Hooks/ContentRepositoryHooks.php b/Classes/Hooks/ContentRepositoryHooks.php index 4e7c127..d35e235 100644 --- a/Classes/Hooks/ContentRepositoryHooks.php +++ b/Classes/Hooks/ContentRepositoryHooks.php @@ -14,56 +14,56 @@ class ContentRepositoryHooks { - /** - * @var TaxonomyService - * @Flow\Inject - */ - protected $taxonomyService; - - /** - * @var DimensionService - * @Flow\Inject - */ - protected $dimensionService; - - /** - * @var bool - */ - protected $preventCascade = false; - - /** - * Signal that is triggered on node create - * - * @param NodeInterface $node - */ - public function nodeAdded(NodeInterface $node) - { - if ($node->getNodeType()->isOfType($this->taxonomyService->getRootNodeType()) || - $node->getNodeType()->isOfType($this->taxonomyService->getVocabularyNodeType()) || - $node->getNodeType()->isOfType($this->taxonomyService->getTaxonomyNodeType())) { - if ($node->isAutoCreated() == false && $this->preventCascade == false) { - $this->preventCascade = true; - $this->dimensionService->ensureBaseVariantsExist($node); - $this->preventCascade = false; - } - } - } - - /** - * Signal that is triggered on node remove - * - * @param NodeInterface $node - */ - public function nodeRemoved(NodeInterface $node) - { - if ($node->getNodeType()->isOfType($this->taxonomyService->getRootNodeType()) || - $node->getNodeType()->isOfType($this->taxonomyService->getVocabularyNodeType()) || - $node->getNodeType()->isOfType($this->taxonomyService->getTaxonomyNodeType())) { - if ($node->isAutoCreated() == false && $this->preventCascade == false) { - $this->preventCascade = true; - $this->dimensionService->removeOtherVariants($node); - $this->preventCascade = false; - } - } - } +// /** +// * @var TaxonomyService +// * @Flow\Inject +// */ +// protected $taxonomyService; +// +// /** +// * @var DimensionService +// * @Flow\Inject +// */ +// protected $dimensionService; +// +// /** +// * @var bool +// */ +// protected $preventCascade = false; +// +// /** +// * Signal that is triggered on node create +// * +// * @param NodeInterface $node +// */ +// public function nodeAdded(NodeInterface $node) +// { +// if ($node->getNodeType()->isOfType($this->taxonomyService->getRootNodeType()) || +// $node->getNodeType()->isOfType($this->taxonomyService->getVocabularyNodeType()) || +// $node->getNodeType()->isOfType($this->taxonomyService->getTaxonomyNodeType())) { +// if ($node->isAutoCreated() == false && $this->preventCascade == false) { +// $this->preventCascade = true; +// $this->dimensionService->ensureBaseVariantsExist($node); +// $this->preventCascade = false; +// } +// } +// } +// +// /** +// * Signal that is triggered on node remove +// * +// * @param NodeInterface $node +// */ +// public function nodeRemoved(NodeInterface $node) +// { +// if ($node->getNodeType()->isOfType($this->taxonomyService->getRootNodeType()) || +// $node->getNodeType()->isOfType($this->taxonomyService->getVocabularyNodeType()) || +// $node->getNodeType()->isOfType($this->taxonomyService->getTaxonomyNodeType())) { +// if ($node->isAutoCreated() == false && $this->preventCascade == false) { +// $this->preventCascade = true; +// $this->dimensionService->removeOtherVariants($node); +// $this->preventCascade = false; +// } +// } +// } } diff --git a/Classes/Package.php b/Classes/Package.php index ed89312..670ff4e 100644 --- a/Classes/Package.php +++ b/Classes/Package.php @@ -19,21 +19,21 @@ public function boot(Bootstrap $bootstrap) if (PHP_SAPI === 'cli') { // no automagic on the cli } else { - $dispatcher = $bootstrap->getSignalSlotDispatcher(); - $dispatcher->connect( - Node::class, - 'nodeAdded', - ContentRepositoryHooks::class, - 'nodeAdded' - ); - - $dispatcher = $bootstrap->getSignalSlotDispatcher(); - $dispatcher->connect( - Node::class, - 'nodeRemoved', - ContentRepositoryHooks::class, - 'nodeRemoved' - ); +// $dispatcher = $bootstrap->getSignalSlotDispatcher(); +// $dispatcher->connect( +// Node::class, +// 'nodeAdded', +// ContentRepositoryHooks::class, +// 'nodeAdded' +// ); +// +// $dispatcher = $bootstrap->getSignalSlotDispatcher(); +// $dispatcher->connect( +// Node::class, +// 'nodeRemoved', +// ContentRepositoryHooks::class, +// 'nodeRemoved' +// ); } } } diff --git a/Classes/Service/DimensionService.php b/Classes/Service/DimensionService.php deleted file mode 100644 index 87fe259..0000000 --- a/Classes/Service/DimensionService.php +++ /dev/null @@ -1,175 +0,0 @@ -fallbackGraphService->getInterDimensionalFallbackGraph(); - // find all dimensionGraphs that have no fallbacks - $baseDimensionGraphs = array_filter( - $interDimensionalFallbackGraph->getSubgraphs(), - function ($subgraph) { - try { - $weight = $subgraph->getWeight(); - return (array_sum($weight) === 0); - } catch (\TypeError $e) { - // TODO: - // Yep, we're catching a TypeError here. That's because - // $subgraph->getWeight() is supposed to return an array, but it doesn't if - // there's no dimension configuration at all. This sure will be fixed in - // future releases of the content repository and should be adjusted at this - // point as well. - return true; - } - } - ); - - return $baseDimensionGraphs; - } - - /** - * @return array|ContentSubgraph[] - */ - public function getDimensionSubgraphByTargetValues($targetValues) - { - $interDimensionalFallbackGraph = $this->fallbackGraphService->getInterDimensionalFallbackGraph(); - $allSubgraphs = $interDimensionalFallbackGraph->getSubgraphs(); - $matchingSubgraphs = array_filter( - $allSubgraphs, - function($subgraph) use ($targetValues){ - foreach($subgraph->getDimensionValues() as $targetDimension => $targetValue) { - $value = (string) $subgraph->getDimensionValue($targetDimension); - if (!array_key_exists($targetDimension, $targetValues)) { - return false; - } - if ($targetValues[$targetDimension] != $value) { - return false; - } - } - return true; - } - ); - - if (count($matchingSubgraphs) == 1) { - return array_pop($matchingSubgraphs); - } - } - - /** - * @return array|ContentSubgraph[] - */ - public function getAllDimensionSubgraphs() - { - $interDimensionalFallbackGraph = $this->fallbackGraphService->getInterDimensionalFallbackGraph(); - return $interDimensionalFallbackGraph; - } - - /** - * @param NodeInterface $node - * @return NodeInterface[] new variants; - */ - public function ensureBaseVariantsExist(NodeInterface $node) - { - $results = []; - $baseDimensionSubgraphs = $this->getBaseDimensionSubgraphs(); - if (count($baseDimensionSubgraphs) > 0) { - $nodeContext = $node->getContext(); - foreach ($baseDimensionSubgraphs as $baseDimensionSubgraph) { - $baseDimensionValues = $this->getDimensionValuesForSubgraph($baseDimensionSubgraph); - $baseDimensionContext = array_merge($nodeContext->getProperties(), $baseDimensionValues); - $targetContext = $this->contextFactory->create($baseDimensionContext); - $adoptedNode = $targetContext->adoptNode($node, true); - $results[] = $adoptedNode; - } - } - $this->persistenceManager->persistAll(); - return $results; - } - - /** - * @param NodeInterface $node - * @return NodeInterface[] removed variants; - */ - public function removeOtherVariants(NodeInterface $node) - { - $results = []; - /** - * @var array $otherNodeVariants - */ - $otherNodeVariants = $node->getOtherNodeVariants(); - foreach ($otherNodeVariants as $nodeVariant) { - /** - * @var NodeInterface $nodeVariant - */ - $nodeVariant->remove(); - $results[] = $nodeVariant; - } - $this->persistenceManager->persistAll(); - return $results; - } - - /** - * @param ContentSubgraph $baseDimensionSubgraph - * @return array - */ - public function getDimensionValuesForSubgraph(ContentSubgraph $baseDimensionSubgraph): array - { - $baseDimensionValues = [ - 'dimensions' => array_map( - function (ContentDimensionValue $contentDimensionValue) { - return [$contentDimensionValue->getValue()]; - }, - $baseDimensionSubgraph->getDimensionValues() - ), - 'targetDimensions' => array_map( - function (ContentDimensionValue $contentDimensionValue) { - return $contentDimensionValue->getValue(); - }, - $baseDimensionSubgraph->getDimensionValues() - ), - ]; - return $baseDimensionValues; - } -} diff --git a/Classes/Service/TaxonomyService.php b/Classes/Service/TaxonomyService.php index 70733b9..8b75b96 100644 --- a/Classes/Service/TaxonomyService.php +++ b/Classes/Service/TaxonomyService.php @@ -1,6 +1,21 @@ rootNodeName; - } - /** * @return string */ @@ -112,56 +92,102 @@ public function getTaxonomyNodeType() return $this->taxonomyNodeType; } - /** - * @param Context $context - * @return NodeInterface - */ - public function getRoot(Context $context = null) + public function getRootNodeTypeName(): NodeTypeName { - if ($context === null) { - $context = $this->contextFactory->create(); - } + return NodeTypeName::fromString($this->rootNodeType); + } - $contextHash = md5(json_encode($context->getProperties())); + public function getVocabularyNodeTypeName(): NodeTypeName + { + return NodeTypeName::fromString($this->vocabularyNodeType); + } - // return memoized root-node - if (array_key_exists($contextHash, $this->taxonomyDataRootNodes) - && $this->taxonomyDataRootNodes[$contextHash] instanceof NodeInterface - ) { - return $this->taxonomyDataRootNodes[$contextHash]; - } + public function getTaxonomyNodeTypeName(): NodeTypeName + { + return NodeTypeName::fromString($this->taxonomyNodeType); + } + + public function getContentRepository(): ContentRepository + { + return $this->crRegistry->get(ContentRepositoryId::fromString($this->crIdentifier)); + } - // return existing root-node - // - // TODO: Find a better way to determine the root node - $taxonomyDataRootNodeData = $this->nodeDataRepository->findOneByPath( - '/' . $this->getRootNodeName(), - $context->getWorkspace() + public function findSubgraph(): ContentSubgraphInterface + { + $contentRepository = $this->getContentRepository(); + $liveWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()); + $generalizations = $contentRepository->getVariationGraph()->getRootGeneralizations(); + $contentGraph = $contentRepository->getContentGraph(); + $subgraph = $contentGraph->getSubgraph( + $liveWorkspace->currentContentStreamId, + reset($generalizations), + VisibilityConstraints::withoutRestrictions() ); + return $subgraph; + } - if ($taxonomyDataRootNodeData !== null) { - $this->taxonomyDataRootNodes[$contextHash] = $this->nodeFactory->createFromNodeData( - $taxonomyDataRootNodeData, - $context - ); + public function findVocabularyForNode(Node $node): Node + { + $subgraph = $this->getContentRepository()->getContentGraph()->getSubgraph( + $node->subgraphIdentity->contentStreamId, + $node->subgraphIdentity->dimensionSpacePoint, + $node->subgraphIdentity->visibilityConstraints, + ); - return $this->taxonomyDataRootNodes[$contextHash]; + $parentNode = $node; + while ($parentNode instanceof Node) { + if ($parentNode->nodeType->isOfType($this->getVocabularyNodeType())) { + return $parentNode; + } + $parentNode = $subgraph->findParentNode($parentNode->nodeAggregateId); + } + throw new \InvalidArgumentException('Node seems to be outside of vocabulary'); + } + + public function getRoot(ContentSubgraphInterface $subgraph): Node + { + $contentRepository = $this->getContentRepository(); + $liveWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()); + $contentGraph = $contentRepository->getContentGraph(); + + try { + $rootNodeAggregate = $contentGraph->findRootNodeAggregateByType( + $liveWorkspace->currentContentStreamId, + NodeTypeName::fromString($this->getRootNodeType()) + ); + return $subgraph->findNodeById($rootNodeAggregate->nodeAggregateId); + } catch (\Exception $e) { + // ignore and create a new root } - // create root-node - $nodeTemplate = new NodeTemplate(); - $nodeTemplate->setNodeType($this->nodeTypeManager->getNodeType($this->rootNodeType)); - $nodeTemplate->setName($this->getRootNodeName()); + $commandResult = $contentRepository->handle( + new CreateRootNodeAggregateWithNode( + $liveWorkspace->currentContentStreamId, + NodeAggregateId::create(), + NodeTypeName::fromString($this->getRootNodeType()), + UserId::forSystemUser() + ) + ); + $commandResult->block(); - $rootNode = $context->getRootNode(); - $this->taxonomyDataRootNodes[$contextHash] = $rootNode->createNodeFromTemplate($nodeTemplate); + $rootNodeAggregate = $contentGraph->findRootNodeAggregateByType( + $liveWorkspace->currentContentStreamId, + NodeTypeName::fromString($this->getRootNodeType()) + ); - // 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. - $this->taxonomyDataRootNodes[$contextHash]->getContext()->getWorkspace(); - $this->persistenceManager->persistAll(); + return $subgraph->findNodeById($rootNodeAggregate->nodeAggregateId); + } - return $this->taxonomyDataRootNodes[$contextHash]; + /** + * @return Nodes + */ + public function getVocabularies(ContentSubgraphInterface $subgraph): Nodes + { + $root = $this->getRoot($subgraph); + return $subgraph->findChildNodes( + $root->nodeAggregateId, + FindChildNodesFilter::create($this->vocabularyNodeType) + ); } /** diff --git a/Configuration/NodeTypes.Root.yaml b/Configuration/NodeTypes.Root.yaml index f071fc0..9c2e41f 100644 --- a/Configuration/NodeTypes.Root.yaml +++ b/Configuration/NodeTypes.Root.yaml @@ -1,7 +1,7 @@ Sitegeist.Taxonomy:Root: label: ${'Taxonomy:Root'} superTypes: - 'Neos.Neos:Node': TRUE + 'Neos.ContentRepository:Root': TRUE constraints: nodeTypes: '*': FALSE diff --git a/Configuration/NodeTypes.Taxonomy.yaml b/Configuration/NodeTypes.Taxonomy.yaml index 449c459..c17014a 100644 --- a/Configuration/NodeTypes.Taxonomy.yaml +++ b/Configuration/NodeTypes.Taxonomy.yaml @@ -1,5 +1,5 @@ Sitegeist.Taxonomy:Taxonomy: - label: "${String.stripTags(q(node).property('title') + ': ' + q(node).property('_path'))}" + label: "${String.stripTags(q(node).property('title'))}" ui: label: i18n icon: 'icon-tag' diff --git a/Configuration/NodeTypes.Vocabulary.yaml b/Configuration/NodeTypes.Vocabulary.yaml index b3d867e..89657a7 100644 --- a/Configuration/NodeTypes.Vocabulary.yaml +++ b/Configuration/NodeTypes.Vocabulary.yaml @@ -1,5 +1,5 @@ Sitegeist.Taxonomy:Vocabulary: - label: "${String.stripTags(q(node).property('title') + ': ' + q(node).property('_path'))}" + label: "${String.stripTags(q(node).property('title'))}" ui: label: i18n icon: 'icon-tags' diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index f54f8ac..a5df928 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -9,7 +9,7 @@ Sitegeist: additionalTaxonomyFieldPrototypes: [] contentRepository: - rootNodeName: 'taxonomies' + identifier: 'default' rootNodeType: 'Sitegeist.Taxonomy:Root' vocabularyNodeType: 'Sitegeist.Taxonomy:Vocabulary' taxonomyNodeType: 'Sitegeist.Taxonomy:Taxonomy' diff --git a/Resources/Private/Fusion/Backend/Form/Taxonomy.fusion b/Resources/Private/Fusion/Backend/Form/Taxonomy.fusion index 1e53440..8fa581b 100644 --- a/Resources/Private/Fusion/Backend/Form/Taxonomy.fusion +++ b/Resources/Private/Fusion/Backend/Form/Taxonomy.fusion @@ -5,18 +5,16 @@ prototype(Sitegeist.Taxonomy:Form.Taxonomy) < prototype(Neos.Fusion:Component) { i18nTaxonomy = ${Translation.value('').package("Sitegeist.Taxonomy").source('NodeTypes/Taxonomy')} targetAction = null - taxonomy = null - defaultTaxonomy = null - parent = null - vocabulary = null + taxonomyNode = null + parentNode = null additionalFieldPrototypeNames = ${Configuration.setting('Sitegeist.Taxonomy.backendModule.additionalTaxonomyFieldPrototypes')} renderer = afx` - + - - + +
diff --git a/Resources/Private/Fusion/Backend/Form/Vocabulary.fusion b/Resources/Private/Fusion/Backend/Form/Vocabulary.fusion index 07b7b0a..0fcfb82 100644 --- a/Resources/Private/Fusion/Backend/Form/Vocabulary.fusion +++ b/Resources/Private/Fusion/Backend/Form/Vocabulary.fusion @@ -4,23 +4,22 @@ prototype(Sitegeist.Taxonomy:Form.Vocabulary) < prototype(Neos.Fusion:Component) i18nVocabulary = ${Translation.value('').package("Sitegeist.Taxonomy").source('NodeTypes/Vocabulary')} targetAction = null - vocabulary = null - defaultVocabulary = null - taxonomyRoot = null + rootNode = null + vocabularyNode = null + additionalFieldPrototypeNames = ${Configuration.setting('Sitegeist.Taxonomy.backendModule.additionalVocabularyFieldPrototypes')} renderer = afx` - + - - + +
@@ -28,7 +27,6 @@ prototype(Sitegeist.Taxonomy:Form.Vocabulary) < prototype(Neos.Fusion:Component)
@@ -39,8 +37,8 @@ prototype(Sitegeist.Taxonomy:Form.Vocabulary) < prototype(Neos.Fusion:Component)
@@ -50,7 +48,7 @@ prototype(Sitegeist.Taxonomy:Form.Vocabulary) < prototype(Neos.Fusion:Component) {props.i18nMain.id('generic.cancel')} diff --git a/Resources/Private/Fusion/Backend/Views/Taxonomy.Edit.fusion b/Resources/Private/Fusion/Backend/Views/Taxonomy.Edit.fusion index 56f1ae3..5cdadd8 100644 --- a/Resources/Private/Fusion/Backend/Views/Taxonomy.Edit.fusion +++ b/Resources/Private/Fusion/Backend/Views/Taxonomy.Edit.fusion @@ -5,13 +5,11 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.Edit) < prototype(Neos.Fusion i18nTaxonomy = ${Translation.value('').package("Sitegeist.Taxonomy").source('NodeTypes/Taxonomy')} renderer = afx` - {props.i18nMain.id('taxon')}: {taxonomy.properties.title} - {props.i18nMain.id('generic.default')}: {defaultTaxonomy.properties.title} + {props.i18nMain.id('taxon')}: {taxonomyNode.properties.title} ` diff --git a/Resources/Private/Fusion/Backend/Views/Taxonomy.List.fusion b/Resources/Private/Fusion/Backend/Views/Taxonomy.List.fusion index 30de808..72c8240 100644 --- a/Resources/Private/Fusion/Backend/Views/Taxonomy.List.fusion +++ b/Resources/Private/Fusion/Backend/Views/Taxonomy.List.fusion @@ -7,8 +7,7 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List) < prototype(Neos.Fusion renderer = afx`
- {props.i18n.id('vocabulary')} {vocabulary.properties.title} - ({defaultVocabulary.properties.title}) + {props.i18n.id('vocabulary')} {vocabularyNode.properties.title}

- {vocabulary.properties.description} + {vocabularyNode.properties.description}

-

+

{props.i18n.id('vocabulary.empty')}

- +
- - + @@ -50,7 +46,7 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List) < prototype(Neos.Fusion
@@ -59,7 +55,7 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List) < prototype(Neos.Fusion   @@ -76,16 +72,13 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List.Item) < prototype(Neos.F renderer = afx`
- @@ -96,7 +89,7 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List.Item) < prototype(Neos.F @@ -105,7 +98,7 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List.Item) < prototype(Neos.F   @@ -117,21 +110,21 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List.Item) < prototype(Neos.F -
+
-
Do you really want to delete the taxonomy "{taxon.node.properties.title}"? This action cannot be undone.
+
Do you really want to delete the taxonomy "{ props.taxon.node.properties.title}"? This action cannot be undone.
diff --git a/Resources/Private/Fusion/Backend/Views/Taxonomy.New.fusion b/Resources/Private/Fusion/Backend/Views/Taxonomy.New.fusion index 6f6fea6..861877a 100644 --- a/Resources/Private/Fusion/Backend/Views/Taxonomy.New.fusion +++ b/Resources/Private/Fusion/Backend/Views/Taxonomy.New.fusion @@ -7,8 +7,8 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.New) < prototype(Neos.Fusion: {props.i18nMain.id('taxon.createBelow')} "{parent.properties.title}" ` } diff --git a/Resources/Private/Fusion/Backend/Views/Vocabulary.Edit.fusion b/Resources/Private/Fusion/Backend/Views/Vocabulary.Edit.fusion index e31edec..4047b8c 100644 --- a/Resources/Private/Fusion/Backend/Views/Vocabulary.Edit.fusion +++ b/Resources/Private/Fusion/Backend/Views/Vocabulary.Edit.fusion @@ -10,9 +10,8 @@ prototype(Sitegeist.Taxonomy:Views.Module.Vocabulary.Edit) < prototype(Neos.Fusi
` diff --git a/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion b/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion index ed34f3f..68af763 100644 --- a/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion +++ b/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion @@ -10,9 +10,9 @@ prototype(Sitegeist.Taxonomy:Views.Module.Vocabulary.List) < prototype(Neos.Fusi
@@ -27,36 +27,40 @@ prototype(Sitegeist.Taxonomy:Views.Module.Vocabulary.List) < prototype(Neos.Fusi
+

- - {vocabulary.node.properties.title} ({vocabulary.defaultNode.properties.title}) + + {vocabulary.properties.title}

-

{vocabulary.node.properties.description}

+

{vocabulary.properties.description}

-
+
-
Do you really want to delete the vocabulary "{vocabulary.node.properties.title}"? This action cannot be undone.
+
Do you really want to delete the vocabulary "{vocabulary.properties.title}"? This action cannot be undone.
+ - - + +
{props.i18nTaxonomy.id('properties.title')} - {defaultVocabulary ? 'Default' : ''} -
- +         {props.taxon.node.properties.title} - {props.taxon.defaultNode ? props.taxon.defaultNode.properties.title : ''} - {props.taxon.node.properties.description} {props.taxon.node.properties.description} diff --git a/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion b/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion index 36004a1..6db19fa 100644 --- a/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion +++ b/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion @@ -36,7 +36,7 @@ prototype(Sitegeist.Taxonomy:Views.Module.Vocabulary.List) < prototype(Neos.Fusi

- {vocabulary.properties.title} + {vocabulary.properties.title} ({vocabulary.properties.name})

diff --git a/Resources/Private/Translations/de/NodeTypes/Taxonomy.xlf b/Resources/Private/Translations/de/NodeTypes/Taxonomy.xlf index b34e84a..9a41d6d 100644 --- a/Resources/Private/Translations/de/NodeTypes/Taxonomy.xlf +++ b/Resources/Private/Translations/de/NodeTypes/Taxonomy.xlf @@ -6,6 +6,10 @@ Taxonomy Taxonomie + + Name + Name + Title Titel diff --git a/Resources/Private/Translations/de/NodeTypes/Vocabulary.xlf b/Resources/Private/Translations/de/NodeTypes/Vocabulary.xlf index 9f0af57..718250d 100644 --- a/Resources/Private/Translations/de/NodeTypes/Vocabulary.xlf +++ b/Resources/Private/Translations/de/NodeTypes/Vocabulary.xlf @@ -6,6 +6,10 @@ Vocabulary Vocabular + + Name + Name + Title Titel diff --git a/Resources/Private/Translations/en/NodeTypes/Taxonomy.xlf b/Resources/Private/Translations/en/NodeTypes/Taxonomy.xlf index d728b16..2d2a4c2 100644 --- a/Resources/Private/Translations/en/NodeTypes/Taxonomy.xlf +++ b/Resources/Private/Translations/en/NodeTypes/Taxonomy.xlf @@ -5,6 +5,9 @@ Taxonomy + + Name + Title diff --git a/Resources/Private/Translations/en/NodeTypes/Vocabulary.xlf b/Resources/Private/Translations/en/NodeTypes/Vocabulary.xlf index 256e338..9013a64 100644 --- a/Resources/Private/Translations/en/NodeTypes/Vocabulary.xlf +++ b/Resources/Private/Translations/en/NodeTypes/Vocabulary.xlf @@ -5,6 +5,9 @@ Vocabulary + + Name + Title From 3de1c5041bdbc6578f0f00837827b407414c060f Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Fri, 16 Jun 2023 17:05:52 +0200 Subject: [PATCH 05/35] TASK: Move NodeTypes to specific folder and manage NodeNames for vocabularies and taxonomies --- Classes/Controller/ModuleController.php | 35 ++++++++-- Classes/Hooks/ContentRepositoryHooks.php | 69 ------------------- Classes/Service/TaxonomyService.php | 4 +- Configuration/Objects.yaml | 10 --- .../Mixin.Referencable.yaml | 0 .../Root.yaml | 0 .../Taxonomy.yaml | 3 - .../Vocabulary.yaml | 3 - .../Fusion/Backend/Form/Taxonomy.fusion | 8 ++- .../Fusion/Backend/Form/Vocabulary.fusion | 9 ++- .../Fusion/Backend/Views/Taxonomy.List.fusion | 2 +- .../Backend/Views/Vocabulary.List.fusion | 2 +- 12 files changed, 45 insertions(+), 100 deletions(-) delete mode 100644 Classes/Hooks/ContentRepositoryHooks.php delete mode 100644 Configuration/Objects.yaml rename Configuration/NodeTypes.Mixin.Referencable.yaml => NodeTypes/Mixin.Referencable.yaml (100%) rename Configuration/NodeTypes.Root.yaml => NodeTypes/Root.yaml (100%) rename Configuration/NodeTypes.Taxonomy.yaml => NodeTypes/Taxonomy.yaml (85%) rename Configuration/NodeTypes.Vocabulary.yaml => NodeTypes/Vocabulary.yaml (85%) diff --git a/Classes/Controller/ModuleController.php b/Classes/Controller/ModuleController.php index c57f6ac..26148e6 100644 --- a/Classes/Controller/ModuleController.php +++ b/Classes/Controller/ModuleController.php @@ -18,6 +18,7 @@ use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetNodeProperties; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite; use Neos\ContentRepository\Core\Feature\NodeRemoval\Command\RemoveNodeAggregate; +use Neos\ContentRepository\Core\Feature\NodeRenaming\Command\ChangeNodeAggregateName; use Neos\ContentRepository\Core\Feature\NodeVariation\Command\CreateNodeVariant; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; @@ -28,6 +29,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTypeConstraints; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy; use Neos\ContentRepository\Core\SharedModel\User\UserId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -207,7 +209,7 @@ public function newVocabularyAction(string $rootNodeAddress = null): void * @param string $rootNodeAddress root node address * @param array $properties */ - public function createVocabularyAction(string $rootNodeAddress, array $properties) + public function createVocabularyAction(string $rootNodeAddress, string $name, array $properties) { $contentRepository = $this->taxonomyService->getContentRepository(); @@ -229,7 +231,7 @@ public function createVocabularyAction(string $rootNodeAddress, array $propertie $originDimensionSpacePoint, $rootNode->nodeAggregateId, null, - null, + NodeName::transliterateFromString($name), PropertyValuesToWrite::fromArray($properties) ) ); @@ -286,7 +288,7 @@ public function editVocabularyAction(string $vocabularyNodeAddress) /** * Apply changes to the given vocabulary */ - public function updateVocabularyAction(string $vocabularyNodeAddress, array $properties) + public function updateVocabularyAction(string $vocabularyNodeAddress, string $name, array $properties) { $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); $subgraph = $this->getSubgraphForNode($vocabularyNode); @@ -301,6 +303,16 @@ public function updateVocabularyAction(string $vocabularyNodeAddress, array $pro ) ); $commandResult->block(); + if ($name != $vocabularyNode->nodeName->value) { + $commandResult = $this->contentRepository->handle( + new ChangeNodeAggregateName( + $vocabularyNode->subgraphIdentity->contentStreamId, + $vocabularyNode->nodeAggregateId, + NodeName::transliterateFromString($name) + ) + ); + $commandResult->block(); + } $updatedVocabularyNode = $subgraph->findNodeById($vocabularyNode->nodeAggregateId); $this->addFlashMessage( @@ -361,7 +373,7 @@ public function newTaxonomyAction(string $parentNodeAddress) /** * Create a new taxonomy */ - public function createTaxonomyAction(string $parentNodeAddress, array $properties): void + public function createTaxonomyAction(string $parentNodeAddress, string $name, array $properties): void { $parentNode = $this->getNodeByNodeAddress($parentNodeAddress); $vocabularyNode = $this->taxonomyService->findVocabularyForNode($parentNode); @@ -382,7 +394,7 @@ public function createTaxonomyAction(string $parentNodeAddress, array $propertie $originDimensionSpacePoint, $parentNode->nodeAggregateId, null, - null, + NodeName::transliterateFromString($name), PropertyValuesToWrite::fromArray($properties) ) ); @@ -436,7 +448,7 @@ public function editTaxonomyAction(string $taxonomyNodeAddress) /** * Apply changes to the given taxonomy */ - public function updateTaxonomyAction(string $taxonomyNodeAddress, array $properties) + public function updateTaxonomyAction(string $taxonomyNodeAddress, string $name, array $properties) { $taxonomyNode = $this->getNodeByNodeAddress($taxonomyNodeAddress); $vocabularyNode = $this->taxonomyService->findVocabularyForNode($taxonomyNode); @@ -451,6 +463,17 @@ public function updateTaxonomyAction(string $taxonomyNodeAddress, array $propert ) ); $commandResult->block(); + if ($name != $taxonomyNode->nodeName->value) { + $commandResult = $this->contentRepository->handle( + new ChangeNodeAggregateName( + $taxonomyNode->subgraphIdentity->contentStreamId, + $taxonomyNode->nodeAggregateId, + NodeName::transliterateFromString($name) + ) + ); + $commandResult->block(); + } + $updatedTaxonomyNode = $subgraph->findNodeById($vocabularyNode->nodeAggregateId); $this->addFlashMessage( diff --git a/Classes/Hooks/ContentRepositoryHooks.php b/Classes/Hooks/ContentRepositoryHooks.php deleted file mode 100644 index d35e235..0000000 --- a/Classes/Hooks/ContentRepositoryHooks.php +++ /dev/null @@ -1,69 +0,0 @@ -getNodeType()->isOfType($this->taxonomyService->getRootNodeType()) || -// $node->getNodeType()->isOfType($this->taxonomyService->getVocabularyNodeType()) || -// $node->getNodeType()->isOfType($this->taxonomyService->getTaxonomyNodeType())) { -// if ($node->isAutoCreated() == false && $this->preventCascade == false) { -// $this->preventCascade = true; -// $this->dimensionService->ensureBaseVariantsExist($node); -// $this->preventCascade = false; -// } -// } -// } -// -// /** -// * Signal that is triggered on node remove -// * -// * @param NodeInterface $node -// */ -// public function nodeRemoved(NodeInterface $node) -// { -// if ($node->getNodeType()->isOfType($this->taxonomyService->getRootNodeType()) || -// $node->getNodeType()->isOfType($this->taxonomyService->getVocabularyNodeType()) || -// $node->getNodeType()->isOfType($this->taxonomyService->getTaxonomyNodeType())) { -// if ($node->isAutoCreated() == false && $this->preventCascade == false) { -// $this->preventCascade = true; -// $this->dimensionService->removeOtherVariants($node); -// $this->preventCascade = false; -// } -// } -// } -} diff --git a/Classes/Service/TaxonomyService.php b/Classes/Service/TaxonomyService.php index c81d666..ba553f2 100644 --- a/Classes/Service/TaxonomyService.php +++ b/Classes/Service/TaxonomyService.php @@ -5,6 +5,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentSubgraph; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryId; +use Neos\ContentRepository\Core\Feature\NodeRenaming\Command\ChangeNodeAggregateName; use Neos\ContentRepository\Core\Feature\RootNodeCreation\Command\CreateRootNodeAggregateWithNode; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -141,8 +142,7 @@ public function getRoot(ContentSubgraphInterface $subgraph): Node new CreateRootNodeAggregateWithNode( $liveWorkspace->currentContentStreamId, NodeAggregateId::create(), - NodeTypeName::fromString($this->getRootNodeType()), - UserId::forSystemUser() + NodeTypeName::fromString($this->getRootNodeType()) ) ); $commandResult->block(); diff --git a/Configuration/Objects.yaml b/Configuration/Objects.yaml deleted file mode 100644 index 53b87b9..0000000 --- a/Configuration/Objects.yaml +++ /dev/null @@ -1,10 +0,0 @@ - -Sitegeist\Taxonomy\Hooks\ContentRepositoryHooks: - properties: - logger: - object: - factoryObjectName: Neos\Flow\Log\PsrLoggerFactoryInterface - factoryMethodName: get - arguments: - 1: - value: systemLogger diff --git a/Configuration/NodeTypes.Mixin.Referencable.yaml b/NodeTypes/Mixin.Referencable.yaml similarity index 100% rename from Configuration/NodeTypes.Mixin.Referencable.yaml rename to NodeTypes/Mixin.Referencable.yaml diff --git a/Configuration/NodeTypes.Root.yaml b/NodeTypes/Root.yaml similarity index 100% rename from Configuration/NodeTypes.Root.yaml rename to NodeTypes/Root.yaml diff --git a/Configuration/NodeTypes.Taxonomy.yaml b/NodeTypes/Taxonomy.yaml similarity index 85% rename from Configuration/NodeTypes.Taxonomy.yaml rename to NodeTypes/Taxonomy.yaml index 65f8adc..c17014a 100644 --- a/Configuration/NodeTypes.Taxonomy.yaml +++ b/NodeTypes/Taxonomy.yaml @@ -10,9 +10,6 @@ Sitegeist.Taxonomy:Taxonomy: '*': FALSE 'Sitegeist.Taxonomy:Taxonomy': TRUE properties: - name: - type: string - scope: nodeAggregate title: type: string description: diff --git a/Configuration/NodeTypes.Vocabulary.yaml b/NodeTypes/Vocabulary.yaml similarity index 85% rename from Configuration/NodeTypes.Vocabulary.yaml rename to NodeTypes/Vocabulary.yaml index 38668da..89657a7 100644 --- a/Configuration/NodeTypes.Vocabulary.yaml +++ b/NodeTypes/Vocabulary.yaml @@ -10,9 +10,6 @@ Sitegeist.Taxonomy:Vocabulary: '*': FALSE 'Sitegeist.Taxonomy:Taxonomy': TRUE properties: - name: - type: string - scope: nodeAggregate title: type: string description: diff --git a/Resources/Private/Fusion/Backend/Form/Taxonomy.fusion b/Resources/Private/Fusion/Backend/Form/Taxonomy.fusion index 0c7209f..2b4f1dd 100644 --- a/Resources/Private/Fusion/Backend/Form/Taxonomy.fusion +++ b/Resources/Private/Fusion/Backend/Form/Taxonomy.fusion @@ -11,7 +11,11 @@ prototype(Sitegeist.Taxonomy:Form.Taxonomy) < prototype(Neos.Fusion:Component) { additionalFieldPrototypeNames = ${Configuration.setting('Sitegeist.Taxonomy.backendModule.additionalTaxonomyFieldPrototypes')} renderer = afx` - + @@ -22,7 +26,7 @@ prototype(Sitegeist.Taxonomy:Form.Taxonomy) < prototype(Neos.Fusion:Component) { {props.i18nTaxonomy.id('properties.name')} : {props.defaultTaxonomy.properties.name} - +
diff --git a/Resources/Private/Fusion/Backend/Form/Vocabulary.fusion b/Resources/Private/Fusion/Backend/Form/Vocabulary.fusion index 26829d9..a238eb2 100644 --- a/Resources/Private/Fusion/Backend/Form/Vocabulary.fusion +++ b/Resources/Private/Fusion/Backend/Form/Vocabulary.fusion @@ -11,8 +11,11 @@ prototype(Sitegeist.Taxonomy:Form.Vocabulary) < prototype(Neos.Fusion:Component) additionalFieldPrototypeNames = ${Configuration.setting('Sitegeist.Taxonomy.backendModule.additionalVocabularyFieldPrototypes')} renderer = afx` - - + @@ -21,7 +24,7 @@ prototype(Sitegeist.Taxonomy:Form.Vocabulary) < prototype(Neos.Fusion:Component) - +
diff --git a/Resources/Private/Fusion/Backend/Views/Taxonomy.List.fusion b/Resources/Private/Fusion/Backend/Views/Taxonomy.List.fusion index ce25e2c..06b4fd5 100644 --- a/Resources/Private/Fusion/Backend/Views/Taxonomy.List.fusion +++ b/Resources/Private/Fusion/Backend/Views/Taxonomy.List.fusion @@ -76,7 +76,7 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List.Item) < prototype(Neos.F   - {props.taxon.node.properties.title} ({props.taxon.node.properties.name}) + {props.taxon.node.properties.title} ({props.taxon.node.nodeName.value})
{props.taxon.node.properties.description} diff --git a/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion b/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion index 6db19fa..8a0e66f 100644 --- a/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion +++ b/Resources/Private/Fusion/Backend/Views/Vocabulary.List.fusion @@ -36,7 +36,7 @@ prototype(Sitegeist.Taxonomy:Views.Module.Vocabulary.List) < prototype(Neos.Fusi

- {vocabulary.properties.title} ({vocabulary.properties.name}) + {vocabulary.properties.title} ({vocabulary.nodeName.value})

From 34969b61ec9a8d83ee6592710273d65f88f09625 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Fri, 16 Jun 2023 18:53:24 +0200 Subject: [PATCH 06/35] TASK: Cleanup and reimplement parts of the command controller --- Classes/Command/TaxonomyCommandController.php | 284 +++--------------- Classes/Controller/ModuleController.php | 26 +- Classes/Eel/TaxonomyHelper.php | 8 +- Classes/Service/TaxonomyService.php | 80 +++-- .../DimensionInformationViewHelper.php | 62 ---- .../Fusion/Backend/Views/Taxonomy.Edit.fusion | 2 +- .../Fusion/Backend/Views/Taxonomy.List.fusion | 41 +-- .../Fusion/Backend/Views/Taxonomy.New.fusion | 2 +- .../Backend/Views/Vocabulary.Edit.fusion | 2 +- .../Backend/Views/Vocabulary.List.fusion | 2 +- 10 files changed, 130 insertions(+), 379 deletions(-) delete mode 100644 Classes/ViewHelpers/DimensionInformationViewHelper.php diff --git a/Classes/Command/TaxonomyCommandController.php b/Classes/Command/TaxonomyCommandController.php index 6418468..3e0abda 100644 --- a/Classes/Command/TaxonomyCommandController.php +++ b/Classes/Command/TaxonomyCommandController.php @@ -1,10 +1,10 @@ taxonomyService->findSubgraph(); - $taxonomyRoot = $this->taxonomyService->getRoot($subgraph); $vocabularies = $this->taxonomyService->findAllVocabularies($subgraph); + $this->output->outputTable( + array_map( + fn(Node $node) => [$node->nodeName->value, $node->getProperty('title'), $node->getProperty('description')], + iterator_to_array($vocabularies->getIterator()) + ), + ['name', 'title', 'description'] + ); + } - /** - * @var Node $vocabularyNode - */ - foreach ($vocabularies->getIterator() as $vocabulary) { - $this->outputLine($vocabulary->getLabel()); + /** + * List all taxonomies of a vocabulary + * + * @param string $path path to the taxonomy starting with the vocabulary name (separated with dots) + */ + public function listTaxonomiesCommand(string $path): void + { + $subgraph = $this->taxonomyService->findSubgraph(); + $node = $this->taxonomyService->findVocabularyOrTaxonomyByPath($subgraph, explode('.', $path)); + if (!$node) { + $this->outputLine('nothing found'); + $this->quit(1); } + $subtree = $this->taxonomyService->findTaxonomySubtree($node); + $this->output->outputTable( + $this->subtreeToTableRowsRecursively($subtree), + ['name', 'title', 'description'] + ); } -// -// /** -// * Import taxonomy content -// * -// * @param string $filename relative path and filename to the XML file to read. -// * @param string $vocabularyNode vocabularay nodename(path) to import (globbing is supported) -// * @return void -// */ -// public function importCommand($filename, $vocabulary = null) -// { -// $xmlReader = new \XMLReader(); -// $xmlReader->open($filename, null, LIBXML_PARSEHUGE); -// -// $taxonomyRoot = $this->taxonomyService->getRoot(); -// -// while ($xmlReader->read()) { -// if ($xmlReader->nodeType != \XMLReader::ELEMENT || $xmlReader->name !== 'vocabulary') { -// continue; -// } -// -// $vocabularyName = (string) $xmlReader->getAttribute('name'); -// if (is_string($vocabulary) && fnmatch($vocabulary, $vocabularyName) == false) { -// continue; -// } -// -// $this->nodeImportService->import($xmlReader, $taxonomyRoot->getPath()); -// $this->outputLine('Imported vocabulary %s from file %s', [$vocabularyName, $filename]); -// } -// } -// -// /** -// * Export taxonomy content -// * -// * @param string $filename filename for the xml that is written. -// * @param string $vocabularyNode vocabularay nodename(path) to export (globbing is supported) -// * @return void -// */ -// public function exportCommand($filename, $vocabulary = null) -// { -// $xmlWriter = new \XMLWriter(); -// $xmlWriter->openUri($filename); -// $xmlWriter->setIndent(true); -// -// $xmlWriter->startDocument('1.0', 'UTF-8'); -// $xmlWriter->startElement('root'); -// -// $taxonomyRoot = $this->taxonomyService->getRoot(); -// -// /** -// * @var NodeInterface[] $vocabularyNodes -// */ -// $vocabularyNodes = (new FlowQuery([$taxonomyRoot])) -// ->children('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ' ]') -// ->get(); -// -// /** -// * @var NodeInterface $vocabularyNode -// */ -// foreach ($vocabularyNodes as $vocabularyNode) { -// $vocabularyName = $vocabularyNode->getName(); -// if (is_string($vocabulary) && fnmatch($vocabulary, $vocabularyName) == false) { -// continue; -// } -// $xmlWriter->startElement('vocabulary'); -// $xmlWriter->writeAttribute('name', $vocabularyName); -// $this->nodeExportService->export($vocabularyNode->getPath(), 'live', $xmlWriter, false, false); -// $this->outputLine('Exported vocabulary %s to file %s', [$vocabularyName, $filename]); -// $xmlWriter->endElement(); -// } -// -// $xmlWriter->endElement(); -// $xmlWriter->endDocument(); -// -// $xmlWriter->flush(); -// } -// -// /** -// * Prune taxonomy content -// * -// * @param string $vocabularyNode vocabularay nodename(path) to prune (globbing is supported) -// * @return void -// */ -// public function pruneCommand($vocabulary) -// { -// $taxonomyRoot = $this->taxonomyService->getRoot(); -// -// /** -// * @var NodeInterface[] $vocabularyNodes -// */ -// $vocabularyNodes = (new FlowQuery([$taxonomyRoot])) -// ->children('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . ' ]') -// ->get(); -// -// /** -// * @var NodeInterface $vocabularyNode -// */ -// foreach ($vocabularyNodes as $vocabularyNode) { -// $vocabularyName = $vocabularyNode->getName(); -// if (is_string($vocabulary) && fnmatch($vocabulary, $vocabularyName) == false) { -// continue; -// } -// $this->nodeDataRepository->removeAllInPath($vocabularyNode->getPath()); -// $dimensionNodes = $this->nodeDataRepository->findByPath($vocabularyNode->getPath()); -// foreach ($dimensionNodes as $node) { -// $this->nodeDataRepository->remove($node); -// } -// -// $this->outputLine('Pruned vocabulary %s', [$vocabularyName]); -// } -// } -// -// /** -// * Reset a taxonimy dimension and create fresh variants from the base dimension -// * -// * @param string $dimensionName -// * @param string $dimensionValue -// * @return void -// */ -// public function pruneDimensionCommand($dimensionName, $dimensionValue) -// { -// $taxonomyRoot = $this->taxonomyService->getRoot(); -// $targetSubgraph = $this->dimensionService->getDimensionSubgraphByTargetValues([$dimensionName => $dimensionValue]); -// -// if (!$targetSubgraph) { -// $this->outputLine('Target subgraph not found'); -// $this->quit(1); -// } -// -// $targetContextValues = $this->dimensionService->getDimensionValuesForSubgraph($targetSubgraph); -// $flowQuery = new FlowQuery([$taxonomyRoot]); -// $taxonomyRootInTargetContest = $flowQuery->context($targetContextValues)->get(0); -// -// if (!$taxonomyRootInTargetContest) { -// $this->outputLine('Not root in target context found'); -// $this->quit(1); -// } -// -// if ($taxonomyRootInTargetContest == $taxonomyRoot) { -// $this->outputLine('The root is the default context and cannot be pruned'); -// $this->quit(1); -// } -// -// $this->outputLine('Removing content all below ' . $taxonomyRootInTargetContest->getContextPath()); -// $flowQuery = new FlowQuery([$taxonomyRootInTargetContest]); -// $allNodes = $flowQuery->find('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . '],[instanceof ' . $this->taxonomyService->getTaxonomyNodeType() . ']')->get(); -// foreach ($allNodes as $node) { -// $this->outputLine(' - remove: ' . $node->getContextPath()); -// $node->remove(); -// } -// $this->outputLine('Done'); -// } -// -// /** -// * Make sure all values from default are present in the target dimension aswell -// * -// * @param string $dimensionName -// * @param string $dimensionValue -// * @return void -// */ -// public function populateDimensionCommand($dimensionName, $dimensionValue) -// { -// $taxonomyRoot = $this->taxonomyService->getRoot(); -// $targetSubgraph = $this->dimensionService->getDimensionSubgraphByTargetValues([$dimensionName => $dimensionValue]); -// -// if (!$targetSubgraph) { -// $this->outputLine('Target subgraph not found'); -// $this->quit(1); -// } -// -// $targetContextValues = $this->dimensionService->getDimensionValuesForSubgraph($targetSubgraph); -// $flowQuery = new FlowQuery([$taxonomyRoot]); -// $taxonomyRootInTargetContest = $flowQuery->context($targetContextValues)->get(0); -// -// if (!$taxonomyRootInTargetContest) { -// $this->outputLine('Not root in target context found'); -// $this->quit(1); -// } -// -// if ($taxonomyRootInTargetContest == $taxonomyRoot) { -// $this->outputLine('The root is the default context and cannot be recreated'); -// $this->quit(1); -// } -// -// if ($taxonomyRootInTargetContest == $taxonomyRoot) { -// $this->outputLine('The root is the default context and cannot be recreated'); -// $this->quit(1); -// } -// -// $this->outputLine('Populating taxonomy content from default below' . $taxonomyRootInTargetContest->getContextPath()); -// $targetContext = $taxonomyRootInTargetContest->getContext(); -// $flowQuery = new FlowQuery([$taxonomyRoot]); -// $allNodes = $flowQuery->find('[instanceof ' . $this->taxonomyService->getVocabularyNodeType() . '],[instanceof ' . $this->taxonomyService->getTaxonomyNodeType() . ']')->get(); -// foreach ($allNodes as $node) { -// $this->outputLine(' - adopt: ' . $node->getContextPath()); -// $targetContext->adoptNode($node); -// } -// $this->outputLine('Done'); -// } + private function subtreeToTableRowsRecursively(Subtree $subtree): array + { + $childRows = array_map(fn(Subtree $subtree)=>$this->subtreeToTableRowsRecursively($subtree), $subtree->children); + $row = [str_repeat(' ', $subtree->level) . $subtree->node->nodeName->value, $subtree->node->getProperty('title'), $subtree->node->getProperty('description')]; + return [$row, ...array_merge(...$childRows)]; + } } diff --git a/Classes/Controller/ModuleController.php b/Classes/Controller/ModuleController.php index 26148e6..7a0eb47 100644 --- a/Classes/Controller/ModuleController.php +++ b/Classes/Controller/ModuleController.php @@ -83,11 +83,6 @@ class ModuleController extends ActionController */ protected $taxonomyService; - /** - * @var NodeInterface - */ - protected $defaultRoot; - /** * @var ContentRepository */ @@ -138,7 +133,7 @@ public function indexAction(string $rootNodeAddress = null): void { if (is_null($rootNodeAddress)) { $subgraph = $this->taxonomyService->findSubgraph(); - $rootNode = $this->taxonomyService->getRoot($subgraph); + $rootNode = $this->taxonomyService->findOrCreateRoot($subgraph); } else { $rootNode = $this->getNodeByNodeAddress($rootNodeAddress); $subgraph = $this->getSubgraphForNode($rootNode); @@ -179,18 +174,11 @@ public function vocabularyAction(string $vocabularyNodeAddress) { $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); $subgraph = $this->getSubgraphForNode($vocabularyNode); + $rootNode = $this->taxonomyService->findOrCreateRoot($subgraph); + $vocabularySubtree = $this->taxonomyService->findTaxonomySubtree($vocabularyNode); - $rootNode = $this->taxonomyService->getRoot($subgraph); $this->view->assign('rootNode', $rootNode); $this->view->assign('vocabularyNode', $vocabularyNode); - - $vocabularySubtree = $subgraph->findSubtree( - $vocabularyNode->nodeAggregateId, - FindSubtreeFilter::create( - $this->taxonomyService->getTaxonomyNodeType() - ) - ); - $this->view->assign('vocabularySubtree', $vocabularySubtree); } @@ -279,7 +267,7 @@ public function editVocabularyAction(string $vocabularyNodeAddress) $vocabularyNode->subgraphIdentity->visibilityConstraints, ); - $rootNode = $this->taxonomyService->getRoot($subgraph); + $rootNode = $this->taxonomyService->findOrCreateRoot($subgraph); $this->view->assign('rootNode', $rootNode); $this->view->assign('vocabularyNode', $vocabularyNode); @@ -292,7 +280,7 @@ public function updateVocabularyAction(string $vocabularyNodeAddress, string $na { $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); $subgraph = $this->getSubgraphForNode($vocabularyNode); - $rootNode = $this->taxonomyService->getRoot($subgraph); + $rootNode = $this->taxonomyService->findOrCreateRoot($subgraph); $commandResult = $this->contentRepository->handle( new SetNodeProperties( @@ -328,7 +316,7 @@ public function deleteVocabularyAction(string $vocabularyNodeAddress) { $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); $subgraph = $this->getSubgraphForNode($vocabularyNode); - $rootNode = $this->taxonomyService->getRoot($subgraph); + $rootNode = $this->taxonomyService->findOrCreateRoot($subgraph); $liveWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName(\Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName::forLive()); $commandResult = $this->contentRepository->handle( @@ -356,7 +344,7 @@ public function newTaxonomyAction(string $parentNodeAddress) { $parentNode = $this->getNodeByNodeAddress($parentNodeAddress); $subgraph = $this->getSubgraphForNode($parentNode); - $rootNode = $this->taxonomyService->getRoot($subgraph); + $rootNode = $this->taxonomyService->findOrCreateRoot($subgraph); $vocabularyNode = null; if ($parentNode->nodeType->isOfType($this->taxonomyService->getTaxonomyNodeType())) { diff --git a/Classes/Eel/TaxonomyHelper.php b/Classes/Eel/TaxonomyHelper.php index f87aada..433fbff 100644 --- a/Classes/Eel/TaxonomyHelper.php +++ b/Classes/Eel/TaxonomyHelper.php @@ -21,17 +21,17 @@ class TaxonomyHelper implements ProtectedContextAwareInterface public function root(ContentSubgraph $subgraph = null): Node { - return $this->taxonomyService->getRoot($subgraph); + return $this->taxonomyService->findOrCreateRoot($subgraph); } public function vocabulary(ContentSubgraph $subgraph, string $vocabulary): ?Node { - return $this->taxonomyService->findVocabulary($subgraph, $vocabulary); + return $this->taxonomyService->findVocabularyByName($subgraph, $vocabulary); } - public function taxonomy(ContentSubgraph $subgraph, string|Node $vocabulary, array $path = null): ?Node + public function taxonomy(ContentSubgraph $subgraph, array $path = []): ?Node { - return $this->taxonomyService->findTaxonomy($vocabulary, $path, $context); + return $this->taxonomyService->findVocabularyOrTaxonomyByPath($subgraph, $path); } /** diff --git a/Classes/Service/TaxonomyService.php b/Classes/Service/TaxonomyService.php index ba553f2..303d693 100644 --- a/Classes/Service/TaxonomyService.php +++ b/Classes/Service/TaxonomyService.php @@ -10,8 +10,10 @@ use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSubtreeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; +use Neos\ContentRepository\Core\Projection\ContentGraph\Subtree; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -122,7 +124,7 @@ public function findVocabularyForNode(Node $node): Node throw new \InvalidArgumentException('Node seems to be outside of vocabulary'); } - public function getRoot(ContentSubgraphInterface $subgraph): Node + public function findOrCreateRoot(ContentSubgraphInterface $subgraph): Node { $contentRepository = $this->getContentRepository(); $liveWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()); @@ -157,47 +159,79 @@ public function getRoot(ContentSubgraphInterface $subgraph): Node public function findAllVocabularies(ContentSubgraphInterface $subgraph): Nodes { - $root = $this->getRoot($subgraph); + $root = $this->findOrCreateRoot($subgraph); return $subgraph->findChildNodes( $root->nodeAggregateId, FindChildNodesFilter::create($this->vocabularyNodeType) ); } - public function findVocabulary(ContentSubgraphInterface $subgraph, string $vocabularyName): ?Node + public function findVocabularyByName(ContentSubgraphInterface $subgraph, string $vocabularyName): ?Node { $vocabularies = $this->findAllVocabularies($subgraph); foreach ($vocabularies as $vocabulary) { - if ($vocabulary->getProperty('name') === $vocabularyName) { + if ($vocabulary->nodeName->value == $vocabularyName) { return $vocabulary; } } return null; } - public function findTaxonomy(ContentSubgraphInterface $subgraph, string|Node $vocabulary, array $taxonomyPath): ?Node + public function findVocabularyOrTaxonomyByPath(ContentSubgraphInterface $subgraph, array $taxonomyPath = []): ?Node { - if (is_string($vocabulary)) { - $vocabulary = $this->findVocabulary($subgraph, $vocabulary); + if (count($taxonomyPath) < 1) { + return null; } - - if ($vocabulary) { - return $vocabulary->getNode($taxonomyPath); + $vocabularyName = array_shift($taxonomyPath); + $vocabularyNode = $this->findVocabularyByName($subgraph, $vocabularyName); + if (!$vocabularyNode) { + return null; + } + $taxonomyNode = $vocabularyNode; + while (count($taxonomyPath)) { + $taxonomyName = array_shift($taxonomyPath); + $taxonomyNode = $subgraph->findChildNodeConnectedThroughEdgeName($taxonomyNode->nodeAggregateId, NodeName::fromString($taxonomyName)); + if (!$taxonomyNode) { + return null; + } } + return $taxonomyNode; } + public function findTaxonomySubtree(Node $node): Subtree + { + $contentRepository = $this->getContentRepository(); + $subgraph = $contentRepository->getContentGraph()->getSubgraph( + $node->subgraphIdentity->contentStreamId, + $node->subgraphIdentity->dimensionSpacePoint, + $node->subgraphIdentity->visibilityConstraints, + ); -// /** -// * @param string $vocabularyName -// * @param string $taxonomyPath -// * @param Context|null $context -// * @param $vocabulary -// */ -// public function getTaxonomy($vocabularyName, $taxonomyPath, Context $context = null) -// { -// $vocabulary = $this->findVocabulary($vocabularyName, $context); -// if ($vocabulary) { -// return $vocabulary->getNode($taxonomyPath); -// } -// } + $vocabularySubtree = $subgraph->findSubtree( + $node->nodeAggregateId, + FindSubtreeFilter::create( + $this->getTaxonomyNodeType() + ) + ); + + return $this->orderSubtreeByNameRecursive($vocabularySubtree); + } + + private function orderSubtreeByNameRecursive(Subtree $subtree): Subtree + { + $children = $subtree->children; + $children = array_map( + fn(Subtree $item) => $this->orderSubtreeByNameRecursive($item), + $children + ); + usort( + $children, + fn(Subtree $a, Subtree $b) => $a->node->nodeName->value <=> $b->node->nodeName->value + ); + return new Subtree( + $subtree->level, + $subtree->node, + $children + ); + } } diff --git a/Classes/ViewHelpers/DimensionInformationViewHelper.php b/Classes/ViewHelpers/DimensionInformationViewHelper.php deleted file mode 100644 index 1ad887c..0000000 --- a/Classes/ViewHelpers/DimensionInformationViewHelper.php +++ /dev/null @@ -1,62 +0,0 @@ -registerArgument('node', NodeInterface::class, 'Node', true); - $this->registerArgument('dimension', 'string', 'Dimension', false, null); - } - - /** - * @return string value with replaced text - * @api - */ - public function render() - { - return self::renderStatic( - [ - 'node' => $this->arguments['node'], - 'dimension' => $this->arguments['dimension'] - ], - $this->buildRenderChildrenClosure(), - $this->renderingContext - ); - } - - /** - * @param array $arguments - * @param \Closure $renderChildrenClosure - * @param RenderingContextInterface $renderingContext - * @return string - * @throws InvalidVariableException - */ - public static function renderStatic( - array $arguments, - \Closure $renderChildrenClosure, - RenderingContextInterface $renderingContext - ) { - /** - * @var NodeInterface $node - */ - $node = $arguments['node']; - $dimension = $arguments['dimension']; - if ($dimension) { - return $node->getContext()->getTargetDimensions()[$dimension]; - } else { - return json_encode($node->getContext()->getTargetDimensions()); - } - } -} diff --git a/Resources/Private/Fusion/Backend/Views/Taxonomy.Edit.fusion b/Resources/Private/Fusion/Backend/Views/Taxonomy.Edit.fusion index 5cdadd8..18e1d05 100644 --- a/Resources/Private/Fusion/Backend/Views/Taxonomy.Edit.fusion +++ b/Resources/Private/Fusion/Backend/Views/Taxonomy.Edit.fusion @@ -5,7 +5,7 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.Edit) < prototype(Neos.Fusion i18nTaxonomy = ${Translation.value('').package("Sitegeist.Taxonomy").source('NodeTypes/Taxonomy')} renderer = afx` - {props.i18nMain.id('taxon')}: {taxonomyNode.properties.title} + {props.i18nMain.id('taxon')}: {taxonomyNode.properties.title} [{taxonomyNode.nodeName.value}] - {props.i18n.id('vocabulary')} {vocabularyNode.properties.title} + {props.i18n.id('vocabulary')} "{vocabularyNode.properties.title}" [{vocabularyNode.nodeName.value}]
{props.i18nTaxonomy.id('properties.title')} -
+ {props.i18nTaxonomy.id('properties.description')} +
@@ -66,20 +68,21 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List) < prototype(Neos.Fusion prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List.Item) < prototype(Neos.Fusion:Component) { - taxon = null + + taxonomySubtree = null renderer = afx` - + 1}>       - -   - {props.taxon.node.properties.title} ({props.taxon.node.nodeName.value}) + +     + {props.taxonomySubtree.node.properties.title} [{props.taxonomySubtree.node.nodeName.value}] - {props.taxon.node.properties.description} + {props.taxonomySubtree.node.properties.description}
@@ -88,7 +91,7 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List.Item) < prototype(Neos.F @@ -97,33 +100,33 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List.Item) < prototype(Neos.F   - +   -
+
-
Do you really want to delete the taxonomy "{ props.taxon.node.properties.title}"? This action cannot be undone.
+
Do you really want to delete the taxonomySubtreeomy "{ props.taxonomySubtree.node.properties.title}"? This action cannot be undone.
@@ -140,8 +143,8 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.List.Item) < prototype(Neos.F - - + + ` diff --git a/Resources/Private/Fusion/Backend/Views/Taxonomy.New.fusion b/Resources/Private/Fusion/Backend/Views/Taxonomy.New.fusion index 861877a..67ca02c 100644 --- a/Resources/Private/Fusion/Backend/Views/Taxonomy.New.fusion +++ b/Resources/Private/Fusion/Backend/Views/Taxonomy.New.fusion @@ -4,7 +4,7 @@ prototype(Sitegeist.Taxonomy:Views.Module.Taxonomy.New) < prototype(Neos.Fusion: i18nTaxonomy = ${Translation.value('').package("Sitegeist.Taxonomy").source('NodeTypes/Taxonomy')} renderer = afx` - {props.i18nMain.id('taxon.createBelow')} "{parent.properties.title}" + {props.i18nMain.id('taxon.createBelow')}: "{parentNode.properties.title}" [{parentNode.nodeName.value}] - {props.i18nMain.id('vocabulary')}: {vocabulary.properties.title} + {props.i18nMain.id('vocabulary')}: "{vocabularyNode.properties.title}" [{vocabularyNode.nodeName.value}] {props.i18nMain.id('generic.default')}: {defaultVocabulary.properties.title}

- {vocabulary.properties.title} ({vocabulary.nodeName.value}) + {vocabulary.properties.title} [{vocabulary.nodeName.value}]

From 2ac90d10ecdca0e9207889bbc5893592f0853c5e Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Mon, 19 Jun 2023 18:01:38 +0200 Subject: [PATCH 07/35] TASK: Adjust starting point and rename mixin to TaxonomyReferences to be in line with the property name --- NodeTypes/Mixin.Referencable.yaml | 23 +++---------------- NodeTypes/Mixin.TaxonomyReferences.yaml | 23 +++++++++++++++++++ README.md | 2 +- ...eferencable.xlf => TaxonomyReferences.xlf} | 0 ...eferencable.xlf => TaxonomyReferences.xlf} | 0 5 files changed, 27 insertions(+), 21 deletions(-) create mode 100644 NodeTypes/Mixin.TaxonomyReferences.yaml rename Resources/Private/Translations/de/NodeTypes/Mixin/{Referencable.xlf => TaxonomyReferences.xlf} (100%) rename Resources/Private/Translations/en/NodeTypes/Mixin/{Referencable.xlf => TaxonomyReferences.xlf} (100%) diff --git a/NodeTypes/Mixin.Referencable.yaml b/NodeTypes/Mixin.Referencable.yaml index 7cb632a..c4996f1 100644 --- a/NodeTypes/Mixin.Referencable.yaml +++ b/NodeTypes/Mixin.Referencable.yaml @@ -1,22 +1,5 @@ +# @deprecated use Sitegeist.Taxonomy:Mixin.TaxonomyReferences instead Sitegeist.Taxonomy:Mixin.Referencable: abstract: true - ui: - inspector: - groups: - taxonomy: - label: i18n - icon: icon-tags - tab: meta - position: '80' - - properties: - taxonomyReferences: - type: references - ui: - label: i18n - inspector: - group: taxonomy - editor: 'Sitegeist.Taxonomy:TaxonomyEditor' - editorOptions: - startingPoint: '/taxonomies' - placeholder: 'assign Taxonomies' + superTypes: + 'Sitegeist.Taxonomy:Mixin.TaxonomyReferences': true diff --git a/NodeTypes/Mixin.TaxonomyReferences.yaml b/NodeTypes/Mixin.TaxonomyReferences.yaml new file mode 100644 index 0000000..157553a --- /dev/null +++ b/NodeTypes/Mixin.TaxonomyReferences.yaml @@ -0,0 +1,23 @@ +Sitegeist.Taxonomy:Mixin.TaxonomyReferences: + abstract: true + ui: + inspector: + groups: + taxonomy: + label: i18n + icon: icon-tags + tab: meta + position: '80' + + properties: + taxonomyReferences: + type: references + ui: + label: i18n + inspector: + group: taxonomy + #editor: 'Sitegeist.Taxonomy:TaxonomyEditor' + editorOptions: + nodeTypes: [ 'Sitegeist.Taxonomy:Taxonomy' ] + startingPoint: '/' + placeholder: 'assign Taxonomies' diff --git a/README.md b/README.md index 12c5ba4..60ef1db 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ We use semantic-versioning, so every breaking change will increase the major ver Sitegeist.Taxonomy defines three basic node types: -- `Sitegeist.Taxonomy:Root` - The root node at the path `/taxonomies`, allows only vocabulary nodes as children +- `Sitegeist.Taxonomy:Root` - The root node at the path `/`, allows only vocabulary nodes as children - `Sitegeist.Taxonomy:Vocabulary` - The root of a hierarchy of meaning, allows only taxonomies nodes as children - `Sitegeist.Taxonomy:Taxonomy` - An item in the hierarchy that represents a specific meaning allows only taxonomy nodes as children diff --git a/Resources/Private/Translations/de/NodeTypes/Mixin/Referencable.xlf b/Resources/Private/Translations/de/NodeTypes/Mixin/TaxonomyReferences.xlf similarity index 100% rename from Resources/Private/Translations/de/NodeTypes/Mixin/Referencable.xlf rename to Resources/Private/Translations/de/NodeTypes/Mixin/TaxonomyReferences.xlf diff --git a/Resources/Private/Translations/en/NodeTypes/Mixin/Referencable.xlf b/Resources/Private/Translations/en/NodeTypes/Mixin/TaxonomyReferences.xlf similarity index 100% rename from Resources/Private/Translations/en/NodeTypes/Mixin/Referencable.xlf rename to Resources/Private/Translations/en/NodeTypes/Mixin/TaxonomyReferences.xlf From 1f646b5ec3e622913cb3c1b3b0bd50b0926ff089 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Tue, 20 Jun 2023 19:48:23 +0200 Subject: [PATCH 08/35] TASK: Adjust TaxonomyEditor to work with Neos 9 - Adjust route path to be in /neos namesapace to avoid conflicts with frontendlogin - Adjust SecondaryInspectorController to new code and pass startingPoint and contextPath seperately - Various cleanups --- Classes/Controller/ModuleController.php | 77 ++++++------------- .../SecondaryInspectorController.php | 44 ++++++++--- Classes/Eel/TaxonomyHelper.php | 3 - Classes/Package.php | 39 ---------- Classes/Service/TaxonomyService.php | 47 ++++++----- Configuration/Routes.yaml | 4 +- Configuration/Settings.yaml | 2 +- NodeTypes/Mixin.TaxonomyReferences.yaml | 2 +- .../TaxonomyEditor/src/TaxonomyTreeSelect.js | 4 +- .../JavaScript/TaxonomyEditor/Plugin.js | 2 +- .../JavaScript/TaxonomyEditor/Plugin.js.map | 2 +- 11 files changed, 91 insertions(+), 135 deletions(-) delete mode 100644 Classes/Package.php diff --git a/Classes/Controller/ModuleController.php b/Classes/Controller/ModuleController.php index 7a0eb47..dafaac8 100644 --- a/Classes/Controller/ModuleController.php +++ b/Classes/Controller/ModuleController.php @@ -20,31 +20,20 @@ use Neos\ContentRepository\Core\Feature\NodeRemoval\Command\RemoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeRenaming\Command\ChangeNodeAggregateName; use Neos\ContentRepository\Core\Feature\NodeVariation\Command\CreateNodeVariant; -use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; -use Neos\ContentRepository\Core\NodeType\NodeTypeNames; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSubtreeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; -use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTypeConstraints; -use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy; -use Neos\ContentRepository\Core\SharedModel\User\UserId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; -use Neos\Error\Messages\Error; -use Neos\Error\Messages\Message; -use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\View\ViewInterface; use Neos\Flow\Mvc\Controller\ActionController; +use Neos\Flow\Annotations as Flow; use Neos\Fusion\View\FusionView; -use Neos\Neos\FrontendRouting\NodeAddress; use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Neos\Fusion\Helper\DimensionHelper; use Neos\Neos\Fusion\Helper\NodeHelper; use Sitegeist\Taxonomy\Service\TaxonomyService; -use Neos\Eel\FlowQuery\FlowQuery; use Neos\Flow\Persistence\PersistenceManagerInterface; use Neos\Utility\Arrays; @@ -135,8 +124,8 @@ public function indexAction(string $rootNodeAddress = null): void $subgraph = $this->taxonomyService->findSubgraph(); $rootNode = $this->taxonomyService->findOrCreateRoot($subgraph); } else { - $rootNode = $this->getNodeByNodeAddress($rootNodeAddress); - $subgraph = $this->getSubgraphForNode($rootNode); + $rootNode = $this->taxonomyService->getNodeByNodeAddress($rootNodeAddress); + $subgraph = $this->taxonomyService->getSubgraphForNode($rootNode); } $vocabularies = $this->taxonomyService->findAllVocabularies($subgraph); @@ -157,7 +146,7 @@ public function indexAction(string $rootNodeAddress = null): void */ public function changeDimensionAction(string $targetAction, string $targetProperty, string $contextNodeAddress, array $dimensions = []) { - $contextNode = $this->getNodeByNodeAddress($contextNodeAddress); + $contextNode = $this->taxonomyService->getNodeByNodeAddress($contextNodeAddress); foreach ($dimensions as $dimensionName => $dimensionValue) { $contextNodeInDimension = $this->dimensionHelper->findVariantInDimension($contextNode, $dimensionName, $dimensionValue); if ($contextNodeInDimension instanceof Node) { @@ -172,8 +161,8 @@ public function changeDimensionAction(string $targetAction, string $targetProper */ public function vocabularyAction(string $vocabularyNodeAddress) { - $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); - $subgraph = $this->getSubgraphForNode($vocabularyNode); + $vocabularyNode = $this->taxonomyService->getNodeByNodeAddress($vocabularyNodeAddress); + $subgraph = $this->taxonomyService->getSubgraphForNode($vocabularyNode); $rootNode = $this->taxonomyService->findOrCreateRoot($subgraph); $vocabularySubtree = $this->taxonomyService->findTaxonomySubtree($vocabularyNode); @@ -187,7 +176,7 @@ public function vocabularyAction(string $vocabularyNodeAddress) */ public function newVocabularyAction(string $rootNodeAddress = null): void { - $node = $this->getNodeByNodeAddress($rootNodeAddress); + $node = $this->taxonomyService->getNodeByNodeAddress($rootNodeAddress); $this->view->assign('rootNode', $node); } @@ -201,8 +190,8 @@ public function createVocabularyAction(string $rootNodeAddress, string $name, ar { $contentRepository = $this->taxonomyService->getContentRepository(); - $rootNode = $this->getNodeByNodeAddress($rootNodeAddress); - $subgraph = $this->getSubgraphForNode($rootNode); + $rootNode = $this->taxonomyService->getNodeByNodeAddress($rootNodeAddress); + $subgraph = $this->taxonomyService->getSubgraphForNode($rootNode); $liveWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()); $generalizations = $contentRepository->getVariationGraph()->getRootGeneralizations(); $nodeAddress = $this->nodeAddressFactory->createFromUriString($rootNodeAddress); @@ -259,7 +248,7 @@ public function createVocabularyAction(string $rootNodeAddress, string $name, ar public function editVocabularyAction(string $vocabularyNodeAddress) { $contentRepository = $this->taxonomyService->getContentRepository(); - $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); + $vocabularyNode = $this->taxonomyService->getNodeByNodeAddress($vocabularyNodeAddress); $subgraph = $contentRepository->getContentGraph()->getSubgraph( $vocabularyNode->subgraphIdentity->contentStreamId, @@ -278,8 +267,8 @@ public function editVocabularyAction(string $vocabularyNodeAddress) */ public function updateVocabularyAction(string $vocabularyNodeAddress, string $name, array $properties) { - $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); - $subgraph = $this->getSubgraphForNode($vocabularyNode); + $vocabularyNode = $this->taxonomyService->getNodeByNodeAddress($vocabularyNodeAddress); + $subgraph = $this->taxonomyService->getSubgraphForNode($vocabularyNode); $rootNode = $this->taxonomyService->findOrCreateRoot($subgraph); $commandResult = $this->contentRepository->handle( @@ -314,8 +303,8 @@ public function updateVocabularyAction(string $vocabularyNodeAddress, string $na */ public function deleteVocabularyAction(string $vocabularyNodeAddress) { - $vocabularyNode = $this->getNodeByNodeAddress($vocabularyNodeAddress); - $subgraph = $this->getSubgraphForNode($vocabularyNode); + $vocabularyNode = $this->taxonomyService->getNodeByNodeAddress($vocabularyNodeAddress); + $subgraph = $this->taxonomyService->getSubgraphForNode($vocabularyNode); $rootNode = $this->taxonomyService->findOrCreateRoot($subgraph); $liveWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName(\Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName::forLive()); @@ -342,8 +331,8 @@ public function deleteVocabularyAction(string $vocabularyNodeAddress) */ public function newTaxonomyAction(string $parentNodeAddress) { - $parentNode = $this->getNodeByNodeAddress($parentNodeAddress); - $subgraph = $this->getSubgraphForNode($parentNode); + $parentNode = $this->taxonomyService->getNodeByNodeAddress($parentNodeAddress); + $subgraph = $this->taxonomyService->getSubgraphForNode($parentNode); $rootNode = $this->taxonomyService->findOrCreateRoot($subgraph); $vocabularyNode = null; @@ -363,9 +352,9 @@ public function newTaxonomyAction(string $parentNodeAddress) */ public function createTaxonomyAction(string $parentNodeAddress, string $name, array $properties): void { - $parentNode = $this->getNodeByNodeAddress($parentNodeAddress); + $parentNode = $this->taxonomyService->getNodeByNodeAddress($parentNodeAddress); $vocabularyNode = $this->taxonomyService->findVocabularyForNode($parentNode); - $subgraph = $this->getSubgraphForNode($parentNode); + $subgraph = $this->taxonomyService->getSubgraphForNode($parentNode); $liveWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()); $generalizations = $this->contentRepository->getVariationGraph()->getRootGeneralizations(); $nodeAddress = $this->nodeAddressFactory->createFromUriString($parentNodeAddress); @@ -426,7 +415,7 @@ public function createTaxonomyAction(string $parentNodeAddress, string $name, ar */ public function editTaxonomyAction(string $taxonomyNodeAddress) { - $taxonomyNode = $this->getNodeByNodeAddress($taxonomyNodeAddress); + $taxonomyNode = $this->taxonomyService->getNodeByNodeAddress($taxonomyNodeAddress); $vocabularyNode = $this->taxonomyService->findVocabularyForNode($taxonomyNode); $this->view->assign('vocabularyNode', $vocabularyNode); @@ -438,9 +427,9 @@ public function editTaxonomyAction(string $taxonomyNodeAddress) */ public function updateTaxonomyAction(string $taxonomyNodeAddress, string $name, array $properties) { - $taxonomyNode = $this->getNodeByNodeAddress($taxonomyNodeAddress); + $taxonomyNode = $this->taxonomyService->getNodeByNodeAddress($taxonomyNodeAddress); $vocabularyNode = $this->taxonomyService->findVocabularyForNode($taxonomyNode); - $subgraph = $this->getSubgraphForNode($taxonomyNode); + $subgraph = $this->taxonomyService->getSubgraphForNode($taxonomyNode); $commandResult = $this->contentRepository->handle( new SetNodeProperties( @@ -476,7 +465,7 @@ public function updateTaxonomyAction(string $taxonomyNodeAddress, string $name, */ public function deleteTaxonomyAction(string $taxonomyNodeAddress) { - $taxonomyNode = $this->getNodeByNodeAddress($taxonomyNodeAddress); + $taxonomyNode = $this->taxonomyService->getNodeByNodeAddress($taxonomyNodeAddress); $vocabularyNode = $this->taxonomyService->findVocabularyForNode($taxonomyNode); $liveWorkspace = $this->contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()); @@ -496,26 +485,4 @@ public function deleteTaxonomyAction(string $taxonomyNodeAddress) $this->redirect('vocabulary', null, null, ['vocabularyNodeAddress' => $this->nodeAddressFactory->createFromNode($vocabularyNode)]); } - - protected function getNodeByNodeAddress(?string $serializedNodeAddress): ?Node - { - $contentRepository = $this->taxonomyService->getContentRepository(); - $nodeAddress = NodeAddressFactory::create($contentRepository)->createFromUriString($serializedNodeAddress); - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $nodeAddress->contentStreamId, - $nodeAddress->dimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); - return $subgraph->findNodeById($nodeAddress->nodeAggregateId); - } - - protected function getSubgraphForNode(Node $node): ContentSubgraphInterface - { - $contentRepository = $this->taxonomyService->getContentRepository(); - return $contentRepository->getContentGraph()->getSubgraph( - $node->subgraphIdentity->contentStreamId, - $node->subgraphIdentity->dimensionSpacePoint, - $node->subgraphIdentity->visibilityConstraints, - ); - } } diff --git a/Classes/Controller/SecondaryInspectorController.php b/Classes/Controller/SecondaryInspectorController.php index 89fd03f..930ccb7 100644 --- a/Classes/Controller/SecondaryInspectorController.php +++ b/Classes/Controller/SecondaryInspectorController.php @@ -12,12 +12,11 @@ * source code. */ +use Neos\ContentRepository\Core\Projection\ContentGraph\AbsoluteNodePath; +use Neos\ContentRepository\Core\Projection\ContentGraph\Subtree; use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\Controller\ActionController; use Neos\Flow\Mvc\View\JsonView; - -use Neos\ContentRepository\Domain\Model\NodeInterface; - use Sitegeist\Taxonomy\Service\TaxonomyService; /** @@ -42,15 +41,38 @@ class SecondaryInspectorController extends ActionController */ protected $defaultViewObjectName = JsonView::class; - /** - * @param NodeInterface $contextNode - * @return void - */ - public function treeAction(NodeInterface $contextNode): void + public function treeAction(string $contextNode, string $startingPoint): void + { + $contextNode = $this->taxonomyService->getNodeByNodeAddress($contextNode); + $subgraph = $this->taxonomyService->getSubgraphForNode($contextNode); + + $path = AbsoluteNodePath::fromString($startingPoint); + $startingPoint = $subgraph->findNodeByAbsolutePath($path); + + $taxonomySubtree = $this->taxonomyService->findTaxonomySubtree($startingPoint); + + $this->view->assign('value', $this->toJson($taxonomySubtree)); + } + + protected function toJson(Subtree $subtree, array $pathSoFar = []): array { - $taxonomyTreeAsArray = $this->taxonomyService - ->getTaxonomyTreeAsArray($contextNode); + $result = []; + + $result['identifier'] = $subtree->node->nodeAggregateId->value; + $result['path'] = implode('/', $pathSoFar); + $result['nodeType'] = $subtree->node->nodeType->name->value; + $result['label'] = $subtree->node->getLabel(); + $result['title'] = $subtree->node->getProperty('title'); + $result['description'] = $subtree->node->getProperty('description'); + + $result['children'] = []; + + $name = $subtree->node->nodeName ? $subtree->node->nodeName->value : $subtree->node->getProperty('title'); + + foreach ($subtree->children as $childSubtree) { + $result['children'][] = $this->toJson($childSubtree, [...$pathSoFar, $name]); + } - $this->view->assign('value', $taxonomyTreeAsArray); + return $result; } } diff --git a/Classes/Eel/TaxonomyHelper.php b/Classes/Eel/TaxonomyHelper.php index 433fbff..1b5dfe2 100644 --- a/Classes/Eel/TaxonomyHelper.php +++ b/Classes/Eel/TaxonomyHelper.php @@ -4,10 +4,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentSubgraph; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\Flow\Annotations as Flow; -use Neos\Eel\FlowQuery\FlowQuery; -use Neos\ContentRepository\Domain\Model\NodeInterface; use Sitegeist\Taxonomy\Service\TaxonomyService; -use Neos\Neos\Domain\Service\ContentContext; use Neos\Eel\ProtectedContextAwareInterface; class TaxonomyHelper implements ProtectedContextAwareInterface diff --git a/Classes/Package.php b/Classes/Package.php deleted file mode 100644 index 670ff4e..0000000 --- a/Classes/Package.php +++ /dev/null @@ -1,39 +0,0 @@ -getSignalSlotDispatcher(); -// $dispatcher->connect( -// Node::class, -// 'nodeAdded', -// ContentRepositoryHooks::class, -// 'nodeAdded' -// ); -// -// $dispatcher = $bootstrap->getSignalSlotDispatcher(); -// $dispatcher->connect( -// Node::class, -// 'nodeRemoved', -// ContentRepositoryHooks::class, -// 'nodeRemoved' -// ); - } - } -} diff --git a/Classes/Service/TaxonomyService.php b/Classes/Service/TaxonomyService.php index 303d693..daf5392 100644 --- a/Classes/Service/TaxonomyService.php +++ b/Classes/Service/TaxonomyService.php @@ -1,11 +1,8 @@ crRegistry->get(ContentRepositoryId::fromString($this->crIdentifier)); + if (is_null($this->contentRepository)) { + $this->contentRepository = $this->crRegistry->get(ContentRepositoryId::fromString($this->crIdentifier)); + } + return $this->contentRepository; } public function findSubgraph(): ContentSubgraphInterface @@ -210,7 +204,7 @@ public function findTaxonomySubtree(Node $node): Subtree $vocabularySubtree = $subgraph->findSubtree( $node->nodeAggregateId, FindSubtreeFilter::create( - $this->getTaxonomyNodeType() + NodeTypeConstraints::fromFilterString($this->getTaxonomyNodeType() . ',' .$this->getVocabularyNodeType()) ) ); @@ -234,4 +228,21 @@ private function orderSubtreeByNameRecursive(Subtree $subtree): Subtree $children ); } + + public function getNodeByNodeAddress(?string $serializedNodeAddress): ?Node + { + $contentRepository = $this->getContentRepository(); + $nodeAddress = NodeAddressFactory::create($contentRepository)->createFromUriString($serializedNodeAddress); + $subgraph = $contentRepository->getContentGraph()->getSubgraph( + $nodeAddress->contentStreamId, + $nodeAddress->dimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() + ); + return $subgraph->findNodeById($nodeAddress->nodeAggregateId); + } + + public function getSubgraphForNode(Node $node): ContentSubgraphInterface + { + return $this->crRegistry->subgraphForNode($node); + } } diff --git a/Configuration/Routes.yaml b/Configuration/Routes.yaml index a771047..200ba9f 100644 --- a/Configuration/Routes.yaml +++ b/Configuration/Routes.yaml @@ -1,9 +1,9 @@ - name: 'Sitegeist.Taxonomy: Secondary Inspector' - uriPattern: 'taxonomy/secondary-inspector/{@action}' + uriPattern: 'neos/taxonomy/secondary-inspector/{@action}' defaults: '@package': 'Sitegeist.Taxonomy' '@subpackage': '' '@controller': 'SecondaryInspector' '@format': 'json' - httpMethods: ['GET'] \ No newline at end of file + httpMethods: ['GET'] diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index a5df928..f790ac9 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -80,7 +80,7 @@ Neos: providers: Neos.Neos:Backend: requestPatterns: - 'Sitegeist.Taxonomy:secondaryInspector': + 'Sitegeist.Taxonomy:SecondaryInspector': pattern: ControllerObjectName patternOptions: controllerObjectNamePattern: 'Sitegeist\Taxonomy\Controller\SecondaryInspectorController' diff --git a/NodeTypes/Mixin.TaxonomyReferences.yaml b/NodeTypes/Mixin.TaxonomyReferences.yaml index 157553a..d0e6621 100644 --- a/NodeTypes/Mixin.TaxonomyReferences.yaml +++ b/NodeTypes/Mixin.TaxonomyReferences.yaml @@ -16,7 +16,7 @@ Sitegeist.Taxonomy:Mixin.TaxonomyReferences: label: i18n inspector: group: taxonomy - #editor: 'Sitegeist.Taxonomy:TaxonomyEditor' + editor: 'Sitegeist.Taxonomy:TaxonomyEditor' editorOptions: nodeTypes: [ 'Sitegeist.Taxonomy:Taxonomy' ] startingPoint: '/' diff --git a/Resources/Private/Scripts/TaxonomyEditor/src/TaxonomyTreeSelect.js b/Resources/Private/Scripts/TaxonomyEditor/src/TaxonomyTreeSelect.js index 98167cc..4b11f20 100755 --- a/Resources/Private/Scripts/TaxonomyEditor/src/TaxonomyTreeSelect.js +++ b/Resources/Private/Scripts/TaxonomyEditor/src/TaxonomyTreeSelect.js @@ -56,11 +56,9 @@ export default class TaxonomyTreeSelect extends PureComponent { get tree() { const {contextForNodeLinking, options} = this.props; - const [, contextString] = contextForNodeLinking.contextNode.split('@'); - const startingPointWithContext = `${options.startingPoint}@${contextString}`; return fetchWithErrorHandling.withCsrfToken(csrfToken => ({ - url: `/taxonomy/secondary-inspector/tree?contextNode=${startingPointWithContext}`, + url: `/neos/taxonomy/secondary-inspector/tree?startingPoint=${options.startingPoint}&contextNode=${contextForNodeLinking.contextNode}`, method: 'GET', credentials: 'include', headers: { diff --git a/Resources/Public/JavaScript/TaxonomyEditor/Plugin.js b/Resources/Public/JavaScript/TaxonomyEditor/Plugin.js index 63af35c..6539b81 100644 --- a/Resources/Public/JavaScript/TaxonomyEditor/Plugin.js +++ b/Resources/Public/JavaScript/TaxonomyEditor/Plugin.js @@ -1,3 +1,3 @@ /*! For license information please see Plugin.js.LICENSE.txt */ -!function(t){var e={};function __webpack_require__(r){if(e[r])return e[r].exports;var n=e[r]={i:r,l:!1,exports:{}};return t[r].call(n.exports,n,n.exports,__webpack_require__),n.l=!0,n.exports}__webpack_require__.m=t,__webpack_require__.c=e,__webpack_require__.d=function(t,e,r){__webpack_require__.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},__webpack_require__.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},__webpack_require__.t=function(t,e){if(1&e&&(t=__webpack_require__(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(__webpack_require__.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)__webpack_require__.d(r,n,function(e){return t[e]}.bind(null,n));return r},__webpack_require__.n=function(t){var e=t&&t.__esModule?function getDefault(){return t.default}:function getModuleExports(){return t};return __webpack_require__.d(e,"a",e),e},__webpack_require__.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},__webpack_require__.p="",__webpack_require__(__webpack_require__.s=222)}([function(t,e,r){var n=r(3),o=r(24).f,i=r(28),a=r(17),u=r(124),c=r(99),s=r(80);t.exports=function(t,e){var r,f,l,p,h,d=t.target,v=t.global,y=t.stat;if(r=v?n:y?n[d]||u(d,{}):(n[d]||{}).prototype)for(f in e){if(p=e[f],l=t.dontCallGetSet?(h=o(r,f))&&h.value:r[f],!s(v?f:d+(y?".":"#")+f,t.forced)&&void 0!==l){if(typeof p==typeof l)continue;c(p,l)}(t.sham||l&&l.sham)&&i(p,"sham",!0),a(r,f,p,t)}}},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e,r){var n=r(77),o=Function.prototype,i=o.call,a=n&&o.bind.bind(i,i);t.exports=n?a:function(t){return function(){return i.apply(t,arguments)}}},function(t,e,r){(function(e){var check=function(t){return t&&t.Math==Math&&t};t.exports=check("object"==typeof globalThis&&globalThis)||check("object"==typeof window&&window)||check("object"==typeof self&&self)||check("object"==typeof e&&e)||function(){return this}()||Function("return this")()}).call(this,r(246))},function(t,e,r){var n=r(1);t.exports=!n((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},function(t,e,r){var n=r(7),o=String,i=TypeError;t.exports=function(t){if(n(t))return t;throw i(o(t)+" is not an object")}},function(t,e,r){var n=r(77),o=Function.prototype.call;t.exports=n?o.bind(o):function(){return o.apply(o,arguments)}},function(t,e,r){var n=r(10),o=r(163),i=o.all;t.exports=o.IS_HTMLDDA?function(t){return"object"==typeof t?null!==t:n(t)||t===i}:function(t){return"object"==typeof t?null!==t:n(t)}},function(t,e,r){var n=r(3),o=r(67),i=r(11),a=r(68),u=r(66),c=r(164),s=o("wks"),f=n.Symbol,l=f&&f.for,p=c?f:f&&f.withoutSetter||a;t.exports=function(t){if(!i(s,t)||!u&&"string"!=typeof s[t]){var e="Symbol."+t;u&&i(f,t)?s[t]=f[t]:s[t]=c&&l?l(e):p(e)}return s[t]}},function(t,e,r){var n=r(42),o=String;t.exports=function(t){if("Symbol"===n(t))throw TypeError("Cannot convert a Symbol value to a string");return o(t)}},function(t,e,r){var n=r(163),o=n.all;t.exports=n.IS_HTMLDDA?function(t){return"function"==typeof t||t===o}:function(t){return"function"==typeof t}},function(t,e,r){var n=r(2),o=r(13),i=n({}.hasOwnProperty);t.exports=Object.hasOwn||function hasOwn(t,e){return i(o(t),e)}},function(t,e,r){"use strict";var n,o,i,a=r(142),u=r(4),c=r(3),s=r(10),f=r(7),l=r(11),p=r(42),h=r(60),d=r(28),v=r(17),y=r(14).f,g=r(36),m=r(38),b=r(43),x=r(8),_=r(68),w=r(20),E=w.enforce,S=w.get,A=c.Int8Array,O=A&&A.prototype,T=c.Uint8ClampedArray,R=T&&T.prototype,I=A&&m(A),P=O&&m(O),M=Object.prototype,k=c.TypeError,j=x("toStringTag"),N=_("TYPED_ARRAY_TAG"),C=a&&!!b&&"Opera"!==p(c.opera),L=!1,D={Int8Array:1,Uint8Array:1,Uint8ClampedArray:1,Int16Array:2,Uint16Array:2,Int32Array:4,Uint32Array:4,Float32Array:4,Float64Array:8},U={BigInt64Array:8,BigUint64Array:8},getTypedArrayConstructor=function(t){var e=m(t);if(f(e)){var r=S(e);return r&&l(r,"TypedArrayConstructor")?r.TypedArrayConstructor:getTypedArrayConstructor(e)}},isTypedArray=function(t){if(!f(t))return!1;var e=p(t);return l(D,e)||l(U,e)};for(n in D)(i=(o=c[n])&&o.prototype)?E(i).TypedArrayConstructor=o:C=!1;for(n in U)(i=(o=c[n])&&o.prototype)&&(E(i).TypedArrayConstructor=o);if((!C||!s(I)||I===Function.prototype)&&(I=function TypedArray(){throw k("Incorrect invocation")},C))for(n in D)c[n]&&b(c[n],I);if((!C||!P||P===M)&&(P=I.prototype,C))for(n in D)c[n]&&b(c[n].prototype,P);if(C&&m(R)!==P&&b(R,P),u&&!l(P,j))for(n in L=!0,y(P,j,{get:function(){return f(this)?this[N]:void 0}}),D)c[n]&&d(c[n],N,n);t.exports={NATIVE_ARRAY_BUFFER_VIEWS:C,TYPED_ARRAY_TAG:L&&N,aTypedArray:function(t){if(isTypedArray(t))return t;throw k("Target is not a typed array")},aTypedArrayConstructor:function(t){if(s(t)&&(!b||g(I,t)))return t;throw k(h(t)+" is not a typed array constructor")},exportTypedArrayMethod:function(t,e,r,n){if(u){if(r)for(var o in D){var i=c[o];if(i&&l(i.prototype,t))try{delete i.prototype[t]}catch(r){try{i.prototype[t]=e}catch(t){}}}P[t]&&!r||v(P,t,r?e:C&&O[t]||e,n)}},exportTypedArrayStaticMethod:function(t,e,r){var n,o;if(u){if(b){if(r)for(n in D)if((o=c[n])&&l(o,t))try{delete o[t]}catch(t){}if(I[t]&&!r)return;try{return v(I,t,r?e:C&&I[t]||e)}catch(t){}}for(n in D)!(o=c[n])||o[t]&&!r||v(o,t,e)}},getTypedArrayConstructor:getTypedArrayConstructor,isView:function isView(t){if(!f(t))return!1;var e=p(t);return"DataView"===e||l(D,e)||l(U,e)},isTypedArray:isTypedArray,TypedArray:I,TypedArrayPrototype:P}},function(t,e,r){var n=r(19),o=Object;t.exports=function(t){return o(n(t))}},function(t,e,r){var n=r(4),o=r(166),i=r(167),a=r(5),u=r(54),c=TypeError,s=Object.defineProperty,f=Object.getOwnPropertyDescriptor;e.f=n?i?function defineProperty(t,e,r){if(a(t),e=u(e),a(r),"function"==typeof t&&"prototype"===e&&"value"in r&&"writable"in r&&!r.writable){var n=f(t,e);n&&n.writable&&(t[e]=r.value,r={configurable:"configurable"in r?r.configurable:n.configurable,enumerable:"enumerable"in r?r.enumerable:n.enumerable,writable:!1})}return s(t,e,r)}:s:function defineProperty(t,e,r){if(a(t),e=u(e),a(r),o)try{return s(t,e,r)}catch(t){}if("get"in r||"set"in r)throw c("Accessors not supported");return"value"in r&&(t[e]=r.value),t}},function(t,e,r){var n=r(30);t.exports=function(t){return n(t.length)}},function(t,e,r){var n=r(3),o=r(10),aFunction=function(t){return o(t)?t:void 0};t.exports=function(t,e){return arguments.length<2?aFunction(n[t]):n[t]&&n[t][e]}},function(t,e,r){var n=r(10),o=r(14),i=r(125),a=r(124);t.exports=function(t,e,r,u){u||(u={});var c=u.enumerable,s=void 0!==u.name?u.name:e;if(n(r)&&i(r,s,u),u.global)c?t[e]=r:a(e,r);else{try{u.unsafe?t[e]&&(c=!0):delete t[e]}catch(t){}c?t[e]=r:o.f(t,e,{value:r,enumerable:!1,configurable:!u.nonConfigurable,writable:!u.nonWritable})}return t}},function(t,e){t.exports=!1},function(t,e,r){var n=r(27),o=TypeError;t.exports=function(t){if(n(t))throw o("Can't call method on "+t);return t}},function(t,e,r){var n,o,i,a=r(168),u=r(3),c=r(7),s=r(28),f=r(11),l=r(123),p=r(98),h=r(78),d=u.TypeError,v=u.WeakMap;if(a||l.state){var y=l.state||(l.state=new v);y.get=y.get,y.has=y.has,y.set=y.set,n=function(t,e){if(y.has(t))throw d("Object already initialized");return e.facade=t,y.set(t,e),e},o=function(t){return y.get(t)||{}},i=function(t){return y.has(t)}}else{var g=p("state");h[g]=!0,n=function(t,e){if(f(t,g))throw d("Object already initialized");return e.facade=t,s(t,g,e),e},o=function(t){return f(t,g)?t[g]:{}},i=function(t){return f(t,g)}}t.exports={set:n,get:o,has:i,enforce:function(t){return i(t)?o(t):n(t,{})},getterFor:function(t){return function(e){var r;if(!c(e)||(r=o(e)).type!==t)throw d("Incompatible receiver, "+t+" required");return r}}}},function(t,e,r){var n=r(10),o=r(60),i=TypeError;t.exports=function(t){if(n(t))return t;throw i(o(t)+" is not a function")}},function(t,e,r){var n=r(170);t.exports=function(t){var e=+t;return e!=e||0===e?0:n(e)}},function(t,e,r){var n=r(40),o=r(2),i=r(65),a=r(13),u=r(15),c=r(83),s=o([].push),createMethod=function(t){var e=1==t,r=2==t,o=3==t,f=4==t,l=6==t,p=7==t,h=5==t||l;return function(d,v,y,g){for(var m,b,x=a(d),_=i(x),w=n(v,y),E=u(_),S=0,A=g||c,O=e?A(d,E):r||p?A(d,0):void 0;E>S;S++)if((h||S in _)&&(b=w(m=_[S],S,x),t))if(e)O[S]=b;else if(b)switch(t){case 3:return!0;case 5:return m;case 6:return S;case 2:s(O,m)}else switch(t){case 4:return!1;case 7:s(O,m)}return l?-1:o||f?f:O}};t.exports={forEach:createMethod(0),map:createMethod(1),filter:createMethod(2),some:createMethod(3),every:createMethod(4),find:createMethod(5),findIndex:createMethod(6),filterReject:createMethod(7)}},function(t,e,r){var n=r(4),o=r(6),i=r(95),a=r(35),u=r(25),c=r(54),s=r(11),f=r(166),l=Object.getOwnPropertyDescriptor;e.f=n?l:function getOwnPropertyDescriptor(t,e){if(t=u(t),e=c(e),f)try{return l(t,e)}catch(t){}if(s(t,e))return a(!o(i.f,t,e),t[e])}},function(t,e,r){var n=r(65),o=r(19);t.exports=function(t){return n(o(t))}},function(t,e,r){var n=r(2),o=n({}.toString),i=n("".slice);t.exports=function(t){return i(o(t),8,-1)}},function(t,e){t.exports=function(t){return null==t}},function(t,e,r){var n=r(4),o=r(14),i=r(35);t.exports=n?function(t,e,r){return o.f(t,e,i(1,r))}:function(t,e,r){return t[e]=r,t}},function(t,e,r){var n=r(132),o=r(11),i=r(172),a=r(14).f;t.exports=function(t){var e=n.Symbol||(n.Symbol={});o(e,t)||a(e,t,{value:i.f(t)})}},function(t,e,r){var n=r(22),o=Math.min;t.exports=function(t){return t>0?o(n(t),9007199254740991):0}},function(t,e,r){var n,o=r(5),i=r(130),a=r(128),u=r(78),c=r(171),s=r(97),f=r(98),l=f("IE_PROTO"),EmptyConstructor=function(){},scriptTag=function(t){return"