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

BUGFIX: Implement document uri path placeholders #5078

Merged
merged 10 commits into from
Jul 30, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?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;

enum DocumentTypeClassification: string
nezaniel marked this conversation as resolved.
Show resolved Hide resolved
nezaniel marked this conversation as resolved.
Show resolved Hide resolved
{
/**
* Satisfied if a node type is an actual document, meaning explicitly no shortcut or site
*/
case CLASSIFICATION_DOCUMENT = 'document';

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

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

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

/**
* Satisfied if a node type does no longer exist and we can't be certain
*/
case CLASSIFICATION_UNKNOWN = '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)
nezaniel marked this conversation as resolved.
Show resolved Hide resolved
]);
}
}
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
nezaniel marked this conversation as resolved.
Show resolved Hide resolved
)
SELECT
nodeaggregateid,
Expand All @@ -994,7 +994,8 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded
precedingnodeaggregateid,
succeedingnodeaggregateid,
shortcuttarget,
nodetypename
nodetypename,
isplaceholder
nezaniel marked this conversation as resolved.
Show resolved Hide resolved
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
Loading