Skip to content

Commit b6672ec

Browse files
committed
Upmerge nodetemplates version 2 changes into version 3-dev
This upmerge includes changes until 2.0.1 aswell as the WIP features #58 and #67
2 parents cc9a6ed + 330fa20 commit b6672ec

76 files changed

Lines changed: 2261 additions & 832 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/tests.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [ main, '[0-9]+.[0-9]' ]
6+
pull_request:
7+
branches: [ main, '[0-9]+.[0-9]' ]
8+
workflow_dispatch: # Allow manual triggering on any branch via `gh workflow run tests.yml -r branch-name`
9+
10+
jobs:
11+
build:
12+
env:
13+
FLOW_CONTEXT: Testing
14+
FLOW_PATH_ROOT: ../neos-base-distribution
15+
16+
runs-on: ubuntu-latest
17+
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
include:
22+
- php-version: 7.4
23+
neos-version: 7.3
24+
- php-version: 8.1
25+
neos-version: 8.3
26+
27+
steps:
28+
- name: Checkout code
29+
uses: actions/checkout@v3
30+
31+
- name: Setup PHP
32+
uses: shivammathur/setup-php@v2
33+
with:
34+
php-version: ${{ matrix.php-version }}
35+
extensions: mbstring, xml, json, zlib, iconv, intl, pdo_sqlite, mysql
36+
37+
- id: composer-cache
38+
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
39+
shell: bash
40+
41+
- uses: actions/cache@v2
42+
with:
43+
path: ${{ steps.composer-cache.outputs.dir }}
44+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
45+
restore-keys: ${{ runner.os }}-composer-
46+
47+
- name: Prepare Neos distribution
48+
run: |
49+
git clone --depth 1 --branch ${{ matrix.neos-version }} https://github.com/neos/neos-base-distribution.git ${FLOW_PATH_ROOT}
50+
cd ${FLOW_PATH_ROOT}
51+
composer config --no-plugins allow-plugins.neos/composer-plugin true
52+
composer config repositories.tested-package path ../Flowpack.NodeTemplates
53+
composer require --no-update --no-interaction flowpack/nodetemplates:@dev
54+
55+
- name: Install dependencies
56+
run: |
57+
cd ${FLOW_PATH_ROOT}
58+
composer install --no-interaction --no-progress --prefer-dist
59+
60+
- name: Run Unit tests
61+
run: |
62+
cd ${FLOW_PATH_ROOT}
63+
bin/phpunit --colors -c Build/BuildEssentials/PhpUnit/UnitTests.xml Packages/Application/Flowpack.NodeTemplates/Tests/Unit
64+
65+
- name: Run Functional tests
66+
run: |
67+
cd ${FLOW_PATH_ROOT}
68+
bin/phpunit --colors -c Build/BuildEssentials/PhpUnit/FunctionalTests.xml Packages/Application/Flowpack.NodeTemplates/Tests/Functional

Classes/Application/Command/NodeTemplateCommandController.php

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
namespace Flowpack\NodeTemplates\Application\Command;
66

7+
use Flowpack\NodeTemplates\Domain\ExceptionHandling\CaughtExceptions;
8+
use Flowpack\NodeTemplates\Domain\NodeCreation\ToBeCreatedNode;
79
use Flowpack\NodeTemplates\Domain\NodeTemplateDumper\NodeTemplateDumper;
10+
use Flowpack\NodeTemplates\Domain\TemplateConfiguration\TemplateConfigurationProcessor;
811
use Neos\Flow\Annotations as Flow;
912
use Neos\Flow\Cli\CommandController;
1013

@@ -16,6 +19,13 @@ class NodeTemplateCommandController extends CommandController
1619
*/
1720
protected $nodeTemplateDumper;
1821

22+
/**
23+
* @Flow\Inject
24+
* @var TemplateConfigurationProcessor
25+
*/
26+
protected $templateConfigurationProcessor;
27+
28+
1929
/**
2030
* Dump the node tree structure into a NodeTemplate YAML structure.
2131
* References to Nodes and non-primitive property values are commented out in the YAML.
@@ -28,7 +38,7 @@ public function createFromNodeSubtreeCommand(string $startingNodeId, string $wor
2838
{
2939
// TODO re-enable
3040
throw new \BadMethodCallException('Not implemented.');
31-
$subgraph = $this->contentContextFactory->create([
41+
$subgraph = $this->contextFactory->create([
3242
'workspaceName' => $workspaceName
3343
]);
3444
$node = $subgraph->getNodeByIdentifier($startingNodeId);
@@ -37,4 +47,83 @@ public function createFromNodeSubtreeCommand(string $startingNodeId, string $wor
3747
}
3848
echo $this->nodeTemplateDumper->createNodeTemplateYamlDumpFromSubtree($node);
3949
}
50+
51+
/**
52+
* Checks if all configured NodeTemplates are valid. E.g no syntax errors in EEL expressions,
53+
* that properties exist on the node type and their types match and other checks.
54+
*
55+
* We process and build all configured NodeType templates. No nodes will be created in the Content Repository.
56+
*
57+
*/
58+
public function validateCommand(): void
59+
{
60+
// TODO re-enable
61+
throw new \BadMethodCallException('Not implemented.');
62+
$templatesChecked = 0;
63+
/**
64+
* nodeTypeNames as index
65+
* @var array<string, array{caughtExceptions: CaughtExceptions, dataWasAccessed: bool}> $faultyNodeTypeTemplates
66+
*/
67+
$faultyNodeTypeTemplates = [];
68+
69+
foreach ($this->nodeTypeManager->getNodeTypes(false) as $nodeType) {
70+
$templateConfiguration = $nodeType->getOptions()['template'] ?? null;
71+
if (!$templateConfiguration) {
72+
continue;
73+
}
74+
$caughtExceptions = CaughtExceptions::create();
75+
76+
$subgraph = $this->contextFactory->create();
77+
78+
$observableEmptyData = new class ([]) extends \ArrayObject
79+
{
80+
public bool $dataWasAccessed = false;
81+
public function offsetExists($key)
82+
{
83+
$this->dataWasAccessed = true;
84+
return false;
85+
}
86+
};
87+
88+
$template = $this->templateConfigurationProcessor->processTemplateConfiguration(
89+
$templateConfiguration,
90+
[
91+
'data' => $observableEmptyData,
92+
'triggeringNode' => $subgraph->getRootNode(),
93+
],
94+
$caughtExceptions
95+
);
96+
97+
$nodeCreation = new NodeCreationService($subgraph);
98+
$nodeCreation->createMutatorCollection($template, ToBeCreatedNode::fromRegular($nodeType), $caughtExceptions);
99+
100+
if ($caughtExceptions->hasExceptions()) {
101+
$faultyNodeTypeTemplates[$nodeType->getName()] = ['caughtExceptions' => $caughtExceptions, 'dataWasAccessed' => $observableEmptyData->dataWasAccessed];
102+
}
103+
$templatesChecked++;
104+
}
105+
106+
if (empty($faultyNodeTypeTemplates)) {
107+
$this->outputLine(sprintf('<success>%d NodeType templates validated.</success>', $templatesChecked));
108+
return;
109+
}
110+
111+
$possiblyFaultyTemplates = count($faultyNodeTypeTemplates);
112+
$this->outputLine(sprintf('<comment>%d of %d NodeType template validated. %d could not be build standalone.</comment>', $templatesChecked - $possiblyFaultyTemplates, $templatesChecked, $possiblyFaultyTemplates));
113+
114+
$this->outputLine();
115+
116+
foreach ($faultyNodeTypeTemplates as $nodeTypeName => ['caughtExceptions' => $caughtExceptions, 'dataWasAccessed' => $dataWasAccessed]) {
117+
if ($dataWasAccessed) {
118+
$this->outputLine(sprintf('<comment>%s</comment> <b>(depends on "data" context)</b>', $nodeTypeName));
119+
} else {
120+
$this->outputLine(sprintf('<error>%s</error>', $nodeTypeName));
121+
}
122+
123+
foreach ($caughtExceptions as $caughtException) {
124+
$this->outputLine(' ' . $caughtException->toMessage());
125+
$this->outputLine();
126+
}
127+
}
128+
}
40129
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flowpack\NodeTemplates\Domain\NodeCreation;
6+
7+
class InvalidReferenceException extends \RuntimeException
8+
{
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Flowpack\NodeTemplates\Domain\NodeCreation;
4+
5+
class NodeConstraintException extends \RuntimeException
6+
{
7+
}

Classes/Domain/NodeCreation/NodeCreationService.php

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,27 @@
2323
use Neos\ContentRepository\Core\SharedModel\Node\ReferenceName;
2424
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
2525
use Neos\Flow\Annotations as Flow;
26+
use Neos\Flow\Property\PropertyMapper;
2627
use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands;
2728
use Neos\Neos\Utility\NodeUriPathSegmentGenerator;
2829

2930
class NodeCreationService
3031
{
31-
/**
32-
* @Flow\Inject
33-
* @var NodeUriPathSegmentGenerator
34-
*/
35-
protected $nodeUriPathSegmentGenerator;
32+
private readonly NodeTypeManager $nodeTypeManager;
33+
34+
private readonly NodeUriPathSegmentGenerator $nodeUriPathSegmentGenerator;
35+
36+
private PropertiesHandler $propertiesHandler;
3637

3738
public function __construct(
38-
private readonly ContentSubgraphInterface $subgraph,
39-
private readonly NodeTypeManager $nodeTypeManager
39+
ContentSubgraphInterface $subgraph,
40+
NodeTypeManager $nodeTypeManager,
41+
PropertyMapper $propertyMapper,
42+
NodeUriPathSegmentGenerator $nodeUriPathSegmentGenerator
4043
) {
44+
$this->nodeTypeManager = $nodeTypeManager;
45+
$this->nodeUriPathSegmentGenerator = $nodeUriPathSegmentGenerator;
46+
$this->propertiesHandler = new PropertiesHandler($subgraph, $propertyMapper);
4147
}
4248

4349
/**
@@ -67,11 +73,11 @@ public function apply(RootTemplate $template, NodeCreationCommands $commands, Ca
6773

6874
return $this->applyTemplateRecursively(
6975
$template->getChildNodes(),
70-
new ToBeCreatedNode(
76+
ToBeCreatedNode::fromRegular(
7177
$commands->first->contentStreamId,
7278
$commands->first->originDimensionSpacePoint,
7379
$commands->first->nodeAggregateId,
74-
$nodeType,
80+
$nodeType
7581
),
7682
$commands->withInitialPropertyValues($initialProperties)->withAdditionalCommands(
7783
...$this->createReferencesCommands(
@@ -87,16 +93,19 @@ public function apply(RootTemplate $template, NodeCreationCommands $commands, Ca
8793

8894
private function applyTemplateRecursively(Templates $templates, ToBeCreatedNode $parentNode, NodeCreationCommands $commands, CaughtExceptions $caughtExceptions): NodeCreationCommands
8995
{
96+
// `hasAutoCreatedChildNode` actually has a bug; it looks up the NodeName parameter against the raw configuration instead of the transliterated NodeName
97+
// https://github.com/neos/neos-ui/issues/3527
98+
$parentNodesAutoCreatedChildNodes = $parentNode->getNodeType()->getAutoCreatedChildNodes();
9099
foreach ($templates as $template) {
91-
if ($template->getName() && $parentNode->nodeType->hasAutoCreatedChildNode($template->getName())) {
100+
if ($template->getName() && isset($parentNodesAutoCreatedChildNodes[$template->getName()->value])) {
92101
if ($template->getType() !== null) {
93102
$caughtExceptions->add(
94103
CaughtException::fromException(new \RuntimeException(sprintf('Template cant mutate type of auto created child nodes. Got: "%s"', $template->getType()->value), 1685999829307))
95104
);
96105
// we continue processing the node
97106
}
98107

99-
$nodeType = $parentNode->nodeType->getTypeOfAutoCreatedChildNode($template->getName());
108+
$nodeType = $parentNodesAutoCreatedChildNodes[$template->getName()->value];
100109
$propertiesAndReferences = PropertiesAndReferences::createFromArrayAndTypeDeclarations($template->getProperties(), $nodeType);
101110

102111
$commands = $commands->withAdditionalCommands(
@@ -151,15 +160,15 @@ private function applyTemplateRecursively(Templates $templates, ToBeCreatedNode
151160
continue;
152161
}
153162

154-
if (!$parentNode->nodeType->allowsChildNodeType($nodeType)) {
163+
try {
164+
$parentNode->requireConstraintsImposedByAncestorsAreMet($nodeType);
165+
} catch (NodeConstraintException $nodeConstraintException) {
155166
$caughtExceptions->add(
156-
CaughtException::fromException(new \RuntimeException(sprintf('Node type "%s" is not allowed for child nodes of type %s', $template->getType()->value, $parentNode->nodeType->name->value), 1686417627173))
167+
CaughtException::fromException($nodeConstraintException)
157168
);
158169
continue;
159170
}
160171

161-
// todo maybe check also allowsGrandchildNodeType
162-
163172
$propertiesAndReferences = PropertiesAndReferences::createFromArrayAndTypeDeclarations($template->getProperties(), $nodeType);
164173

165174
$nodeName = $template->getName() ?? NodeName::fromString(uniqid('node-', false));
@@ -195,7 +204,7 @@ private function applyTemplateRecursively(Templates $templates, ToBeCreatedNode
195204

196205
$commands = $this->applyTemplateRecursively(
197206
$template->getChildNodes(),
198-
$parentNode->withNodeTypeAndNodeAggregateId(
207+
$parentNode->forRegularChildNode(
199208
$nodeType,
200209
$nodeAggregateId
201210
),
@@ -229,6 +238,8 @@ private function createReferencesCommands(ContentStreamId $contentStreamId, Node
229238
/**
230239
* All document node types get a uri path segmfent; if it is not explicitly set in the properties,
231240
* it should be built based on the title property
241+
*
242+
* @param Template|RootTemplate $template
232243
*/
233244
private function ensureNodeHasUriPathSegment(
234245
NodeType $nodeType,

0 commit comments

Comments
 (0)