diff --git a/Neos.Fusion/Classes/Core/FusionSourceCodeCollection.php b/Neos.Fusion/Classes/Core/FusionSourceCodeCollection.php
index 0469f0f33ec..c1d4c809330 100644
--- a/Neos.Fusion/Classes/Core/FusionSourceCodeCollection.php
+++ b/Neos.Fusion/Classes/Core/FusionSourceCodeCollection.php
@@ -52,6 +52,9 @@ public static function tryFromFilePath(string $filePath): self
return self::fromFilePath($filePath);
}
+ /**
+ * @deprecated with Neos 9, remove me :)
+ */
public static function tryFromPackageRootFusion(string $packageKey): self
{
$fusionPathAndFilename = sprintf('resource://%s/Private/Fusion/Root.fusion', $packageKey);
diff --git a/Neos.Neos/Classes/Domain/Service/FusionAutoIncludeHandler.php b/Neos.Neos/Classes/Domain/Service/FusionAutoIncludeHandler.php
new file mode 100644
index 00000000000..b167eff1bdf
--- /dev/null
+++ b/Neos.Neos/Classes/Domain/Service/FusionAutoIncludeHandler.php
@@ -0,0 +1,15 @@
+fusionConfigurationCache->cacheFusionConfigurationBySite($site, function () use ($site) {
$siteResourcesPackageKey = $site->getSiteResourcesPackageKey();
-
return $this->fusionParser->parseFromSource(
- $this->fusionSourceCodeFactory->createFromNodeTypeDefinitions($site->getConfiguration()->contentRepositoryId)
- ->union(
- $this->fusionSourceCodeFactory->createFromAutoIncludes()
- )
- ->union(
- FusionSourceCodeCollection::tryFromPackageRootFusion($siteResourcesPackageKey)
- )
+ $this->fusionAutoIncludeHandler->loadFusionFromPackage(
+ $siteResourcesPackageKey,
+ $this->fusionSourceCodeFactory->createFromNodeTypeDefinitions($site->getConfiguration()->contentRepositoryId)
+ ->union(
+ $this->fusionSourceCodeFactory->createFromAutoIncludes()
+ )
+ )
);
});
}
diff --git a/Neos.Neos/Classes/Domain/Service/FusionSourceCodeFactory.php b/Neos.Neos/Classes/Domain/Service/FusionSourceCodeFactory.php
index 948994053cb..e8766ba3e5a 100644
--- a/Neos.Neos/Classes/Domain/Service/FusionSourceCodeFactory.php
+++ b/Neos.Neos/Classes/Domain/Service/FusionSourceCodeFactory.php
@@ -36,6 +36,9 @@ class FusionSourceCodeFactory
#[Flow\InjectConfiguration("fusion.autoInclude")]
protected array $autoIncludeConfiguration = [];
+ #[Flow\Inject]
+ protected FusionAutoIncludeHandler $fusionAutoIncludeHandler;
+
#[Flow\Inject]
protected ContentRepositoryRegistry $contentRepositoryRegistry;
@@ -50,14 +53,15 @@ public function createFromAutoIncludes(): FusionSourceCodeCollection
$sourcecode = FusionSourceCodeCollection::empty();
foreach (array_keys($this->packageManager->getAvailablePackages()) as $packageKey) {
if (isset($this->autoIncludeConfiguration[$packageKey]) && $this->autoIncludeConfiguration[$packageKey] === true) {
- $sourcecode = $sourcecode->union(
- FusionSourceCodeCollection::tryFromPackageRootFusion($packageKey)
- );
+ $sourcecode = $this->fusionAutoIncludeHandler->loadFusionFromPackage($packageKey, $sourcecode);
}
}
return $sourcecode;
}
+ /**
+ * @deprecated with Neos 9 - YAGNI from the start :)
+ */
public function createFromSite(Site $site): FusionSourceCodeCollection
{
return FusionSourceCodeCollection::tryFromPackageRootFusion($site->getSiteResourcesPackageKey());
diff --git a/Neos.Neos/Classes/Domain/Service/ResourceFusionAutoIncludeHandler.php b/Neos.Neos/Classes/Domain/Service/ResourceFusionAutoIncludeHandler.php
new file mode 100644
index 00000000000..38365f9ea68
--- /dev/null
+++ b/Neos.Neos/Classes/Domain/Service/ResourceFusionAutoIncludeHandler.php
@@ -0,0 +1,22 @@
+union(
+ FusionSourceCodeCollection::tryFromFilePath(sprintf('resource://%s/Private/Fusion/Root.fusion', $packageKey))
+ );
+ }
+}
diff --git a/Neos.Neos/Classes/Testing/TestingFusionAutoIncludeHandler.php b/Neos.Neos/Classes/Testing/TestingFusionAutoIncludeHandler.php
new file mode 100644
index 00000000000..94e2dc76cb3
--- /dev/null
+++ b/Neos.Neos/Classes/Testing/TestingFusionAutoIncludeHandler.php
@@ -0,0 +1,60 @@
+
+ */
+ private array $overriddenIncludes = [];
+
+ public function setIncludeFusionPackage(string $packageKey): void
+ {
+ $this->overriddenIncludes[$packageKey] = true;
+ }
+
+ public function setFusionForPackage(string $packageKey, FusionSourceCodeCollection $packageFusionSource): void
+ {
+ $this->overriddenIncludes[$packageKey] = $packageFusionSource;
+ }
+
+ public function reset(): void
+ {
+ $this->overriddenIncludes = [];
+ }
+
+ /**
+ * If no override is set via {@see setIncludeFusionPackage} or {@see setFusionForPackage} we load all the fusion via the default implementation
+ */
+ public function loadFusionFromPackage(string $packageKey, FusionSourceCodeCollection $sourceCodeCollection): FusionSourceCodeCollection
+ {
+ if ($this->overriddenIncludes === []) {
+ return $this->defaultHandler->loadFusionFromPackage($packageKey, $sourceCodeCollection);
+ }
+ $override = $this->overriddenIncludes[$packageKey] ?? null;
+ if ($override === null) {
+ return $sourceCodeCollection;
+ }
+ if ($override === true) {
+ return $this->defaultHandler->loadFusionFromPackage($packageKey, $sourceCodeCollection);
+ }
+ return $sourceCodeCollection->union($override);
+ }
+}
diff --git a/Neos.Neos/Configuration/Objects.yaml b/Neos.Neos/Configuration/Objects.yaml
index be5cd60dd91..20da5dd98aa 100644
--- a/Neos.Neos/Configuration/Objects.yaml
+++ b/Neos.Neos/Configuration/Objects.yaml
@@ -20,6 +20,9 @@ Neos\Neos\Domain\Service\FusionConfigurationCache:
2:
setting: "Neos.Neos.fusion.enableObjectTreeCache"
+Neos\Neos\Domain\Service\FusionAutoIncludeHandler:
+ className: Neos\Neos\Domain\Service\ResourceFusionAutoIncludeHandler
+
Neos\Fusion\Core\Cache\RuntimeContentCache:
properties:
serializer:
diff --git a/Neos.Neos/Configuration/Testing/Objects.yaml b/Neos.Neos/Configuration/Testing/Objects.yaml
new file mode 100644
index 00000000000..faa9e83523a
--- /dev/null
+++ b/Neos.Neos/Configuration/Testing/Objects.yaml
@@ -0,0 +1,2 @@
+Neos\Neos\Domain\Service\FusionAutoIncludeHandler:
+ className: Neos\Neos\Testing\TestingFusionAutoIncludeHandler
diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php
index 9d06ce491eb..fce5b7a3278 100644
--- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php
+++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php
@@ -39,6 +39,7 @@ class FeatureContext implements BehatContext
use CRBehavioralTestsSubjectProvider;
use RoutingTrait;
use MigrationsTrait;
+ use FrontendNodeControllerTrait;
use FusionTrait;
use ContentCacheTrait;
diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FrontendNodeControllerTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FrontendNodeControllerTrait.php
new file mode 100644
index 00000000000..982262f17d2
--- /dev/null
+++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FrontendNodeControllerTrait.php
@@ -0,0 +1,92 @@
+ $className
+ *
+ * @return T
+ */
+ abstract private function getObject(string $className): object;
+
+ /**
+ * @BeforeScenario
+ */
+ public function setupFrontendNodeControllerTrait(): void
+ {
+ $this->getObject(ContentCache::class)->flush();
+ $this->getObject(\Neos\Neos\Testing\TestingFusionAutoIncludeHandler::class)->reset();
+ $this->frontendNodeControllerResponse = null;
+ }
+
+ /**
+ * @When the Fusion code for package :package is:
+ */
+ public function iHaveTheFollowingFusionCodeForTheSite(PyStringNode $fusionCode, string $package)
+ {
+ $testingFusionHandler = $this->getObject(\Neos\Neos\Testing\TestingFusionAutoIncludeHandler::class);
+ $testingFusionHandler->setFusionForPackage($package, \Neos\Fusion\Core\FusionSourceCodeCollection::fromString($fusionCode->getRaw()));
+ }
+
+ /**
+ * @When I dispatch the following request :requestUri
+ */
+ public function iDispatchTheFollowingRequest(string $requestUri)
+ {
+ $testingFusionHandler = $this->getObject(\Neos\Neos\Testing\TestingFusionAutoIncludeHandler::class);
+ $testingFusionHandler->setIncludeFusionPackage('Neos.Fusion');
+ $testingFusionHandler->setIncludeFusionPackage('Neos.Neos');
+
+ $httpRequest = $this->getObject(ServerRequestFactoryInterface::class)->createServerRequest('GET', $requestUri);
+
+ $this->frontendNodeControllerResponse = $this->getObject(\Neos\Flow\Http\Middleware\MiddlewaresChain::class)->handle(
+ $httpRequest
+ );
+ }
+
+ /**
+ * @Then I expect the following response header:
+ */
+ public function iExpectTheFollowingResponseHeader(PyStringNode $expectedResult): void
+ {
+ Assert::assertNotNull($this->frontendNodeControllerResponse);
+ Assert::assertSame($expectedResult->getRaw(), $this->frontendNodeControllerResponse->getBody()->getContents());
+ }
+
+ /**
+ * @Then I expect the following response:
+ */
+ public function iExpectTheFollowingResponse(PyStringNode $expectedResult): void
+ {
+ Assert::assertNotNull($this->frontendNodeControllerResponse);
+ Assert::assertEquals($expectedResult->getRaw(), str_replace("\r\n", "\n", Message::toString($this->frontendNodeControllerResponse)));
+ }
+}
diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php
index 3eb2dc1a93a..f5232d55a10 100644
--- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php
+++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/RoutingTrait.php
@@ -82,14 +82,15 @@ abstract private function getObject(string $className): object;
/**
* @Given A site exists for node name :nodeName
* @Given A site exists for node name :nodeName and domain :domain
+ * @Given A site exists for node name :nodeName and domain :domain and package :package
*/
- public function theSiteExists(string $nodeName, string $domain = null): void
+ public function theSiteExists(string $nodeName, string $domain = null, string $package = null): void
{
$siteRepository = $this->getObject(SiteRepository::class);
$persistenceManager = $this->getObject(PersistenceManagerInterface::class);
$site = new Site($nodeName);
- $site->setSiteResourcesPackageKey('Neos.Neos');
+ $site->setSiteResourcesPackageKey($package ?: 'Neos.Neos');
$site->setState(Site::STATE_ONLINE);
$siteRepository->add($site);
diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendNodeController/DefaultFusionRendering.feature b/Neos.Neos/Tests/Behavior/Features/FrontendNodeController/DefaultFusionRendering.feature
new file mode 100644
index 00000000000..8e2d8dfcd12
--- /dev/null
+++ b/Neos.Neos/Tests/Behavior/Features/FrontendNodeController/DefaultFusionRendering.feature
@@ -0,0 +1,113 @@
+@flowEntities
+Feature: Test the default Fusion rendering for a request
+ Background:
+ Given using no content dimensions
+ And using the following node types:
+ """yaml
+ 'Neos.ContentRepository:Root': {}
+ 'Neos.Neos:ContentCollection': {}
+ 'Neos.Neos:Content': {}
+ 'Neos.Neos:Sites':
+ superTypes:
+ 'Neos.ContentRepository:Root': true
+ 'Neos.Neos:Document':
+ properties:
+ title:
+ type: string
+ uriPathSegment:
+ type: string
+ 'Neos.Neos:Site':
+ superTypes:
+ 'Neos.Neos:Document': true
+ childNodes:
+ main:
+ type: 'Neos.Neos:ContentCollection'
+ 'Neos.Neos:Test.DocumentType':
+ superTypes:
+ 'Neos.Neos:Document': true
+ childNodes:
+ main:
+ type: 'Neos.Neos:ContentCollection'
+ 'Neos.Neos:Test.ContentType':
+ superTypes:
+ 'Neos.Neos:Content': true
+ properties:
+ text:
+ type: string
+ """
+ And using identifier "default", I define a content repository
+ And I am in content repository "default"
+ When the command CreateRootWorkspace is executed with payload:
+ | Key | Value |
+ | workspaceName | "live" |
+ | newContentStreamId | "cs-identifier" |
+ And I am in workspace "live" and dimension space point {}
+ And the command CreateRootNodeAggregateWithNode is executed with payload:
+ | Key | Value |
+ | nodeAggregateId | "root" |
+ | nodeTypeName | "Neos.Neos:Sites" |
+ And the following CreateNodeAggregateWithNode commands are executed:
+ | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | tetheredDescendantNodeAggregateIds | nodeName |
+ | a | root | Neos.Neos:Site | {"title": "Node a"} | {} | a |
+ | a1 | a | Neos.Neos:Test.DocumentType | {"uriPathSegment": "a1", "title": "Node a1"} | {"main": "a-tetherton" } | |
+ | a1a1 | a-tetherton | Neos.Neos:Test.ContentType | {"text": "my first text"} | {} | |
+ | a1a2 | a-tetherton | Neos.Neos:Test.ContentType | {"text": "my second text"} | {} | |
+ And A site exists for node name "a" and domain "http://localhost" and package "Vendor.Site"
+ And the sites configuration is:
+ """yaml
+ Neos:
+ Neos:
+ sites:
+ 'a':
+ preset: default
+ uriPathSuffix: ''
+ contentDimensions:
+ resolver:
+ factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory
+ """
+
+ Scenario: Default output
+ And the Fusion code for package "Vendor.Site" is:
+ """fusion
+ prototype(Neos.Neos:Test.DocumentType) < prototype(Neos.Neos:Page) {
+ body {
+ content = Neos.Fusion:Component {
+ renderer = afx`
+ {String.chr(10)}title: {node.properties.title}
+ {String.chr(10)}children: