diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83e43bff18d..89af872e8d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,6 +151,11 @@ jobs: working-directory: extra/${{ matrix.extension }} run: "composer require --no-update 'symfony/translation-contracts:^1.1|^2.0'" + - name: "Prevent installing tempest/markdown, which requires PHP 8.5" + if: "matrix.extension == 'markdown-extra' && matrix.php-version != '8.5'" + working-directory: extra/${{ matrix.extension }} + run: composer remove --dev --no-update tempest/markdown + - name: "Composer install ${{ matrix.extension }}" working-directory: extra/${{ matrix.extension }} run: composer install diff --git a/.gitignore b/.gitignore index b197246ba80..35ce42ad147 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /composer.lock /phpunit.xml /vendor +.php-cs-fixer.cache .phpunit.result.cache diff --git a/CHANGELOG b/CHANGELOG index 1e8067d1fe9..cf0c97ecff1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.29.0 (2026-XX-XX) + * Add `TempestMarkdown` to use `tempest/markdown` as the `markdown_to_html` converter * Fix imported macros not resolving their own template-level macro imports * Add the `Twig\Sandbox\SandboxInterface` interface and `Twig\Sandbox\Sandbox` class to render untrusted templates through a dedicated, always-sandboxed environment crafted for it * Add the `Twig\Extension\SandboxBridgeExtension` to render sandboxed templates from trusted templates with an explicit output escaping strategy diff --git a/doc/filters/markdown_to_html.rst b/doc/filters/markdown_to_html.rst index 780f0112b96..4218ed37ce2 100644 --- a/doc/filters/markdown_to_html.rst +++ b/doc/filters/markdown_to_html.rst @@ -84,10 +84,11 @@ Using a Custom Converter The ``markdown_to_html`` filter delegates the conversion to a class implementing ``Twig\Extra\Markdown\MarkdownInterface``. Several implementations are provided: ``LeagueMarkdown`` (``league/commonmark``), ``MichelfMarkdown`` -(``michelf/php-markdown``), and ``ErusevMarkdown`` (``erusev/parsedown``). Each -accepts a pre-configured converter in its constructor, so you can tune the -underlying library or switch to another implementation (for instance -``ParsedownExtra``, which extends ``Parsedown``):: +(``michelf/php-markdown``), ``ErusevMarkdown`` (``erusev/parsedown``), and +``TempestMarkdown`` (``tempest/markdown``). Each accepts a pre-configured +converter in its constructor, so you can tune the underlying library or switch +to another implementation (for instance ``ParsedownExtra``, which extends +``Parsedown``):: use Twig\Extra\Markdown\ErusevMarkdown; @@ -96,6 +97,14 @@ underlying library or switch to another implementation (for instance $markdown = new ErusevMarkdown($parsedown); +.. note:: + + ``tempest/markdown`` requires PHP 8.5 or later. It also differs from the + other libraries on two points worth knowing about: Setext headings + (``Title`` underlined with ``===``) are not supported, so use ATX headings + (``# Title``) instead, and any YAML front matter is parsed out of the + rendered HTML rather than being rendered. + When using ``twig/extra-bundle``, register your converter as the ``twig.markdown.default`` service to make it the one used by the filter: diff --git a/extra/markdown-extra/DefaultMarkdown.php b/extra/markdown-extra/DefaultMarkdown.php index a20993d45da..38902208219 100644 --- a/extra/markdown-extra/DefaultMarkdown.php +++ b/extra/markdown-extra/DefaultMarkdown.php @@ -13,6 +13,7 @@ use League\CommonMark\CommonMarkConverter; use Michelf\MarkdownExtra; +use Tempest\Markdown\Markdown; class DefaultMarkdown implements MarkdownInterface { @@ -26,6 +27,8 @@ public function __construct() $this->converter = new MichelfMarkdown(); } elseif (class_exists(\Parsedown::class)) { $this->converter = new ErusevMarkdown(); + } elseif (class_exists(Markdown::class)) { + $this->converter = new TempestMarkdown(); } else { throw new \LogicException('You cannot use the "markdown_to_html" filter as no Markdown library is available; try running "composer require league/commonmark".'); } diff --git a/extra/markdown-extra/TempestMarkdown.php b/extra/markdown-extra/TempestMarkdown.php new file mode 100644 index 00000000000..1e037ac998b --- /dev/null +++ b/extra/markdown-extra/TempestMarkdown.php @@ -0,0 +1,29 @@ +converter = $converter ?: new Markdown(); + } + + public function convert(string $body): string + { + return $this->converter->parse($body)->html; + } +} diff --git a/extra/markdown-extra/Tests/FunctionalTest.php b/extra/markdown-extra/Tests/FunctionalTest.php index 863221d189b..6fc57d86c28 100644 --- a/extra/markdown-extra/Tests/FunctionalTest.php +++ b/extra/markdown-extra/Tests/FunctionalTest.php @@ -20,6 +20,7 @@ use Twig\Extra\Markdown\MarkdownInterface; use Twig\Extra\Markdown\MarkdownRuntime; use Twig\Extra\Markdown\MichelfMarkdown; +use Twig\Extra\Markdown\TempestMarkdown; use Twig\Loader\ArrayLoader; use Twig\RuntimeLoader\RuntimeLoaderInterface; @@ -30,12 +31,16 @@ class FunctionalTest extends TestCase */ public function testMarkdown(string $template, string $expected): void { - foreach ([LeagueMarkdown::class, ErusevMarkdown::class, /* MichelfMarkdown::class, */ DefaultMarkdown::class] as $class) { + $classes = [LeagueMarkdown::class, ErusevMarkdown::class, /* MichelfMarkdown::class, */ DefaultMarkdown::class]; + if (class_exists(\Tempest\Markdown\Markdown::class)) { + $classes[] = TempestMarkdown::class; + } + + foreach ($classes as $class) { $twig = new Environment(new ArrayLoader([ 'index' => $template, 'html' => <<Hello\n+

Great!

"], +EOF, "]*>Hello\n+

Great!\s*

"], [<<Hello\n+

Great!

"], - ["{{ include('html')|markdown_to_html }}", "

Hello

\n+

Great!

"], +EOF, "]*>Hello\n+

Great!\s*

"], + ["{{ include('html')|markdown_to_html }}", "]*>Hello\n+

Great!\s*

"], [<<Paragraph 1

\n+

Paragraph 2

"], +EOF, "

Paragraph 1

\n+

Paragraph 2\s*

"], ]; } diff --git a/extra/markdown-extra/composer.json b/extra/markdown-extra/composer.json index 703b25c20fb..1d92d7575ee 100644 --- a/extra/markdown-extra/composer.json +++ b/extra/markdown-extra/composer.json @@ -24,7 +24,8 @@ "erusev/parsedown": "dev-master as 1.x-dev", "league/commonmark": "^2.7", "league/html-to-markdown": "^4.8|^5.0", - "michelf/php-markdown": "^1.8|^2.0" + "michelf/php-markdown": "^1.8|^2.0", + "tempest/markdown": "^1.2" }, "autoload": { "files": [ "Resources/functions.php" ],