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' => << Great! Great!\s* Great! Great! Great!\s* Great!\s*]*>Hello
\n+Hello
\n+]*>Hello
\n+]*>Hello
\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" ],