diff --git a/.travis.yml b/.travis.yml
index 3f0ab1bde..b7f8605be 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,6 +15,7 @@ install:
script:
- ./vendor/bin/simple-phpunit
- (cd extra/html-extra && ./vendor/bin/simple-phpunit)
+ - (cd extra/markdown-extra && travis_retry composer install)
jobs:
fast_finish: true
diff --git a/CHANGELOG b/CHANGELOG
index 1ebc3a100..c999e2563 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
* 2.12.0 (2019-XX-XX)
- * added the HtmlExtension: "html_classes" function and "data_uri" filter
+ * added the Markdown extension in the "extra" repositories
+ * added the HtmlExtension extension in the "extra" repositories
* fixed cache when opcache is installed but disabled
* fixed using macros in arrow functions
diff --git a/doc/filters/html_to_markdown.rst b/doc/filters/html_to_markdown.rst
new file mode 100644
index 000000000..32482f76c
--- /dev/null
+++ b/doc/filters/html_to_markdown.rst
@@ -0,0 +1,65 @@
+``html_to_markdown``
+====================
+
+.. versionadded:: 2.12
+ The ``html_to_markdown`` filter was added in Twig 2.12.
+
+The ``html_to_markdown`` filter converts a block of HTML to Markdown:
+
+.. code-block:: twig
+
+ {% apply html_to_markdown %}
+
+
Hello!
+
+ {% endapply %}
+
+You can also add some options by passing them as an argument to the filter:
+
+.. code-block:: twig
+
+ {% apply html_to_markdown({hard_break: false}) %}
+
+ Hello!
+
+ {% endapply %}
+
+.. note::
+
+ The options are the ones provided by the ``league/html-to-markdown`` package.
+
+You can also use the filter on an included file:
+
+.. code-block:: twig
+
+ {{ include('some_template.html.twig')|html_to_markdown }}
+
+.. note::
+
+ The ``markdown_to_html`` filter is part of the ``MarkdownExtension`` which
+ is not installed by default. Install it first:
+
+ .. code-block:: bash
+
+ $ composer req twig/markdown-extension
+
+ Then, add it on the Twig environment::
+
+ use Twig\Extra\Markdown\MarkdownMarkdownExtension;
+
+ $twig = new \Twig\Environment(...);
+ $twig->addExtension(new MarkdownExtension());
+
+ If you are not using Symfony, you must also register the extension runtime::
+
+ use Twig\Extra\Markdown\DefaultMarkdown;
+ use Twig\Extra\Markdown\MarkdownRuntime;
+ use Twig\RuntimeLoader\RuntimeLoaderInterface;
+
+ $twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
+ public function load($class) {
+ if (MarkdownRuntime::class === $class) {
+ return new MarkdownRuntime(new DefaultMarkdown());
+ }
+ }
+ });
diff --git a/doc/filters/index.rst b/doc/filters/index.rst
index 9eeff175e..ae8e8ee84 100644
--- a/doc/filters/index.rst
+++ b/doc/filters/index.rst
@@ -17,6 +17,7 @@ Filters
filter
first
format
+ html_to_markdown
join
json_encode
keys
@@ -24,6 +25,7 @@ Filters
length
lower
map
+ markdown_to_html
merge
nl2br
number_format
diff --git a/doc/filters/markdown_to_html.rst b/doc/filters/markdown_to_html.rst
new file mode 100644
index 000000000..dc4f46c73
--- /dev/null
+++ b/doc/filters/markdown_to_html.rst
@@ -0,0 +1,64 @@
+``markdown_to_html``
+====================
+
+.. versionadded:: 2.12
+ The ``markdown_to_html`` filter was added in Twig 2.12.
+
+The ``markdown_to_html`` filter converts a block of Markdown to HTML:
+
+.. code-block:: twig
+
+ {% apply markdown_to_html %}
+ Title
+ ======
+
+ Hello!
+ {% endapply %}
+
+Note that you can indent the Markdown content as leading whitespaces will be
+removed consistently before conversion:
+
+.. code-block:: twig
+
+ {% apply markdown_to_html %}
+ Title
+ ======
+
+ Hello!
+ {% endapply %}
+
+You can also use the filter on an included file:
+
+.. code-block:: twig
+
+ {{ include('some_template.markdown.twig')|markdown_to_html }}
+
+.. note::
+
+ The ``markdown_to_html`` filter is part of the ``MarkdownExtension`` which
+ is not installed by default. Install it first:
+
+ .. code-block:: bash
+
+ $ composer req twig/markdown-extension
+
+ Then, add it on the Twig environment::
+
+ use Twig\Extra\Markdown\MarkdownMarkdownExtension;
+
+ $twig = new \Twig\Environment(...);
+ $twig->addExtension(new MarkdownExtension());
+
+ If you are not using Symfony, you must also register the extension runtime::
+
+ use Twig\Extra\Markdown\DefaultMarkdown;
+ use Twig\Extra\Markdown\MarkdownRuntime;
+ use Twig\RuntimeLoader\RuntimeLoaderInterface;
+
+ $twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
+ public function load($class) {
+ if (MarkdownRuntime::class === $class) {
+ return new MarkdownRuntime(new DefaultMarkdown());
+ }
+ }
+ });
diff --git a/extra/markdown-extra/.gitignore b/extra/markdown-extra/.gitignore
new file mode 100644
index 000000000..76367ee5b
--- /dev/null
+++ b/extra/markdown-extra/.gitignore
@@ -0,0 +1,4 @@
+vendor/
+composer.lock
+phpunit.xml
+.phpunit.result.cache
diff --git a/extra/markdown-extra/LICENSE b/extra/markdown-extra/LICENSE
new file mode 100644
index 000000000..1a1869751
--- /dev/null
+++ b/extra/markdown-extra/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/extra/markdown-extra/README.md b/extra/markdown-extra/README.md
new file mode 100644
index 000000000..6cdd06bdd
--- /dev/null
+++ b/extra/markdown-extra/README.md
@@ -0,0 +1,11 @@
+Twig Markdown Extension
+=======================
+
+This package is a Twig extension that provides the following:
+
+ * [`markdown_to_html`][1] filter: generates HTML from a Markdown block;
+
+ * [`html_to_markdown`][2] filter: generates Markdown from an HTML block.
+
+[1]: https://twig.symfony.com/markdown_to_html
+[2]: https://twig.symfony.com/html_to_markdown
diff --git a/extra/markdown-extra/composer.json b/extra/markdown-extra/composer.json
new file mode 100644
index 000000000..28e7e9426
--- /dev/null
+++ b/extra/markdown-extra/composer.json
@@ -0,0 +1,42 @@
+{
+ "name": "twig/markdown-extra",
+ "type": "library",
+ "description": "A Twig extension for Markdown",
+ "keywords": ["twig", "html", "markdown"],
+ "homepage": "https://twig.symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com",
+ "homepage": "http://fabien.potencier.org",
+ "role": "Lead Developer"
+ }
+ ],
+ "require": {
+ "php": "^7.1.3",
+ "twig/twig": "^2.4|^3.0"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "^4.4@dev",
+ "erusev/parsedown": "^1.7",
+ "league/commonmark": "^1.0",
+ "league/html-to-markdown": "^4.8",
+ "michelf/php-markdown": "^1.8"
+ },
+ "autoload": {
+ "psr-4" : {
+ "Twig\\Extra\\Markdown\\" : "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4" : {
+ "Twig\\Extra\\Markdown\\Tests\\" : "tests/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.12-dev"
+ }
+ }
+}
diff --git a/extra/markdown-extra/phpunit.xml.dist b/extra/markdown-extra/phpunit.xml.dist
new file mode 100644
index 000000000..4b84ca9c2
--- /dev/null
+++ b/extra/markdown-extra/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./
+
+ ./tests
+ ./vendor
+
+
+
+
diff --git a/extra/markdown-extra/src/DefaultMarkdown.php b/extra/markdown-extra/src/DefaultMarkdown.php
new file mode 100644
index 000000000..24aad2e34
--- /dev/null
+++ b/extra/markdown-extra/src/DefaultMarkdown.php
@@ -0,0 +1,39 @@
+converter = new ErusevMarkdown();
+ } elseif (class_exists(CommonMarkConverter::class)) {
+ $this->converter = new LeagueMarkdown();
+ } elseif (class_exists(MarkdownExtra::class)) {
+ $this->converter = new MichelfMarkdown();
+ } else {
+ throw new \LogicException('You cannot use the "markdown_to_html" filter as no Markdown library is available; try running "composer require erusev/parsedown".');
+ }
+ }
+
+ public function convert(string $body): string
+ {
+ return $this->converter->convert($body);
+ }
+}
diff --git a/extra/markdown-extra/src/ErusevMarkdown.php b/extra/markdown-extra/src/ErusevMarkdown.php
new file mode 100644
index 000000000..f4f7e1c48
--- /dev/null
+++ b/extra/markdown-extra/src/ErusevMarkdown.php
@@ -0,0 +1,29 @@
+converter = $converter ?: new Parsedown();
+ }
+
+ public function convert(string $body): string
+ {
+ return $this->converter->text($body);
+ }
+}
diff --git a/extra/markdown-extra/src/LeagueMarkdown.php b/extra/markdown-extra/src/LeagueMarkdown.php
new file mode 100644
index 000000000..ed7e7dd32
--- /dev/null
+++ b/extra/markdown-extra/src/LeagueMarkdown.php
@@ -0,0 +1,29 @@
+converter = $converter ?: new CommonMarkConverter();
+ }
+
+ public function convert(string $body): string
+ {
+ return $this->converter->convertToHtml($body);
+ }
+}
diff --git a/extra/markdown-extra/src/MarkdownExtension.php b/extra/markdown-extra/src/MarkdownExtension.php
new file mode 100644
index 000000000..ff6a6d3d6
--- /dev/null
+++ b/extra/markdown-extra/src/MarkdownExtension.php
@@ -0,0 +1,48 @@
+ ['all']]),
+ new TwigFilter('html_to_markdown', 'Twig\\Extra\\Markdown\\twig_html_to_markdown', ['is_safe' => ['all']]),
+ ];
+ }
+}
+
+function twig_html_to_markdown(string $body, array $options = []): string
+{
+ static $converters;
+
+ if (!class_exists(HtmlConverter::class)) {
+ throw new \LogicException('You cannot use the "html_to_markdown" filter as league/html-to-markdown is not installed; try running "composer require league/html-to-markdown".');
+ }
+
+ $options = $options + [
+ 'hard_break' => true,
+ 'strip_tags' => true,
+ 'remove_nodes' => 'head style',
+ ];
+
+ if (!isset($converters[$key = serialize($options)])) {
+ $converters[$key] = new HtmlConverter($options);
+ }
+
+ return $converters[$key]->convert($body);
+}
diff --git a/extra/markdown-extra/src/MarkdownInterface.php b/extra/markdown-extra/src/MarkdownInterface.php
new file mode 100644
index 000000000..10406ea90
--- /dev/null
+++ b/extra/markdown-extra/src/MarkdownInterface.php
@@ -0,0 +1,17 @@
+converter = $converter;
+ }
+
+ public function convert(string $body): string
+ {
+ // remove indentation
+ if ($white = substr($body, 0, strspn($body, " \t\r\n\0\x0B"))) {
+ $body = preg_replace("{^$white}m", '', $body);
+ }
+
+ return $this->converter->convert($body);
+ }
+}
diff --git a/extra/markdown-extra/src/MichelfMarkdown.php b/extra/markdown-extra/src/MichelfMarkdown.php
new file mode 100644
index 000000000..c87b4aa07
--- /dev/null
+++ b/extra/markdown-extra/src/MichelfMarkdown.php
@@ -0,0 +1,34 @@
+hard_wrap = true;
+ }
+
+ $this->converter = $converter;
+ }
+
+ public function convert(string $body): string
+ {
+ return $this->converter->defaultTransform($body);
+ }
+}
diff --git a/extra/markdown-extra/tests/Fixtures/html_to_markdown.test b/extra/markdown-extra/tests/Fixtures/html_to_markdown.test
new file mode 100644
index 000000000..52ef0b6cb
--- /dev/null
+++ b/extra/markdown-extra/tests/Fixtures/html_to_markdown.test
@@ -0,0 +1,37 @@
+--TEST--
+"html_to_markdown" filter
+--TEMPLATE--
+{% apply html_to_markdown %}
+
+ Hello
+ Great!
+
+{% endapply %}
+
+
+{% apply html_to_markdown({hard_break: false}) %}
+ Great
Break
+{% endapply %}
+
+
+{{ include('html')|html_to_markdown }}
+--TEMPLATE(html)--
+
+ Hello
+ Great!
+
+--DATA--
+return []
+--EXPECT--
+Hello
+=====
+
+**Great!**
+
+Great
+Break
+
+Hello
+=====
+
+**Great!**
diff --git a/extra/markdown-extra/tests/FunctionalTest.php b/extra/markdown-extra/tests/FunctionalTest.php
new file mode 100644
index 000000000..26d057b2d
--- /dev/null
+++ b/extra/markdown-extra/tests/FunctionalTest.php
@@ -0,0 +1,86 @@
+ $template,
+ 'html' => <<addExtension(new MarkdownExtension());
+ $twig->addRuntimeLoader(new class($class) implements RuntimeLoaderInterface {
+ private $class;
+
+ public function __construct(string $class)
+ {
+ $this->class = $class;
+ }
+
+ public function load($c)
+ {
+ if (MarkdownRuntime::class === $c) {
+ return new $c(new $this->class());
+ }
+ }
+ });
+ $this->assertRegExp('{'.$expected.'}m', trim($twig->render('index')));
+ }
+ }
+
+ public function getMarkdownTests()
+ {
+ return [
+ [<<Hello\n+Great!
"],
+ [<<Hello\n+Great!
"],
+ ["{{ include('html')|markdown_to_html }}", "Hello
\n+Great!
"],
+ ];
+ }
+}
diff --git a/extra/markdown-extra/tests/IntegrationTest.php b/extra/markdown-extra/tests/IntegrationTest.php
new file mode 100644
index 000000000..7474ec769
--- /dev/null
+++ b/extra/markdown-extra/tests/IntegrationTest.php
@@ -0,0 +1,30 @@
+getRootNode();
+
+ $rootNode
+ ->children()
+ ->arrayNode('html')
+ ->{class_exists(HtmlExtension::class) ? 'canBeDisabled' : 'canBeEnabled'}()
+ ->end()
+ ->end()
+ ;
+
+ $rootNode
+ ->children()
+ ->arrayNode('markdown')
+ ->{class_exists(MarkdownExtension::class) ? 'canBeDisabled' : 'canBeEnabled'}()
+ ->end()
+ ->end()
+ ;
+
+ return $treeBuilder;
+ }
+}
diff --git a/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php b/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php
new file mode 100644
index 000000000..ba4bc54e1
--- /dev/null
+++ b/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php
@@ -0,0 +1,38 @@
+
+ */
+class TwigExtraExtension extends Extension
+{
+ public function load(array $configs, ContainerBuilder $container)
+ {
+ $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+ $configuration = $this->getConfiguration($configs, $container);
+ $config = $this->processConfiguration($configuration, $configs);
+
+ if ($this->isConfigEnabled($container, $config['html'])) {
+ $loader->load('html.xml');
+ }
+
+ if ($this->isConfigEnabled($container, $config['markdown'])) {
+ $loader->load('markdown.xml');
+ }
+ }
+}
diff --git a/extra/twig-extra-bundle/LICENSE b/extra/twig-extra-bundle/LICENSE
new file mode 100644
index 000000000..1a1869751
--- /dev/null
+++ b/extra/twig-extra-bundle/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/extra/twig-extra-bundle/README.md b/extra/twig-extra-bundle/README.md
new file mode 100644
index 000000000..f7602e4fe
--- /dev/null
+++ b/extra/twig-extra-bundle/README.md
@@ -0,0 +1,5 @@
+Twig Extra Bundle
+=================
+
+This package is a Symfony bundle that allows to use all "extra" extensions
+without any configuration.
diff --git a/extra/twig-extra-bundle/Resources/config/html.xml b/extra/twig-extra-bundle/Resources/config/html.xml
new file mode 100644
index 000000000..203a05af0
--- /dev/null
+++ b/extra/twig-extra-bundle/Resources/config/html.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/twig-extra-bundle/Resources/config/markdown.xml b/extra/twig-extra-bundle/Resources/config/markdown.xml
new file mode 100644
index 000000000..258245217
--- /dev/null
+++ b/extra/twig-extra-bundle/Resources/config/markdown.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/twig-extra-bundle/TwigExtraBundle.php b/extra/twig-extra-bundle/TwigExtraBundle.php
new file mode 100644
index 000000000..c1c2e24cc
--- /dev/null
+++ b/extra/twig-extra-bundle/TwigExtraBundle.php
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./
+
+ ./tests
+ ./vendor
+
+
+
+