Skip to content

Commit

Permalink
Merge pull request #142 from dlubitz/feature/command-hook-for-identif…
Browse files Browse the repository at this point in the history
…ier-uniqueness

FEATURE: Ensure unique form element identifier with a CommandHook
  • Loading branch information
bwaidelich authored Nov 12, 2024
2 parents 8834cb7 + 0415ef3 commit 3c8438d
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 0 deletions.
111 changes: 111 additions & 0 deletions Classes/CommandHook/UniqueIdentifierCommandHook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

namespace Neos\Form\Builder\CommandHook;

use Neos\ContentRepository\Core\CommandHandler\CommandHookInterface;
use Neos\ContentRepository\Core\CommandHandler\CommandInterface;
use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode;
use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetNodeProperties;
use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\ContentRepository\Core\NodeType\NodeTypeName;
use Neos\ContentRepository\Core\NodeType\NodeTypeNames;
use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface;
use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindDescendantNodesFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\NodeTypeCriteria;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueEquals;
use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\Node\PropertyName;

class UniqueIdentifierCommandHook implements CommandHookInterface
{
const NEOS_FORM_BUILDER_NODE_BASED_FORM = 'Neos.Form.Builder:NodeBasedForm';
const NEOS_FORM_BUILDER_IDENTIFIER_MIXIN = 'Neos.Form.Builder:IdentifierMixin';

public function __construct(
protected ContentGraphReadModelInterface $contentGraphReadModel,
protected NodeTypeManager $nodeTypeMananger
) {
}

public function onBeforeHandle(CommandInterface $command): CommandInterface
{
return match (true) {
$command instanceof SetNodeProperties => $this->handleSetNodeProperties($command),
$command instanceof CreateNodeAggregateWithNode => $this->handleCreateNodeAggregateWithNode($command),
default => $command
};
}

private function handleSetNodeProperties(SetNodeProperties $command): CommandInterface
{
if (isset($command->propertyValues->values['identifier'])) {
$contentGraph = $this->contentGraphReadModel->getContentGraph($command->workspaceName);
$subgraph = $contentGraph->getSubgraph($command->originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions());
$node = $subgraph->findNodeById($command->nodeAggregateId);
if ($node === null || !$this->nodeTypeMananger->getNodeType($node->nodeTypeName)->isOfType(NodeTypeName::fromString(self::NEOS_FORM_BUILDER_IDENTIFIER_MIXIN))) {
return $command;
}
$identifier = $this->findUniqueIdentifier($subgraph, $command->nodeAggregateId, $command->propertyValues->values['identifier']);
return SetNodeProperties::create(
$command->workspaceName,
$command->nodeAggregateId,
$command->originDimensionSpacePoint,
$command->propertyValues->withValue('identifier', $identifier),
);
}
return $command;
}

private function handleCreateNodeAggregateWithNode(CreateNodeAggregateWithNode $command): CommandInterface
{
if (isset($command->initialPropertyValues->values['identifier'])) {
$contentGraph = $this->contentGraphReadModel->getContentGraph($command->workspaceName);
$subgraph = $contentGraph->getSubgraph($command->originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions());

$identifier = $this->findUniqueIdentifier($subgraph, $command->nodeAggregateId, $command->initialPropertyValues->values['identifier']);
$command = $command->withInitialPropertyValues(
$command->initialPropertyValues->withValue('identifier', $identifier)
);
}

return $command;
}

private function findUniqueIdentifier(ContentSubgraphInterface $subgraph, NodeAggregateId $currentNodeAggregateId, string $identifier): string
{
$form = $subgraph->findClosestNode(
$currentNodeAggregateId,
FindClosestNodeFilter::create(
NodeTypeCriteria::createWithAllowedNodeTypeNames(
NodeTypeNames::with(NodeTypeName::fromString(self::NEOS_FORM_BUILDER_NODE_BASED_FORM)),
)
)
);

$uniqueIdentifier = null;
$possibleIdentifier = $identifier;
$i = 1;
while ($uniqueIdentifier === null) {
$descendants = $subgraph->findDescendantNodes(
$form->aggregateId,
FindDescendantNodesFilter::create(
nodeTypes: NodeTypeCriteria::createWithAllowedNodeTypeNames(
NodeTypeNames::with(NodeTypeName::fromString(self::NEOS_FORM_BUILDER_IDENTIFIER_MIXIN)),
),
propertyValue: PropertyValueEquals::create(PropertyName::fromString('identifier'), $possibleIdentifier, false),
)
);

if ($descendants->count() === 0
|| $descendants->count() === 1 && $descendants->first()->aggregateId->equals($currentNodeAggregateId)) {
$uniqueIdentifier = $possibleIdentifier;
} else {
$possibleIdentifier = $identifier . '-' . $i++;
}
}
return $uniqueIdentifier;
}
}
18 changes: 18 additions & 0 deletions Classes/CommandHook/UniqueIdentifierCommandHookFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Neos\Form\Builder\CommandHook;

use Neos\ContentRepository\Core\CommandHandler\CommandHookInterface;
use Neos\ContentRepository\Core\Factory\CommandHookFactoryInterface;
use Neos\ContentRepository\Core\Factory\CommandHooksFactoryDependencies;

class UniqueIdentifierCommandHookFactory implements CommandHookFactoryInterface
{
public function build(CommandHooksFactoryDependencies $commandHooksFactoryDependencies): CommandHookInterface
{
return new UniqueIdentifierCommandHook(
$commandHooksFactoryDependencies->contentGraphReadModel,
$commandHooksFactoryDependencies->nodeTypeManager
);
}
}
7 changes: 7 additions & 0 deletions Configuration/Settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,10 @@ Neos:
javascript:
"Neos.Form.Builder:PlaceholderInsert":
resource: '${"resource://Neos.Form.Builder/Public/JavaScript/PlaceholderInsert/Plugin.js"}'

ContentRepositoryRegistry:
presets:
'default':
commandHooks:
'UniqueIdentifierCommandHookFactory':
factoryObjectName: Neos\Form\Builder\CommandHook\UniqueIdentifierCommandHookFactory

0 comments on commit 3c8438d

Please # to comment.