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

TASK: improve document title node creation handler #3521

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 19 additions & 10 deletions Classes/Domain/Model/Changes/AbstractCreate.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\Node\NodeName;
use Neos\Neos\Ui\Exception\InvalidNodeCreationHandlerException;
use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands;
use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface;

abstract class AbstractCreate extends AbstractStructuralChange
Expand Down Expand Up @@ -100,7 +101,13 @@ protected function createNode(
// $name = $this->getName() ?: $this->nodeService->generateUniqueNodeName($parent->findParentNode());
$nodeName = NodeName::fromString($this->getName() ?: uniqid('node-', false));

$nodeAggregateId = NodeAggregateId::create(); // generate a new NodeAggregateId
// deterministic node id
$nodeAggregateId = NodeAggregateId::fromParentNodeAggregateIdAndNodeName(
$parentNode->nodeAggregateId,
$nodeName
);

$contentRepository = $this->contentRepositoryRegistry->get($parentNode->subgraphIdentity->contentRepositoryId);

$command = new CreateNodeAggregateWithNode(
$parentNode->subgraphIdentity->contentStreamId,
Expand All @@ -111,14 +118,16 @@ protected function createNode(
$succeedingSiblingNodeAggregateId,
$nodeName
);
$contentRepository = $this->contentRepositoryRegistry->get($parentNode->subgraphIdentity->contentRepositoryId);

$command = $this->applyNodeCreationHandlers($command, $nodeTypeName, $contentRepository);
$commands = $this->applyNodeCreationHandlers(new NodeCreationCommands($command), $nodeTypeName, $contentRepository);

foreach ($commands as $command) {
$contentRepository->handle($command)->block();
}

$contentRepository->handle($command)->block();
/** @var Node $newlyCreatedNode */
$newlyCreatedNode = $this->contentRepositoryRegistry->subgraphForNode($parentNode)
->findChildNodeConnectedThroughEdgeName($parentNode->nodeAggregateId, $nodeName);
->findNodeById($nodeAggregateId);

$this->finish($newlyCreatedNode);
// NOTE: we need to run "finish" before "addNodeCreatedFeedback"
Expand All @@ -131,15 +140,15 @@ protected function createNode(
* @throws InvalidNodeCreationHandlerException
*/
protected function applyNodeCreationHandlers(
CreateNodeAggregateWithNode $command,
NodeCreationCommands $commands,
NodeTypeName $nodeTypeName,
ContentRepository $contentRepository
): CreateNodeAggregateWithNode {
): NodeCreationCommands {
$data = $this->getData() ?: [];
$nodeType = $contentRepository->getNodeTypeManager()->getNodeType($nodeTypeName);
if (!isset($nodeType->getOptions()['nodeCreationHandlers'])
|| !is_array($nodeType->getOptions()['nodeCreationHandlers'])) {
return $command;
return $commands;
}
foreach ($nodeType->getOptions()['nodeCreationHandlers'] as $nodeCreationHandlerConfiguration) {
$nodeCreationHandler = new $nodeCreationHandlerConfiguration['nodeCreationHandler']();
Expand All @@ -150,8 +159,8 @@ protected function applyNodeCreationHandlers(
get_class($nodeCreationHandler)
), 1364759956);
}
$command = $nodeCreationHandler->handle($command, $data, $contentRepository);
$commands = $nodeCreationHandler->handle($commands, $data, $contentRepository);
}
return $command;
return $commands;
}
}
20 changes: 5 additions & 15 deletions Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@
*/

use Neos\ContentRepository\Core\ContentRepository;
use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException;
use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode;
use Neos\Flow\Annotations as Flow;
use Neos\Neos\Service\TransliterationService;

/**
* Node creation handler that sets the "title" property for new content elements according
Expand All @@ -27,32 +23,26 @@
*/
class ContentTitleNodeCreationHandler implements NodeCreationHandlerInterface
{
/**
* @Flow\Inject
* @var TransliterationService
*/
protected $transliterationService;

/**
* Set the node title for the newly created Content node
*
* @param array<string|int,mixed> $data incoming data from the creationDialog
* @throws NodeTypeNotFoundException
*/
public function handle(CreateNodeAggregateWithNode $command, array $data, ContentRepository $contentRepository): CreateNodeAggregateWithNode
public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands
{
if (
!$contentRepository->getNodeTypeManager()->getNodeType($command->nodeTypeName)
!$contentRepository->getNodeTypeManager()->getNodeType($commands->initialCreateCommand->nodeTypeName)
->isOfType('Neos.Neos:Content')
) {
return $command;
return $commands;
}

$propertyValues = $command->initialPropertyValues;
$propertyValues = $commands->initialCreateCommand->initialPropertyValues;
if (isset($data['title'])) {
$propertyValues = $propertyValues->withValue('title', $data['title']);
}

return $command->withInitialPropertyValues($propertyValues);
return $commands->withInitialPropertyValues($propertyValues);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
*/

use Neos\ContentRepository\Core\ContentRepository;
use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Property\PropertyMapper;
use Neos\Flow\Property\TypeConverter\PersistentObjectConverter;
Expand All @@ -35,14 +33,14 @@ class CreationDialogPropertiesCreationHandler implements NodeCreationHandlerInte
/**
* @param array<string|int,mixed> $data
*/
public function handle(CreateNodeAggregateWithNode $command, array $data, ContentRepository $contentRepository): CreateNodeAggregateWithNode
public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands
{
$propertyMappingConfiguration = $this->propertyMapper->buildPropertyMappingConfiguration();
$propertyMappingConfiguration->forProperty('*')->allowAllProperties();
$propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED, true);

$nodeType = $contentRepository->getNodeTypeManager()->getNodeType($command->nodeTypeName);
$propertyValues = $command->initialPropertyValues;
$nodeType = $contentRepository->getNodeTypeManager()->getNodeType($commands->initialCreateCommand->nodeTypeName);
$propertyValues = $commands->initialCreateCommand->initialPropertyValues;
foreach ($nodeType->getConfiguration('properties') as $propertyName => $propertyConfiguration) {
if (
!isset($propertyConfiguration['ui']['showInCreationDialog'])
Expand All @@ -62,6 +60,6 @@ public function handle(CreateNodeAggregateWithNode $command, array $data, Conten
$propertyValues = $propertyValues->withValue($propertyName, $propertyValue);
}

return $command->withInitialPropertyValues($propertyValues);
return $commands->withInitialPropertyValues($propertyValues);
}
}
72 changes: 25 additions & 47 deletions Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,7 @@

use Neos\ContentRepository\Core\ContentRepository;
use Neos\Flow\Annotations as Flow;
use Behat\Transliterator\Transliterator;
use Neos\ContentRepository\Core\Dimension\ContentDimensionId;
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode;
use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\Flow\I18n\Exception\InvalidLocaleIdentifierException;
use Neos\Flow\I18n\Locale;
use Neos\Neos\Service\TransliterationService;
use Neos\Neos\Utility\NodeUriPathSegmentGenerator;

/**
* Node creation handler that
Expand All @@ -35,60 +28,45 @@ class DocumentTitleNodeCreationHandler implements NodeCreationHandlerInterface
{
/**
* @Flow\Inject
* @var TransliterationService
* @var NodeUriPathSegmentGenerator
*/
protected $transliterationService;
protected $nodeUriPathSegmentGenerator;

/**
* @param array<string|int,mixed> $data
*/
public function handle(CreateNodeAggregateWithNode $command, array $data, ContentRepository $contentRepository): CreateNodeAggregateWithNode
public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands
{
$initialCommand = $commands->initialCreateCommand;
if (
!$contentRepository->getNodeTypeManager()->getNodeType($command->nodeTypeName)
!$contentRepository->getNodeTypeManager()->getNodeType($initialCommand->nodeTypeName)
->isOfType('Neos.Neos:Document')
) {
return $command;
return $commands;
}
$propertyValues = $command->initialPropertyValues;
$propertyValues = $initialCommand->initialPropertyValues;
if (isset($data['title'])) {
$propertyValues = $propertyValues->withValue('title', $data['title']);
}

// if specified, the uriPathSegment equals the title
$uriPathSegment = $data['title'];
$uriPathSegment = match(true) {
// if specified, the uriPathSegment equals the title
!empty($data['title']) => $data['title'],
// otherwise, we fall back to the node name
$initialCommand->nodeName !== null => $initialCommand->nodeName,
// last resort: set it to a random string
default => uniqid('', true),
};

// otherwise, we fall back to the node name
if ($uriPathSegment === null && $command->nodeName !== null) {
$uriPathSegment = $command->nodeName->value;
}

// if not empty, we transliterate the uriPathSegment according to the language of the new node
if ($uriPathSegment !== null && $uriPathSegment !== '') {
$uriPathSegment = $this->transliterateText(
$command->originDimensionSpacePoint->toDimensionSpacePoint(),
$uriPathSegment
);
} else {
// alternatively we set it to a random string
$uriPathSegment = uniqid('', true);
}
$uriPathSegment = Transliterator::urlize($uriPathSegment);
$propertyValues = $propertyValues->withValue('uriPathSegment', $uriPathSegment);
$propertyValues = $propertyValues->withValue(
'uriPathSegment',
$this->nodeUriPathSegmentGenerator->generateUriPathSegmentFromTextForDimension(
$uriPathSegment,
// we transliterate the uriPathSegment according to the language of the new node
$initialCommand->originDimensionSpacePoint->toDimensionSpacePoint()
)
);

return $command->withInitialPropertyValues($propertyValues);
}

private function transliterateText(DimensionSpacePoint $dimensionSpacePoint, string $text): string
{
$languageDimensionValue = $dimensionSpacePoint->getCoordinate(new ContentDimensionId('language'));
if ($languageDimensionValue !== null) {
try {
$language = (new Locale($languageDimensionValue))->getLanguage();
} catch (InvalidLocaleIdentifierException $e) {
// we don't need to do anything here; we'll just transliterate the text.
}
}
return $this->transliterationService->transliterate($text, $language ?? null);
return $commands->withInitialPropertyValues($propertyValues);
}
}
62 changes: 62 additions & 0 deletions Classes/NodeCreationHandler/NodeCreationCommands.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);

namespace Neos\Neos\Ui\NodeCreationHandler;

use Neos\ContentRepository\Core\CommandHandler\CommandInterface;
use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode;
use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\DisableNodeAggregate;
use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\EnableNodeAggregate;
use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetNodeProperties;
use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite;
use Neos\ContentRepository\Core\Feature\NodeReferencing\Command\SetNodeReferences;

class NodeCreationCommands implements \IteratorAggregate
{
/** @var array<int|string, CommandInterface> */
public readonly array $additionalCommands;

/**
* @internal to guarantee that the initial create command is mostly preserved as intended.
* You should use {@see self::withInitialPropertyValues()} and {@see self::withAdditionalCommands()} instead.
*/
public function __construct(
/**
* The original node creation command.
* It is only allowed to mutate the properties via {@see self::withInitialPropertyValues()}
*/
public readonly CreateNodeAggregateWithNode $initialCreateCommand,
mhsdesign marked this conversation as resolved.
Show resolved Hide resolved
/**
* Add a list of commands that are executed after the initial created command was run.
* This allows you to create child-nodes and other operations.
*/
CreateNodeAggregateWithNode|SetNodeProperties|DisableNodeAggregate|EnableNodeAggregate|SetNodeReferences ...$additionalCommands
mhsdesign marked this conversation as resolved.
Show resolved Hide resolved
) {
$this->additionalCommands = $additionalCommands;
}

/**
* Augment the initial {@see CreateNodeAggregateWithNode} command with altered properties.
*/
public function withInitialPropertyValues(PropertyValuesToWrite $newInitialPropertyValues): self
{
return new self(
$this->initialCreateCommand->withInitialPropertyValues($newInitialPropertyValues),
...$this->additionalCommands
);
}

public function withAdditionalCommands(CommandInterface ...$additionalCommands): self
{
return new self($this->initialCreateCommand, ...$this->additionalCommands, ...$additionalCommands);
}

/**
* @return \Traversable<int, CommandInterface>
*/
public function getIterator(): \Traversable
{
yield $this->initialCreateCommand;
yield from $this->additionalCommands;
}
}
7 changes: 3 additions & 4 deletions Classes/NodeCreationHandler/NodeCreationHandlerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
*/

use Neos\ContentRepository\Core\ContentRepository;
use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode;

/**
* Contract for Node Creation handler that allow to hook into the process just before a node is being added
Expand All @@ -23,9 +22,9 @@ interface NodeCreationHandlerInterface
/**
* Do something with the newly created node
*
* @param CreateNodeAggregateWithNode $command The original node creation command
* @param NodeCreationCommands $commands original node creation command
* @param array<string|int,mixed> $data incoming data from the creationDialog
* @return CreateNodeAggregateWithNode the original command or a new creation command with altered properties
* @return NodeCreationCommands the original command (or with altered properties) with additional commands
*/
public function handle(CreateNodeAggregateWithNode $command, array $data, ContentRepository $contentRepository): CreateNodeAggregateWithNode;
public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
*/

use Neos\ContentRepository\Core\ContentRepository;
use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Property\PropertyMapper;
use Neos\Flow\Property\TypeConverter\PersistentObjectConverter;
use Neos\Media\Domain\Model\ImageInterface;
use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands;
use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface;

class ImagePropertyNodeCreationHandler implements NodeCreationHandlerInterface
Expand All @@ -27,17 +27,17 @@ class ImagePropertyNodeCreationHandler implements NodeCreationHandlerInterface
*/
protected $propertyMapper;

public function handle(CreateNodeAggregateWithNode $command, array $data, ContentRepository $contentRepository): CreateNodeAggregateWithNode
public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands
{
if (!isset($data['image'])) {
return $command;
return $commands;
}
$propertyMappingConfiguration = $this->propertyMapper->buildPropertyMappingConfiguration();
$propertyMappingConfiguration->forProperty('*')->allowAllProperties();
$propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED, true);
$image = $this->propertyMapper->convert($data['image'], ImageInterface::class, $propertyMappingConfiguration);

$propertyValues = $command->initialPropertyValues->withValue('image', $image);
return $command->withInitialPropertyValues($propertyValues);
$propertyValues = $commands->initialCreateCommand->initialPropertyValues->withValue('image', $image);
return $commands->withInitialPropertyValues($propertyValues);
}
}