diff --git a/Neos.Fusion/Classes/Core/Cache/RuntimeContentCache.php b/Neos.Fusion/Classes/Core/Cache/RuntimeContentCache.php index d673724605c..f744fe46ef7 100644 --- a/Neos.Fusion/Classes/Core/Cache/RuntimeContentCache.php +++ b/Neos.Fusion/Classes/Core/Cache/RuntimeContentCache.php @@ -13,7 +13,6 @@ use Neos\Flow\Annotations as Flow; use Neos\Utility\TypeHandling; -use Neos\Utility\Unicode\Functions; use Neos\Fusion\Core\Runtime; use Neos\Fusion\Exception; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -82,22 +81,14 @@ public function injectSerializer(NormalizerInterface&DenormalizerInterface $seri /** * Adds a tag built from the given key and value. * - * @param string $key - * @param string $value - * @return void * @throws Exception */ - public function addTag($key, $value) + public function addTag(string $tag): void { - $key = trim($key); - if ($key === '') { - throw new Exception('Tag Key must not be empty', 1448264366); - } - $value = trim($value); - if ($value === '') { + $tag = trim($tag); + if ($tag === '') { throw new Exception('Tag Value must not be empty', 1448264367); } - $tag = Functions::ucfirst($key) . 'DynamicTag_' . $value; $this->tags[$tag] = true; } diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 85902625a24..67e121b068b 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -217,16 +217,14 @@ public function injectSettings(array $settings) * * During Fusion rendering the method can be used to add tag dynamicaly for the current cache segment. * - * @param string $key - * @param string $value - * @return void + * @throws Exceptions */ - public function addCacheTag($key, $value) + public function addCacheTag(string $tag): void { if ($this->runtimeContentCache->getEnableContentCache() === false) { return; } - $this->runtimeContentCache->addTag($key, $value); + $this->runtimeContentCache->addTag($tag); } /** @@ -519,7 +517,7 @@ protected function evaluateObjectOrRetrieveFromCache($fusionObject, $fusionPath, { $output = null; $evaluationStatus = self::EVALUATION_SKIPPED; - list($cacheHit, $cachedResult) = $this->runtimeContentCache->preEvaluate($cacheContext, $fusionObject); + [$cacheHit, $cachedResult] = $this->runtimeContentCache->preEvaluate($cacheContext, $fusionObject); if ($cacheHit) { return $cachedResult; } diff --git a/Neos.Neos/Classes/Fusion/Cache/CacheTag.php b/Neos.Neos/Classes/Fusion/Cache/CacheTag.php new file mode 100644 index 00000000000..c04b04f80c3 --- /dev/null +++ b/Neos.Neos/Classes/Fusion/Cache/CacheTag.php @@ -0,0 +1,134 @@ +value + + ); + } + + final public static function forNodeAggregateFromNode(Node $node): self + { + return self::forNodeAggregate( + $node->subgraphIdentity->contentRepositoryId, + $node->subgraphIdentity->contentStreamId, + $node->nodeAggregateId + ); + } + + final public static function forDescendantOfNode( + ContentRepositoryId $contentRepositoryId, + ContentStreamId $contentStreamId, + NodeAggregateId $nodeAggregateId, + ): self { + return new self( + 'DescendantOf_' + . self::getHashForContentStreamIdAndContentRepositoryId($contentStreamId, $contentRepositoryId) + . '_' . $nodeAggregateId->value + ); + } + + final public static function forDescendantOfNodeFromNode(Node $node): self + { + return self::forDescendantOfNode( + $node->subgraphIdentity->contentRepositoryId, + $node->subgraphIdentity->contentStreamId, + $node->nodeAggregateId + ); + } + + final public static function forAncestorNode( + ContentRepositoryId $contentRepositoryId, + ContentStreamId $contentStreamId, + NodeAggregateId $nodeAggregateId, + ): self { + return new self( + 'Ancestor' + . self::getHashForContentStreamIdAndContentRepositoryId($contentStreamId, $contentRepositoryId) + . '_' . $nodeAggregateId->value + ); + } + + final public static function forAncestorNodeFromNode(Node $node): self + { + return self::forAncestorNode( + $node->subgraphIdentity->contentRepositoryId, + $node->subgraphIdentity->contentStreamId, + $node->nodeAggregateId + ); + } + + final public static function forNodeTypeName( + ContentRepositoryId $contentRepositoryId, + ContentStreamId $contentStreamId, + NodeTypeName $nodeTypeName, + ): self { + return new self( + 'NodeType_' + . self::getHashForContentStreamIdAndContentRepositoryId($contentStreamId, $contentRepositoryId) + . '_' . \strtr($nodeTypeName->value, '.:', '_-') + ); + } + + final public static function forDynamicNodeAggregate( + ContentRepositoryId $contentRepositoryId, + ContentStreamId $contentStreamId, + NodeAggregateId $nodeAggregateId, + ): self { + return new self( + 'DynamicNodeTag_' + . self::getHashForContentStreamIdAndContentRepositoryId($contentStreamId, $contentRepositoryId) + . '_' . $nodeAggregateId->value + + ); + } + + final public static function fromString(string $string): self + { + if (preg_match(self::PATTERN, $string) !== 1) { + throw new \InvalidArgumentException( + 'Given value "' . $string . '" is no valid cache tag, must match the defined pattern.', + 1658093413 + ); + } + + return new self($string); + } + + protected static function getHashForContentStreamIdAndContentRepositoryId( + ContentStreamId $contentStreamId, + ContentRepositoryId $contentRepositoryId, + ): string { + return sha1($contentStreamId->value . '@' . $contentRepositoryId->value); + } +} \ No newline at end of file diff --git a/Neos.Neos/Classes/Fusion/Cache/CacheTagSet.php b/Neos.Neos/Classes/Fusion/Cache/CacheTagSet.php new file mode 100644 index 00000000000..4c6b9ac48f5 --- /dev/null +++ b/Neos.Neos/Classes/Fusion/Cache/CacheTagSet.php @@ -0,0 +1,98 @@ + + */ + private array $tags; + + public function __construct(CacheTag ...$tags) + { + $uniqueTags = []; + foreach ($tags as $tag) { + $uniqueTags[$tag->value] = $tag; + } + + $this->tags = $uniqueTags; + } + + public static function forDescendantOfNodesFromNodes( + Nodes $nodes + ): self { + return new self(...array_map( + fn(Node $node): CacheTag => CacheTag::forDescendantOfNodeFromNode( + $node + ), + iterator_to_array($nodes) + )); + } + + public static function forNodeAggregatesFromNodes( + Nodes $nodes + ): self { + return new self(...array_map( + fn(Node $node): CacheTag => CacheTag::forNodeAggregateFromNode( + $node + ), + iterator_to_array($nodes) + )); + } + + + public static function forNodeTypeNames( + ContentRepositoryId $contentRepositoryId, + ContentStreamId $contentStreamId, + NodeTypeNames $nodeTypeNames + ): self { + return new self(...array_map( + fn(NodeTypeName $nodeTypeName): CacheTag => CacheTag::forNodeTypeName( + $contentRepositoryId, + $contentStreamId, + $nodeTypeName + ), + iterator_to_array($nodeTypeNames) + )); + } + + public function add(CacheTag $cacheTag): self + { + $tags = $this->tags; + $tags[$cacheTag->value] = $cacheTag; + + return new self(...$tags); + } + + /** + * @return array + */ + public function toStringArray(): array + { + return array_map( + fn(CacheTag $tag): string => $tag->value, + array_values($this->tags) + ); + } + + public function union(self $other): self + { + return new self(...array_merge($this->tags, $other->tags)); + } +} \ No newline at end of file diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index 58abd4ab3bf..518b6b9477a 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -26,6 +26,7 @@ use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Model\AssetVariantInterface; use Psr\Log\LoggerInterface; +use Neos\ContentRepository\Core\Factory\ContentRepositoryId; /** * This service flushes Fusion content caches triggered by node changes. @@ -65,7 +66,7 @@ public function flushNodeAggregate( $tagsToFlush[ContentCache::TAG_EVERYTHING] = 'which were tagged with "Everything".'; - $this->registerChangeOnNodeIdentifier($contentStreamId, $nodeAggregateId, $tagsToFlush); + $this->registerChangeOnNodeIdentifier($contentRepository->id, $contentStreamId, $nodeAggregateId, $tagsToFlush); $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( $contentStreamId, $nodeAggregateId @@ -77,6 +78,7 @@ public function flushNodeAggregate( $this->registerChangeOnNodeType( $nodeAggregate->nodeTypeName, + $contentRepository->id, $contentStreamId, $nodeAggregateId, $tagsToFlush, @@ -112,22 +114,13 @@ public function flushNodeAggregate( } $processedNodeAggregateIds[$nodeAggregate->nodeAggregateId->value] = true; - $tagName = 'DescendantOf_%' . $nodeAggregate->contentStreamId->value - . '%_' . $nodeAggregate->nodeAggregateId->value; - $tagsToFlush[$tagName] = sprintf( + $tagName = CacheTag::forDescendantOfNode($contentRepository->id, $nodeAggregate->contentStreamId, $nodeAggregate->nodeAggregateId); + $tagsToFlush[$tagName->value] = sprintf( 'which were tagged with "%s" because node "%s" has changed.', $tagName, $nodeAggregate->nodeAggregateId->value ); - // Legacy - $legacyTagName = 'DescendantOf_' . $nodeAggregate->nodeAggregateId->value; - $tagsToFlush[$legacyTagName] = sprintf( - 'which were tagged with legacy "%s" because node "%s" has changed.', - $legacyTagName, - $nodeAggregate->nodeAggregateId->value - ); - foreach ( $contentRepository->getContentGraph()->findParentNodeAggregates( $nodeAggregate->contentStreamId, @@ -150,33 +143,23 @@ public function flushNodeAggregate( * @param array &$tagsToFlush */ private function registerChangeOnNodeIdentifier( + ContentRepositoryId $contentRepositoryId, ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId, array &$tagsToFlush ): void { - $cacheIdentifier = '%' . $contentStreamId->value . '%_' . $nodeAggregateId->value; - $tagsToFlush['Node_' . $cacheIdentifier] = sprintf( - 'which were tagged with "Node_%s" because that identifier has changed.', - $cacheIdentifier - ); - $tagName = 'DescendantOf_' . $cacheIdentifier; - $tagsToFlush[$tagName] = sprintf( - 'which were tagged with "%s" because node "%s" has changed.', - $tagName, - $cacheIdentifier - ); - // Legacy - $cacheIdentifier = $nodeAggregateId->value; - $tagsToFlush['Node_' . $cacheIdentifier] = sprintf( - 'which were tagged with "Node_%s" because that identifier has changed.', - $cacheIdentifier + $nodeCacheIdentifier = CacheTag::forNodeAggregate($contentRepositoryId, $contentStreamId, $nodeAggregateId); + $tagsToFlush[$nodeCacheIdentifier->value] = sprintf( + 'which were tagged with "%s" because that identifier has changed.', + $nodeCacheIdentifier->value ); - $tagName = 'DescendantOf_' . $cacheIdentifier; - $tagsToFlush[$tagName] = sprintf( + + $descandantOfNodeCacheIdentifier = CacheTag::forDescendantOfNode($contentRepositoryId, $contentStreamId, $nodeAggregateId); + $tagsToFlush[$descandantOfNodeCacheIdentifier->value] = sprintf( 'which were tagged with "%s" because node "%s" has changed.', - $tagName, - $cacheIdentifier + $descandantOfNodeCacheIdentifier->value, + $nodeCacheIdentifier->value ); } @@ -189,35 +172,29 @@ private function registerChangeOnNodeIdentifier( */ private function registerChangeOnNodeType( NodeTypeName $nodeTypeName, + ContentRepositoryId $contentRepositoryId, ContentStreamId $contentStreamId, ?NodeAggregateId $referenceNodeIdentifier, array &$tagsToFlush, ContentRepository $contentRepository ): void { try { - $nodeTypesToFlush = $this->getAllImplementedNodeTypeNames( + $nodeTypesNamesToFlush = $this->getAllImplementedNodeTypeNames( $contentRepository->getNodeTypeManager()->getNodeType($nodeTypeName) ); } catch (NodeTypeNotFoundException $e) { // as a fallback, we flush the single NodeType - $nodeTypesToFlush = [$nodeTypeName->value]; + $nodeTypesNamesToFlush = [$nodeTypeName->value]; } - foreach ($nodeTypesToFlush as $nodeTypeNameToFlush) { - $tagsToFlush['NodeType_%' . $contentStreamId->value . '%_' . $nodeTypeNameToFlush] = sprintf( - 'which were tagged with "NodeType_%s" because node "%s" has changed and was of type "%s".', - $nodeTypeNameToFlush, + foreach ($nodeTypesNamesToFlush as $nodeTypeNameToFlush) { + $nodeTypeNameCacheIdentifier = CacheTag::forNodeTypeName($contentRepositoryId, $contentStreamId, NodeTypeName::fromString($nodeTypeNameToFlush)); + $tagsToFlush[$nodeTypeNameCacheIdentifier->value] = sprintf( + 'which were tagged with "%s" because node "%s" has changed and was of type "%s".', + $nodeTypeNameCacheIdentifier->value, ($referenceNodeIdentifier?->value ?? ''), $nodeTypeName->value ); - - // Legacy, but still used - $tagsToFlush['NodeType_' . $nodeTypeNameToFlush] = sprintf( - 'which were tagged with "NodeType_%s" because node "%s" has changed and was of type "%s".', - $nodeTypeNameToFlush, - ($referenceNodeIdentifier->value ?? ''), - $nodeTypeName->value - ); } } @@ -250,7 +227,7 @@ protected function flushTags(array $tagsToFlush): void * @param NodeType $nodeType * @return array */ - protected function getAllImplementedNodeTypeNames(NodeType $nodeType) + protected function getAllImplementedNodeTypeNames(NodeType $nodeType): array { $self = $this; $types = array_reduce( @@ -261,8 +238,7 @@ function (array $types, NodeType $superType) use ($self) { [$nodeType->name->value] ); - $types = array_unique($types); - return $types; + return array_unique($types); } diff --git a/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php b/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php index 9cf34254f07..5cd037130a3 100644 --- a/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php +++ b/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php @@ -30,6 +30,7 @@ use Neos\Media\Domain\Repository\AssetRepository; use Neos\Neos\Domain\Exception as NeosException; use Psr\Log\LoggerInterface; +use Neos\Neos\Fusion\Cache\CacheTag; /** * A Fusion Object that converts link references in the format "://" to proper URIs @@ -102,6 +103,7 @@ class ConvertUrisImplementation extends AbstractFusionObject */ public function evaluate() { + $text = $this->fusionValue('value'); if ($text === '' || $text === null) { @@ -139,7 +141,7 @@ public function evaluate() $unresolvedUris = []; $absolute = $this->fusionValue('absolute'); - $processedContent = preg_replace_callback(self::PATTERN_SUPPORTED_URIS, function (array $matches) use ($nodeAddress, &$unresolvedUris, $absolute) { + $processedContent = preg_replace_callback(self::PATTERN_SUPPORTED_URIS, function (array $matches) use ($contentRepository, $nodeAddress, &$unresolvedUris, $absolute) { $resolvedUri = null; switch ($matches[1]) { case 'node': @@ -154,7 +156,9 @@ public function evaluate() } catch (NoMatchingRouteException) { $this->systemLogger->warning(sprintf('Could not resolve "%s" to a node uri. Arguments: %s', $matches[0], json_encode($uriBuilder->getLastArguments())), LogEnvironment::fromMethodName(__METHOD__)); } - $this->runtime->addCacheTag('node', $matches[2]); + $this->runtime->addCacheTag( + CacheTag::forDynamicNodeAggregate($contentRepository->id, $nodeAddress->contentStreamId, NodeAggregateId::fromString($matches[2]))->value + ); break; case 'asset': $asset = $this->assetRepository->findByIdentifier($matches[2]); @@ -162,7 +166,6 @@ public function evaluate() $resolvedUri = $this->resourceManager->getPublicPersistentResourceUri( $asset->getResource() ); - $this->runtime->addCacheTag('asset', $matches[2]); } break; } diff --git a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php index 1d9ed58c17c..1468160c2c1 100644 --- a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php @@ -16,13 +16,15 @@ use Neos\Flow\Annotations as Flow; use Neos\ContentRepository\Core\Projection\Workspace\Workspace; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Eel\ProtectedContextAwareInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\Neos\Fusion\Cache\CacheTag; +use Neos\Neos\Fusion\Cache\CacheTagSet; +use Neos\ContentRepository\Core\NodeType\NodeTypeNames; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; use Neos\Neos\Domain\Model\NodeCacheEntryIdentifier; -use Neos\Neos\Exception; -use Neos\ContentRepository\Core\NodeType\NodeType; /** * Caching helper to make cache tag generation easier. @@ -35,57 +37,23 @@ class CachingHelper implements ProtectedContextAwareInterface */ protected $contentRepositoryRegistry; - /** - * Render a caching configuration for array of Nodes - * - * @return array - * @throws Exception - */ - protected function convertArrayOfNodesToArrayOfNodeIdentifiersWithPrefix(mixed $nodes, string $prefix): array - { - if ($nodes === null) { - $nodes = []; - } - - if (!is_array($nodes) && ($nodes instanceof Node)) { - $nodes = [$nodes]; - } - - if (!is_array($nodes) && !$nodes instanceof \Traversable) { - throw new Exception(sprintf( - 'FlowQuery result, Array or Traversable expected by this helper, given: "%s".', - gettype($nodes) - ), 1437169992); - } - - $prefixedNodeIdentifiers = []; - foreach ($nodes as $node) { - if ($node instanceof Node) { - $prefixedNodeIdentifiers[] = $prefix . '_' - . $this->renderContentStreamIdTag($node->subgraphIdentity->contentStreamId) - . '_' . $node->nodeAggregateId->value; - } else { - throw new Exception(sprintf( - 'One of the elements in array passed to this helper was not a Node, but of type: "%s".', - gettype($node) - ), 1437169991); - } - } - return $prefixedNodeIdentifiers; - } - /** * Generate a `@cache` entry tag for a single node, array of nodes or a FlowQuery result * A cache entry with this tag will be flushed whenever one of the * given nodes (for any variant) is updated. * - * @param mixed $nodes (A single Node or array or \Traversable of Nodes) - * @return array - * @throws Exception + * @param iterable|Node $nodes (A single Node or array or \Traversable of Nodes) + * @return array, */ - public function nodeTag(mixed $nodes): array + public function nodeTag(iterable|Node $nodes): array { - return $this->convertArrayOfNodesToArrayOfNodeIdentifiersWithPrefix($nodes, 'Node'); + if (!is_iterable($nodes)) { + $nodes = [$nodes]; + } else { + $nodes = iterator_to_array($nodes); + } + + return CacheTagSet::forNodeAggregatesFromNodes(Nodes::fromArray($nodes))->toStringArray(); } public function entryIdentifierForNode(Node $node): NodeCacheEntryIdentifier @@ -98,73 +66,39 @@ public function entryIdentifierForNode(Node $node): NodeCacheEntryIdentifier * entry tag will respect the workspace hash. * * @param string $identifier - * @param ?Node $contextNode + * @param Node $contextNode * @return string */ - public function nodeTagForIdentifier(string $identifier, Node $contextNode = null): string + public function nodeTagForIdentifier(string $identifier, Node $contextNode): string { - $contentStreamTag = ''; - if ($contextNode instanceof Node) { - $contentStreamTag = $this->renderContentStreamIdTag( - $contextNode->subgraphIdentity->contentStreamId - ) . '_'; - } - - return 'Node_' . $contentStreamTag . $identifier; + return CacheTag::forNodeAggregate( + $contextNode->subgraphIdentity->contentRepositoryId, + $contextNode->subgraphIdentity->contentStreamId, + NodeAggregateId::fromString($identifier) + )->value; } /** * Generate an `@cache` entry tag for a node type * A cache entry with this tag will be flushed whenever a node - * (for any variant) that is of the given node type(s) + * (for any variant) that is of the given node type name(s) * (including inheritance) is updated. * - * @param string|NodeType|string[]|NodeType[]|\Traversable|\Traversable $nodeType - * @param Node|null $contextNode - * @return string|string[] - */ - public function nodeTypeTag($nodeType, Node $contextNode = null) - { - if (!is_array($nodeType) && !($nodeType instanceof \Traversable)) { - return $this->getNodeTypeTagFor($nodeType, $contextNode); - } - - $result = []; - foreach ($nodeType as $singleNodeType) { - $result[] = $this->getNodeTypeTagFor($singleNodeType, $contextNode); - } - - return array_filter($result); - } - - /** - * @param string|NodeType $nodeType - * @param ?Node $contextNode - * @return string + * @return array */ - protected function getNodeTypeTagFor($nodeType, Node $contextNode = null) + public function nodeTypeTag(string|iterable $nodeTypes, Node $contextNode): array { - $nodeTypeName = ''; - $contentStreamTag = ''; - - if ($contextNode instanceof Node) { - $contentStreamTag = $this->renderContentStreamIdTag( - $contextNode->subgraphIdentity->contentStreamId - ) . '_'; - } - - if (is_string($nodeType)) { - $nodeTypeName .= $nodeType; - } - if ($nodeType instanceof NodeType) { - $nodeTypeName .= $nodeType->name->value; - } - - if ($nodeTypeName === '') { - return ''; + if (!is_iterable($nodeTypes)) { + $nodeTypes = [$nodeTypes]; + } else { + $nodeTypes = iterator_to_array($nodeTypes); } - return 'NodeType_' . $contentStreamTag . $nodeTypeName; + return CacheTagSet::forNodeTypeNames( + $contextNode->subgraphIdentity->contentRepositoryId, + $contextNode->subgraphIdentity->contentStreamId, + NodeTypeNames::fromStringArray($nodeTypes) + )->toStringArray(); } /** @@ -173,26 +107,24 @@ protected function getNodeTypeTagFor($nodeType, Node $contextNode = null) * (for any variant) that is a descendant (child on any level) of one of * the given nodes is updated. * - * @param mixed $nodes (A single Node or array or \Traversable of Nodes) + * @param iterable|Node $nodes (A single Node or array or \Traversable of Nodes) * @return array - * @throws Exception */ - public function descendantOfTag(mixed $nodes): array + public function descendantOfTag(iterable|Node $nodes): array { - return $this->convertArrayOfNodesToArrayOfNodeIdentifiersWithPrefix($nodes, 'DescendantOf'); - } + if (!is_iterable($nodes)) { + $nodes = [$nodes]; + } else { + $nodes = iterator_to_array($nodes); + } - /** - * @param ContentStreamId $contentStreamId - * @return string - */ - private function renderContentStreamIdTag(ContentStreamId $contentStreamId) - { - return '%' . $contentStreamId->value . '%'; + return CacheTagSet::forDescendantOfNodesFromNodes( + Nodes::fromArray($nodes) + )->toStringArray(); } /** - * @param Node $node + * @param Node|null $node * @return array */ public function getWorkspaceChain(?Node $node): array diff --git a/Neos.Neos/Tests/Unit/Fusion/ConvertUrisImplementationTest.php b/Neos.Neos/Tests/Unit/Fusion/ConvertUrisImplementationTest.php deleted file mode 100644 index d7f7d418dab..00000000000 --- a/Neos.Neos/Tests/Unit/Fusion/ConvertUrisImplementationTest.php +++ /dev/null @@ -1,418 +0,0 @@ -markTestSkipped('TODO: rewrite with ES CR and Neos 9.0'); - - $this->convertUrisImplementation = $this->getAccessibleMock(ConvertUrisImplementation::class, ['fusionValue'], [], '', false); - - $this->mockWorkspace = $this->getMockBuilder(Workspace::class)->disableOriginalConstructor()->getMock(); - - $this->mockContext = $this->getMockBuilder(Context::class)->disableOriginalConstructor()->getMock(); - $this->mockContext->expects(self::any())->method('getWorkspace')->will(self::returnValue($this->mockWorkspace)); - $this->mockContext->expects(self::any())->method('getWorkspaceName')->willReturnCallback(function () { - return $this->mockWorkspace->getName(); - }); - - $this->mockNode = $this->getMockBuilder(Node::class)->getMock(); - $this->mockNode->expects(self::any())->method('getContext')->will(self::returnValue($this->mockContext)); - - $this->mockHttpUri = $this->getMockBuilder(Uri::class)->disableOriginalConstructor()->getMock(); - $this->mockHttpUri->expects(self::any())->method('getHost')->will(self::returnValue('localhost')); - - $this->mockHttpRequest = $this->getMockBuilder(ServerRequestInterface::class)->disableOriginalConstructor()->getMock(); - $this->mockHttpRequest->expects(self::any())->method('getUri')->willReturn($this->mockHttpUri); - - $this->mockActionRequest = $this->getMockBuilder(ActionRequest::class)->disableOriginalConstructor()->getMock(); - $this->mockActionRequest->expects(self::any())->method('getHttpRequest')->will(self::returnValue($this->mockHttpRequest)); - - $this->mockControllerContext = $this->getMockBuilder(ControllerContext::class)->disableOriginalConstructor()->getMock(); - $this->mockControllerContext->expects(self::any())->method('getRequest')->will(self::returnValue($this->mockActionRequest)); - - $this->mockLinkingService = $this->createMock(LinkingService::class); - $this->convertUrisImplementation->_set('linkingService', $this->mockLinkingService); - - $this->mockCachingHelper = $this->createMock(CachingHelper::class); - $this->convertUrisImplementation->_set('cachingHelper', $this->mockCachingHelper); - - $this->mockRuntime = $this->getMockBuilder(Runtime::class)->disableOriginalConstructor()->getMock(); - $this->mockRuntime->expects(self::any())->method('getControllerContext')->will(self::returnValue($this->mockControllerContext)); - $this->convertUrisImplementation->_set('runtime', $this->mockRuntime); - } - - protected function addValueExpectation($value, $node = null, $forceConversion = false, $externalLinkTarget = null, $resourceLinkTarget = null, $absolute = false, $setNoOpener = true, $setExternal = true) - { - $this->convertUrisImplementation - ->expects(self::atLeastOnce()) - ->method('fusionValue') - ->will($this->returnValueMap([ - ['value', $value], - ['node', $node ?: $this->mockNode], - ['forceConversion', $forceConversion], - ['externalLinkTarget', $externalLinkTarget], - ['resourceLinkTarget', $resourceLinkTarget], - ['absolute', $absolute], - ['setNoOpener', $setNoOpener], - ['setExternal', $setExternal] - ])); - } - - /** - * @test - */ - public function evaluateThrowsExceptionIfValueIsNoString() - { - $this->expectException(Exception::class); - $someObject = new \stdClass(); - $this->addValueExpectation($someObject); - - $this->convertUrisImplementation->evaluate(); - } - - /** - * @test - */ - public function evaluateThrowsExceptionIfTheCurrentContextArrayDoesNotContainANode() - { - $this->expectException(Exception::class); - $this->addValueExpectation('some string', new \stdClass()); - - $this->convertUrisImplementation->evaluate(); - } - - /** - * @test - */ - public function evaluateDoesNotModifyTheValueIfItDoesNotContainNodeUris() - { - $value = ' this Is some string with line' . chr(10) . ' breaks, special chärß and leading/trailing space '; - $this->addValueExpectation($value); - - $this->mockWorkspace->expects(self::any())->method('getName')->will(self::returnValue('live')); - - $actualResult = $this->convertUrisImplementation->evaluate(); - self::assertSame($value, $actualResult); - } - - /** - * @test - */ - public function evaluateDoesNotModifyTheValueIfNotExecutedInLiveWorkspace() - { - $this->mockWorkspace->expects(self::any())->method('getName')->will(self::returnValue('not-live')); - - $value = 'This string contains a node URI: node://aeabe76a-551a-495f-a324-ad9a86b2aff7 and two node links.'; - $this->addValueExpectation($value); - - $actualResult = $this->convertUrisImplementation->evaluate(); - self::assertSame($value, $actualResult); - } - - /** - * @test - */ - public function evaluateDoesModifyTheValueIfExecutedInLiveWorkspaceWithTheForceConvertionOptionSet() - { - $nodeIdentifier1 = 'aeabe76a-551a-495f-a324-ad9a86b2aff7'; - $nodeIdentifier2 = 'cb2d0e4a-7d2f-4601-981a-f9a01530f53f'; - $value = 'This string contains a node URI: node://' . $nodeIdentifier1 . ' and two node links.'; - $this->addValueExpectation($value, null, true); - - $this->mockWorkspace->expects(self::any())->method('getName')->will(self::returnValue('live')); - - $self = $this; - $this->mockLinkingService->expects(self::atLeastOnce())->method('resolveNodeUri')->will(self::returnCallback(function ($nodeUri) use ($self, $nodeIdentifier1, $nodeIdentifier2) { - if ($nodeUri === 'node://' . $nodeIdentifier1) { - return 'http://localhost/replaced/uri/01'; - } elseif ($nodeUri === 'node://' . $nodeIdentifier2) { - return 'http://localhost/replaced/uri/02'; - } else { - $self->fail('Unexpected node URI "' . $nodeUri . '"'); - } - })); - - $expectedResult = 'This string contains a node URI: http://localhost/replaced/uri/01 and two node links.'; - $actualResult = $this->convertUrisImplementation->evaluate(); - self::assertSame($expectedResult, $actualResult); - } - - /** - * @test - */ - public function evaluateReplacesAllNodeUrisInTheGivenValue() - { - $nodeIdentifier1 = 'aeabe76a-551a-495f-a324-ad9a86b2aff7'; - $nodeIdentifier2 = 'cb2d0e4a-7d2f-4601-981a-f9a01530f53f'; - $value = 'This string contains a node URI: node://' . $nodeIdentifier1 . ' and two node links.'; - $this->addValueExpectation($value); - - $this->mockWorkspace->expects(self::any())->method('getName')->will(self::returnValue('live')); - - $self = $this; - $this->mockLinkingService->expects(self::atLeastOnce())->method('resolveNodeUri')->will(self::returnCallback(function ($nodeUri) use ($self, $nodeIdentifier1, $nodeIdentifier2) { - if ($nodeUri === 'node://' . $nodeIdentifier1) { - return 'http://localhost/replaced/uri/01'; - } elseif ($nodeUri === 'node://' . $nodeIdentifier2) { - return 'http://localhost/replaced/uri/02'; - } else { - $self->fail('Unexpected node URI "' . $nodeUri . '"'); - } - })); - - $expectedResult = 'This string contains a node URI: http://localhost/replaced/uri/01 and two node links.'; - $actualResult = $this->convertUrisImplementation->evaluate(); - self::assertSame($expectedResult, $actualResult); - } - - - /** - * This only verifies the current behavior that might be changed in the future (e.g. we could remove unresolved links instead of creating empty href attributes) - * - * @test - */ - public function evaluateReplacesUnresolvableNodeUrisWithAnEmptyString() - { - $unknownNodeIdentifier = 'aeabe76a-551a-495f-a324-ad9a86b2aff7'; - $value = 'This string contains an unresolvable node URI: node://' . $unknownNodeIdentifier . ' and a link.'; - $this->addValueExpectation($value); - - $this->mockWorkspace->expects(self::any())->method('getName')->will(self::returnValue('live')); - - $expectedResult = 'This string contains an unresolvable node URI: and a link.'; - $actualResult = $this->convertUrisImplementation->evaluate(); - self::assertSame($expectedResult, $actualResult); - } - - /** - * This test checks that targets for external links are correctly replaced - * - * @test - */ - public function evaluateReplaceExternalLinkTargets() - { - $nodeIdentifier = 'aeabe76a-551a-495f-a324-ad9a86b2aff7'; - $externalLinkTarget = '_blank'; - - $value = 'This string contains a link to a node: node and one to an external url with a target set example and one without a target example2'; - $this->addValueExpectation($value, null, false, $externalLinkTarget, null); - - $this->mockWorkspace->expects(self::any())->method('getName')->will(self::returnValue('live')); - - $self = $this; - $this->mockLinkingService->expects(self::atLeastOnce())->method('resolveNodeUri')->will(self::returnCallback(function ($nodeUri) use ($self, $nodeIdentifier) { - if ($nodeUri === 'node://' . $nodeIdentifier) { - return 'http://localhost/uri/01'; - } else { - $self->fail('Unexpected node URI "' . $nodeUri . '"'); - } - })); - - $expectedResult = 'This string contains a link to a node: node and one to an external url with a target set example and one without a target example2'; - $actualResult = $this->convertUrisImplementation->evaluate(); - self::assertSame($expectedResult, $actualResult); - } - - /** - * This test checks that targets for resource links are correctly replaced - * - * @test - */ - public function evaluateReplaceResourceLinkTargets() - { - $assetIdentifier = 'aeabe76a-551a-495f-a324-ad9a86b2aff8'; - $resourceLinkTarget = '_blank'; - - $value = 'This string contains two asset links and an external link: one with a target set example and one without a target example2 and an external link example3'; - $this->addValueExpectation($value, null, false, null, $resourceLinkTarget); - - $this->mockWorkspace->expects(self::any())->method('getName')->will(self::returnValue('live')); - - $self = $this; - $this->mockLinkingService->expects(self::atLeastOnce())->method('resolveAssetUri')->will(self::returnCallback(function ($assetUri) use ($self, $assetIdentifier) { - if ($assetUri !== 'asset://' . $assetIdentifier) { - $self->fail('Unexpected asset URI "' . $assetUri . '"'); - } - return 'http://localhost/_Resources/01'; - })); - - $expectedResult = 'This string contains two asset links and an external link: one with a target set example and one without a target example2 and an external link example3'; - $actualResult = $this->convertUrisImplementation->evaluate(); - self::assertSame($expectedResult, $actualResult); - } - - /** - * @test - */ - public function disablingSetNoOpenerWorks() - { - $nodeIdentifier = 'aeabe76a-551a-495f-a324-ad9a86b2aff7'; - $externalLinkTarget = '_blank'; - - $value = 'This string contains a link to a node: node and one to an external url with a target set example and one without a target example2'; - $this->addValueExpectation($value, null, false, $externalLinkTarget, null, false, false); - - $this->mockWorkspace->expects(self::any())->method('getName')->will(self::returnValue('live')); - - $self = $this; - $this->mockLinkingService->expects(self::atLeastOnce())->method('resolveNodeUri')->will(self::returnCallback(function ($nodeUri) use ($self, $nodeIdentifier) { - if ($nodeUri === 'node://' . $nodeIdentifier) { - return 'http://localhost/uri/01'; - } else { - $self->fail('Unexpected node URI "' . $nodeUri . '"'); - } - })); - - $expectedResult = 'This string contains a link to a node: node and one to an external url with a target set example and one without a target example2'; - $actualResult = $this->convertUrisImplementation->evaluate(); - self::assertSame($expectedResult, $actualResult); - } - - /** - * @test - */ - public function disablingSetExternalWorks() - { - $nodeIdentifier = 'aeabe76a-551a-495f-a324-ad9a86b2aff7'; - $externalLinkTarget = '_blank'; - - $value = 'This string contains a link to a node: node and one to an external url with a target set example and one without a target example2'; - $this->addValueExpectation($value, null, false, $externalLinkTarget, null, false, true, false); - - $this->mockWorkspace->expects(self::any())->method('getName')->will(self::returnValue('live')); - - $self = $this; - $this->mockLinkingService->expects(self::atLeastOnce())->method('resolveNodeUri')->will(self::returnCallback(function ($nodeUri) use ($self, $nodeIdentifier) { - if ($nodeUri === 'node://' . $nodeIdentifier) { - return 'http://localhost/uri/01'; - } else { - $self->fail('Unexpected node URI "' . $nodeUri . '"'); - } - })); - - $expectedResult = 'This string contains a link to a node: node and one to an external url with a target set example and one without a target example2'; - $actualResult = $this->convertUrisImplementation->evaluate(); - self::assertSame($expectedResult, $actualResult); - } - - /** - * This test checks that targets for resource links are correctly replaced if the a Tag is inside a tag with the name beginning wit a - * - * @test - */ - public function evaluateReplaceResourceLinkTargetsInsideTag() - { - $assetIdentifier = 'aeabe76a-551a-495f-a324-ad9a86b2aff8'; - $resourceLinkTarget = '_blank'; - - $value = 'and an external link inside another tag beginning with a '; - $this->addValueExpectation($value, null, false, null, $resourceLinkTarget); - - $this->mockWorkspace->expects($this->any())->method('getName')->will($this->returnValue('live')); - - $self = $this; - $this->mockLinkingService->expects($this->atLeastOnce())->method('resolveAssetUri')->will($this->returnCallback(function ($assetUri) use ($self, $assetIdentifier) { - if ($assetUri !== 'asset://' . $assetIdentifier) { - $self->fail('Unexpected asset URI "' . $assetUri . '"'); - } - return 'http://localhost/_Resources/01'; - })); - - $expectedResult = 'and an external link inside another tag beginning with a '; - $actualResult = $this->convertUrisImplementation->evaluate(); - $this->assertSame($expectedResult, $actualResult); - } - - /** - * @test - */ - public function evaluateDoesAddCacheTags() - { - $workspaceName = 'live'; - $workspaceNameHash = 'hashedworkspacename'; - $nodeIdentifier = 'aeabe76a-551a-495f-a324-ad9a86b2aff7'; - $nodeCacheIdentifier = 'hashedworkspacename_aeabe76a-551a-495f-a324-ad9a86b2aff7'; - $assetIdentifier = 'cb2d0e4a-7d2f-4601-981a-f9a01530f53f'; - $assetCacheIdentifier = 'hashedworkspacename_cb2d0e4a-7d2f-4601-981a-f9a01530f53f'; - - $value = 'This string contains a node URI: node://' . $nodeIdentifier . ' and asset link.'; - $this->addValueExpectation($value, null, true); - - $this->mockWorkspace->expects(self::any())->method('getName')->willReturn($workspaceName); - $this->mockCachingHelper->expects(self::any())->method('renderWorkspaceTagForContextNode')->with($workspaceName)->willReturn($workspaceNameHash); - - $this->mockRuntime->expects(self::exactly(2))->method('addCacheTag')->withConsecutive(['node', $nodeCacheIdentifier], ['asset', $assetCacheIdentifier]); - - $this->convertUrisImplementation->evaluate(); - } -} diff --git a/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php b/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php index 7f93efda6bd..7e3ae091573 100644 --- a/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php +++ b/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php @@ -13,11 +13,24 @@ */ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; -use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Domain\Model\Workspace; use Neos\ContentRepository\Domain\Service\Context; use Neos\Flow\Tests\UnitTestCase; use Neos\Neos\Fusion\Helper\CachingHelper; +use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphIdentity; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; +use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; +use Neos\ContentRepository\Core\Projection\ContentGraph\Timestamps; +use Neos\ContentRepository\Core\Factory\ContentRepositoryId; +use Neos\ContentRepository\Core\Projection\ContentGraph\PropertyCollection; +use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; +use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; +use Symfony\Component\Serializer\Serializer; /** * Tests the CachingHelper @@ -27,7 +40,6 @@ class CachingHelperTest extends UnitTestCase public function setUp(): void { parent::setUp(); - $this->markTestSkipped('Update with Neos 9.0'); } /** @@ -37,46 +49,26 @@ public function setUp(): void */ public function nodeTypeTagDataProvider() { - $this->markTestSkipped('TODO: fix with Neos 9.0'); - $nodeTypeName1 = 'Neos.Neos:Foo'; $nodeTypeName2 = 'Neos.Neos:Bar'; $nodeTypeName3 = 'Neos.Neos:Moo'; - $nodeTypeObject1 = $this->getMockBuilder(NodeType::class)->disableOriginalConstructor()->getMock(); - $nodeTypeObject1->expects(self::any())->method('getName')->willReturn($nodeTypeName1); - - $nodeTypeObject2 = $this->getMockBuilder(NodeType::class)->disableOriginalConstructor()->getMock(); - $nodeTypeObject2->expects(self::any())->method('getName')->willReturn($nodeTypeName2); - - $nodeTypeObject3 = $this->getMockBuilder(NodeType::class)->disableOriginalConstructor()->getMock(); - $nodeTypeObject3->expects(self::any())->method('getName')->willReturn($nodeTypeName3); - return [ - [$nodeTypeName1, 'NodeType_' . $nodeTypeName1], + [$nodeTypeName1, ['NodeType_90ce081cc57c057ff24ad13818166a6c64a38eda_Neos_Neos-Foo']], [[$nodeTypeName1, $nodeTypeName2, $nodeTypeName3], [ - 'NodeType_' . $nodeTypeName1, - 'NodeType_' . $nodeTypeName2, - 'NodeType_' . $nodeTypeName3 + 'NodeType_90ce081cc57c057ff24ad13818166a6c64a38eda_Neos_Neos-Foo', + 'NodeType_90ce081cc57c057ff24ad13818166a6c64a38eda_Neos_Neos-Bar', + 'NodeType_90ce081cc57c057ff24ad13818166a6c64a38eda_Neos_Neos-Moo', ] ], - [$nodeTypeObject1, 'NodeType_' . $nodeTypeName1], - [[$nodeTypeName1, $nodeTypeObject2, $nodeTypeObject3], + [(new \ArrayObject([$nodeTypeName1, $nodeTypeName2, $nodeTypeName3])), [ - 'NodeType_' . $nodeTypeName1, - 'NodeType_' . $nodeTypeName2, - 'NodeType_' . $nodeTypeName3 + 'NodeType_90ce081cc57c057ff24ad13818166a6c64a38eda_Neos_Neos-Foo', + 'NodeType_90ce081cc57c057ff24ad13818166a6c64a38eda_Neos_Neos-Bar', + 'NodeType_90ce081cc57c057ff24ad13818166a6c64a38eda_Neos_Neos-Moo', ] ], - [(new \ArrayObject([$nodeTypeObject1, $nodeTypeObject2, $nodeTypeObject3])), - [ - 'NodeType_' . $nodeTypeName1, - 'NodeType_' . $nodeTypeName2, - 'NodeType_' . $nodeTypeName3 - ] - ], - [(object)['stdClass' => 'will do nothing'], ''] ]; } @@ -89,85 +81,8 @@ public function nodeTypeTagDataProvider() */ public function nodeTypeTagProvidesExpectedResult($input, $expectedResult) { - $helper = new CachingHelper(); - $actualResult = $helper->nodeTypeTag($input); - self::assertEquals($expectedResult, $actualResult); - } - - /** - * Provides datasets for testing the CachingHelper::nodeTypeTag method with an context node. - * - * @return array - */ - public function nodeTypeTagWithContextNodeDataProvider() - { - $this->markTestSkipped('TODO: fix with Neos 9.0'); - - $cacheHelper = new CachingHelper(); + $contextNode = $this->createNode(NodeAggregateId::fromString("na")); - $workspaceName = 'live'; - $workspaceMock = $this->getMockBuilder(Workspace::class)->disableOriginalConstructor()->getMock(); - $workspaceMock->expects(self::any())->method('getName')->willReturn($workspaceName); - - $contextMock = $this->getMockBuilder(Context::class)->disableOriginalConstructor()->getMock(); - $contextMock->expects(self::any())->method('getWorkspace')->willReturn($workspaceMock); - - $contextNode = $this->getMockBuilder(Node::class)->disableOriginalConstructor()->getMock(); - $contextNode->expects(self::any())->method('getContext')->willReturn($contextMock); - - $hashedWorkspaceName = $cacheHelper->renderWorkspaceTagForContextNode($workspaceName); - - $nodeTypeName1 = 'Neos.Neos:Foo'; - $nodeTypeName2 = 'Neos.Neos:Bar'; - $nodeTypeName3 = 'Neos.Neos:Moo'; - - $nodeTypeObject1 = $this->getMockBuilder(NodeType::class)->disableOriginalConstructor()->getMock(); - $nodeTypeObject1->expects(self::any())->method('getName')->willReturn($nodeTypeName1); - - $nodeTypeObject2 = $this->getMockBuilder(NodeType::class)->disableOriginalConstructor()->getMock(); - $nodeTypeObject2->expects(self::any())->method('getName')->willReturn($nodeTypeName2); - - $nodeTypeObject3 = $this->getMockBuilder(NodeType::class)->disableOriginalConstructor()->getMock(); - $nodeTypeObject3->expects(self::any())->method('getName')->willReturn($nodeTypeName3); - - return [ - [$nodeTypeName1, $contextNode, 'NodeType_' . $hashedWorkspaceName . '_' . $nodeTypeName1], - [[$nodeTypeName1, $nodeTypeName2, $nodeTypeName3], $contextNode, - [ - 'NodeType_' . $hashedWorkspaceName . '_' . $nodeTypeName1, - 'NodeType_' . $hashedWorkspaceName . '_' . $nodeTypeName2, - 'NodeType_' . $hashedWorkspaceName . '_' . $nodeTypeName3 - ] - ], - [$nodeTypeObject1, $contextNode, 'NodeType_' . $hashedWorkspaceName . '_' . $nodeTypeName1], - [[$nodeTypeName1, $nodeTypeObject2, $nodeTypeObject3], $contextNode, - [ - 'NodeType_' . $hashedWorkspaceName . '_' . $nodeTypeName1, - 'NodeType_' . $hashedWorkspaceName . '_' . $nodeTypeName2, - 'NodeType_' . $hashedWorkspaceName . '_' . $nodeTypeName3 - ] - ], - [(new \ArrayObject([$nodeTypeObject1, $nodeTypeObject2, $nodeTypeObject3])), $contextNode, - [ - 'NodeType_' . $hashedWorkspaceName . '_' . $nodeTypeName1, - 'NodeType_' . $hashedWorkspaceName . '_' . $nodeTypeName2, - 'NodeType_' . $hashedWorkspaceName . '_' . $nodeTypeName3 - ] - ], - [(object)['stdClass' => 'will do nothing'], $contextNode, ''] - ]; - } - - /** - * @test - * @dataProvider nodeTypeTagWithContextNodeDataProvider - * - * @param $input - * @param $contextNode - * @param $expectedResult - */ - public function nodeTypeTagRespectsContextNodesWorkspace($input, $contextNode, $expectedResult) - { $helper = new CachingHelper(); $actualResult = $helper->nodeTypeTag($input, $contextNode); self::assertEquals($expectedResult, $actualResult); @@ -178,35 +93,22 @@ public function nodeTypeTagRespectsContextNodesWorkspace($input, $contextNode, $ */ public function nodeDataProvider() { - $this->markTestSkipped('TODO: fix with Neos 9.0'); + $nodeIdentifier1 = 'ca511a55-c5c0-f7d7-8d71-8edeffc75306'; + $node1 = $this->createNode(NodeAggregateId::fromString($nodeIdentifier1)); - $cachingHelper = new CachingHelper(); - - $workspaceName = 'live'; - $workspaceMock = $this->getMockBuilder(Workspace::class)->disableOriginalConstructor()->getMock(); - $workspaceMock->expects(self::any())->method('getName')->willReturn($workspaceName); - - $contextMock = $this->getMockBuilder(Context::class)->disableOriginalConstructor()->getMock(); - $contextMock->expects(self::any())->method('getWorkspace')->willReturn($workspaceMock); - - $nodeIdentifier = 'ca511a55-c5c0-f7d7-8d71-8edeffc75306'; - $node = $this->getMockBuilder(Node::class)->disableOriginalConstructor()->getMock(); - $node->expects(self::any())->method('getContext')->willReturn($contextMock); - $node->expects(self::any())->method('getIdentifier')->willReturn($nodeIdentifier); - - $anotherNodeIdentifier = '7005c7cf-4d19-ce36-0873-476b6cadb71a'; - $anotherNode = $this->getMockBuilder(Node::class)->disableOriginalConstructor()->getMock(); - $anotherNode->expects(self::any())->method('getContext')->willReturn($contextMock); - $anotherNode->expects(self::any())->method('getIdentifier')->willReturn($anotherNodeIdentifier); - - $hashedWorkspaceName = $cachingHelper->renderWorkspaceTagForContextNode($workspaceName); + $nodeIdentifier2 = '7005c7cf-4d19-ce36-0873-476b6cadb71a'; + $node2 = $this->createNode(NodeAggregateId::fromString($nodeIdentifier2)); return [ - [$node, ['Node_' . $hashedWorkspaceName . '_' . $nodeIdentifier]], - [[$node], ['Node_' . $hashedWorkspaceName . '_' . $nodeIdentifier]], - [[$node, $anotherNode], [ - 'Node_' . $hashedWorkspaceName . '_' . $nodeIdentifier, - 'Node_' . $hashedWorkspaceName . '_' . $anotherNodeIdentifier + [$node1, ['Node_90ce081cc57c057ff24ad13818166a6c64a38eda_ca511a55-c5c0-f7d7-8d71-8edeffc75306']], + [[$node1], ['Node_90ce081cc57c057ff24ad13818166a6c64a38eda_ca511a55-c5c0-f7d7-8d71-8edeffc75306']], + [[$node1, $node2], [ + 'Node_90ce081cc57c057ff24ad13818166a6c64a38eda_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'Node_90ce081cc57c057ff24ad13818166a6c64a38eda_7005c7cf-4d19-ce36-0873-476b6cadb71a' + ]], + [(new \ArrayObject([$node1, $node2])), [ + 'Node_90ce081cc57c057ff24ad13818166a6c64a38eda_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'Node_90ce081cc57c057ff24ad13818166a6c64a38eda_7005c7cf-4d19-ce36-0873-476b6cadb71a' ]] ]; } @@ -232,23 +134,12 @@ public function nodeTagsCanBeInitializedWithAnIdentifierString() { $helper = new CachingHelper(); - $workspaceName = 'live'; - $workspaceMock = $this->getMockBuilder(Workspace::class)->disableOriginalConstructor()->getMock(); - $workspaceMock->expects(self::any())->method('getName')->willReturn($workspaceName); - - $contextMock = $this->getMockBuilder(Context::class)->disableOriginalConstructor()->getMock(); - $contextMock->expects(self::any())->method('getWorkspace')->willReturn($workspaceMock); - $nodeIdentifier = 'ca511a55-c5c0-f7d7-8d71-8edeffc75306'; - $node = $this->getMockBuilder(Node::class)->disableOriginalConstructor()->getMock(); - $node->expects(self::any())->method('getContext')->willReturn($contextMock); - $node->expects(self::any())->method('getIdentifier')->willReturn($nodeIdentifier); - - $hashedWorkspaceName = $helper->renderWorkspaceTagForContextNode($workspaceName); + $node = $this->createNode(NodeAggregateId::fromString($nodeIdentifier)); $actual = $helper->nodeTagForIdentifier($nodeIdentifier, $node); - self::assertEquals('Node_' . $hashedWorkspaceName . '_' . $nodeIdentifier, $actual); + self::assertEquals('Node_90ce081cc57c057ff24ad13818166a6c64a38eda_ca511a55-c5c0-f7d7-8d71-8edeffc75306', $actual); } /** @@ -259,43 +150,31 @@ public function nodeTagForIdentifierStringWillFallbackToLegacyTagIfNoContextNode $helper = new CachingHelper(); $identifier = 'some-uuid-identifier'; - $actual = $helper->nodeTagForIdentifier($identifier); - self::assertEquals('Node_' . $identifier, $actual); + $contextNode = $this->createNode(NodeAggregateId::fromString("na")); + + $actual = $helper->nodeTagForIdentifier($identifier, $contextNode); + self::assertEquals('Node_90ce081cc57c057ff24ad13818166a6c64a38eda_some-uuid-identifier', $actual); } - /** - * - */ public function descendantOfDataProvider() { - $this->markTestSkipped('TODO: fix with Neos 9.0'); - $cachingHelper = new CachingHelper(); - - $workspaceName = 'live'; - $workspaceMock = $this->getMockBuilder(Workspace::class)->disableOriginalConstructor()->getMock(); - $workspaceMock->expects(self::any())->method('getName')->willReturn($workspaceName); - - $contextMock = $this->getMockBuilder(Context::class)->disableOriginalConstructor()->getMock(); - $contextMock->expects(self::any())->method('getWorkspace')->willReturn($workspaceMock); - - $nodeIdentifier = 'ca511a55-c5c0-f7d7-8d71-8edeffc75306'; - $node = $this->getMockBuilder(Node::class)->disableOriginalConstructor()->getMock(); - $node->expects(self::any())->method('getContext')->willReturn($contextMock); - $node->expects(self::any())->method('getIdentifier')->willReturn($nodeIdentifier); + $nodeIdentifier1 = 'ca511a55-c5c0-f7d7-8d71-8edeffc75306'; + $node1 = $this->createNode(NodeAggregateId::fromString($nodeIdentifier1)); - $anotherNodeIdentifier = '7005c7cf-4d19-ce36-0873-476b6cadb71a'; - $anotherNode = $this->getMockBuilder(Node::class)->disableOriginalConstructor()->getMock(); - $anotherNode->expects(self::any())->method('getContext')->willReturn($contextMock); - $anotherNode->expects(self::any())->method('getIdentifier')->willReturn($anotherNodeIdentifier); + $nodeIdentifier2 = '7005c7cf-4d19-ce36-0873-476b6cadb71a'; + $node2 = $this->createNode(NodeAggregateId::fromString($nodeIdentifier2)); - $hashedWorkspaceName = $cachingHelper->renderWorkspaceTagForContextNode($workspaceName); return [ - [$node, ['DescendantOf_' . $hashedWorkspaceName . '_' . $nodeIdentifier]], - [[$node], ['DescendantOf_' . $hashedWorkspaceName . '_' . $nodeIdentifier]], - [[$node, $anotherNode], [ - 'DescendantOf_' . $hashedWorkspaceName . '_' . $nodeIdentifier, - 'DescendantOf_' . $hashedWorkspaceName . '_' . $anotherNodeIdentifier + [$node1, ['DescendantOf_90ce081cc57c057ff24ad13818166a6c64a38eda_ca511a55-c5c0-f7d7-8d71-8edeffc75306']], + [[$node1], ['DescendantOf_90ce081cc57c057ff24ad13818166a6c64a38eda_ca511a55-c5c0-f7d7-8d71-8edeffc75306']], + [[$node1, $node2], [ + 'DescendantOf_90ce081cc57c057ff24ad13818166a6c64a38eda_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'DescendantOf_90ce081cc57c057ff24ad13818166a6c64a38eda_7005c7cf-4d19-ce36-0873-476b6cadb71a' + ]], + [(new \ArrayObject([$node1, $node2])), [ + 'DescendantOf_90ce081cc57c057ff24ad13818166a6c64a38eda_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'DescendantOf_90ce081cc57c057ff24ad13818166a6c64a38eda_7005c7cf-4d19-ce36-0873-476b6cadb71a' ]] ]; } @@ -313,4 +192,25 @@ public function descendantOfTagsAreSetupWithWorkspaceAndIdentifier($nodes, $expe $actualResult = $helper->descendantOfTag($nodes); self::assertEquals($expectedResult, $actualResult); } + + private function createNode(NodeAggregateId $nodeAggregateId): Node + { + $now = new \DateTimeImmutable(); + return Node::create( + ContentSubgraphIdentity::create( + ContentRepositoryId::fromString("default"), + ContentStreamId::fromString("cs-identifier"), + DimensionSpacePoint::createWithoutDimensions(), + VisibilityConstraints::withoutRestrictions() + ), + $nodeAggregateId, + OriginDimensionSpacePoint::createWithoutDimensions(), + NodeAggregateClassification::CLASSIFICATION_REGULAR, + NodeTypeName::fromString("SomeNodeTypeName"), + null, + new PropertyCollection(SerializedPropertyValues::fromArray([]), new PropertyConverter(new Serializer([], []))), + null, + Timestamps::create($now, $now, null, null) + ); + } }