Commit ad0aa9a5 authored by Stefan Froemken's avatar Stefan Froemken Committed by Benni Mack
Browse files

[FEATURE] Add content object related arguments to HtmlViewHelper

The HtmlViewHelper is extended for a couple of new arguments,
used to influence the initialization of the content object.

Following arguments are added:

- "data" (array or object)
- "current"
- "currentValueKey"
- "table"

This allows to properly work with dynamic data in a custom parseFunc.

With the provided data record ("data" argument), the individual
entries can now be accessed using TS:field. With the "current"
argument, TS:current can be filled individually and with the
"currentValueKey" argument, a specific value from the data
record can be set as new TS:current.

The "table" argument is added for completeness, so that the
ContentObjectRenderer can be initialized correctly.

Resolves: #92749
Releases: main
Change-Id: Ibecdb7530a33462dd5464524ac7aff31abb7432b
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72803


Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 528b90fe
.. include:: ../../Includes.txt
=========================================================================
Feature: #92749 - Improve content object initialization in HtmlViewHelper
=========================================================================
See :issue:`92749`
Description
===========
New options are available for the :html:`f:format.html` ViewHelper,
related to the initialization of the underlying content object. The options
are similar to the ones, available for the :html:`f:cObject` ViewHelper.
With the `data` argument an integrator can pass an array or an object (e.g. a
domain model), which will be used as data record on initialization.
With the `currentValueKey` argument, one can specify the array key of the
provided data record, which should be used as the current value.
Alternatively, one can use the new `current` argument to set a static value
as current value for the content object.
Additionally, with the `table` argument, the :php:`ContentObjectRenderer`
receives the table name, the given data record is from.
Example
=======
Access a news record title with `CURRENT:1` and resolve a marker:
.. code-block:: html
<f:format.html parseFuncTSPath="lib.news" data="{uid: 1, title: \'Great news\'}" currentValueKey="title">###PROJECT### news:</f:format.html>
.. code-block:: typoscript
constants.PROJECT = TYPO3
lib.news {
htmlSanitize = 1
constants = 1
plainTextStdWrap.noTrimWrap = || |
plainTextStdWrap.dataWrap = |{CURRENT:1}
}
This will result in:
.. code-block:: html
TYPO3 news: Great news
Impact
======
The :html:`f:format.html` ViewHelper can now be utilized in more customized use-cases.
.. index:: Frontend, TypoScript, ext:fluid
......@@ -20,6 +20,7 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Format;
use TYPO3\CMS\Core\Http\ApplicationType;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
......@@ -39,11 +40,11 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
*
* ::
*
* <f:format.html>foo <b>bar</b>. Some <LINK 1>link</LINK>.</f:format.html>
* <f:format.html>###PROJECT### is a cool <b>CMS</b> (<a href="https://www.typo3.org">TYPO3</a>).</f:format.html>
*
* Output::
*
* <p class="bodytext">foo <b>bar</b>. Some <a href="index.php?id=1" >link</a>.</p>
* <p class="bodytext">TYPO3 is a cool <strong>CMS</strong> (<a href="https://www.typo3.org" target="_blank">TYPO3</a>).</p>
*
* Depending on TYPO3 setup.
*
......@@ -52,11 +53,51 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
*
* ::
*
* <f:format.html parseFuncTSPath="lib.parseFunc">foo <b>bar</b>. Some <LINK 1>link</LINK>.</f:format.html>
* <f:format.html parseFuncTSPath="lib.parseFunc">###PROJECT### is a cool <b>CMS</b> (<a href="https://www.typo3.org">TYPO3</a>).</f:format.html>
*
* Output::
*
* foo <b>bar</b>. Some <a href="index.php?id=1" >link</a>.
* TYPO3 is a cool <strong>CMS</strong> (<a href="https://www.typo3.org" target="_blank">TYPO3</a>).
*
* Data argument
* --------------
*
* If you work with TypoScript "field" property, you should add the current record as "data"
* to the ViewHelper to allow processing the "field" and "dataWrap" properties correctly.
*
* ::
*
* <f:format.html data="{newsRecord}" parseFuncTSPath="lib.news">News title: </f:format.html>
*
* After "dataWrap = |<strong>{FIELD:title}</strong>" you may have this Output::
*
* News title: <strong>TYPO3, greatest CMS ever</strong>
*
* Current argument
* -----------------
*
* Use the current argument to set the current value of the content object.
*
* ::
*
* <f:format.html current="{strContent}" parseFuncTSPath="lib.info">I'm gone</f:format.html>
*
* After "setContentToCurrent = 1" you may have this Output::
*
* Thanks Kasper for this great CMS
*
* CurrentValueKey argument
* -------------------------
*
* Use the currentValueKey argument to define a value of data object as the current value.
*
* ::
*
* <f:format.html data="{contentRecord}" currentValueKey="header" parseFuncTSPath="lib.content">Content: </f:format.html>
*
* After "dataWrap = |{CURRENT:1}" you may have this Output::
*
* Content: How to install TYPO3 in under 2 minutes ;-)
*
* Inline notation
* ---------------
......@@ -67,7 +108,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
*
* Output::
*
* foo <b>bar</b>. Some <a href="index.php?id=1" >link</a>.
* TYPO3 is a cool <strong>CMS</strong> (<a href="https://www.typo3.org" target="_blank">TYPO3</a>).
*
* .. _parseFunc: https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Parsefunc.html
*/
......@@ -91,23 +132,50 @@ final class HtmlViewHelper extends AbstractViewHelper
public function initializeArguments(): void
{
$this->registerArgument('parseFuncTSPath', 'string', ' path to TypoScript parseFunc setup.', false, 'lib.parseFunc_RTE');
$this->registerArgument('parseFuncTSPath', 'string', 'Path to the TypoScript parseFunc setup.', false, 'lib.parseFunc_RTE');
$this->registerArgument('data', 'mixed', 'Initialize the content object with this set of data. Either an array or object.');
$this->registerArgument('current', 'string', 'Initialize the content object with this value for current property.');
$this->registerArgument('currentValueKey', 'string', 'Define the value key, used to locate the current value for the content object');
$this->registerArgument('table', 'string', 'The table name associated with the "data" argument.', false, '');
}
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string
{
$parseFuncTSPath = $arguments['parseFuncTSPath'];
$data = $arguments['data'];
$current = $arguments['current'];
$currentValueKey = $arguments['currentValueKey'];
$table = $arguments['table'];
$isBackendRequest = ApplicationType::fromRequest($renderingContext->getRequest())->isBackend();
if ($isBackendRequest) {
$tsfeBackup = self::simulateFrontendEnvironment();
}
$value = $renderChildrenClosure();
// Prepare data array
if (is_object($data)) {
$data = ObjectAccess::getGettableProperties($data);
} elseif (!is_array($data)) {
$data = (array)$data;
}
$contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$contentObject->start([]);
$contentObject->start($data, $table);
if ($current !== null) {
$contentObject->setCurrentVal($current);
} elseif ($currentValueKey !== null && isset($data[$currentValueKey])) {
$contentObject->setCurrentVal($data[$currentValueKey]);
}
$content = $contentObject->parseFunc($value, [], '< ' . $parseFuncTSPath);
if ($isBackendRequest) {
self::resetFrontendEnvironment($tsfeBackup);
}
return $content;
}
......
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Fluid\Tests\Functional\ViewHelpers\Format;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
class HtmlViewHelperTest extends FunctionalTestCase
{
use SiteBasedTestTrait;
/**
* @var array Used by buildDefaultLanguageConfiguration() of SiteBasedTestTrait
*/
protected const LANGUAGE_PRESETS = [
'EN' => ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8'],
];
public function renderDataProvider(): array
{
return [
'format.html: process lib.parseFunc_RTE by default' => [
'<f:format.html>###PROJECT### is a cool CMS</f:format.html>',
'TYPO3 is a cool CMS',
],
'format.html: process specific TS path' => [
'<f:format.html parseFuncTSPath="lib.foo">###FOO### is BAR</f:format.html>',
'BAR is BAR',
],
'format.html: specific TS path with current' => [
'<f:format.html parseFuncTSPath="lib.inventor" current="Kasper">Hello</f:format.html>',
'Hello Kasper',
],
'format.html: specific TS path with data' => [
'<f:format.html parseFuncTSPath="lib.record" data="{uid: 1, pid: 12, title: \'foo\'}">Hello</f:format.html>',
'Hello foo',
],
'format.html: specific TS path with data and currentValueKey' => [
'<f:format.html parseFuncTSPath="lib.record" data="{uid: 1, pid: 12, title: \'Bar\'}" currentValueKey="title">Hello</f:format.html>',
'Hello Bar',
],
'format.html: specific TS path with data, currentValueKey and a constant' => [
'<f:format.html parseFuncTSPath="lib.news" data="{uid: 1, pid: 12, title: \'Greate news\'}" currentValueKey="title">###PROJECT### news:</f:format.html>',
'TYPO3 news: Greate news',
],
// table attribute is hard to test. It was only used as parent for CONTENT and RECORD cObj.
// Further the table will be used in FILES cObj as fallback, if a table was not given in references array.
];
}
/**
* @test
* @dataProvider renderDataProvider
*/
public function render(string $template, string $expected): void
{
$this->importCSVDataSet(__DIR__ . '/../../Fixtures/pages.csv');
$this->writeSiteConfiguration(
'test',
$this->buildSiteConfiguration(1, '/'),
[
$this->buildDefaultLanguageConfiguration('EN', '/en/'),
]
);
(new ConnectionPool())->getConnectionForTable('sys_template')
->insert(
'sys_template',
[
'pid' => 1,
'root' => 1,
'clear' => 1,
'config' => <<<EOT
constants.PROJECT = TYPO3
constants.FOO = BAR
lib.parseFunc_RTE {
htmlSanitize = 1
constants = 1
}
lib.foo {
htmlSanitize = 1
constants = 1
}
lib.inventor {
htmlSanitize = 1
plainTextStdWrap.noTrimWrap = || |
plainTextStdWrap.dataWrap = |{CURRENT:1}
}
lib.record {
htmlSanitize = 1
plainTextStdWrap.noTrimWrap = || |
plainTextStdWrap.dataWrap = |{FIELD:title}
}
lib.news {
htmlSanitize = 1
constants = 1
plainTextStdWrap.noTrimWrap = || |
plainTextStdWrap.dataWrap = |{CURRENT:1}
}
page = PAGE
page.10 = FLUIDTEMPLATE
page.10 {
template = TEXT
template.value = $template
}
EOT
]
);
$response = $this->executeFrontendSubRequest(
(new InternalRequest())->withPageId(1)
);
self::assertStringContainsString($expected, (string)$response->getBody());
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment