diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index cc2d334538c3..a3cbe3426002 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add `emojify` twig filter + 7.0 --- diff --git a/src/Symfony/Bridge/Twig/Extension/EmojiExtension.php b/src/Symfony/Bridge/Twig/Extension/EmojiExtension.php new file mode 100644 index 000000000000..b98798dac014 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/EmojiExtension.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Emoji\EmojiTransliterator; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * @author Grégoire Pineau + */ +final class EmojiExtension extends AbstractExtension +{ + private static array $transliterators = []; + + public function __construct( + private readonly string $defaultCatalog = 'text', + ) { + if (!class_exists(EmojiTransliterator::class)) { + throw new \LogicException('You cannot use the "emojify" filter as the "Emoji" component is not installed. Try running "composer require symfony/emoji".'); + } + } + + public function getFilters(): array + { + return [ + new TwigFilter('emojify', $this->emojify(...)), + ]; + } + + /** + * Converts emoji short code (:wave:) to real emoji (👋) + */ + public function emojify(string $string, ?string $catalog = null): string + { + $catalog ??= $this->defaultCatalog; + + try { + $tr = self::$transliterators[$catalog] ??= EmojiTransliterator::create($catalog, EmojiTransliterator::REVERSE); + } catch (\IntlException $e) { + throw new \LogicException(sprintf('The emoji catalog "%s" is not available.', $catalog), previous: $e); + } + + return (string) $tr->transliterate($string); + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/EmojiExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/EmojiExtensionTest.php new file mode 100644 index 000000000000..492929a341e7 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/EmojiExtensionTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\Extension\EmojiExtension; + +/** + * @requires extension intl + */ +class EmojiExtensionTest extends TestCase +{ + /** + * @testWith ["🅰️", ":a:"] + * ["🅰️", ":a:", "slack"] + * ["🅰", ":a:", "github"] + */ + public function testEmojify(string $expected, string $string, ?string $catalog = null) + { + $extension = new EmojiExtension(); + $this->assertSame($expected, $extension->emojify($string, $catalog)); + } +} diff --git a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php index 0ae2893cb8ad..c9f502f67bd4 100644 --- a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php +++ b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php @@ -37,6 +37,7 @@ class UndefinedCallableHandler 'asset_version' => 'asset', 'importmap' => 'asset-mapper', 'dump' => 'debug-bundle', + 'emojify' => 'emoji', 'encore_entry_link_tags' => 'webpack-encore-bundle', 'encore_entry_script_tags' => 'webpack-encore-bundle', 'expression' => 'expression-language', diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 71fb6a2f8af2..90eb69ed0c64 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -27,6 +27,7 @@ "symfony/asset": "^6.4|^7.0", "symfony/asset-mapper": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", + "symfony/emoji": "^7.1", "symfony/finder": "^6.4|^7.0", "symfony/form": "^6.4|^7.0", "symfony/html-sanitizer": "^6.4|^7.0", diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index 58aa92168620..2cae63608121 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Emoji\EmojiTransliterator; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Workflow\Workflow; @@ -31,6 +32,10 @@ public function process(ContainerBuilder $container): void $container->removeDefinition('twig.extension.assets'); } + if (!class_exists(EmojiTransliterator::class)) { + $container->removeDefinition('twig.extension.emoji'); + } + if (!class_exists(Expression::class)) { $container->removeDefinition('twig.extension.expression'); } @@ -125,6 +130,10 @@ public function process(ContainerBuilder $container): void $container->getDefinition('twig.extension.expression')->addTag('twig.extension'); } + if ($container->hasDefinition('twig.extension.emoji')) { + $container->getDefinition('twig.extension.emoji')->addTag('twig.extension'); + } + if (!class_exists(Workflow::class) || !$container->has('workflow.registry')) { $container->removeDefinition('workflow.twig_extension'); } else { diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php index e1b47de24558..02631d28c39a 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php @@ -17,6 +17,7 @@ use Symfony\Bridge\Twig\ErrorRenderer\TwigErrorRenderer; use Symfony\Bridge\Twig\EventListener\TemplateAttributeListener; use Symfony\Bridge\Twig\Extension\AssetExtension; +use Symfony\Bridge\Twig\Extension\EmojiExtension; use Symfony\Bridge\Twig\Extension\ExpressionExtension; use Symfony\Bridge\Twig\Extension\HtmlSanitizerExtension; use Symfony\Bridge\Twig\Extension\HttpFoundationExtension; @@ -115,6 +116,8 @@ ->set('twig.extension.expression', ExpressionExtension::class) + ->set('twig.extension.emoji', EmojiExtension::class) + ->set('twig.extension.htmlsanitizer', HtmlSanitizerExtension::class) ->args([tagged_locator('html_sanitizer', 'sanitizer')]) diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 88c1dd5b8541..bfee47e03d99 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -20,7 +20,7 @@ "composer-runtime-api": ">=2.1", "symfony/config": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", - "symfony/twig-bridge": "^6.4|^7.0", + "symfony/twig-bridge": "^7.1", "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "twig/twig": "^3.0.4"