diff --git a/mod/wiki/parser/markups/creole.php b/mod/wiki/parser/markups/creole.php index d253fe45186..0f642d88d19 100644 --- a/mod/wiki/parser/markups/creole.php +++ b/mod/wiki/parser/markups/creole.php @@ -91,6 +91,12 @@ class creole_parser extends wiki_markup_parser { parent::before_parsing(); } + public function get_section($header, $text, $clean = false) { + // The requested header is likely to have been passed to htmlspecialchars in + // self::before_parsing(), therefore we should decode it when looking for it. + return parent::get_section(htmlspecialchars_decode($header), $text, $clean); + } + protected function header_block_rule($match) { $num = strlen($match[1]); diff --git a/mod/wiki/parser/markups/html.php b/mod/wiki/parser/markups/html.php index f2c7067d324..a2ae932e09e 100644 --- a/mod/wiki/parser/markups/html.php +++ b/mod/wiki/parser/markups/html.php @@ -17,10 +17,14 @@ class html_parser extends nwiki_parser { public function __construct() { parent::__construct(); - $this->tagrules = array('link' => $this->tagrules['link'], 'url' => $this->tagrules['url']); - - // Headers are considered tags here. - $this->tagrules['header'] = array('expression' => "/<\s*h([1-$this->maxheaderdepth])\s*>(.+?)<\/h[1-$this->maxheaderdepth]>/is" + // The order is important, headers should be parsed before links. + $this->tagrules = array( + // Headers are considered tags here. + 'header' => array( + 'expression' => "/<\s*h([1-$this->maxheaderdepth])\s*>(.+?)<\/h[1-$this->maxheaderdepth]>/is" + ), + 'link' => $this->tagrules['link'], + 'url' => $this->tagrules['url'] ); } @@ -52,7 +56,8 @@ class html_parser extends nwiki_parser { $h1 = array("<\s*h1\s*>", "<\/h1>"); - preg_match("/(.*?)({$h1[0]}\s*\Q$header\E\s*{$h1[1]}.*?)((?:\n{$h1[0]}.*)|$)/is", $text, $match); + $regex = "/(.*?)({$h1[0]}\s*".preg_quote($header, '/')."\s*{$h1[1]}.*?)((?:\n{$h1[0]}.*)|$)/is"; + preg_match($regex, $text, $match); if (!empty($match)) { return array($match[1], $match[2], $match[3]); diff --git a/mod/wiki/parser/markups/wikimarkup.php b/mod/wiki/parser/markups/wikimarkup.php index 1a675d5b814..0346a45f4b6 100644 --- a/mod/wiki/parser/markups/wikimarkup.php +++ b/mod/wiki/parser/markups/wikimarkup.php @@ -401,7 +401,8 @@ abstract class wiki_markup_parser extends generic_parser { $text .= "\n\n"; } - preg_match("/(.*?)(=\ *\Q$header\E\ *=*\n.*?)((?:\n=[^=]+.*)|$)/is", $text, $match); + $regex = "/(.*?)(=\ *".preg_quote($header, '/')."\ *=*\n.*?)((?:\n=[^=]+.*)|$)/is"; + preg_match($regex, $text, $match); if (!empty($match)) { return array($match[1], $match[2], $match[3]); diff --git a/mod/wiki/tests/wikiparser_test.php b/mod/wiki/tests/wikiparser_test.php index a7418e08a91..b09cef64788 100644 --- a/mod/wiki/tests/wikiparser_test.php +++ b/mod/wiki/tests/wikiparser_test.php @@ -64,7 +64,7 @@ class mod_wiki_wikiparser_test extends basic_testcase { $result['parsed_text'] = preg_replace('~[\r\n]~', '', $result['parsed_text']); $output = preg_replace('~[\r\n]~', '', $output); - $this->assertEquals($result['parsed_text'], $output); + $this->assertEquals($output, $result['parsed_text']); return true; } @@ -74,4 +74,143 @@ class mod_wiki_wikiparser_test extends basic_testcase { $i++; } } + + /** + * Check that headings with special characters work as expected with HTML. + * + * - The heading itself is well displayed, + * - The TOC heading is well display, + * - The edit link points to the right page, + * - The links properly works with get_section. + */ + public function test_special_headings() { + + // First testing HTML markup. + + // Test section name using HTML entities. + $input = '

Code & Test

'; + $output = '

Code & Test[edit]

' . "\n"; + $toc = '

Table of contents

1. Code & Test[edit]

'; + $section = wiki_parser_proxy::get_section($input, 'html', 'Code & Test'); + $actual = wiki_parser_proxy::parse($input, 'html'); + $this->assertEquals($output, $actual['parsed_text']); + $this->assertEquals($toc, $actual['toc']); + $this->assertNotEquals(false, $section); + + // Test section name using non-ASCII characters. + $input = '

Another áéíóúç€ test

'; + $output = '

Another áéíóúç€ test[edit]

' . "\n"; + $toc = '

Table of contents

1. Another áéíóúç€ test[edit]

'; + $section = wiki_parser_proxy::get_section($input, 'html', 'Another áéíóúç€ test'); + $actual = wiki_parser_proxy::parse($input, 'html'); + $this->assertEquals($output, $actual['parsed_text']); + $this->assertEquals($toc, $actual['toc']); + $this->assertNotEquals(false, $section); + + // Test section name with a URL. + $input = '

Another http://moodle.org test

'; + $output = '

Another http://moodle.org test[edit]

' . "\n"; + $toc = '

Table of contents

1. Another http://moodle.org test[edit]

'; + $section = wiki_parser_proxy::get_section($input, 'html', 'Another http://moodle.org test'); + $actual = wiki_parser_proxy::parse($input, 'html', array( + 'link_callback' => '/mod/wiki/locallib.php:wiki_parser_link' + )); + $this->assertEquals($output, $actual['parsed_text']); + $this->assertEquals($toc, $actual['toc']); + $this->assertNotEquals(false, $section); + + // Now going to test Creole markup. + // Note that Creole uses links to the escaped version of the section. + + // Test section name using HTML entities. + $input = '= Code & Test ='; + $output = '

Code & Test[edit]

' . "\n"; + $toc = '

Table of contents

1. Code & Test[edit]

'; + $section = wiki_parser_proxy::get_section($input, 'creole', 'Code & Test'); + $actual = wiki_parser_proxy::parse($input, 'creole'); + $this->assertEquals($output, $actual['parsed_text']); + $this->assertEquals($toc, $actual['toc']); + $this->assertNotEquals(false, $section); + + // Test section name using non-ASCII characters. + $input = '= Another áéíóúç€ test ='; + $output = '

Another áéíóúç€ test[edit]

' . "\n"; + $toc = '

Table of contents

1. Another áéíóúç€ test[edit]

'; + $section = wiki_parser_proxy::get_section($input, 'creole', 'Another áéíóúç€ test'); + $actual = wiki_parser_proxy::parse($input, 'creole'); + $this->assertEquals($output, $actual['parsed_text']); + $this->assertEquals($toc, $actual['toc']); + $this->assertNotEquals(false, $section); + + // Test section name with a URL, creole does not support linking links in a heading. + $input = '= Another http://moodle.org test ='; + $output = '

Another http://moodle.org test[edit]

' . "\n"; + $toc = '

Table of contents

1. Another http://moodle.org test[edit]

'; + $section = wiki_parser_proxy::get_section($input, 'creole', 'Another http://moodle.org test'); + $actual = wiki_parser_proxy::parse($input, 'creole'); + $this->assertEquals($output, $actual['parsed_text']); + $this->assertEquals($toc, $actual['toc']); + $this->assertNotEquals(false, $section); + + // Now going to test NWiki markup. + // Note that Creole uses links to the escaped version of the section. + + // Test section name using HTML entities. + $input = '= Code & Test ='; + $output = '

Code & Test[edit]

' . "\n"; + $toc = '

Table of contents

1. Code & Test[edit]

'; + $section = wiki_parser_proxy::get_section($input, 'nwiki', 'Code & Test'); + $actual = wiki_parser_proxy::parse($input, 'nwiki'); + $this->assertEquals($output, $actual['parsed_text']); + $this->assertEquals($toc, $actual['toc']); + $this->assertNotEquals(false, $section); + + // Test section name using non-ASCII characters. + $input = '= Another áéíóúç€ test ='; + $output = '

Another áéíóúç€ test[edit]

' . "\n"; + $toc = '

Table of contents

1. Another áéíóúç€ test[edit]

'; + $section = wiki_parser_proxy::get_section($input, 'nwiki', 'Another áéíóúç€ test'); + $actual = wiki_parser_proxy::parse($input, 'nwiki'); + $this->assertEquals($output, $actual['parsed_text']); + $this->assertEquals($toc, $actual['toc']); + $this->assertNotEquals(false, $section); + + // Test section name with a URL, nwiki does not support linking links in a heading. + $input = '= Another http://moodle.org test ='; + $output = '

Another http://moodle.org test[edit]

' . "\n"; + $toc = '

Table of contents

1. Another http://moodle.org test[edit]

'; + $section = wiki_parser_proxy::get_section($input, 'nwiki', 'Another http://moodle.org test'); + $actual = wiki_parser_proxy::parse($input, 'nwiki'); + $this->assertEquals($output, $actual['parsed_text']); + $this->assertEquals($toc, $actual['toc']); + $this->assertNotEquals(false, $section); + } + }