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

Feature: Version 2.x reintroduce disabling nodes (_hidden=true) #55

Open
wants to merge 3 commits into
base: 2.0
Choose a base branch
from
Open
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
4 changes: 3 additions & 1 deletion Classes/Domain/NodeCreation/NodeCreationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public function createMutatorsForRootTemplate(RootTemplate $template, NodeType $

return NodeMutatorCollection::from(
NodeMutator::setProperties($validProperties),
NodeMutator::setDisabled($template->getTags()->isDisabled()),
$this->createMutatorForUriPathSegment($template->getProperties()),
)->merge(
$this->createMutatorsForChildNodeTemplates(
Expand All @@ -67,7 +68,7 @@ public function createMutatorsForRootTemplate(RootTemplate $template, NodeType $

private function createMutatorsForChildNodeTemplates(Templates $templates, TransientNode $parentNode, ProcessingErrors $processingErrors): NodeMutatorCollection
{
$nodeMutators = NodeMutatorCollection::empty();
$nodeMutators = NodeMutatorCollection::createEmpty();

// `hasAutoCreatedChildNode` actually has a bug; it looks up the NodeName parameter against the raw configuration instead of the transliterated NodeName
// https://github.com/neos/neos-ui/issues/3527
Expand Down Expand Up @@ -153,6 +154,7 @@ private function createMutatorsForChildNodeTemplates(Templates $templates, Trans
NodeMutatorCollection::from(
NodeMutator::createAndSelectNode($template->getType(), $template->getName()),
NodeMutator::setProperties($validProperties),
NodeMutator::setDisabled($template->getTags()->isDisabled()),
$this->createMutatorForUriPathSegment($template->getProperties())
)->merge($this->createMutatorsForChildNodeTemplates(
$template->getChildNodes(),
Expand Down
13 changes: 13 additions & 0 deletions Classes/Domain/NodeCreation/NodeMutator.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ public static function setProperties(array $properties): self
});
}

/**
* Queues to disable the current node.
*
* Preserves the current node pointer.
*/
public static function setDisabled(bool $disabled): self
{
return new self(function (NodeInterface $nodePointer) use ($disabled) {
$nodePointer->setHidden($disabled);
return null;
});
}

/**
* Queues to execute the collection {@see NodeMutatorCollection} on the current node.
* Any selections made in the collection {@see self::selectChildNode()} won't change the pointer to $this current node.
Expand Down
2 changes: 1 addition & 1 deletion Classes/Domain/NodeCreation/NodeMutatorCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static function from(NodeMutator ...$items): self
return new self(...$items);
}

public static function empty(): self
public static function createEmpty(): self
{
return new self();
}
Expand Down
3 changes: 3 additions & 0 deletions Classes/Domain/NodeCreation/PropertiesProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ private function assertValidPropertyName($propertyName): void
}
if ($propertyName[0] === '_') {
$lowerPropertyName = strtolower($propertyName);
if ($lowerPropertyName === '_hidden') {
throw new PropertyIgnoredException('Using "_hidden" as property declaration was removed. Please use "tags.disabled" instead.', 1719079679);
}
foreach ($legacyInternalProperties as $legacyInternalProperty) {
if ($lowerPropertyName === strtolower($legacyInternalProperty)) {
throw new PropertyIgnoredException(sprintf('Because internal legacy property "%s" not implement.', $propertyName), 1686149513158);
Expand Down
13 changes: 11 additions & 2 deletions Classes/Domain/Template/RootTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,24 @@ class RootTemplate implements \JsonSerializable
*/
private array $properties;

private SubtreeTags $tags;

private Templates $childNodes;

/**
* @internal
* @param array<string, mixed> $properties
*/
public function __construct(array $properties, Templates $childNodes)
public function __construct(array $properties, SubtreeTags $tags, Templates $childNodes)
{
$this->properties = $properties;
$this->tags = $tags;
$this->childNodes = $childNodes;
}

public static function empty(): self
{
return new RootTemplate([], Templates::empty());
return new RootTemplate([], SubtreeTags::createEmpty(), Templates::empty());
}

/**
Expand All @@ -42,6 +45,11 @@ public function getProperties(): array
return $this->properties;
}

public function getTags(): SubtreeTags
{
return $this->tags;
}

public function getChildNodes(): Templates
{
return $this->childNodes;
Expand All @@ -51,6 +59,7 @@ public function jsonSerialize(): array
{
return [
'properties' => $this->properties,
'tags' => $this->tags,
'childNodes' => $this->childNodes
];
}
Expand Down
53 changes: 53 additions & 0 deletions Classes/Domain/Template/SubtreeTags.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

/*
* This file is part of the Neos.ContentRepository 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 Flowpack\NodeTemplates\Domain\Template;

use Neos\Flow\Annotations as Flow;

/**
* Minimal shim of Neos9' \Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTags
* Only Neos 8.3' hidden aka disabled tag is allowed.
*
* @Flow\Proxy(false)
*/
final class SubtreeTags implements \JsonSerializable
{
private bool $isDisabled;

private function __construct(bool $isDisabled)
{
$this->isDisabled = $isDisabled;
}

public static function createEmpty(): self
{
return new self(false);
}

public static function createDisabled(): self
{
return new self(true);
}

public function isDisabled(): bool
{
return $this->isDisabled;
}

public function jsonSerialize(): array
{
return $this->isDisabled ? ['disabled'] : [];
}
}
11 changes: 10 additions & 1 deletion Classes/Domain/Template/Template.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,20 @@ class Template implements \JsonSerializable
*/
private array $properties;

private SubtreeTags $tags;

private Templates $childNodes;

/**
* @internal
* @param array<string, mixed> $properties
*/
public function __construct(?NodeTypeName $type, ?NodeName $name, array $properties, Templates $childNodes)
public function __construct(?NodeTypeName $type, ?NodeName $name, array $properties, SubtreeTags $tags, Templates $childNodes)
{
$this->type = $type;
$this->name = $name;
$this->properties = $properties;
$this->tags = $tags;
$this->childNodes = $childNodes;
}

Expand All @@ -55,6 +58,11 @@ public function getProperties(): array
return $this->properties;
}

public function getTags(): SubtreeTags
{
return $this->tags;
}

public function getChildNodes(): Templates
{
return $this->childNodes;
Expand All @@ -66,6 +74,7 @@ public function jsonSerialize(): array
'type' => $this->type,
'name' => $this->name,
'properties' => $this->properties,
'tags' => $this->tags,
'childNodes' => $this->childNodes
];
}
Expand Down
1 change: 1 addition & 0 deletions Classes/Domain/Template/Templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public function toRootTemplate(): RootTemplate
foreach ($this->items as $first) {
return new RootTemplate(
$first->getProperties(),
$first->getTags(),
$first->getChildNodes()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Flowpack\NodeTemplates\Domain\ErrorHandling\ProcessingErrors;
use Flowpack\NodeTemplates\Domain\Template\RootTemplate;
use Flowpack\NodeTemplates\Domain\Template\SubtreeTags;
use Flowpack\NodeTemplates\Domain\Template\Template;
use Flowpack\NodeTemplates\Domain\Template\Templates;
use Neos\ContentRepository\Domain\NodeAggregate\NodeName;
Expand Down Expand Up @@ -119,6 +120,30 @@ private function createTemplateFromTemplatePart(TemplatePart $templatePart): Tem
}
}

// process the tags (in Neos 8.3 only tags.disabled is allowed)
$isDisabled = false;
foreach ($templatePart->getRawConfiguration('tags') ?? [] as $tagName => $value) {
if (!is_string($value) && !is_bool($value) && !is_null($value)) {
$templatePart->addProcessingErrorForPath(
new \RuntimeException(sprintf('Template configuration tags can only hold string|bool|null. Tag "%s" has type "%s"', $tagName, gettype($value)), 1719653856),
['tags', $tagName]
);
continue;
}
if ($tagName !== 'disabled') {
$templatePart->addProcessingErrorForPath(
new \RuntimeException('Template configuration only allows "disabled" tag to hide Nodes in Neos 8.3', 1719653919),
['tags', $tagName]
);
continue;
}
try {
$isDisabled = $templatePart->processConfiguration(['tags', 'disabled']);
} catch (StopBuildingTemplatePartException $e) {
}
}
$processedTags = $isDisabled === true ? SubtreeTags::createDisabled() : SubtreeTags::createEmpty();

// process the childNodes
$childNodeTemplates = Templates::empty();
foreach ($templatePart->getRawConfiguration('childNodes') ?? [] as $childNodeConfigurationPath => $childNodeConfiguration) {
Expand All @@ -140,6 +165,7 @@ private function createTemplateFromTemplatePart(TemplatePart $templatePart): Tem
$type !== null ? NodeTypeName::fromString($type) : null,
$name !== null ? NodeName::fromString(Utility::renderValidNodeName($name)) : null,
$processedProperties,
$processedTags,
$childNodeTemplates
);
}
Expand Down
4 changes: 2 additions & 2 deletions Classes/Domain/TemplateConfiguration/TemplatePart.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,15 @@ private function validateTemplateConfigurationKeys(): void
{
$isRootTemplate = $this->fullPathToConfiguration === [];
foreach (array_keys($this->configuration) as $key) {
if (!in_array($key, ['type', 'name', 'properties', 'childNodes', 'when', 'withItems', 'withContext'], true)) {
if (!in_array($key, ['type', 'name', 'properties', 'tags', 'childNodes', 'when', 'withItems', 'withContext'], true)) {
$this->addProcessingErrorForPath(
new \InvalidArgumentException(sprintf('Template configuration has illegal key "%s"', $key), 1686150349274),
$key
);
throw new StopBuildingTemplatePartException();
}
if ($isRootTemplate) {
if (!in_array($key, ['properties', 'childNodes', 'when', 'withContext'], true)) {
if (!in_array($key, ['properties', 'tags', 'childNodes', 'when', 'withContext'], true)) {
$this->addProcessingErrorForPath(
new \InvalidArgumentException(sprintf('Root template configuration doesnt allow option "%s', $key), 1686150340657),
$key
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,22 @@ The following example creates three different text child nodes in the main conte
We call conditions ``when`` and loops ``withItems`` (instead of ``if`` and ``forEach``),
because it inspires a more declarative mood. The naming is inspired by Ansible.

## Disable / Hide nodes (`tags.disabled`)
> In version 1.0 available via the magic property `"_hidden"`

To disable a node one can set the `"disabled"` tag to `true`. Either explicitly or by the result of an EEL expression.
> Note that other custom tags are not supported by Neos 8.3 but will be with Neos 9.0

The following example disables a newly created node:

```yaml
'Neos.NodeTypes:Page':
options:
template:
tags:
disabled: true
```

## EEL context variables

There are several variables available in the EEL context for example.
Expand Down
28 changes: 17 additions & 11 deletions Tests/Functional/AbstractNodeTemplateTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ abstract class AbstractNodeTemplateTestCase extends FunctionalTestCase
use WithConfigurationTrait;
use FakeNodeTypeManagerTrait;

protected static bool $showDisabledNodesInSubgraph = false;

protected static $testablePersistenceEnabled = true;

private ContextFactoryInterface $contextFactory;
Expand All @@ -44,21 +46,21 @@ abstract class AbstractNodeTemplateTestCase extends FunctionalTestCase

private string $fixturesDir;

/** @deprecated please use {@see self::getObject()} instead */
/** @internal please use {@see self::getObject()} instead */
protected $objectManager;

public function setUp(): void
{
parent::setUp();

$this->nodeTypeManager = $this->objectManager->get(NodeTypeManager::class);
$this->nodeTypeManager = $this->getObject(NodeTypeManager::class);

$this->loadFakeNodeTypes();

$this->setupContentRepository();
$this->nodeTemplateDumper = $this->objectManager->get(NodeTemplateDumper::class);
$this->nodeTemplateDumper = $this->getObject(NodeTemplateDumper::class);

$templateFactory = $this->objectManager->get(TemplateConfigurationProcessor::class);
$templateFactory = $this->getObject(TemplateConfigurationProcessor::class);

$templateFactoryMock = $this->getMockBuilder(TemplateConfigurationProcessor::class)->disableOriginalConstructor()->getMock();
$templateFactoryMock->expects(self::once())->method('processTemplateConfiguration')->willReturnCallback(function (...$args) use($templateFactory) {
Expand All @@ -76,7 +78,7 @@ public function tearDown(): void
{
parent::tearDown();
$this->inject($this->contextFactory, 'contextInstances', []);
$this->objectManager->get(FeedbackCollection::class)->reset();
$this->getObject(FeedbackCollection::class)->reset();
$this->objectManager->forgetInstance(ContentDimensionRepository::class);
$this->objectManager->forgetInstance(TemplateConfigurationProcessor::class);
$this->objectManager->forgetInstance(NodeTypeManager::class);
Expand All @@ -96,20 +98,20 @@ final protected function getObject(string $className): object
private function setupContentRepository(): void
{
// Create an environment to create nodes.
$this->objectManager->get(ContentDimensionRepository::class)->setDimensionsConfiguration([]);
$this->getObject(ContentDimensionRepository::class)->setDimensionsConfiguration([]);

$liveWorkspace = new Workspace('live');
$workspaceRepository = $this->objectManager->get(WorkspaceRepository::class);
$workspaceRepository = $this->getObject(WorkspaceRepository::class);
$workspaceRepository->add($liveWorkspace);

$testSite = new Site('test-site');
$testSite->setSiteResourcesPackageKey('Test.Site');
$siteRepository = $this->objectManager->get(SiteRepository::class);
$siteRepository = $this->getObject(SiteRepository::class);
$siteRepository->add($testSite);

$this->persistenceManager->persistAll();
$this->contextFactory = $this->objectManager->get(ContextFactoryInterface::class);
$subgraph = $this->contextFactory->create(['workspaceName' => 'live']);
$this->contextFactory = $this->getObject(ContextFactoryInterface::class);
$subgraph = $this->contextFactory->create(['workspaceName' => 'live', 'invisibleContentShown' => static::$showDisabledNodesInSubgraph]);

$rootNode = $subgraph->getRootNode();

Expand Down Expand Up @@ -153,7 +155,11 @@ protected function createNodeInto(NodeInterface $targetNode, string $nodeTypeNam
assert($changeCollection instanceof ChangeCollection);
$changeCollection->apply();

return $targetNode->getNode('new-node');
$newNode = $targetNode->getNode('new-node');
if (!$newNode) {
throw new \RuntimeException('New node not found at expected location. Try settting $showDisabledNodesInSubgraph.', 1719079419);
}
return $newNode;
}

protected function createFakeNode(string $nodeAggregateId): NodeInterface
Expand Down
Loading
Loading