Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: FEATURE: Refactoring for Neos 9 #66

Draft
wants to merge 35 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c9fa2b0
WIP: Initial refactioring for neos 9
mficzel Jun 9, 2023
8c8a7a1
TASK: Reimplement dimension switch in backend module
mficzel Jun 14, 2023
0cbf33e
TASK: Minor cleanup
mficzel Jun 15, 2023
9b6ad96
TASK: Add name property with aggregate scope to taxonomies and vocabu…
mficzel Jun 15, 2023
3de1c50
TASK: Move NodeTypes to specific folder and manage NodeNames for voca…
mficzel Jun 16, 2023
34969b6
TASK: Cleanup and reimplement parts of the command controller
mficzel Jun 16, 2023
2ac90d1
TASK: Adjust starting point and rename mixin to TaxonomyReferences to…
mficzel Jun 19, 2023
1f646b5
TASK: Adjust TaxonomyEditor to work with Neos 9
mficzel Jun 20, 2023
a6a8925
TASK: Cleanup and improve names
mficzel Jun 20, 2023
c0eb42f
TASK: Add linting and static analysis
mficzel Jun 21, 2023
e0dbbbc
TASK: Cleanup
mficzel Jun 21, 2023
e93d839
TASK: Make linter almost happy
mficzel Jun 22, 2023
7e7c95d
FEATURE: Add flowQueryOperations `taxonomyReferencingNodes`, `taxonom…
mficzel Jun 22, 2023
6019381
TASK: Make linter as possible for now
mficzel Jun 23, 2023
026542a
TASK: Taxonomy FlowQuery operations `taxonomyDescendants`, `taxonomyA…
mficzel Jun 29, 2023
21e2bdd
TASK: Make phpstan happy
mficzel Jul 9, 2023
6dfe690
TASK: Update docs and remove eel helper
mficzel Jul 9, 2023
078fde5
TASK: Remove flow query operations, those should be added in a separa…
mficzel Sep 13, 2023
c628438
TASK: Use named `create` constructor for all commands
ahaeslich Sep 17, 2023
f4d1177
Merge pull request #67 from queoGmbH/create-command-constructor
mficzel Sep 18, 2023
21330bd
TASK: Add flowQuery operations
mficzel Sep 18, 2023
0347006
TASK: Use cr registry to get the subgraph and also fix bug when creat…
mficzel Sep 18, 2023
3efb8bd
TASK: Remove Silhouettes from configuration
mficzel Oct 13, 2023
93371fc
TASK: Avoid using node->nodeType->isOf ... instead use node->nodeType…
mficzel Oct 13, 2023
b35d846
TASK: Remove configurable nodeTypeNames and hardwire them in Taxonomy…
mficzel Oct 13, 2023
d049283
TASK: Remove usage of upstream deleted exception
mficzel Oct 18, 2023
757ba63
TASK: Rebase current user workspace after changes to taxonomies
mficzel Oct 18, 2023
0608751
Properly handle tethered nodes
Oct 30, 2023
1daf464
TASK: Adjust to renamed NodeTypeConstraints
ahaeslich Nov 3, 2023
da9ba82
Merge pull request #69 from queoGmbH/task/adjustToRenamedNodeType
mficzel Nov 3, 2023
7d5b643
Merge branch 'neos-9' into tetheredNodes
Nov 15, 2023
48fa93a
Remove redundant StopCommandException
Nov 17, 2023
3e42990
Merge pull request #68 from sitegeist/tetheredNodes
nezaniel Nov 17, 2023
db5ffc6
TASK: Remove plow-js, Update to 8.3 extensibility and esbuild stack
grebaldi Jul 16, 2024
b5282b6
Merge pull request #73 from grebaldi/bugfix/neos-9-ui-compatibility
nezaniel Jul 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: build

on:
push:
branches:
- 'main'
pull_request: ~

jobs:
test:
name: "Test (PHP ${{ matrix.php-versions }}, Neos ${{ matrix.neos-versions }})"

strategy:
fail-fast: false
matrix:
php-versions: ['8.2']
neos-versions: ['9.0']

runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v2
with:
path: ${{ env.FLOW_FOLDER }}

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, xml, json, zlib, iconv, intl, pdo_sqlite
ini-values: date.timezone="Africa/Tunis", opcache.fast_shutdown=0, apc.enable_cli=on

# disabled as long as we have no official 9.0 version
#- name: Set Neos Version
# run: composer require neos/neos ^${{ matrix.neos-versions }} --no-progress --no-interaction

- name: Run Tests
run: composer test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
Resources/Private/Scripts/TaxonomyEditor/node_modules
composer.lock
Packages
vendor
304 changes: 61 additions & 243 deletions Classes/Command/TaxonomyCommandController.php
Original file line number Diff line number Diff line change
@@ -1,285 +1,103 @@
<?php

/**
* This file is part of the Sitegeist.Taxonomies package
*
* (c) 2017
* Martin Ficzel <[email protected]>
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Sitegeist\Taxonomy\Command;

use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath;
use Neos\ContentRepository\Core\Projection\ContentGraph\Subtree;
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 Neos\Flow\Cli\Exception\StopCommandException;
use Neos\Flow\Mvc\Exception\StopActionException;
use Sitegeist\Taxonomy\Service\TaxonomyService;

/**
* @Flow\Scope("singleton")
*/
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 TaxonomyService
* @Flow\Inject
*/
protected $taxonomyService;

/**
* @var DimensionService
* @Flow\Inject
*/
protected $dimensionService;

/**
* @var PersistenceManagerInterface
* @Flow\Inject
* List all vocabularies
*/
protected $persistenceManager;

/**
* List taxonomy vocabularies
*
* @param string $vocabularyNode vocabularay nodename(path) to prune (globbing is supported)
* @return void
*/
public function listCommand()
public function vocabulariesCommand(): void
{
$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());
}
$subgraph = $this->taxonomyService->getDefaultSubgraph();
$vocabularies = $this->taxonomyService->findAllVocabularies($subgraph);
$this->output->outputTable(
array_map(
fn(Node $node) => [
$node->nodeName?->value ?? $node->nodeAggregateId->value,
$node->getProperty('title'),
$node->getProperty('description')
],
iterator_to_array($vocabularies->getIterator())
),
['name', 'title', 'description']
);
}

/**
* Import taxonomy content
* List taxonomies inside a vocabulary
*
* @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
* @param string $vocabulary name of the vocabulary to access
* @param string $path path to the taxonomy starting at the vocabulary
*/
public function importCommand($filename, $vocabulary = null)
public function taxonomiesCommand(string $vocabulary, string $path = ''): void
{
$xmlReader = new \XMLReader();
$xmlReader->open($filename, null, LIBXML_PARSEHUGE);
$subgraph = $this->taxonomyService->getDefaultSubgraph();

$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]);
if ($path) {
$startPoint = $this->taxonomyService->findTaxonomyByVocabularyNameAndPath($subgraph, $vocabulary, $path);
} else {
$startPoint = $this->taxonomyService->findVocabularyByName($subgraph, $vocabulary);
}
}

/**
* 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');
if (!$startPoint) {
$this->outputLine('nothing found');
$this->quit(1);
}

$targetContextValues = $this->dimensionService->getDimensionValuesForSubgraph($targetSubgraph);
$flowQuery = new FlowQuery([$taxonomyRoot]);
$taxonomyRootInTargetContest = $flowQuery->context($targetContextValues)->get(0);
$subtree = $this->taxonomyService->findSubtree($startPoint);

if (!$taxonomyRootInTargetContest) {
$this->outputLine('Not root in target context found');
$this->quit(1);
if ($subtree) {
$this->output->outputTable(
$this->subtreeToTableRowsRecursively($subtree),
['name', 'title', 'description']
);
}

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
* @return array<int, array<int, string>>
*/
public function populateDimensionCommand($dimensionName, $dimensionValue)
private function subtreeToTableRowsRecursively(Subtree $subtree): array
{
$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');
$rows = array_map(fn(Subtree $subtree)=>$this->subtreeToTableRowsRecursively($subtree), $subtree->children);
$row = [
str_repeat(' ', $subtree->level) . ($subtree->node->nodeName?->value ?? $subtree->node->nodeAggregateId->value),
(string) $subtree->node->getProperty('title'),
(string) $subtree->node->getProperty('description')
];

return array_merge([$row], ...$rows);
}
}
Loading
Loading