Skip to content

Commit 818399e

Browse files
author
roadiz-ci
committed
Merge branch hotfix/2.7.37
1 parent 71c27f6 commit 818399e

7 files changed

Lines changed: 151 additions & 26 deletions

File tree

.github/workflows/run-test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Unit tests, static analysis and code style
1+
name: Static analysis and code style
22

33
on:
44
push:
@@ -15,11 +15,11 @@ on:
1515
- ready_for_review
1616

1717
jobs:
18-
run-tests:
18+
static-analysis-tests:
1919
runs-on: ubuntu-latest
2020
strategy:
2121
matrix:
22-
php-version: ['8.2', '8.3']
22+
php-version: ['8.3', '8.4', '8.5']
2323
steps:
2424
- uses: shivammathur/setup-php@v2
2525
with:

LICENSE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright © 2024 Ambroise Maupate
3+
Copyright © 2025 Ambroise Maupate
44

55
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:
66

composer.json

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
"description": "Markdown services and Twig extension for Roadiz",
44
"type": "library",
55
"require": {
6-
"php": ">=8.2",
6+
"php": ">=8.3",
77
"doctrine/collections": ">=1.6",
88
"league/commonmark": "^2.2.0",
9-
"symfony/stopwatch": "6.4.*",
10-
"twig/twig": "^3.16"
9+
"symfony/stopwatch": "7.4.*",
10+
"twig/twig": "^3.21"
1111
},
1212
"require-dev": {
13-
"phpstan/phpstan": "^1.5.3",
14-
"phpstan/phpdoc-parser": "<2"
13+
"phpstan/phpstan": "^2.1.36",
14+
"phpstan/phpdoc-parser": "<2",
15+
"phpunit/phpunit": "^9.6"
1516
},
1617
"license": "MIT",
1718
"authors": [
@@ -27,10 +28,15 @@
2728
"RZ\\Roadiz\\Markdown\\": "src/"
2829
}
2930
},
31+
"autoload-dev": {
32+
"psr-4": {
33+
"RZ\\Roadiz\\Markdown\\Tests\\": "tests/"
34+
}
35+
},
3036
"extra": {
3137
"branch-alias": {
32-
"dev-master": "2.5.x-dev",
33-
"dev-develop": "2.6.x-dev"
38+
"dev-master": "2.7.x-dev",
39+
"dev-develop": "2.8.x-dev"
3440
}
3541
}
3642
}

src/CommonMark.php

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,41 @@ public function __construct(
1313
private MarkdownConverter $textConverter,
1414
private MarkdownConverter $textExtraConverter,
1515
private MarkdownConverter $lineConverter,
16+
private MarkdownConverter $textHtmlConverter,
17+
private MarkdownConverter $textExtraHtmlConverter,
1618
private ?Stopwatch $stopwatch = null,
1719
) {
1820
}
1921

20-
public function text(?string $markdown = null): string
22+
#[\Override]
23+
public function text(?string $markdown = null, bool $allowHtml = false): string
2124
{
2225
if (null === $markdown) {
2326
return '';
2427
}
2528
$this->stopwatch?->start(CommonMark::class.'::text');
26-
$html = $this->textConverter->convert($markdown)->getContent();
29+
$converter = $allowHtml ? $this->textHtmlConverter : $this->textConverter;
30+
$html = $converter->convert($markdown)->getContent();
2731
$this->stopwatch?->stop(CommonMark::class.'::text');
2832

2933
return $html;
3034
}
3135

32-
public function textExtra(?string $markdown = null): string
36+
#[\Override]
37+
public function textExtra(?string $markdown = null, bool $allowHtml = false): string
3338
{
3439
if (null === $markdown) {
3540
return '';
3641
}
3742
$this->stopwatch?->start(CommonMark::class.'::textExtra');
38-
$html = $this->textExtraConverter->convert($markdown)->getContent();
43+
$converter = $allowHtml ? $this->textExtraHtmlConverter : $this->textExtraConverter;
44+
$html = $converter->convert($markdown)->getContent();
3945
$this->stopwatch?->stop(CommonMark::class.'::textExtra');
4046

4147
return $html;
4248
}
4349

50+
#[\Override]
4451
public function line(?string $markdown = null): string
4552
{
4653
if (null === $markdown) {
@@ -52,4 +59,25 @@ public function line(?string $markdown = null): string
5259

5360
return $html;
5461
}
62+
63+
#[\Override]
64+
public function strip(?string $markdown = null): ?string
65+
{
66+
if (!is_string($markdown)) {
67+
return null;
68+
}
69+
/*
70+
* Strip Markdown syntax
71+
*/
72+
$markdown = $this->textExtra($markdown);
73+
// replace BR with space to avoid merged words.
74+
$markdown = str_replace(['<br>', '<br />', '<br/>'], ' ', $markdown);
75+
$markdown = strip_tags($markdown);
76+
/*
77+
* Remove control characters (including DEL).
78+
*/
79+
$markdown = preg_replace('/[\x00-\x1F\x7F]/', '', $markdown);
80+
81+
return $markdown;
82+
}
5583
}

src/MarkdownInterface.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,27 @@ interface MarkdownInterface
88
{
99
/**
1010
* Convert Markdown to HTML using standard Markdown syntax.
11+
*
12+
* @param bool $allowHtml Pass true to allow raw HTML (including script/style tags) through
13+
* unchanged. Defaults to false (raw HTML is stripped).
1114
*/
12-
public function text(?string $markdown = null): string;
15+
public function text(?string $markdown = null, bool $allowHtml = false): string;
1316

1417
/**
1518
* Convert Markdown to HTML using standard Markdown Extra syntax.
19+
*
20+
* @param bool $allowHtml Pass true to allow raw HTML (including script/style tags) through
21+
* unchanged. Defaults to false (raw HTML is stripped).
1622
*/
17-
public function textExtra(?string $markdown = null): string;
23+
public function textExtra(?string $markdown = null, bool $allowHtml = false): string;
1824

1925
/**
2026
* Convert Markdown to HTML using only inline HTML elements.
2127
*/
2228
public function line(?string $markdown = null): string;
29+
30+
/**
31+
* Convert Markdown then strip tags, control characters and replace br with whitespace.
32+
*/
33+
public function strip(?string $markdown = null): ?string;
2334
}

src/Twig/MarkdownExtension.php

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,32 @@ public function __construct(private readonly MarkdownInterface $markdown)
1414
{
1515
}
1616

17+
#[\Override]
1718
public function getFilters(): array
1819
{
1920
return [
20-
new TwigFilter('markdown', [$this, 'markdown'], ['is_safe' => ['html']]),
21-
new TwigFilter('inlineMarkdown', [$this, 'inlineMarkdown'], ['is_safe' => ['html']]),
22-
new TwigFilter('inline_markdown', [$this, 'inlineMarkdown'], ['is_safe' => ['html']]),
23-
new TwigFilter('markdownExtra', [$this, 'markdownExtra'], ['is_safe' => ['html']]),
24-
new TwigFilter('markdown_extra', [$this, 'markdownExtra'], ['is_safe' => ['html']]),
21+
new TwigFilter('markdown', $this->markdown(...), ['is_safe' => ['html']]),
22+
new TwigFilter('inlineMarkdown', $this->inlineMarkdown(...), ['is_safe' => ['html']]),
23+
new TwigFilter('inline_markdown', $this->inlineMarkdown(...), ['is_safe' => ['html']]),
24+
new TwigFilter('markdownExtra', $this->markdownExtra(...), ['is_safe' => ['html']]),
25+
new TwigFilter('markdown_extra', $this->markdownExtra(...), ['is_safe' => ['html']]),
26+
new TwigFilter('strip_markdown', $this->strip(...)),
27+
new TwigFilter('stripMarkdown', $this->strip(...)),
2528
];
2629
}
2730

28-
public function markdown(?string $input): string
31+
/**
32+
* @param bool $allowHtml Set to true to allow raw HTML (including script/style tags) through
33+
* unchanged. Defaults to false — raw HTML is stripped.
34+
* Usage: {{ content|markdown(true) }}
35+
*/
36+
public function markdown(?string $input, bool $allowHtml = false): string
2937
{
3038
if (null === $input) {
3139
return '';
3240
}
3341

34-
return $this->markdown->text($input);
42+
return $this->markdown->text($input, $allowHtml);
3543
}
3644

3745
public function inlineMarkdown(?string $input): string
@@ -43,12 +51,26 @@ public function inlineMarkdown(?string $input): string
4351
return $this->markdown->line($input);
4452
}
4553

46-
public function markdownExtra(?string $input): string
54+
/**
55+
* @param bool $allowHtml Set to true to allow raw HTML (including script/style tags) through
56+
* unchanged. Defaults to false — raw HTML is stripped.
57+
* Usage: {{ content|markdownExtra(true) }}
58+
*/
59+
public function markdownExtra(?string $input, bool $allowHtml = false): string
4760
{
4861
if (null === $input) {
4962
return '';
5063
}
5164

52-
return $this->markdown->textExtra($input);
65+
return $this->markdown->textExtra($input, $allowHtml);
66+
}
67+
68+
public function strip(?string $input): string
69+
{
70+
if (null === $input) {
71+
return '';
72+
}
73+
74+
return $this->markdown->strip($input) ?? '';
5375
}
5476
}

tests/CommonMarkTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RZ\Roadiz\Markdown\Tests;
6+
7+
use League\CommonMark\Environment\Environment;
8+
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
9+
use League\CommonMark\MarkdownConverter;
10+
use PHPUnit\Framework\TestCase;
11+
use RZ\Roadiz\Markdown\CommonMark;
12+
13+
final class CommonMarkTest extends TestCase
14+
{
15+
private function createCommonMark(): CommonMark
16+
{
17+
$environment = new Environment(['html_input' => 'strip']);
18+
$environment->addExtension(new CommonMarkCoreExtension());
19+
$converter = new MarkdownConverter($environment);
20+
21+
// strip() only relies on the textExtra converter, but the constructor
22+
// requires all five converters.
23+
return new CommonMark($converter, $converter, $converter, $converter, $converter);
24+
}
25+
26+
/**
27+
* @dataProvider stripProvider
28+
*/
29+
public function testStrip(?string $input, ?string $expected): void
30+
{
31+
$this->assertSame($expected, $this->createCommonMark()->strip($input));
32+
}
33+
34+
/**
35+
* @return array<array{0: ?string, 1: ?string}>
36+
*/
37+
public static function stripProvider(): array
38+
{
39+
return [
40+
'null returns null' => [null, null],
41+
'plain text is untouched' => ['Hello world', 'Hello world'],
42+
'markdown emphasis is stripped' => ['**Hello** _world_', 'Hello world'],
43+
'headings are stripped' => ['# Title', 'Title'],
44+
'links keep their label only' => ['[Roadiz](https://roadiz.io)', 'Roadiz'],
45+
'hard line break becomes a space' => ["Line1 \nLine2", 'Line1 Line2'],
46+
'control characters are removed' => ["a\x07b", 'ab'],
47+
'DEL character is removed' => ["a\x7Fb", 'ab'],
48+
];
49+
}
50+
51+
public function testStripRemovesHtmlTags(): void
52+
{
53+
$result = $this->createCommonMark()->strip('Some **bold** text with a [link](https://roadiz.io).');
54+
55+
$this->assertStringNotContainsString('<', (string) $result);
56+
$this->assertStringNotContainsString('>', (string) $result);
57+
}
58+
}

0 commit comments

Comments
 (0)