[TikiWiki-commits] [Git][tikiwiki/tiki][master] [FIX] Markdown: square brackets content in code block syntax gets parsed as link
"luci \(@luciash\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <6a74b9db9e464_3828c6cc89292c@gitlab-sidekiq-low-urgency-cpu-bound-v2-c84d8dfcb-lfk5h.mail> |
luci pushed to branch master at Tiki Wiki CMS Groupware / Tiki
Commits:
8007188b by Boss Ibrahim Mussa Gregoire at 2026-08-06T16:26:46+00:00
[FIX] Markdown: square brackets content in code block syntax gets parsed as link
---
* [ENH] Markdown Convert: add codeblock unit test.
* [FIX] Markdown conversion: code content get parse as link.
* [ENH] markdown code: replace markdown code to plugin code
See merge request tikiwiki/tiki!7403
- - - - -
5 changed files:
- lib/core/WikiParser/Parsable.php
- lib/core/WikiParser/ParsableMarkdown.php
- lib/test/Core/Search/Formatter/BaseTest.php
- lib/test/TikiLib/MarkdownParserTest.php
- lib/test/language/TranslationSanitizationTest.php
Changes:
=====================================
lib/core/WikiParser/Parsable.php
=====================================
@@ -435,9 +435,9 @@ if ( \$('#$id') ) {
global $prefs;
if ($this->option['is_markdown'] && $prefs['markdown_enabled'] === 'y') {
- $parsable = new WikiParser_ParsableMarkdown($data);
+ $parsable = new WikiParser_ParsableMarkdown();
} else {
- $parsable = new WikiParser_ParsableWiki($data);
+ $parsable = new WikiParser_ParsableWiki();
}
$parsable->setOptions($this->option);
return $parsable->wikiParse($data, $noparsed);
=====================================
lib/core/WikiParser/ParsableMarkdown.php
=====================================
@@ -84,11 +84,40 @@ class WikiParser_ParsableMarkdown extends ParserLib
$data = $this->autolinks($data);
}
+ /**
+ * extract Markdown code blocks to be processed later
+ * This regex matches code blocks that:
+ * - start with a fence of 3 or more backticks or tildes, also allows up to three spaces before opening,
+ * - language identifier (info string) is optional on the opening line and can contain anything,
+ * - end with a matching fence of the same character as at start,
+ * - allows up to three spaces before both opening and closing fences.
+ *
+ * Note: the first capturing group contains the opening fence itself.
+ * The fenced block content is matched by the middle non-capturing part
+ * and is preserved through the full match.
+ *
+ * Reference: https://spec.commonmark.org/0.31.2/#fenced-code-blocks
+ * */
+ $pattern = '/^(?: {0,3})([`~])\1{2,}([^`\r\n]*)?\r?\n(?:[\s\S]*?)(?:\r?\n(?: {0,3})\1{1,})/m';
+ preg_match_all($pattern, $data, $matches);
+ $list_code = [];
+ foreach ($matches[0] as $match) {
+ $hash = '§' . md5(uniqid()) . '§';
+ $data = str_replace($match, $hash, $data);
+ $list_code[] = [
+ 'value' => $match,
+ 'hash' => $hash
+ ];
+ }
// wiki page links and external links are handled in Tiki-syntax to allow sister sites and other semantic linking
$data = $this->parse_data_wikilinks($data, false, $this->option['wysiwyg']);
$data = $this->parse_data_externallinks($data, false);
$data = preg_replace(';~hc~(.*?)~/hc~;s', '<!-- $1 -->', $data);
+ // restore code blocks
+ foreach ($list_code as $code) {
+ $data = str_replace($code['hash'], $code['value'], $data);
+ }
// converter/parser expects UTF-8, try to cleanup invalid characters
$data = mb_convert_encoding($data, 'UTF-8', 'UTF-8');
=====================================
lib/test/Core/Search/Formatter/BaseTest.php
=====================================
@@ -16,6 +16,17 @@ use TikiLib;
class BaseTest extends TestCase
{
+ protected function setUp(): void
+ {
+ // Simulate query string parameters
+ $_GET['foo'] = 'bar';
+
+ // Simulate POST parameters
+ $_POST['baz'] = 'qux';
+
+ // Now $_REQUEST will contain both
+ $_REQUEST = array_merge($_GET, $_POST, $_COOKIE);
+ }
public function testBasicFormatter()
{
$plugin = new Search_Formatter_Plugin_WikiTemplate("* {display name=object_id} ({display name=object_type})\n");
@@ -406,4 +417,11 @@ OUT;
$this->assertEquals("x", $output);
}
+ protected function tearDown(): void
+ {
+ $_GET = [];
+ $_POST = [];
+ $_COOKIE = [];
+ $_REQUEST = [];
+ }
}
=====================================
lib/test/TikiLib/MarkdownParserTest.php
=====================================
@@ -57,8 +57,8 @@ class TikiLib_MarkdownParserTest extends TikiTestCase
$prefs['feature_wiki_argvariable'] = 'y';
$heading_links_pref = $prefs['wiki_heading_links'];
$prefs['wiki_heading_links'] = 'n';
-
- $this->assertEquals($this->html(), TikiLib::lib('parser')->parse_data('{syntax type=markdown}' . $this->markdown()));
+ $parse_data = TikiLib::lib('parser')->parse_data('{syntax type=markdown}' . $this->markdown());
+ $this->assertEquals($this->html(), $parse_data);
$prefs['wiki_heading_links'] = $heading_links_pref;
}
@@ -78,6 +78,129 @@ class TikiLib_MarkdownParserTest extends TikiTestCase
$prefs['wiki_heading_links'] = $heading_links_pref;
}
+ /**
+ * /**
+ * Test Markdown fenced code block parsing with standard triple fences.
+ *
+ * This method validates that the Markdown parser correctly handles:
+ * - Triple backtick fences with a language specifier (e.g. ```php)
+ * - Triple tilde fences with a language specifier (e.g. ~~~html)
+ *
+ * The test ensures that:
+ * - Code blocks are wrapped in <pre><code> tags
+ * - The appropriate language class is applied when specified
+ * - Special characters inside the code block are properly escaped
+ *
+ *
+ * @return void
+ * @throws Exception
+ */
+ public function testEscapeMarkdownCode(): void
+ {
+ global $prefs, $user;
+
+ $user = 'admin';
+ $prefs['markdown_enabled'] = 'y';
+
+ $data = '```php
+\$rules=[
+"name"=>John,
+"email"=>[email protected],
+"link"=>http://example.com,
+]
+```';
+ $expected_html = '<div class="codelisting_container"><div class="icon_copy_code far fa-clipboard" tabindex="0" data-clipboard-target="#md-codebox1"><span class="copy_code_tooltiptext">Copy to clipboard</span></div><pre class="codelisting" id="md-codebox1" dir="ltr" style="white-space:pre-wrap; overflow-wrap: break-word; word-wrap: break-word;" data-syntax="php"><div class="code">\$rules=[
+"name"=>John,
+"email"=>[email protected],
+"link"=>http://example.com,
+]
+</div></pre></div>
+';
+ $parse_data = TikiLib::lib('parser')->parse_data('{syntax type=markdown}' . $data);
+ $this->assertEquals($expected_html, $parse_data);
+
+ // title test example
+ $title_example = '~~~html
+<div class="container">
+<h1>My First Bootstrap Page</h1>
+<p>This is some text.</p>
+</div>
+~~~';
+ $tilde_data = "{syntax type=markdown}" . $title_example;
+ $tilde_output = '<div class="codelisting_container"><div class="icon_copy_code far fa-clipboard" tabindex="0" data-clipboard-target="#md-codebox2"><span class="copy_code_tooltiptext">Copy to clipboard</span></div><pre class="codelisting" id="md-codebox2" dir="ltr" style="white-space:pre-wrap; overflow-wrap: break-word; word-wrap: break-word;" data-syntax="html"><div class="code"><div class="container">
+<h1>My First Bootstrap Page</h1>
+<p>This is some text.</p>
+</div>
+</div></pre></div>
+';
+ $parse_tilde_data = TikiLib::lib('parser')->parse_data($tilde_data);
+
+ $this->assertEquals($tilde_output, $parse_tilde_data);
+ }
+
+ /**
+ * Test markdown fenced code block parsing with variable fence lengths and syntax variations.
+ *
+ * This method ensures that the Markdown parser correctly handles:
+ * - Backtick fences of length greater than 3 (e.g. 4 backticks)
+ * - Tilde fences of length greater than 3 (e.g. 5 tildes)
+ * - Indented fences (up to 3 spaces before the opening fence)
+ * - Fences with and without language specifiers
+ *
+ * Each case validates that the parser produces the expected HTML output
+ * with proper <pre><code> wrapping, language class assignment when present,
+ * and correct escaping of special characters.
+ * @return void
+ * @throws Exception
+ */
+ public function testMarkdownCodeFencesVariations(): void
+ {
+ global $prefs;
+ $prefs['markdown_enabled'] = 'y';
+
+ // Case 1: 4 backticks with language
+ $data4 = '````js
+console.log("Hello");
+````';
+ $expected4 = '<div class="codelisting_container"><div class="icon_copy_code far fa-clipboard" tabindex="0" data-clipboard-target="#md-codebox1"><span class="copy_code_tooltiptext">Copy to clipboard</span></div><pre class="codelisting" id="md-codebox1" dir="ltr" style="white-space:pre-wrap; overflow-wrap: break-word; word-wrap: break-word;" data-syntax="js"><div class="code">console.log("Hello");
+</div></pre></div>
+';
+ $this->assertEquals($expected4, TikiLib::lib('parser')->parse_data('{syntax type=markdown}' . $data4));
+
+ // Case 2: 5 tildes, no language
+ $data5 = '~~~~~
+Plain text block
+~~~~~';
+ $expected5 = '<div class="codelisting_container"><div class="icon_copy_code far fa-clipboard" tabindex="0" data-clipboard-target="#md-codebox2"><span class="copy_code_tooltiptext">Copy to clipboard</span></div><pre class="codelisting" id="md-codebox2" dir="ltr" style="white-space:pre-wrap; overflow-wrap: break-word; word-wrap: break-word;"><div class="code">Plain text block
+</div></pre></div>
+';
+ $this->assertEquals($expected5, TikiLib::lib('parser')->parse_data('{syntax type=markdown}' . $data5));
+
+ // Case 3: Indented fence (3 spaces before backticks)
+ $dataIndented = " ```python\nprint(\"Indented\")\n ```";
+ $expectedIndented = '<div class="codelisting_container"><div class="icon_copy_code far fa-clipboard" tabindex="0" data-clipboard-target="#md-codebox3"><span class="copy_code_tooltiptext">Copy to clipboard</span></div><pre class="codelisting" id="md-codebox3" dir="ltr" style="white-space:pre-wrap; overflow-wrap: break-word; word-wrap: break-word;" data-syntax="python"><div class="code">print("Indented")
+</div></pre></div>
+';
+ $this->assertEquals($expectedIndented, TikiLib::lib('parser')->parse_data('{syntax type=markdown}' . $dataIndented));
+
+ // Case 4: Fence with empty language specifier
+ $dataEmptyLang = '```
+No language here
+```';
+ $expectedEmptyLang = '<div class="codelisting_container"><div class="icon_copy_code far fa-clipboard" tabindex="0" data-clipboard-target="#md-codebox4"><span class="copy_code_tooltiptext">Copy to clipboard</span></div><pre class="codelisting" id="md-codebox4" dir="ltr" style="white-space:pre-wrap; overflow-wrap: break-word; word-wrap: break-word;"><div class="code">No language here
+</div></pre></div>
+';
+ $this->assertEquals($expectedEmptyLang, TikiLib::lib('parser')->parse_data('{syntax type=markdown}' . $dataEmptyLang));
+
+ // Case 5: Negative test - only 2 backticks should NOT be parsed as a code block
+ $dataInvalid = "``js
+console.log('Not valid');
+``";
+ // Expectation: parser should leave it untouched, since it's not a valid fence
+ $expectedInvalid = "<p><code>js console.log('Not valid'); </code></p>\n";
+ $this->assertEquals($expectedInvalid, TikiLib::lib('parser')->parse_data('{syntax type=markdown}' . $dataInvalid));
+ }
+
public function testReplaceLinks(): void
{
$template = "Link to ((%s)) in wiki syntax, as a ((%s|description)) and in [markdown](%s) syntax.";
@@ -179,6 +302,14 @@ Footnote 2 link[^second].
Inline footnote^[Text of inline footnote] definition.
+```php
+\$rules=[
+\"name\"=>\$validate->string()->required()->min(3)->max(30)->check(),
+\"email\"=>\$validate->string()->required()->min(3)->max(60)->email()->check(),
+\"link\"=>\$validate->string()->required()->min(3)->max(60)->url()->check(),
+\"age\"=>\$validate->number()->required()->positive()->check()
+];
+```
Duplicated footnote reference[^second].
[^first]: Footnote **can have markup**
@@ -308,6 +439,13 @@ line 3 of code
<p>Footnote 1 link<sup id="fnref:first"><a class="footnote-ref" href="#fn:first" role="doc-noteref">1</a></sup>.</p>
<p>Footnote 2 link<sup id="fnref:second"><a class="footnote-ref" href="#fn:second" role="doc-noteref">2</a></sup>.</p>
<p>Inline footnote<sup id="fnref:text-of-inline-footn"><a class="footnote-ref" href="#fn:text-of-inline-footn" role="doc-noteref">3</a></sup> definition.</p>
+<div class="codelisting_container"><div class="icon_copy_code far fa-clipboard" tabindex="0" data-clipboard-target="#md-codebox2"><span class="copy_code_tooltiptext">Copy to clipboard</span></div><pre class="codelisting" id="md-codebox2" dir="ltr" style="white-space:pre-wrap; overflow-wrap: break-word; word-wrap: break-word;" data-syntax="php"><div class="code">$rules=[
+"name"=>$validate->string()->required()->min(3)->max(30)->check(),
+"email"=>$validate->string()->required()->min(3)->max(60)->email()->check(),
+"link"=>$validate->string()->required()->min(3)->max(60)->url()->check(),
+"age"=>$validate->number()->required()->positive()->check()
+];
+</div></pre></div>
<p>Duplicated footnote reference<sup id="fnref:second__2"><a class="footnote-ref" href="#fn:second" role="doc-noteref">2</a></sup>.</p>
<div class="footnotes" role="doc-endnotes"><hr /><ol><li class="footnote" id="fn:first" role="doc-endnote"><p>Footnote <strong>can have markup</strong></p>
<p>and multiple paragraphs. <a class="footnote-backref" rev="footnote" href="#fnref:first" role="doc-backlink">↩</a></p></li>
=====================================
lib/test/language/TranslationSanitizationTest.php
=====================================
@@ -31,7 +31,7 @@ class TranslationSanitizationTest extends \TikiTestCase
$this->lang = 'ts_' . $testCounter++;
$this->langDir = $this->tikiroot . 'lang/' . $this->lang;
$this->customFile = $this->langDir . '/custom.php';
-
+ $_SERVER['REQUEST_URI'] = '/test/language/TranslationSanitizationTest.php';
chdir($this->tikiroot);
// Enable database translations
@@ -61,6 +61,8 @@ $lang = array(
$reflection = new \ReflectionClass(LanguageTranslator::class);
$instancesProperty = $reflection->getProperty('instances');
$instancesProperty->setValue(null, []);
+ // Clean up to avoid leaking state between tests
+ unset($_SERVER['REQUEST_URI']);
}
protected function tearDown(): void
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/8007188b613a14e10450e28bdaf56149309cce6b
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/8007188b613a14e10450e28bdaf56149309cce6b
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs