-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEATURE] StripTagsViewHelper for StandaloneFluid (#840)
* [FEATURE] StripTagsViewHelper for StandaloneFluid StripTagsViewHelper doesn't have any dependencies to TYPO3, so it can be moved to Fluid Standalone. * [BUGFIX] Throw exceptions for invalid input
- Loading branch information
Showing
2 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file belongs to the package "TYPO3 Fluid". | ||
* See LICENSE.txt that was shipped with this package. | ||
*/ | ||
|
||
namespace TYPO3Fluid\Fluid\ViewHelpers\Format; | ||
|
||
use Stringable; | ||
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; | ||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; | ||
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic; | ||
|
||
/** | ||
* Removes tags from the given string (applying PHPs :php:`strip_tags()` function) | ||
* See https://www.php.net/manual/function.strip-tags.php. | ||
* | ||
* Examples | ||
* ======== | ||
* | ||
* Default notation | ||
* ---------------- | ||
* | ||
* :: | ||
* | ||
* <f:format.stripTags>Some Text with <b>Tags</b> and an Ümlaut.</f:format.stripTags> | ||
* | ||
* Some Text with Tags and an Ümlaut. :php:`strip_tags()` applied. | ||
* | ||
* .. note:: | ||
* Encoded entities are not decoded. | ||
* | ||
* Default notation with allowedTags | ||
* --------------------------------- | ||
* | ||
* :: | ||
* | ||
* <f:format.stripTags allowedTags="<p><span><div><script>"> | ||
* <p>paragraph</p><span>span</span><div>divider</div><iframe>iframe</iframe><script>script</script> | ||
* </f:format.stripTags> | ||
* | ||
* Output:: | ||
* | ||
* <p>paragraph</p><span>span</span><div>divider</div>iframe<script>script</script> | ||
* | ||
* Inline notation | ||
* --------------- | ||
* | ||
* :: | ||
* | ||
* {text -> f:format.stripTags()} | ||
* | ||
* Text without tags :php:`strip_tags()` applied. | ||
* | ||
* Inline notation with allowedTags | ||
* -------------------------------- | ||
* | ||
* :: | ||
* | ||
* {text -> f:format.stripTags(allowedTags: "<p><span><div><script>")} | ||
* | ||
* Text with p, span, div and script Tags inside, all other tags are removed. | ||
*/ | ||
final class StripTagsViewHelper extends AbstractViewHelper | ||
{ | ||
use CompileWithContentArgumentAndRenderStatic; | ||
|
||
/** | ||
* No output escaping as some tags may be allowed | ||
* | ||
* @var bool | ||
*/ | ||
protected $escapeOutput = false; | ||
|
||
public function initializeArguments(): void | ||
{ | ||
$this->registerArgument('value', 'string', 'string to format'); | ||
$this->registerArgument('allowedTags', 'string', 'Optional string of allowed tags as required by PHPs strip_tags() function'); | ||
} | ||
|
||
/** | ||
* To ensure all tags are removed, child node's output must not be escaped | ||
* | ||
* @var bool | ||
*/ | ||
protected $escapeChildren = false; | ||
|
||
/** | ||
* Applies strip_tags() on the specified value if it's string-able. | ||
* | ||
* @see https://www.php.net/manual/function.strip-tags.php | ||
* @return string | ||
*/ | ||
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string | ||
{ | ||
$value = $renderChildrenClosure(); | ||
$allowedTags = $arguments['allowedTags']; | ||
|
||
if (is_array($value)) { | ||
throw new \InvalidArgumentException('Specified array cannot be converted to string.', 1700819707); | ||
} | ||
if (is_object($value) && !($value instanceof Stringable)) { | ||
throw new \InvalidArgumentException('Specified object cannot be converted to string.', 1700819706); | ||
} | ||
return strip_tags((string)$value, $allowedTags); | ||
} | ||
|
||
/** | ||
* Explicitly set argument name to be used as content. | ||
*/ | ||
public function resolveContentArgumentName(): string | ||
{ | ||
return 'value'; | ||
} | ||
} |
119 changes: 119 additions & 0 deletions
119
tests/Functional/ViewHelpers/Format/StripTagsViewHelperTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file belongs to the package "TYPO3 Fluid". | ||
* See LICENSE.txt that was shipped with this package. | ||
*/ | ||
|
||
namespace TYPO3Fluid\Fluid\Tests\Functional\ViewHelpers\Format; | ||
|
||
use stdClass; | ||
use TYPO3Fluid\Fluid\Tests\Functional\AbstractFunctionalTestCase; | ||
use TYPO3Fluid\Fluid\View\TemplateView; | ||
|
||
final class StripTagsViewHelperTest extends AbstractFunctionalTestCase | ||
{ | ||
public static function renderDataProvider(): array | ||
{ | ||
return [ | ||
'renderUsesValueAsSourceIfSpecified' => [ | ||
'<f:format.stripTags value="Some string" />', | ||
'Some string', | ||
], | ||
'int as input' => [ | ||
'<f:format.stripTags value="123" />', | ||
'123', | ||
], | ||
'renderUsesChildnodesAsSourceIfSpecified' => [ | ||
'<f:format.stripTags>Some string</f:format.stripTags>', | ||
'Some string', | ||
], | ||
'no special chars' => [ | ||
'<f:format.stripTags>This is a sample text without special characters.</f:format.stripTags>', | ||
'This is a sample text without special characters.', | ||
], | ||
'some tags' => [ | ||
'<f:format.stripTags>This is a sample text <b>with <i>some</i> tags</b>.</f:format.stripTags>', | ||
'This is a sample text with some tags.', | ||
], | ||
'some umlauts' => [ | ||
'<f:format.stripTags>This text contains some "Ümlaut".</f:format.stripTags>', | ||
'This text contains some "Ümlaut".', | ||
], | ||
'allowed tags' => [ | ||
'<f:format.stripTags allowedTags="<strong>">This text <i>contains</i> some <strong>allowed</strong> tags.</f:format.stripTags>', | ||
'This text contains some <strong>allowed</strong> tags.', | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @test | ||
* @dataProvider renderDataProvider | ||
*/ | ||
public function render(string $template, string $expected): void | ||
{ | ||
$view = new TemplateView(); | ||
$view->getRenderingContext()->setCache(self::$cache); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($template); | ||
self::assertSame($expected, $view->render()); | ||
|
||
$view = new TemplateView(); | ||
$view->getRenderingContext()->setCache(self::$cache); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($template); | ||
self::assertSame($expected, $view->render()); | ||
} | ||
|
||
/** | ||
* Ensures that objects are handled properly: | ||
* + class having __toString() method gets tags stripped off | ||
* | ||
* @test | ||
*/ | ||
public function renderEscapesObjectIfPossible(): void | ||
{ | ||
$toStringClass = new class () { | ||
public function __toString(): string | ||
{ | ||
return '<script>alert(\'"xss"\')</script>'; | ||
} | ||
}; | ||
$view = new TemplateView(); | ||
$view->getRenderingContext()->setCache(self::$cache); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource('<f:format.stripTags>{value}</f:format.stripTags>'); | ||
$view->assign('value', $toStringClass); | ||
self::assertEquals('alert(\'"xss"\')', $view->render()); | ||
} | ||
|
||
public static function throwsExceptionForInvalidInputDataProvider(): array | ||
{ | ||
return [ | ||
'array input' => [ | ||
[1, 2, 3], | ||
1700819707, | ||
'Specified array cannot be converted to string.', | ||
], | ||
'object input' => [ | ||
new stdClass(), | ||
1700819706, | ||
'Specified object cannot be converted to string.', | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @test | ||
* @dataProvider throwsExceptionForInvalidInputDataProvider | ||
*/ | ||
public function throwsExceptionForInvalidInput(mixed $value, int $expectedExceptionCode, string $expectedExceptionMessage): void | ||
{ | ||
self::expectExceptionCode($expectedExceptionCode); | ||
self::expectExceptionMessage($expectedExceptionMessage); | ||
$view = new TemplateView(); | ||
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource('<f:format.stripTags>{value}</f:format.stripTags>'); | ||
$view->assign('value', $value); | ||
$view->render(); | ||
} | ||
} |