diff --git a/core/lib/Drupal/Component/Render/FormattableMarkup.php b/core/lib/Drupal/Component/Render/FormattableMarkup.php index ec42e068a7934fa14ec4cb9d9d0ce3ca3966e645..52d6da66fe64cae40f147be6b9caeae020110581 100644 --- a/core/lib/Drupal/Component/Render/FormattableMarkup.php +++ b/core/lib/Drupal/Component/Render/FormattableMarkup.php @@ -65,7 +65,7 @@ * @see \Drupal\Core\StringTranslation\PluralTranslatableMarkup * @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat() */ -class FormattableMarkup implements MarkupInterface { +class FormattableMarkup implements MarkupInterface, \Countable { /** * The arguments to replace placeholders with. diff --git a/core/lib/Drupal/Component/Render/HtmlEscapedText.php b/core/lib/Drupal/Component/Render/HtmlEscapedText.php index fd1e69d94ea6f941145eb37b6c555f8639bdf37e..f12e4e9b7a7c3cfa062115ae1c35c7527d513dc1 100644 --- a/core/lib/Drupal/Component/Render/HtmlEscapedText.php +++ b/core/lib/Drupal/Component/Render/HtmlEscapedText.php @@ -18,7 +18,7 @@ * * @ingroup sanitization */ -class HtmlEscapedText implements MarkupInterface { +class HtmlEscapedText implements MarkupInterface, \Countable { /** * The string to escape. diff --git a/core/lib/Drupal/Core/GeneratedLink.php b/core/lib/Drupal/Core/GeneratedLink.php index d7e96e1297a0e461a69c56903470ea9b0fdd65d4..a612f665256e707cb22b4046599267fb0bb838ac 100644 --- a/core/lib/Drupal/Core/GeneratedLink.php +++ b/core/lib/Drupal/Core/GeneratedLink.php @@ -8,6 +8,7 @@ namespace Drupal\Core; use Drupal\Component\Render\MarkupInterface; +use Drupal\Component\Utility\Unicode; use Drupal\Core\Render\BubbleableMetadata; /** @@ -16,7 +17,7 @@ * Note: not to be confused with \Drupal\Core\Link, which is for passing around * ungenerated links (typically link text + route name + route parameters). */ -class GeneratedLink extends BubbleableMetadata implements MarkupInterface { +class GeneratedLink extends BubbleableMetadata implements MarkupInterface, \Countable { /** * The HTML string value containing a link. @@ -61,4 +62,11 @@ public function jsonSerialize() { return $this->__toString(); } + /** + * {@inheritdoc} + */ + public function count() { + return Unicode::strlen($this->__toString()); + } + } diff --git a/core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php b/core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php index 024c3a42a30386da9da2d3e715cbe9476d95e3eb..a0111eac8de726e9818a6dc2c105cabf34703f31 100644 --- a/core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php +++ b/core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php @@ -9,6 +9,7 @@ use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Utility\ToStringTrait; +use Drupal\Component\Utility\Unicode; /** * Provides translatable markup class. @@ -231,4 +232,14 @@ protected function getStringTranslation() { return $this->stringTranslation; } + /** + * Returns the string length. + * + * @return int + * The length of the string. + */ + public function count() { + return Unicode::strlen($this->render()); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Theme/TwigMarkupInterfaceTest.php b/core/tests/Drupal/KernelTests/Core/Theme/TwigMarkupInterfaceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ed11a315746f29b5b0817e98689f727d3e1f4751 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Theme/TwigMarkupInterfaceTest.php @@ -0,0 +1,112 @@ +<?php + +/** + * @file + * Contains \Drupal\KernelTests\Core\Theme\ThemeRenderAndAutoescapeTest. + */ + +namespace Drupal\KernelTests\Core\Theme; + +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Component\Render\MarkupInterface; +use Drupal\Component\Render\MarkupTrait; +use Drupal\Core\GeneratedLink; +use Drupal\Core\Render\RenderContext; +use Drupal\Core\Render\Markup; +use Drupal\Core\Site\Settings; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\KernelTests\KernelTestBase; + +/** + * Tests Twig with MarkupInterface objects. + * + * @group Theme + */ +class TwigMarkupInterfaceTest extends KernelTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = [ + 'language' + ]; + + /** + * @dataProvider providerTestMarkupInterfaceEmpty + */ + public function testMarkupInterfaceEmpty($expected, $variable) { + $this->assertEquals($expected, $this->renderObjectWithTwig($variable)); + } + + /** + * Provide test examples. + */ + public function providerTestMarkupInterfaceEmpty() { + return [ + 'empty TranslatableMarkup' => ['', new TranslatableMarkup('')], + 'non-empty TranslatableMarkup' => ['<span>test</span>', new TranslatableMarkup('test')], + 'empty FormattableMarkup' => ['', new FormattableMarkup('', ['@foo' => 'bar'])], + 'non-empty FormattableMarkup' => ['<span>bar</span>', new FormattableMarkup('@foo', ['@foo' => 'bar'])], + 'non-empty Markup' => ['<span>test</span>', Markup::create('test')], + 'empty GeneratedLink' => ['', new GeneratedLink()], + 'non-empty GeneratedLink' => ['<span><a hef="http://www.example.com">test</a></span>', (new GeneratedLink())->setGeneratedLink('<a hef="http://www.example.com">test</a>')], + // Test objects that do not implement \Countable. + 'empty SafeMarkupTestMarkup' => ['<span></span>', SafeMarkupTestMarkup::create('')], + 'non-empty SafeMarkupTestMarkup' => ['<span>test</span>', SafeMarkupTestMarkup::create('test')], + ]; + } + + /** + * Tests behaviour if a string is translated to become an empty string. + */ + public function testEmptyTranslation() { + $settings = Settings::getAll(); + $settings['locale_custom_strings_en'] = ['' => ['test' => '']]; + // Recreate the settings static. + new Settings($settings); + + $variable = new TranslatableMarkup('test'); + $this->assertEquals('', $this->renderObjectWithTwig($variable)); + + $variable = new TranslatableMarkup('test', [], ['langcode' => 'de']); + $this->assertEquals('<span>test</span>', $this->renderObjectWithTwig($variable)); + } + + /** + * @return \Drupal\Component\Render\MarkupInterface + * The rendered HTML. + */ + protected function renderObjectWithTwig($variable) { + /** @var \Drupal\Core\Render\RendererInterface $renderer */ + $renderer = \Drupal::service('renderer'); + $context = new RenderContext(); + return $renderer->executeInRenderContext($context, function () use ($renderer, $variable) { + $elements = [ + '#type' => 'inline_template', + '#template' => '{%- if variable is not empty -%}<span>{{ variable }}</span>{%- endif -%}', + '#context' => array('variable' => $variable), + ]; + return $renderer->render($elements); + }); + } + +} + +/** + * Implements MarkupInterface without implementing \Countable + */ +class SafeMarkupTestMarkup implements MarkupInterface { + use MarkupTrait; + + /** + * Overrides MarkupTrait::create() to allow creation with empty strings. + */ + public static function create($string) { + $object = new static(); + $object->string = $string; + return $object; + } + +}