Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Documentation/ViewHelpers/Fluid.json

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions Documentation/ViewHelpers/Fluid/Argument.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.. This reStructured text file has been automatically generated, do not change.
.. Source: https://github.com/TYPO3/Fluid/blob/main/src/ViewHelpers/ArgumentViewHelper.php

:edit-on-github-link: https://github.com/TYPO3/Fluid/edit/main/src/ViewHelpers/ArgumentViewHelper.php
:navigation-title: argument

.. include:: /Includes.rst.txt

.. _typo3fluid-fluid-argument:

==================================
Argument ViewHelper `<f:argument>`
==================================

.. .. note::
.. This reference is part of the documentation of Fluid Standalone.
.. If you are working with Fluid in TYPO3 CMS, please refer to
.. :doc:`TYPO3's ViewHelper reference <t3viewhelper:Global/Argument>` instead.

.. typo3:viewhelper:: argument
:source: ../Fluid.json
9 changes: 9 additions & 0 deletions examples/Resources/Private/Singles/TemplateArguments.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<f:argument name="title" type="string" />
<f:argument name="tags" type="string[]" optional="{true}" />
<f:argument name="user" type="string" optional="{true}" default="admin" />

Title: {title}<br />
<f:if condition="{tags}">
Tags: {tags -> f:join(separator: ', ')}<br />
</f:if>
User: {user}
36 changes: 36 additions & 0 deletions examples/example_templatearguments.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

/*
* This file belongs to the package "TYPO3 Fluid".
* See LICENSE.txt that was shipped with this package.
*/

/**
* EXAMPLE: Template arguments
*
* This example demonstrates the possibility to declare
* argument definitions for template files, which need
* to be met by the provided variables.
*/

use TYPO3Fluid\FluidExamples\Helper\ExampleHelper;

require_once __DIR__ . '/../vendor/autoload.php';

$exampleHelper = new ExampleHelper();
$view = $exampleHelper->init();

$view->assignMultiple([
'title' => 'My title',
'tags' => ['tag1', 'tag2'],
]);

// Assigning the template path and filename to be rendered. Doing this overrides
// resolving normally done by the TemplatePaths and directly renders this file.
$paths = $view->getRenderingContext()->getTemplatePaths();
$paths->setTemplatePathAndFilename(__DIR__ . '/Resources/Private/Singles/TemplateArguments.html');

// Rendering the View: plain old rendering of single file, no bells and whistles.
$output = $view->render();

$exampleHelper->output($output);
5 changes: 5 additions & 0 deletions src/Core/Compiler/AbstractCompiledTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public function getVariableContainer(): VariableProviderInterface
return new StandardVariableProvider();
}

public function getArgumentDefinitions(): array
{
return [];
}

/**
* Render the parsed template with rendering context
*
Expand Down
28 changes: 28 additions & 0 deletions src/Core/Compiler/TemplateCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\RootNode;
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\ArgumentDefinition;

/**
* @internal Nobody should need to override this class.
Expand Down Expand Up @@ -178,11 +179,13 @@ public function store(string $identifier, ParsingState $parsingState): ?string
' $renderingContext->getViewHelperResolver()->addNamespaces(%s);' . chr(10) .
' }' . chr(10) .
' %s' . chr(10) .
' %s' . chr(10) .
'}' . chr(10),
'class ' . $identifier . ' extends \TYPO3Fluid\Fluid\Core\Compiler\AbstractCompiledTemplate',
$this->generateCodeForLayoutName($storedLayoutName),
($parsingState->hasLayout() ? 'true' : 'false'),
var_export($this->renderingContext->getViewHelperResolver()->getNamespaces(), true),
$this->generateArgumentDefinitionsCodeFromParsingState($parsingState),
$generatedRenderFunctions,
);
$this->renderingContext->getCache()->set($identifier, $templateCode);
Expand Down Expand Up @@ -222,6 +225,31 @@ protected function generateSectionCodeFromParsingState(ParsingState $parsingStat
return $generatedRenderFunctions;
}

protected function generateArgumentDefinitionsCodeFromParsingState(ParsingState $parsingState): string
{
$argumentDefinitions = $parsingState->getArgumentDefinitions();
if ($argumentDefinitions === []) {
return '';
}
$argumentDefinitionsCode = array_map(
static fn(ArgumentDefinition $argumentDefinition): string => sprintf(
'new \\TYPO3Fluid\\Fluid\\Core\\ViewHelper\\ArgumentDefinition(%s, %s, %s, %s, %s, %s)',
var_export($argumentDefinition->getName(), true),
var_export($argumentDefinition->getType(), true),
var_export($argumentDefinition->getDescription(), true),
var_export($argumentDefinition->isRequired(), true),
var_export($argumentDefinition->getDefaultValue(), true),
var_export($argumentDefinition->getEscape(), true),
),
$argumentDefinitions,
);
return 'public function getArgumentDefinitions(): array {' . chr(10) .
' return [' . chr(10) .
' ' . implode(',' . chr(10) . ' ', $argumentDefinitionsCode) . ',' . chr(10) .
' ];' . chr(10) .
' }';
}

/**
* Replaces special characters by underscores
* @see http://www.php.net/manual/en/language.variables.basics.php
Expand Down
6 changes: 6 additions & 0 deletions src/Core/Parser/ParsedTemplateInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\NodeInterface;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\Variables\VariableProviderInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\ArgumentDefinition;

/**
* This interface is returned by \TYPO3Fluid\Fluid\Core\Parser\TemplateParser->parse()
Expand All @@ -25,6 +26,11 @@ public function setIdentifier(string $identifier);

public function getIdentifier(): string;

/**
* @return ArgumentDefinition[]
*/
public function getArgumentDefinitions(): array;

/**
* Render the parsed template with rendering context
*
Expand Down
37 changes: 37 additions & 0 deletions src/Core/Parser/ParsingState.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\RootNode;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\Variables\VariableProviderInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\ArgumentDefinition;
use TYPO3Fluid\Fluid\View;

/**
Expand All @@ -26,6 +27,11 @@ class ParsingState implements ParsedTemplateInterface
{
protected string $identifier;

/**
* @var array<string, ArgumentDefinition>
*/
protected array $argumentDefinitions = [];

/**
* Root node reference
*/
Expand Down Expand Up @@ -80,6 +86,22 @@ public function getRootNode(): RootNode
return $this->rootNode;
}

/**
* @return array<string, ArgumentDefinition>
*/
public function getArgumentDefinitions(): array
{
return $this->argumentDefinitions;
}

/**
* @param array<string, ArgumentDefinition> $argumentDefinitions
*/
public function setArgumentDefinitions(array $argumentDefinitions): void
{
$this->argumentDefinitions = $argumentDefinitions;
}

/**
* Render the parsed template with rendering context
*
Expand Down Expand Up @@ -111,6 +133,21 @@ public function getNodeFromStack(): NodeInterface
return $this->nodeStack[count($this->nodeStack) - 1];
}

/**
* Checks if the specified node type exists in the current stack
*
* @param class-string $nodeType
*/
public function hasNodeTypeInStack(string $nodeType): bool
{
foreach ($this->nodeStack as $node) {
if ($node instanceof $nodeType) {
return true;
}
}
return false;
}

/**
* Pop the top stack element (=remove it) and return it back.
*
Expand Down
71 changes: 71 additions & 0 deletions src/View/AbstractTemplateView.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContext;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\Variables\VariableProviderInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\ArgumentProcessorInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\StrictArgumentProcessor;
use TYPO3Fluid\Fluid\View\Exception\InvalidSectionException;
use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException;
use TYPO3Fluid\Fluid\ViewHelpers\SectionViewHelper;
Expand Down Expand Up @@ -144,6 +147,16 @@ public function render($actionName = null)

if (!$parsedTemplate->hasLayout()) {
$this->startRendering(self::RENDERING_TEMPLATE, $parsedTemplate, $this->baseRenderingContext);
try {
// @todo make argument processor configurable with Fluid v5
$this->processAndValidateTemplateVariables(
$parsedTemplate,
$this->baseRenderingContext->getVariableProvider(),
new StrictArgumentProcessor(),
);
} catch (Exception $validationError) {
return $this->baseRenderingContext->getErrorHandler()->handleViewError($validationError);
}
$output = $parsedTemplate->render($this->baseRenderingContext);
$this->stopRendering();
} else {
Expand All @@ -159,6 +172,16 @@ function ($parent, TemplatePaths $paths) use ($layoutName) {
return $error->getSource();
}
$this->startRendering(self::RENDERING_LAYOUT, $parsedTemplate, $this->baseRenderingContext);
try {
// @todo make argument processor configurable with Fluid v5
$this->processAndValidateTemplateVariables(
$parsedLayout,
$this->baseRenderingContext->getVariableProvider(),
new StrictArgumentProcessor(),
);
} catch (Exception $validationError) {
return $this->baseRenderingContext->getErrorHandler()->handleViewError($validationError);
}
$output = $parsedLayout->render($this->baseRenderingContext);
$this->stopRendering();
}
Expand Down Expand Up @@ -286,6 +309,16 @@ function ($parent, TemplatePaths $paths) use ($partialName) {
if ($sectionName !== null) {
$output = $this->renderSection($sectionName, $variables, $ignoreUnknown);
} else {
try {
// @todo make argument processor configurable with Fluid v5
$this->processAndValidateTemplateVariables(
$parsedPartial,
$renderingContext->getVariableProvider(),
new StrictArgumentProcessor(),
);
} catch (Exception $validationError) {
return $renderingContext->getErrorHandler()->handleViewError($validationError);
}
$output = $parsedPartial->render($renderingContext);
}
$this->stopRendering();
Expand Down Expand Up @@ -362,4 +395,42 @@ protected function getCurrentRenderingContext()
$currentRendering = end($this->renderingStack);
return !empty($currentRendering['renderingContext']) ? $currentRendering['renderingContext'] : $this->baseRenderingContext;
}

protected function processAndValidateTemplateVariables(
ParsedTemplateInterface $parsedTemplate,
VariableProviderInterface $variableProvider,
ArgumentProcessorInterface $argumentProcessor,
): void {
$renderingTypeLabel = match ($this->getCurrentRenderingType()) {
self::RENDERING_PARTIAL => 'partial',
self::RENDERING_TEMPLATE => 'template',
self::RENDERING_LAYOUT => 'layout',
};
foreach ($parsedTemplate->getArgumentDefinitions() as $argumentDefinition) {
$argumentName = $argumentDefinition->getName();
if ($variableProvider->exists($argumentName)) {
$processedValue = $argumentProcessor->process($variableProvider->get($argumentName), $argumentDefinition);
if (!$argumentProcessor->isValid($processedValue, $argumentDefinition)) {
throw new Exception(sprintf(
'The argument "%s" for %s "%s" is registered with type "%s", but the provided value is of type "%s".',
$argumentName,
$renderingTypeLabel,
$parsedTemplate->getIdentifier(),
$argumentDefinition->getType(),
is_object($processedValue) ? get_class($processedValue) : gettype($processedValue),
), 1746637333);
}
$variableProvider->add($argumentName, $processedValue);
} elseif ($argumentDefinition->isRequired()) {
throw new Exception(sprintf(
'The argument "%s" for %s "%s" is required, but was not provided.',
$argumentName,
$renderingTypeLabel,
$parsedTemplate->getIdentifier(),
), 1746637334);
} else {
$variableProvider->add($argumentName, $argumentDefinition->getDefaultValue());
}
}
}
}
Loading