Skip to content

Commit

Permalink
Merge pull request #5078 from neos/5077-documentUriPlaceholders
Browse files Browse the repository at this point in the history
Implement document uri path placeholders
  • Loading branch information
nezaniel authored Jul 30, 2024
2 parents a023bea + 07c3cd0 commit 4d1ebdc
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

/*
* This file is part of the Neos.Neos package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* 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 Neos\Neos\FrontendRouting\Projection;

use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\ContentRepository\Core\NodeType\NodeTypeName;
use Neos\Neos\Domain\Service\NodeTypeNameFactory;

/**
* @internal is subject to change and may be removed / refactored beyond recognition at any time
*/
enum DocumentTypeClassification
{
/**
* Satisfied if a node type is an actual document, meaning explicitly no shortcut or site
*/
case CLASSIFICATION_DOCUMENT;

/**
* Satisfied if a node type is a shortcut
*/
case CLASSIFICATION_SHORTCUT;

/**
* Satisfied if a node type is a site
*/
case CLASSIFICATION_SITE;

/**
* Satisfied if a node type is neither of the above
*/
case CLASSIFICATION_NONE;

/**
* Satisfied if a node type does no longer exist and we can't be certain
*/
case CLASSIFICATION_UNKNOWN;

public static function forNodeType(NodeTypeName $nodeTypeName, NodeTypeManager $nodeTypeManager): self
{
$nodeType = $nodeTypeManager->getNodeType($nodeTypeName);
if ($nodeType === null) {
return self::CLASSIFICATION_UNKNOWN;
}

if ($nodeType->isOfType(NodeTypeNameFactory::forSite())) {
return self::CLASSIFICATION_SITE;
} elseif ($nodeType->isOfType(NodeTypeNameFactory::forShortcut())) {
return self::CLASSIFICATION_SHORTCUT;
} elseif ($nodeType->isOfType(NodeTypeNameFactory::forDocument())) {
return self::CLASSIFICATION_DOCUMENT;
}

return self::CLASSIFICATION_NONE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public function getEnabledBySiteNodeNameUriPathAndDimensionSpacePointHash(
'dimensionSpacePointHash = :dimensionSpacePointHash
AND siteNodeName = :siteNodeName
AND uriPath = :uriPath
AND disabled = 0',
AND disabled = 0
AND isPlaceholder = 0',
[
'dimensionSpacePointHash' => $dimensionSpacePointHash,
'siteNodeName' => $siteNodeName->value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
use Neos\EventStore\Model\Event\SequenceNumber;
use Neos\EventStore\Model\EventEnvelope;
use Neos\Neos\Domain\Model\SiteNodeName;
use Neos\Neos\Domain\Service\NodeTypeNameFactory;
use Neos\Neos\FrontendRouting\Exception\NodeNotFoundException;

/**
Expand All @@ -54,9 +53,9 @@ final class DocumentUriPathProjection implements ProjectionInterface, WithMarkSt
private ?DocumentUriPathFinder $stateAccessor = null;

/**
* @var array<string, array<string, bool>>
* @var array<string, DocumentTypeClassification>
*/
private array $nodeTypeImplementsRuntimeCache = [];
private array $documentTypeClassificationRuntimeCache = [];

public function __construct(
private readonly NodeTypeManager $nodeTypeManager,
Expand Down Expand Up @@ -269,15 +268,16 @@ private function whenNodeAggregateWithNodeWasCreated(NodeAggregateWithNodeWasCre
if (!$this->getState()->isLiveContentStream($event->contentStreamId)) {
return;
}
if (!$this->isDocumentNodeType($event->nodeTypeName)) {
$documentTypeClassification = $this->getDocumentTypeClassification($event->nodeTypeName);
if ($documentTypeClassification === DocumentTypeClassification::CLASSIFICATION_NONE) {
return;
}

$propertyValues = $event->initialPropertyValues->getPlainValues();
$uriPathSegment = $propertyValues['uriPathSegment'] ?? '';

$shortcutTarget = null;
if ($this->isShortcutNodeType($event->nodeTypeName)) {
if ($documentTypeClassification === DocumentTypeClassification::CLASSIFICATION_SHORTCUT) {
$shortcutTarget = [
'mode' => $propertyValues['targetMode'] ?? 'firstChildNode',
'target' => $propertyValues['target'] ?? null,
Expand Down Expand Up @@ -353,6 +353,7 @@ private function whenNodeAggregateWithNodeWasCreated(NodeAggregateWithNodeWasCre
'shortcutTarget' => $shortcutTarget,
'nodeTypeName' => $event->nodeTypeName->value,
'disabled' => $parentNode->getDisableLevel(),
'isPlaceholder' => (int)($documentTypeClassification === DocumentTypeClassification::CLASSIFICATION_UNKNOWN)
]);
}
}
Expand All @@ -362,20 +363,33 @@ private function whenNodeAggregateTypeWasChanged(NodeAggregateTypeWasChanged $ev
if (!$this->getState()->isLiveContentStream($event->contentStreamId)) {
return;
}
if ($this->isShortcutNodeType($event->newNodeTypeName)) {
// The node has been turned into a shortcut node, but since the shortcut mode is not yet set
// we'll set it to "firstChildNode" in order to prevent an invalid mode
$this->updateNodeQuery('SET shortcuttarget = COALESCE(shortcuttarget,\'{"mode":"firstChildNode","target":null}\'), nodeTypeName=:nodeTypeName
switch ($this->getDocumentTypeClassification($event->newNodeTypeName)) {
case DocumentTypeClassification::CLASSIFICATION_SHORTCUT:
// The node has been turned into a shortcut node, but since the shortcut mode is not yet set
// we'll set it to "firstChildNode" in order to prevent an invalid mode
$this->updateNodeQuery('SET shortcuttarget = COALESCE(shortcuttarget,\'{"mode":"firstChildNode","target":null}\'), nodeTypeName=:nodeTypeName, isPlaceholder=:isPlaceholder
WHERE nodeAggregateId = :nodeAggregateId', [
'nodeAggregateId' => $event->nodeAggregateId->value,
'nodeTypeName' => $event->newNodeTypeName->value,
]);
} elseif ($this->isDocumentNodeType($event->newNodeTypeName)) {
$this->updateNodeQuery('SET shortcuttarget = NULL, nodeTypeName=:nodeTypeName
'nodeAggregateId' => $event->nodeAggregateId->value,
'nodeTypeName' => $event->newNodeTypeName->value,
'isPlaceholder' => 0
]);
break;
case DocumentTypeClassification::CLASSIFICATION_DOCUMENT:
$this->updateNodeQuery('SET shortcuttarget = NULL, nodeTypeName=:nodeTypeName, isPlaceholder=:isPlaceholder
WHERE nodeAggregateId = :nodeAggregateId', [
'nodeAggregateId' => $event->nodeAggregateId->value,
'nodeTypeName' => $event->newNodeTypeName->value,
]);
'nodeAggregateId' => $event->nodeAggregateId->value,
'nodeTypeName' => $event->newNodeTypeName->value,
'isPlaceholder' => 0
]);
break;
case DocumentTypeClassification::CLASSIFICATION_SITE:
// Sites cannot be moved or type-changed to anything else, so it must have been a site befor
// -> nothing to do
break;
case DocumentTypeClassification::CLASSIFICATION_UNKNOWN:
case DocumentTypeClassification::CLASSIFICATION_NONE:
// @todo: probably set to isPlaceholder: true if anything is found
break;
}
}

Expand Down Expand Up @@ -561,7 +575,8 @@ private function whenNodePropertiesWereSet(NodePropertiesWereSet $event, EventEn

if (
$node === null
|| $this->isSiteNodeType($node->getNodeTypeName())
|| $this->getDocumentTypeClassification($node->getNodeTypeName())
=== DocumentTypeClassification::CLASSIFICATION_SITE
) {
// probably not a document node
continue;
Expand Down Expand Up @@ -716,35 +731,19 @@ private function isNodeExplicitlyDisabled(DocumentNodeInfo $node): bool
return $node->getDisableLevel() - $parentDisabledLevel !== 0;
}

private function isSiteNodeType(NodeTypeName $nodeTypeName): bool
{
return $this->isNodeTypeOfType($nodeTypeName, NodeTypeNameFactory::forSite());
}

private function isDocumentNodeType(NodeTypeName $nodeTypeName): bool
private function getDocumentTypeClassification(NodeTypeName $nodeTypeName): DocumentTypeClassification
{
return $this->isNodeTypeOfType($nodeTypeName, NodeTypeNameFactory::forDocument());
}

private function isShortcutNodeType(NodeTypeName $nodeTypeName): bool
{
return $this->isNodeTypeOfType($nodeTypeName, NodeTypeNameFactory::forShortcut());
}

private function isNodeTypeOfType(NodeTypeName $nodeTypeName, NodeTypeName $superNodeTypeName): bool
{
if (!array_key_exists($superNodeTypeName->value, $this->nodeTypeImplementsRuntimeCache)) {
$this->nodeTypeImplementsRuntimeCache[$superNodeTypeName->value] = [];
}
if (!array_key_exists($nodeTypeName->value, $this->nodeTypeImplementsRuntimeCache[$superNodeTypeName->value])) {
if (!array_key_exists($nodeTypeName->value, $this->documentTypeClassificationRuntimeCache)) {
// HACK: We consider the currently configured node type of the given node.
// This is a deliberate side effect of this projector!
// Note: We could add some hash over all node type decisions to the projected read model
// to tell whether a replay is required (e.g. if a document node type was changed to a content type vice versa)
// With https://github.com/neos/neos-development-collection/issues/4468 this can be compared in the `getStatus()` implementation
$this->nodeTypeImplementsRuntimeCache[$superNodeTypeName->value][$nodeTypeName->value] = $this->nodeTypeManager->getNodeType($nodeTypeName)?->isOfType($superNodeTypeName) ?? false;
$this->documentTypeClassificationRuntimeCache[$nodeTypeName->value]
= DocumentTypeClassification::forNodeType($nodeTypeName, $this->nodeTypeManager);
}
return $this->nodeTypeImplementsRuntimeCache[$superNodeTypeName->value][$nodeTypeName->value];

return $this->documentTypeClassificationRuntimeCache[$nodeTypeName->value];
}

private function tryGetNode(\Closure $closure): ?DocumentNodeInfo
Expand Down Expand Up @@ -980,7 +979,8 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded
precedingnodeaggregateid,
succeedingnodeaggregateid,
shortcuttarget,
nodetypename
nodetypename,
isplaceholder
)
SELECT
nodeaggregateid,
Expand All @@ -994,7 +994,8 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded
precedingnodeaggregateid,
succeedingnodeaggregateid,
shortcuttarget,
nodetypename
nodetypename,
isplaceholder
FROM
' . $this->tableNamePrefix . '_uri
WHERE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ private function createUriTable(): Table
DbalSchemaFactory::columnForNodeAggregateId('precedingnodeaggregateid')->setNotNull(false),
DbalSchemaFactory::columnForNodeAggregateId('succeedingnodeaggregateid')->setNotNull(false),
(new Column('shortcuttarget', Type::getType(Types::STRING)))->setLength(1000)->setNotnull(false)->setPlatformOption('collation', self::DEFAULT_TEXT_COLLATION),
DbalSchemaFactory::columnForNodeTypeName('nodetypename')
DbalSchemaFactory::columnForNodeTypeName('nodetypename'),
(new Column('isplaceholder', Type::getType(Types::INTEGER)))->setLength(4)->setUnsigned(true)->setDefault(0)->setNotnull(true),
]);

return $table
Expand Down
Loading

0 comments on commit 4d1ebdc

Please sign in to comment.