Skip to content

Commit

Permalink
FEATURE: Neos9 support for flow nodeTemplate:validate
Browse files Browse the repository at this point in the history
Introduced via #58
  • Loading branch information
mhsdesign committed Jun 21, 2024
1 parent 383d614 commit 88b623e
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 64 deletions.
82 changes: 69 additions & 13 deletions Classes/Application/Command/NodeTemplateCommandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,18 @@
use Flowpack\NodeTemplates\Domain\NodeCreation\NodeCreationService;
use Flowpack\NodeTemplates\Domain\NodeTemplateDumper\NodeTemplateDumper;
use Flowpack\NodeTemplates\Domain\TemplateConfiguration\TemplateConfigurationProcessor;
use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode;
use Neos\ContentRepository\Core\Projection\ContentGraph\AbsoluteNodePath;
use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath;
use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Cli\CommandController;
use Neos\Neos\Domain\Service\ContentContext;
use Neos\Neos\Domain\Repository\SiteRepository;
use Neos\Neos\Domain\Service\NodeTypeNameFactory;
use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationCommands;

class NodeTemplateCommandController extends CommandController
{
Expand All @@ -32,6 +41,17 @@ class NodeTemplateCommandController extends CommandController
*/
protected $templateConfigurationProcessor;

/**
* @var SiteRepository
* @Flow\Inject
*/
protected $siteRepository;

/**
* @var ContentRepositoryRegistry
* @Flow\Inject
*/
protected $contentRepositoryRegistry;

/**
* Dump the node tree structure into a NodeTemplate YAML structure.
Expand All @@ -43,8 +63,8 @@ class NodeTemplateCommandController extends CommandController
*/
public function createFromNodeSubtreeCommand(string $startingNodeId, string $workspaceName = 'live'): void
{
// TODO re-enable
throw new \BadMethodCallException('Not implemented.');
/*
$subgraph = $this->contextFactory->create([
'workspaceName' => $workspaceName
]);
Expand All @@ -53,6 +73,7 @@ public function createFromNodeSubtreeCommand(string $startingNodeId, string $wor
throw new \InvalidArgumentException("Node $startingNodeId doesnt exist in workspace $workspaceName.");
}
echo $this->nodeTemplateDumper->createNodeTemplateYamlDumpFromSubtree($node);
*/
}

/**
Expand All @@ -62,26 +83,51 @@ public function createFromNodeSubtreeCommand(string $startingNodeId, string $wor
* We process and build all configured NodeType templates. No nodes will be created in the Content Repository.
*
*/
public function validateCommand(): void
public function validateCommand(?string $site = null): void
{
// TODO re-enable
throw new \BadMethodCallException('Not implemented.');
$siteInstance = $site
? $this->siteRepository->findOneByNodeName($site)
: $this->siteRepository->findDefault();

if (!$siteInstance) {
$this->outputLine(sprintf('<error>Site "%s" does not exist.</error>', $site));
$this->quit(2);
}

$siteConfiguration = $siteInstance->getConfiguration();

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

$templatesChecked = 0;
/**
* nodeTypeNames as index
* @var array<string, array{processingErrors: ProcessingErrors, dataWasAccessed: bool}> $faultyNodeTypeTemplates
*/
$faultyNodeTypeTemplates = [];

foreach ($this->nodeTypeManager->getNodeTypes(false) as $nodeType) {
// default context? https://github.com/neos/neos-development-collection/issues/5113
$subgraph = $contentRepository->getContentGraph(WorkspaceName::forLive())->getSubgraph(
$siteConfiguration->defaultDimensionSpacePoint,
VisibilityConstraints::frontend()
);

$siteNode = $subgraph->findNodeByAbsolutePath(AbsoluteNodePath::fromRootNodeTypeNameAndRelativePath(
NodeTypeNameFactory::forSites(),
NodePath::fromNodeNames($siteInstance->getNodeName()->toNodeName())
));

if (!$siteNode) {
$this->outputLine(sprintf('<error>Could not resolve site node for site "%s".</error>', $siteInstance->getNodeName()->value));
$this->quit(3);
}

foreach ($contentRepository->getNodeTypeManager()->getNodeTypes(false) as $nodeType) {
$templateConfiguration = $nodeType->getOptions()['template'] ?? null;
if (!$templateConfiguration) {
continue;
}
$processingErrors = ProcessingErrors::create();

/** @var ContentContext $subgraph */
$subgraph = $this->contextFactory->create();

$observableEmptyData = new class ([]) extends \ArrayObject
{
Expand All @@ -93,27 +139,37 @@ public function offsetExists($key): bool
}
};

$siteNode = $subgraph->getCurrentSiteNode();

$template = $this->templateConfigurationProcessor->processTemplateConfiguration(
$templateConfiguration,
[
'data' => $observableEmptyData,
'triggeringNode' => $siteNode, // @deprecated
'site' => $siteNode,
'parentNode' => $siteNode,
],
$processingErrors
);

$this->nodeCreationService->createMutatorsForRootTemplate($template, $nodeType, $this->nodeTypeManager, $subgraph, $processingErrors);
$fakeNodeCreationCommands = NodeCreationCommands::fromFirstCommand(
CreateNodeAggregateWithNode::create(
$siteNode->workspaceName,
NodeAggregateId::create(),
$nodeType->name,
$siteNode->originDimensionSpacePoint,
$siteNode->aggregateId
),
$contentRepository->getNodeTypeManager()
);

$this->nodeCreationService->apply($template, $fakeNodeCreationCommands, $contentRepository->getNodeTypeManager(), $subgraph, $nodeType, $processingErrors);

if ($processingErrors->hasError()) {
$faultyNodeTypeTemplates[$nodeType->getName()] = ['processingErrors' => $processingErrors, 'dataWasAccessed' => $observableEmptyData->dataWasAccessed];
$faultyNodeTypeTemplates[$nodeType->name->value] = ['processingErrors' => $processingErrors, 'dataWasAccessed' => $observableEmptyData->dataWasAccessed];
}
$templatesChecked++;
}

$this->output(sprintf('<comment>Content repository "%s": </comment>', $contentRepository->id->value));

if ($templatesChecked === 0) {
$this->outputLine('<comment>No NodeType templates found.</comment>');
return;
Expand Down
9 changes: 9 additions & 0 deletions Configuration/Testing/Settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,12 @@ Neos:
factoryObjectName: Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\FakeUserIdProviderFactory
clock:
factoryObjectName: Neos\ContentRepository\BehavioralTests\TestSuite\Behavior\FakeClockFactory

Neos:
sites:
'node-templates-site':
uriPathSuffix: '.html'
contentRepository: node_templates
contentDimensions:
resolver:
factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\AutoUriPathResolverFactory
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
10 of 15 NodeType template validated. 5 could not be build standalone.
Content repository "node_templates": 10 of 15 NodeType template validated. 5 could not be build standalone.

Flowpack.NodeTemplates:Content.DisallowedChildNodes
NodeConstraintException(Node type "Flowpack.NodeTemplates:Content.Text" is not allowed below tethered child nodes "content" of nodes of type "Flowpack.NodeTemplates:Content.DisallowedChildNodes", 1687541480146)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,56 @@
namespace Flowpack\NodeTemplates\Tests\Functional\Features\StandaloneValidationCommand;

use Flowpack\NodeTemplates\Application\Command\NodeTemplateCommandController;
use Flowpack\NodeTemplates\Domain\NodeTemplateDumper\NodeTemplateDumper;
use Flowpack\NodeTemplates\Domain\Template\RootTemplate;
use Flowpack\NodeTemplates\Tests\Functional\ContentRepositoryTestTrait;
use Flowpack\NodeTemplates\Tests\Functional\FakeNodeTypeManagerTrait;
use Flowpack\NodeTemplates\Tests\Functional\SnapshotTrait;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Domain\Model\Workspace;
use Neos\ContentRepository\Domain\Repository\ContentDimensionRepository;
use Neos\ContentRepository\Domain\Repository\WorkspaceRepository;
use Neos\ContentRepository\Domain\Service\ContextFactoryInterface;
use Neos\ContentRepository\Domain\Service\NodeTypeManager;
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint;
use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode;
use Neos\ContentRepository\Core\Feature\RootNodeCreation\Command\CreateRootNodeAggregateWithNode;
use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateRootWorkspace;
use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\ContentRepository\Core\NodeType\NodeTypeName;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\Node\NodeName;
use Neos\ContentRepository\Core\SharedModel\User\UserId;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceDescription;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceTitle;
use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\Helpers\FakeUserIdProvider;
use Neos\Flow\Cli\Exception\StopCommandException;
use Neos\Flow\Cli\Response;
use Neos\Flow\Tests\FunctionalTestCase;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\ObjectManagement\ObjectManagerInterface;
use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Repository\SiteRepository;
use Neos\Neos\Ui\Domain\Model\FeedbackCollection;
use Neos\Utility\ObjectAccess;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Output\BufferedOutput;

final class StandaloneValidationCommandTest extends FunctionalTestCase
final class StandaloneValidationCommandTest extends TestCase // we don't use Flows functional test case as it would reset the database afterwards
{
use SnapshotTrait;
use ContentRepositoryTestTrait;
use FakeNodeTypeManagerTrait;

protected static $testablePersistenceEnabled = true;

private ContextFactoryInterface $contextFactory;

protected NodeInterface $homePageNode;

protected NodeInterface $homePageMainContentCollectionNode;

private NodeTemplateDumper $nodeTemplateDumper;

private RootTemplate $lastCreatedRootTemplate;
/**
* Matching configuration in Neos.Neos.sites.node-templates-site
*/
private const TEST_SITE_NAME = 'node-templates-site';

private NodeTypeManager $nodeTypeManager;

private string $fixturesDir;

protected ObjectManagerInterface $objectManager;

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

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

$this->loadFakeNodeTypes();
$this->objectManager = Bootstrap::$staticObjectManager;

$this->setupContentRepository();

Expand All @@ -61,51 +64,72 @@ public function setUp(): void

public function tearDown(): void
{
parent::tearDown();
$this->inject($this->contextFactory, 'contextInstances', []);
$this->objectManager->get(FeedbackCollection::class)->reset();
$this->objectManager->forgetInstance(ContentDimensionRepository::class);
$this->objectManager->forgetInstance(NodeTypeManager::class);
}

private function setupContentRepository(): void
{
// Create an environment to create nodes.
$this->objectManager->get(ContentDimensionRepository::class)->setDimensionsConfiguration([]);
$this->initCleanContentRepository(ContentRepositoryId::fromString('node_templates'));

$this->nodeTypeManager = $this->contentRepository->getNodeTypeManager();
$this->loadFakeNodeTypes();

$liveWorkspace = new Workspace('live');
$workspaceRepository = $this->objectManager->get(WorkspaceRepository::class);
$workspaceRepository->add($liveWorkspace);
$liveWorkspaceCommand = CreateRootWorkspace::create(
$workspaceName = WorkspaceName::fromString('live'),
new WorkspaceTitle('Live'),
new WorkspaceDescription('The live workspace'),
ContentStreamId::fromString('cs-identifier')
);

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

$this->persistenceManager->persistAll();
$this->contextFactory = $this->objectManager->get(ContextFactoryInterface::class);
$subgraph = $this->contextFactory->create(['workspaceName' => 'live']);
FakeUserIdProvider::setUserId(UserId::fromString('initiating-user-identifier'));

$rootNode = $subgraph->getRootNode();
$rootNodeCommand = CreateRootNodeAggregateWithNode::create(
$workspaceName,
$sitesId = NodeAggregateId::fromString('sites'),
NodeTypeName::fromString('Neos.Neos:Sites')
);

$sitesRootNode = $rootNode->createNode('sites');
$testSiteNode = $sitesRootNode->createNode('test-site');
$this->homePageNode = $testSiteNode->createNode(
'homepage',
$this->nodeTypeManager->getNodeType('Flowpack.NodeTemplates:Document.HomePage')
$this->contentRepository->handle($rootNodeCommand);

$siteNodeCommand = CreateNodeAggregateWithNode::create(
$workspaceName,
NodeAggregateId::fromString('test-site'),
NodeTypeName::fromString('Flowpack.NodeTemplates:Document.HomePage'),
OriginDimensionSpacePoint::fromDimensionSpacePoint(
DimensionSpacePoint::fromArray([])
),
$sitesId,
nodeName: NodeName::fromString(self::TEST_SITE_NAME)
);

$this->contentRepository->handle($siteNodeCommand);
}

/** @test */
public function itMatchesSnapshot()
{
$commandController = $this->objectManager->get(NodeTemplateCommandController::class);

$testSite = new Site(self::TEST_SITE_NAME);
$testSite->setSiteResourcesPackageKey('Test.Site');

$siteRepositoryMock = $this->getMockBuilder(SiteRepository::class)->disableOriginalConstructor()->getMock();
$siteRepositoryMock->expects(self::once())->method('findOneByNodeName')->willReturnCallback(function (string $nodeName) use ($testSite) {
return $nodeName === $testSite->getNodeName()->value
? $testSite
: null;
});

ObjectAccess::setProperty($commandController, 'siteRepository', $siteRepositoryMock, true);


ObjectAccess::setProperty($commandController, 'response', $cliResponse = new Response(), true);
ObjectAccess::getProperty($commandController, 'output', true)->setOutput($bufferedOutput = new BufferedOutput());

try {
$commandController->validateCommand();
$commandController->validateCommand(self::TEST_SITE_NAME);
} catch (StopCommandException $e) {
}

Expand Down
1 change: 0 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ parameters:
paths:
- Classes
excludePaths:
- Classes/Application/Command/NodeTemplateCommandController.php
- Classes/Domain/NodeTemplateDumper/NodeTemplateDumper.php

0 comments on commit 88b623e

Please sign in to comment.