[FEATURE] Auto-render HeaderAssets and FooterAssets in FLUIDTEMPLATE 82/51382/6
authorClaus Due <claus@namelesscoder.net>
Sun, 22 Jan 2017 16:48:10 +0000 (17:48 +0100)
committerGeorg Ringer <georg.ringer@gmail.com>
Mon, 6 Feb 2017 06:24:18 +0000 (07:24 +0100)
This patch adds a new feature for FLUIDTEMPLATE
content objects, allowing two new sections named
`HeaderAsstes` and `FooterAssets` to be defined.
When rendering the content object, those sections
then get rendered and assigned as either header or
footer data via PageRenderer.

Both sections are optional and can be combined.

Change-Id: I981f2148050b972ef42d9b18a1b428f874407615
Resolves: #79413
Releases: master
Reviewed-on: https://review.typo3.org/51382
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Thomas Hohn <thomas@hohn.dk>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
typo3/sysext/core/Documentation/Changelog/master/Feature-79413-AutorenderAssetSectionsInFluidTemplateContentObject.rst [new file with mode: 0644]
typo3/sysext/frontend/Classes/ContentObject/FluidTemplateContentObject.php
typo3/sysext/frontend/Tests/Unit/ContentObject/FluidTemplateContentObjectTest.php

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-79413-AutorenderAssetSectionsInFluidTemplateContentObject.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-79413-AutorenderAssetSectionsInFluidTemplateContentObject.rst
new file mode 100644 (file)
index 0000000..33bd74b
--- /dev/null
@@ -0,0 +1,37 @@
+.. include:: ../../Includes.txt
+
+=============================================================================
+Feature: #79413 - Auto-render Assets sections in FLUIDTEMPLATE content object
+=============================================================================
+
+See :issue:`79413`
+
+
+Description
+===========
+
+FLUIDTEMPLATE content object will now automatically render two sections and insert the rendered content as assets via
+PageRenderer. The two sections can be defined in the template file rendered by the FLUIDTEMPLATE object.
+
+* `<f:section name="HeaderAssets">` for assets intented for the `<head>` tag
+* `<f:section name="FooterAssets">` for assets intented for the end of the `<body>` tag
+
+Both sections are optional.
+
+When rendering, `{contentObject}` is available as template variable in both sections, allowing you to make decisions
+based on various aspects of the configured content object instance. In addition, all variables you declared for the
+content object are available when rendering either section.
+
+All content you write into these sections will be output in the respective location, meaning you must write the entire
+`<script>` or whichever tag you are writing, including all attributes. You can of course use various Fluid ViewHelpers
+to resolve extension asset paths.
+
+
+Impact
+======
+
+* Fluid templates renderered through the FLUIDTEMPLATE content object may now contain two new sections for either
+  `HeaderAssets` or `FooterAssets` depending on desired output. Content of these sections will be rendered
+  and assigned via PageRenderer to either header or footer.
+
+.. index:: Fluid, Frontend
index 1562137..4d63e71 100644 (file)
@@ -102,6 +102,7 @@ class FluidTemplateContentObject extends AbstractContentObject
 
         $this->view->assignMultiple($variables);
 
+        $this->renderFluidTemplateAssetsIntoPageRenderer();
         $content = $this->renderFluidView();
         $content = $this->applyStandardWrapToRenderedContent($content, $conf);
 
@@ -110,6 +111,24 @@ class FluidTemplateContentObject extends AbstractContentObject
     }
 
     /**
+     * Attempts to render HeaderAssets and FooterAssets sections from the
+     * Fluid template, then adds each (if not empty) to either header or
+     * footer, as appropriate, using PageRenderer.
+     */
+    protected function renderFluidTemplateAssetsIntoPageRenderer()
+    {
+        $pageRenderer = $this->getPageRenderer();
+        $headerAssets = $this->view->renderSection('HeaderAssets', ['contentObject' => $this], true);
+        $footerAssets = $this->view->renderSection('FooterAssets', ['contentObject' => $this], true);
+        if (!empty(trim($headerAssets))) {
+            $pageRenderer->addHeaderData($headerAssets);
+        }
+        if (!empty(trim($footerAssets))) {
+            $pageRenderer->addFooterData($footerAssets);
+        }
+    }
+
+    /**
      * Creating standalone view instance must not be done in construct() as
      * it can lead to a nasty cache issue since content object instances
      * are not always re-created by the content object rendered for every
index 8e90248..84999bc 100644 (file)
@@ -14,12 +14,14 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Mvc\Request;
 use TYPO3\CMS\Extbase\Service\TypoScriptService;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
 use TYPO3\CMS\Frontend\ContentObject\FluidTemplateContentObject;
+use TYPO3Fluid\Fluid\View\TemplateView;
 
 /**
  * Testcase
@@ -792,4 +794,56 @@ class FluidTemplateContentObjectTest extends \TYPO3\Components\TestingFramework\
             ->with('baz', ['foo' => 'bar']);
         $this->subject->render($configuration);
     }
+
+    /**
+     * @param TemplateView $viewMock
+     * @param string|null $expectedHeader
+     * @param string|null $expectedFooter
+     * @test
+     * @dataProvider headerAssetDataProvider
+     */
+    public function renderFluidTemplateAssetsIntoPageRendererRendersAndAttachesAssets($viewMock, $expectedHeader, $expectedFooter)
+    {
+        $pageRendererMock = $this->getMockBuilder(PageRenderer::class)->setMethods(['addHeaderData', 'addFooterData'])->getMock();
+        if (!empty(trim($expectedHeader))) {
+            $pageRendererMock->expects($this->once())->method('addHeaderData')->with($expectedHeader);
+        } else {
+            $pageRendererMock->expects($this->never())->method('addHeaderData');
+        }
+        if (!empty(trim($expectedFooter))) {
+            $pageRendererMock->expects($this->once())->method('addFooterData')->with($expectedFooter);
+        } else {
+            $pageRendererMock->expects($this->never())->method('addFooterData');
+        }
+        $subject = $this->getMockBuilder(FluidTemplateContentObject::class)->setMethods(['getPageRenderer'])->disableOriginalConstructor()->getMock();
+        $subject->expects($this->once())->method('getPageRenderer')->willReturn($pageRendererMock);
+        $viewProperty = new \ReflectionProperty($subject, 'view');
+        $viewProperty->setAccessible(true);
+        $viewProperty->setValue($subject, $viewMock);
+
+        $method = new \ReflectionMethod($subject, 'renderFluidTemplateAssetsIntoPageRenderer');
+        $method->setAccessible(true);
+        $method->invoke($subject);
+    }
+
+    /**
+     * @return array
+     */
+    public function headerAssetDataProvider()
+    {
+        $viewWithHeaderData = $this->getMockBuilder(TemplateView::class)->setMethods(['renderSection'])->disableOriginalConstructor()->getMock();
+        $viewWithHeaderData->expects($this->at(0))->method('renderSection')->with('HeaderAssets', $this->anything(), true)->willReturn('custom-header-data');
+        $viewWithHeaderData->expects($this->at(1))->method('renderSection')->with('FooterAssets', $this->anything(), true)->willReturn(null);
+        $viewWithFooterData = $this->getMockBuilder(TemplateView::class)->setMethods(['renderSection'])->disableOriginalConstructor()->getMock();
+        $viewWithFooterData->expects($this->at(0))->method('renderSection')->with('HeaderAssets', $this->anything(), true)->willReturn(null);
+        $viewWithFooterData->expects($this->at(1))->method('renderSection')->with('FooterAssets', $this->anything(), true)->willReturn('custom-footer-data');
+        $viewWithBothData = $this->getMockBuilder(TemplateView::class)->setMethods(['renderSection'])->disableOriginalConstructor()->getMock();
+        $viewWithBothData->expects($this->at(0))->method('renderSection')->with('HeaderAssets', $this->anything(), true)->willReturn('custom-header-data');
+        $viewWithBothData->expects($this->at(1))->method('renderSection')->with('FooterAssets', $this->anything(), true)->willReturn('custom-footer-data');
+        return [
+            [$viewWithHeaderData, 'custom-header-data', null],
+            [$viewWithFooterData, null, 'custom-footer-data'],
+            [$viewWithBothData, 'custom-header-data', 'custom-footer-data']
+        ];
+    }
 }