Skip to content

Commit cd2d0b9

Browse files
Merge pull request #8362 from Sesquipedalian/3.0/markdown_fixes
2 parents 5a3114e + 41c82e8 commit cd2d0b9

File tree

12 files changed

+133
-66
lines changed

12 files changed

+133
-66
lines changed

Sources/Actions/Moderation/WatchedUsers.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ public static function list_getWatchedUserPosts(int $start, int $items_per_page,
429429

430430
$row['body'] = Parser::transform(
431431
string: $row['body'],
432-
input_types: Parser::INPUT_BBC | Parser::INPUT_MARKDOWN | ((bool) $row['last_smileys'] ? Parser::INPUT_SMILEYS : 0),
432+
input_types: Parser::INPUT_BBC | Parser::INPUT_MARKDOWN | ((bool) $row['smileys_enabled'] ? Parser::INPUT_SMILEYS : 0),
433433
options: ['cache_id' => (int) $row['id_msg']],
434434
);
435435

Sources/Actions/Post.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,7 @@ protected function showPreview(): void
959959
// Do all bulletin board code tags, with or without smileys.
960960
Utils::$context['preview_message'] = Parser::transform(
961961
string: Utils::$context['preview_message'],
962-
input_types: Parser::INPUT_BBC | Parser::INPUT_MARKDOWN | (isset($_REQUEST['ns']) ? Parser::INPUT_SMILEYS : 0),
962+
input_types: Parser::INPUT_BBC | Parser::INPUT_MARKDOWN | (!isset($_REQUEST['ns']) ? Parser::INPUT_SMILEYS : 0),
963963
);
964964

965965
Lang::censorText(Utils::$context['preview_message']);

Sources/Mail.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public static function send(
118118
}
119119

120120
// Use real tabs.
121-
$message = strtr($message, [Utils::TAB_SUBSTITUTE => $send_html ? '<span style="white-space: pre-wrap;">' . "\t" . '</span>' : "\t"]);
121+
$message = strtr($message, [Utils::TAB_SUBSTITUTE => $send_html ? '<span style="white-space: pre;">' . "\t" . '</span>' : "\t"]);
122122

123123
list(, $from_name) = self::mimespecialchars(addcslashes($from !== null ? $from : Utils::$context['forum_name'], '<>()\'\\"'), true, $hotmail_fix, $line_break);
124124
list(, $subject) = self::mimespecialchars($subject, true, $hotmail_fix, $line_break);

Sources/Msg.php

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -707,15 +707,13 @@ function ($a) {
707707
}
708708

709709
// Replace code BBC with placeholders. We'll restore them at the end.
710-
$parts = preg_split('~(\[/code\]|\[code(?:=[^\]]+)?\])~i', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
710+
$parts = preg_split('/(\[code(?:=[^\]]+)?\](?:[^\[]|\[(?!\/code\])|(?R))*\[\/code])/i', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
711711

712712
for ($i = 0, $n = count($parts); $i < $n; $i++) {
713-
// It goes 0 = outside, 1 = begin tag, 2 = inside, 3 = close tag, repeat.
714-
if ($i % 4 == 2) {
715-
$code_tag = $parts[$i - 1] . $parts[$i] . $parts[$i + 1];
716-
$substitute = $parts[$i - 1] . $i . $parts[$i + 1];
717-
$code_tags[$substitute] = $code_tag;
718-
$parts[$i] = $i;
713+
if ($i % 2 == 1) {
714+
$substitute = md5($parts[$i]);
715+
$code_tags[$substitute] = $parts[$i];
716+
$parts[$i] = $substitute;
719717
}
720718
}
721719

@@ -919,16 +917,14 @@ public static function un_preparsecode(string $message): string
919917
// Any hooks want to work here?
920918
IntegrationHook::call('integrate_unpreparsecode', [&$message]);
921919

922-
$parts = preg_split('~(\[/code\]|\[code(?:=[^\]]+)?\])~i', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
923-
924920
// We're going to unparse only the stuff outside [code]...
921+
$parts = preg_split('/(\[code(?:=[^\]]+)?\](?:[^\[]|\[(?!\/code\])|(?R))*\[\/code])/i', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
922+
925923
for ($i = 0, $n = count($parts); $i < $n; $i++) {
926-
// If $i is a multiple of four (0, 4, 8, ...) then it's not a code section...
927-
if ($i % 4 == 2) {
928-
$code_tag = $parts[$i - 1] . $parts[$i] . $parts[$i + 1];
929-
$substitute = $parts[$i - 1] . $i . $parts[$i + 1];
930-
$code_tags[$substitute] = $code_tag;
931-
$parts[$i] = $i;
924+
if ($i % 2 == 1) {
925+
$substitute = md5($parts[$i]);
926+
$code_tags[$substitute] = $parts[$i];
927+
$parts[$i] = $substitute;
932928
}
933929
}
934930

Sources/Parser.php

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -371,22 +371,21 @@ public static function highlightPhpCode(string $code): string
371371

372372
$oldlevel = error_reporting(0);
373373

374-
$buffer = str_replace(["\n", "\r"], '', @highlight_string($code, true));
374+
$buffer = @highlight_string($code, true);
375375

376376
error_reporting($oldlevel);
377377

378-
$buffer = preg_replace_callback_array(
378+
return preg_replace_callback_array(
379379
[
380-
'~(?:' . Utils::TAB_SUBSTITUTE . ')+~u' => fn ($matches) => '<span style="white-space: pre-wrap;">' . strtr($matches[0], [Utils::TAB_SUBSTITUTE => "\t"]) . '</span>',
381-
'~<span style="color: #[0-9a-fA-F]{6}">(<span style="white-space: pre-wrap;">\h*</span>)</span>~' => fn ($matches) => $matches[1],
380+
'~(?:' . Utils::TAB_SUBSTITUTE . ')+~u' => fn ($matches) => '<span style="white-space: pre;">' . strtr($matches[0], [Utils::TAB_SUBSTITUTE => "\t"]) . '</span>',
381+
'~<span style="color: #[0-9a-fA-F]{6}">(<span style="white-space: pre;">\h*</span>)</span>~' => fn ($matches) => $matches[1],
382+
'~\R~' => fn ($matches) => '<br>',
383+
'/\'/' => fn ($matches) => '&#039;',
384+
// PHP 8.3 changed the returned HTML.
385+
'/^(<pre>)?<code[^>]*>|<\/code>(<\/pre>)?$/' => fn ($matches) => '',
382386
],
383387
$buffer,
384388
);
385-
386-
// PHP 8.3 changed the returned HTML.
387-
$buffer = preg_replace('/^(<pre>)?<code[^>]*>|<\/code>(<\/pre>)?$/', '', $buffer);
388-
389-
return strtr($buffer, ['\'' => '&#039;']);
390389
}
391390

392391
/**
@@ -526,6 +525,7 @@ protected function setDisabled(): void
526525
$this->disabled['iurl'] = true;
527526
$this->disabled['email'] = true;
528527
$this->disabled['flash'] = true;
528+
$this->disabled['youtube'] = true;
529529

530530
// @todo Change maybe?
531531
if (!isset($_GET['images'])) {
@@ -628,10 +628,13 @@ protected static function toHTML(string $string, int $input_types, array $option
628628

629629
// Parse the BBCode.
630630
if ($input_types & self::INPUT_BBC) {
631-
$string = BBcodeParser::load(!empty($options['for_print']))->parse($string, $options['cache_id'], $options['parse_tags']);
631+
$string = BBcodeParser::load(!empty($options['for_print']))->parse($string, !empty($input_types & self::INPUT_SMILEYS), $options['cache_id'], $options['parse_tags']);
632+
633+
// BBCodeParser calls the SmileyParser internally; don't repeat.
634+
$input_types &= ~self::INPUT_SMILEYS;
632635
}
633636

634-
// Parse the smileys.
637+
// Parse the smileys, if we haven't already.
635638
if ($input_types & self::INPUT_SMILEYS) {
636639
$string = SmileyParser::load()->parse($string);
637640
}

Sources/Parsers/BBCodeParser.php

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ class BBCodeParser extends Parser
4444
*/
4545
protected ?string $alltags_regex = null;
4646

47+
/**
48+
* @var bool
49+
*
50+
* Whether smileys should be parsed while we are parsing BBCode.
51+
*/
52+
protected bool $smileys = true;
53+
4754
/**
4855
* @var array
4956
*
@@ -824,6 +831,7 @@ public function __construct(bool $for_print = false)
824831
* Parse bulletin board code in a string.
825832
*
826833
* @param string|bool $message The string to parse.
834+
* @param bool $smileys Whether to parse smileys. Default: true.
827835
* @param string|int $cache_id The cache ID.
828836
* If $cache_id is left empty, an ID will be generated automatically.
829837
* Manually specifying a ID is helpful in cases when an integration hook
@@ -832,7 +840,7 @@ public function __construct(bool $for_print = false)
832840
* @param array $parse_tags If set, only parses these tags rather than all of them.
833841
* @return string The parsed string.
834842
*/
835-
public function parse(string $message, string|int $cache_id = '', array $parse_tags = []): string
843+
public function parse(string $message, bool $smileys = true, string|int $cache_id = '', array $parse_tags = []): string
836844
{
837845
// Don't waste cycles
838846
if (strval($message) === '') {
@@ -843,6 +851,7 @@ public function parse(string $message, string|int $cache_id = '', array $parse_t
843851
$this->resetRuntimeProperties();
844852

845853
$this->message = $message;
854+
$this->smileys = $smileys;
846855
$this->parse_tags = $parse_tags;
847856

848857
$this->setDisabled();
@@ -857,6 +866,10 @@ public function parse(string $message, string|int $cache_id = '', array $parse_t
857866
}
858867

859868
if (!self::$enable_bbc) {
869+
if ($this->smileys === true) {
870+
$this->message = SmileyParser::load()->parse($this->message);
871+
}
872+
860873
$this->message = $this->fixHtml($this->message);
861874

862875
return $this->message;
@@ -1863,7 +1876,7 @@ public static function codeValidate(array &$tag, array|string &$data, array $dis
18631876
// Fix the PHP code stuff...
18641877
$code = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
18651878

1866-
$code = str_replace("\t", "<span style=\"white-space: pre-wrap;\">\t</span>", $code);
1879+
$code = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $code);
18671880

18681881
if ($add_begin) {
18691882
$code = preg_replace(['/^(.+?)&lt;\?.{0,40}?php(?:&nbsp;|\s)/', '/\?&gt;((?:\s*<\/(font|span)>)*)$/m'], '$1', $code, 2);
@@ -2215,7 +2228,20 @@ protected function parseMessage(): void
22152228
$this->message .= "\n" . $tag['after'] . "\n";
22162229
}
22172230

2218-
$this->message = strtr($this->message, ["\n" => '']);
2231+
// Parse the smileys within the parts where it can be done safely.
2232+
if ($this->smileys === true) {
2233+
$message_parts = explode("\n", $this->message);
2234+
2235+
for ($i = 0, $n = count($message_parts); $i < $n; $i += 2) {
2236+
$message_parts[$i] = SmileyParser::load()->parse($message_parts[$i]);
2237+
}
2238+
2239+
$this->message = implode('', $message_parts);
2240+
}
2241+
// No smileys, just get rid of the markers.
2242+
else {
2243+
$this->message = strtr($this->message, ["\n" => '']);
2244+
}
22192245

22202246
// Transform the first table row into a table header and wrap the rest
22212247
// in table body tags.

Sources/Parsers/MarkdownParser.php

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ class MarkdownParser extends Parser
191191
// or
192192
'|' .
193193
// Non-space, non-control characters.
194-
'[^\s\p{Cc}]+' .
194+
'[^\s\p{Cc}]+?' .
195195
')' .
196196
')';
197197

@@ -397,7 +397,7 @@ class MarkdownParser extends Parser
397397
'interrupts_p' => true,
398398
'marker_pattern' => '/^((?P<bullet>[*+-])|(?P<number>\d+)(?P<num_punct>[.)]))\h+/u',
399399
'opener_test' => 'testOpensListItem',
400-
'continue_test' => 'testContinuesListItem',
400+
'continue_test' => false,
401401
'closer_test' => 'testClosesListItem',
402402
'add' => 'addListItem',
403403
'append' => null,
@@ -1226,12 +1226,16 @@ protected function testIsIndentedCode(array $line_info): bool
12261226
return true;
12271227
}
12281228

1229+
if ($this->in_code === 2) {
1230+
return false;
1231+
}
1232+
12291233
if ($this->testIsBlank($line_info) && $this->in_code === 1) {
12301234
return true;
12311235
}
12321236

12331237
if ($line_info['indent'] < 4) {
1234-
$this->in_code = $this->in_code === 1 ? 0 : $this->in_code;
1238+
$this->in_code = 0;
12351239

12361240
return false;
12371241
}
@@ -1248,7 +1252,7 @@ protected function testIsIndentedCode(array $line_info): bool
12481252
&& $open_block['properties']['indent'] >= $line_info['indent']
12491253
)
12501254
) {
1251-
$this->in_code = $this->in_code === 1 ? 0 : $this->in_code;
1255+
$this->in_code = 0;
12521256

12531257
return false;
12541258
}
@@ -1395,21 +1399,6 @@ protected function testOpensListItem(array $line_info): bool
13951399
);
13961400
}
13971401

1398-
/**
1399-
* Tests whether a line is part of a list item.
1400-
*
1401-
* @param array $line_info Info about the current line.
1402-
* @return bool Whether this line is part of a list item.
1403-
*/
1404-
protected function testContinuesListItem(array $line_info, int $last_container, int $o): bool
1405-
{
1406-
return (bool) (
1407-
$this->open[$o]['type'] === 'list_item'
1408-
&& $this->open[$o - 1]['type'] === 'list'
1409-
&& $line_info['indent'] >= $this->open[$o]['properties']['indent']
1410-
);
1411-
}
1412-
14131402
/**
14141403
* Tests whether a line closes a list item.
14151404
*
@@ -1827,6 +1816,21 @@ protected function addListItem(array $line_info, int $last_container, int $o): v
18271816

18281817
$indent = $line_info['indent'] + mb_strlen($marker) + strspn($line_info['content'], ' ', strlen($marker));
18291818

1819+
// Check for nested lists.
1820+
if (
1821+
$this->open[$last_container]['type'] === 'list'
1822+
&& $line_info['indent'] >= $this->open[$last_container]['properties']['indent']
1823+
) {
1824+
// Close the open paragraph (or whatever) inside the open list item.
1825+
while ($this->open[$o]['type'] !== 'list_item') {
1826+
$this->getMethod($this->block_types[$this->open[$o]['type']]['close'] ?? 'closeBlock')($o);
1827+
$o--;
1828+
}
1829+
1830+
// Consider the open list item to be our container.
1831+
$last_container = $o;
1832+
}
1833+
18301834
// If this list item doesn't match the existing list's type,
18311835
// exit the existing list so we can start a new one.
18321836
if (
@@ -2475,8 +2479,41 @@ protected function parseInlineSecondPass(array $content): array
24752479
}
24762480

24772481
// We need more info to make decisions about this run of delimiter chars.
2478-
$prev_char = html_entity_decode($chars[$start - 1] ?? ' ');
2479-
$next_char = html_entity_decode($chars[$i + 1] ?? ' ');
2482+
if (isset($chars[$start - 1])) {
2483+
$prev_char = $chars[$start - 1];
2484+
} elseif (!isset($content[$c - 1])) {
2485+
$prev_char = ' ';
2486+
} else {
2487+
$temp = $content[$c - 1];
2488+
2489+
while (isset($temp[array_key_last($temp)]['content'])) {
2490+
$temp = $temp[array_key_last($temp)]['content'];
2491+
}
2492+
2493+
if (is_string(end($temp['content']))) {
2494+
$prev_char = mb_substr(end($temp['content']), -1);
2495+
} else {
2496+
$prev_char = ' ';
2497+
}
2498+
}
2499+
2500+
if (isset($chars[$i + 1])) {
2501+
$next_char = $chars[$i + 1];
2502+
} elseif (!isset($content[$c + 1])) {
2503+
$next_char = ' ';
2504+
} else {
2505+
$temp = $content[$c + 1];
2506+
2507+
while (isset($temp[0]['content'])) {
2508+
$temp = $temp[0]['content'];
2509+
}
2510+
2511+
if (is_string(reset($temp['content']))) {
2512+
$next_char = mb_substr(reset($temp['content']), 0, 1);
2513+
} else {
2514+
$next_char = ' ';
2515+
}
2516+
}
24802517

24812518
$prev_is_space = preg_match('/\s/u', $prev_char);
24822519
$prev_is_punct = $prev_is_space ? false : preg_match('/\pP/u', $prev_char);
@@ -2660,10 +2697,8 @@ protected function parseLink(array $chars, int &$i, array &$content): void
26602697

26612698
$str = implode('', array_slice($chars, $delim['properties']['position'], $i - $delim['properties']['position'])) . ']' . mb_substr(implode('', $chars), $i + 1);
26622699

2663-
$prefix = $delim['type'] === '![' ? '!' : '';
2664-
26652700
// Inline link/image?
2666-
if (preg_match('~^' . $prefix . self::REGEX_LINK_INLINE . '~u', $str, $matches)) {
2701+
if (preg_match('~^' . self::REGEX_LINK_INLINE . '~u', $str, $matches)) {
26672702
$this->parseEmphasis($content, $c);
26682703

26692704
$text = array_slice($content, $c + 1);
@@ -2693,7 +2728,7 @@ protected function parseLink(array $chars, int &$i, array &$content): void
26932728
self::REGEX_LINK_REF_COLLAPSED,
26942729
self::REGEX_LINK_REF_SHORTCUT,
26952730
] as $regex) {
2696-
if (preg_match('~' . $prefix . $regex . '~u', $str, $matches)) {
2731+
if (preg_match('~' . $regex . '~u', $str, $matches)) {
26972732
break;
26982733
}
26992734
}
@@ -3256,6 +3291,8 @@ protected function renderBlockquote(array $element): void
32563291
*/
32573292
protected function renderList(array $element): void
32583293
{
3294+
static $nesting_level = 0;
3295+
32593296
switch ($this->output_type) {
32603297
case self::OUTPUT_BBC:
32613298
if ($element['content'] === []) {
@@ -3270,7 +3307,10 @@ protected function renderList(array $element): void
32703307
return;
32713308
}
32723309

3273-
$style_type = $element['properties']['ordered'] ? 'decimal' : 'disc';
3310+
$ordered_styles = ['decimal', 'lower-roman', 'lower-alpha'];
3311+
$unordered_styles = ['disc', 'circle', 'square'];
3312+
3313+
$style_type = $element['properties']['ordered'] ? $ordered_styles[$nesting_level % 3] : $unordered_styles[$nesting_level % 3];
32743314

32753315
foreach (BBCodeParser::getCodes() as $code) {
32763316
if (
@@ -3297,7 +3337,9 @@ protected function renderList(array $element): void
32973337
$this->rendered .= "\n";
32983338

32993339
foreach ($element['content'] as $content_element) {
3340+
$nesting_level++;
33003341
$this->render($content_element);
3342+
$nesting_level--;
33013343
}
33023344

33033345
switch ($this->output_type) {

0 commit comments

Comments
 (0)