Skip to content

Commit

Permalink
[FEATURE] Add compatibility layer to basically all ViewHelpers
Browse files Browse the repository at this point in the history
Adds a massive number of backwards/cross compatibility
handlers, because Fluid decided to introduce a large number
of completely unnecessary breaking changes in v4, because
"it's cleaner" and apparently nobody cares about devs anymore.
  • Loading branch information
NamelessCoder committed Jan 26, 2025
1 parent 5b3ef99 commit 9cab935
Show file tree
Hide file tree
Showing 140 changed files with 577 additions and 92 deletions.
28 changes: 28 additions & 0 deletions Classes/Traits/ArgumentOverride.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php
namespace FluidTYPO3\Vhs\Traits;

/*
* This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
*
* For the full copyright and license information, please read the
* LICENSE.md file that was distributed with this source code.
*/

use TYPO3\CMS\Core\Utility\VersionNumberUtility;

trait ArgumentOverride
{
protected function overrideArgument(
$name,
$type,
$description,
$required = false,
$defaultValue = null,
$escape = null
) {
if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '13.4', '>=')) {
return parent::registerArgument($name, $type, $description, $required, $defaultValue, $escape);
}
return parent::overrideArgument($name, $type, $description, $required, $defaultValue, $escape);
}
}
184 changes: 184 additions & 0 deletions Classes/Traits/CompileWithContentArgumentAndRenderStatic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<?php
namespace FluidTYPO3\Vhs\Traits;

/*
* This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
*
* For the full copyright and license information, please read the
* LICENSE.md file that was distributed with this source code.
*/

use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler;
use TYPO3Fluid\Fluid\Core\Compiler\ViewHelperCompiler;
use TYPO3Fluid\Fluid\Core\Exception;
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;

/**
* Class CompilableWithContentArgumentAndRenderStatic
*
* Provides default methods for rendering and compiling
* any ViewHelper that conforms to the `renderStatic`
* method pattern but has the added common use case that
* an argument value must be checked and used instead of
* the normal render children closure, if that named
* argument is specified and not empty.
*/
trait CompileWithContentArgumentAndRenderStatic
{
/**
* Name of variable that contains the value to use
* instead of render children closure, if specified.
* If no name is provided here, the first variable
* registered in `initializeArguments` of the ViewHelper
* will be used.
*
* Note: it is significantly better practice defining
* this property in your ViewHelper class and so fix it
* to one particular argument instead of resolving,
* especially when your ViewHelper is called multiple
* times within an uncompiled template!
*
* This property cannot be directly set in consuming
* ViewHelper, instead set the property in ViewHelper
* constructor '__construct()', for example with
* $this->contentArgumentName = 'explicitlyToUseArgumentName';
*
* Another possible way would be to override the method
* 'resolveContentArgumentName()' and return the name.
*
* public function resolveContentArgumentName()
* {
* return 'explicitlyToUseArgumentName';
* }
*
* Note: Setting this through 'initializeArguments()' will
* not work as expected, and other methods should be
* avoided to override this.
*
* Following test ViewHelpers are tested and demonstrates
* that the setting posibillities works.
*
* @var string
*/
protected $contentArgumentName;

/**
* Default render method to render ViewHelper with
* first defined optional argument as content.
*
* @return mixed Rendered result
* @api
*/
public function render()
{
return static::renderStatic(
$this->arguments,
$this->buildRenderChildrenClosure(),
$this->renderingContext,
);
}

/**
* @param string $argumentsName
* @param string $closureName
* @param string $initializationPhpCode
* @param ViewHelperNode $node
* @param TemplateCompiler $compiler
* @return string
*/
public function compile(
$argumentsName,
$closureName,
&$initializationPhpCode,
ViewHelperNode $node,
TemplateCompiler $compiler
) {
[$initialization, $execution] = ViewHelperCompiler::getInstance()->compileWithCallToStaticMethod(
$this,
$argumentsName,
$closureName,
ViewHelperCompiler::RENDER_STATIC,
static::class
);
$contentArgumentName = $this->resolveContentArgumentName();
$initializationPhpCode .= sprintf(
'%s = (%s[\'%s\'] !== null) ? function() use (%s) { return %s[\'%s\']; } : %s;',
$closureName,
$argumentsName,
$contentArgumentName,
$argumentsName,
$argumentsName,
$contentArgumentName,
$closureName
);
$initializationPhpCode .= $initialization;
return $execution;
}

/**
* Helper which is mostly needed when calling renderStatic() from within
* render().
*
* No public API yet.
*
* @return \Closure
*/
protected function buildRenderChildrenClosure()
{
$argumentName = $this->resolveContentArgumentName();
$arguments = $this->arguments;
if (!empty($argumentName) && isset($arguments[$argumentName])) {
$renderChildrenClosure = function () use ($arguments, $argumentName) {
return $arguments[$argumentName];
};
} else {
$self = clone $this;
$renderChildrenClosure = function () use ($self) {
return $self->renderChildren();
};
}
return $renderChildrenClosure;
}

/**
* Helper method which triggers the rendering of everything between the
* opening and the closing tag.
*
* @return mixed The finally rendered child nodes.
* @api
*/
public function renderChildren()
{
if ($this->renderChildrenClosure !== null) {
$closure = $this->renderChildrenClosure;
return $closure();
}
return $this->viewHelperNode->evaluateChildNodes($this->renderingContext);
}

/**
* @return string
*/
public function resolveContentArgumentName()
{
if (empty($this->contentArgumentName)) {
$registeredArguments = $this->prepareArguments();
foreach ($registeredArguments as $registeredArgument) {
if (!$registeredArgument->isRequired()) {
$this->contentArgumentName = $registeredArgument->getName();
return $this->contentArgumentName;
}
}
throw new Exception(
sprintf('Attempting to compile %s failed. Chosen compile method requires that ViewHelper has ' .
'at least one registered and optional argument', __CLASS__)
);
}
return $this->contentArgumentName;
}

public function getContentArgumentName(): ?string
{
return $this->resolveContentArgumentName();
}
}
40 changes: 40 additions & 0 deletions Classes/Traits/CompileWithRenderStatic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
namespace FluidTYPO3\Vhs\Traits;

/*
* This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
*
* For the full copyright and license information, please read the
* LICENSE.md file that was distributed with this source code.
*/

/**
* Class CompilableWithRenderStatic
*
* Provides default methods for rendering and compiling
* any ViewHelper that conforms to the `renderStatic`
* method pattern.
*/
trait CompileWithRenderStatic
{
/**
* Default render method - simply calls renderStatic() with a
* prepared set of arguments.
*
* @return mixed Rendered result
* @api
*/
public function render()
{
return static::renderStatic(
$this->arguments,
$this->buildRenderChildrenClosure(),
$this->renderingContext,
);
}

/**
* @return \Closure
*/
abstract protected function buildRenderChildrenClosure();
}
66 changes: 66 additions & 0 deletions Classes/Traits/TagViewHelperCompatibility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
namespace FluidTYPO3\Vhs\Traits;

/*
* This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
*
* For the full copyright and license information, please read the
* LICENSE.md file that was distributed with this source code.
*/

use TYPO3\CMS\Core\Utility\VersionNumberUtility;

trait TagViewHelperCompatibility
{
/**
* Register a new tag attribute. Tag attributes are all arguments which will be directly appended to a tag if you
* call $this->initializeTag()
*
* @param string $name Name of tag attribute
* @param string $type Type of the tag attribute
* @param string $description Description of tag attribute
* @param bool $required set to true if tag attribute is required. Defaults to false.
* @param mixed $defaultValue Optional, default value of attribute if one applies
* @return void
* @api
*/
protected function registerTagAttribute($name, $type, $description, $required = false, $defaultValue = null)
{
if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '13.4', '>=')) {
$this->registerArgument($name, $type, $description, $required, $defaultValue);
return;
}
parent::registerTagAttribute($name, $type, $description, $required, $defaultValue);
}

/**
* Registers all standard HTML universal attributes.
* Should be used inside registerArguments();
*
* @return void
* @api
*/
protected function registerUniversalTagAttributes()
{
if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '13.4', '>=')) {
return;
}
$this->registerTagAttribute('class', 'string', 'CSS class(es) for this element');
$this->registerTagAttribute(
'dir',
'string',
'Text direction for this HTML element. Allowed strings: "ltr" (left to right), "rtl" (right to left)'
);
$this->registerTagAttribute('id', 'string', 'Unique (in this file) identifier for this HTML element.');
$this->registerTagAttribute(
'lang',
'string',
'Language for this element. Use short names specified in RFC 1766'
);
$this->registerTagAttribute('style', 'string', 'Individual CSS styles for this element');
$this->registerTagAttribute('title', 'string', 'Tooltip text of element');
$this->registerTagAttribute('accesskey', 'string', 'Keyboard shortcut to access this element');
$this->registerTagAttribute('tabindex', 'integer', 'Specifies the tab order of this element');
$this->registerTagAttribute('onclick', 'string', 'JavaScript evaluated for the onclick event');
}
}
42 changes: 41 additions & 1 deletion Classes/Traits/TagViewHelperTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* LICENSE.md file that was distributed with this source code.
*/

use TYPO3\CMS\Core\Utility\VersionNumberUtility;

/**
* Class TagViewHelperTrait
*
Expand All @@ -31,13 +33,51 @@ public function registerArguments(): void
$this->registerUniversalTagAttributes();
}

/**
* Register a new tag attribute. Tag attributes are all arguments which will be directly appended to a tag if you
* call $this->initializeTag()
*
* @param string $name Name of tag attribute
* @param string $type Type of the tag attribute
* @param string $description Description of tag attribute
* @param bool $required set to true if tag attribute is required. Defaults to false.
* @param mixed $defaultValue Optional, default value of attribute if one applies
* @return void
* @api
*/
protected function registerTagAttribute($name, $type, $description, $required = false, $defaultValue = null)
{
if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '13.4', '>=')) {
$this->registerArgument($name, $type, $description, $required, $defaultValue);
return;
}
parent::registerTagAttribute($name, $type, $description, $required, $defaultValue);
}

/**
* Registers all standard and HTML5 universal attributes.
* Should be used inside registerArguments();
*/
protected function registerUniversalTagAttributes(): void
{
parent::registerUniversalTagAttributes();
$this->registerTagAttribute('class', 'string', 'CSS class(es) for this element');
$this->registerTagAttribute(
'dir',
'string',
'Text direction for this HTML element. Allowed strings: "ltr" (left to right), "rtl" (right to left)'
);
$this->registerTagAttribute('id', 'string', 'Unique (in this file) identifier for this HTML element.');
$this->registerTagAttribute(
'lang',
'string',
'Language for this element. Use short names specified in RFC 1766'
);
$this->registerTagAttribute('style', 'string', 'Individual CSS styles for this element');
$this->registerTagAttribute('title', 'string', 'Tooltip text of element');
$this->registerTagAttribute('accesskey', 'string', 'Keyboard shortcut to access this element');
$this->registerTagAttribute('tabindex', 'integer', 'Specifies the tab order of this element');
$this->registerTagAttribute('onclick', 'string', 'JavaScript evaluated for the onclick event');

$this->registerArgument(
'forceClosingTag',
'boolean',
Expand Down
Loading

0 comments on commit 9cab935

Please sign in to comment.