diff --git a/filter/mathjaxloader/filter.php b/filter/mathjaxloader/filter.php
index 50e568e45d2..0906e18fb49 100644
--- a/filter/mathjaxloader/filter.php
+++ b/filter/mathjaxloader/filter.php
@@ -128,37 +128,116 @@ class filter_mathjaxloader extends moodle_text_filter {
$text = str_replace('\\]', '\\)', $text);
}
- $hasinline = strpos($text, '\\(') !== false && strpos($text, '\\)') !== false;
- $hasdisplay = (strpos($text, '$$') !== false) ||
- (strpos($text, '\\[') !== false && strpos($text, '\\]') !== false);
-
$hasextra = false;
-
foreach ($extradelimiters as $extra) {
if ($extra && strpos($text, $extra) !== false) {
$hasextra = true;
break;
}
}
- if ($hasinline || $hasdisplay || $hasextra) {
+
+ $hasdisplayorinline = false;
+ if ($hasextra) {
+ // If custom dilimeters are used, wrap whole text to prevent autolinking.
+ $text = '' . $text . '';
+ } else {
+ // Wrap display and inline math environments in nolink spans.
+ // Do not wrap nested environments, i.e., if inline math is nested
+ // inside display math, only the outer display math is wrapped in
+ // a span. The span HTML inside a LaTex math environment would break
+ // MathJax. See MDL-61981.
+ list($text, $hasdisplayorinline) = $this->wrap_math_in_nolink($text);
+ }
+
+ if ($hasdisplayorinline || $hasextra) {
$PAGE->requires->yui_module('moodle-filter_mathjaxloader-loader', 'M.filter_mathjaxloader.typeset');
- if ($hasextra) {
- // If custom dilimeters are used, wrap whole text to prevent autolinking.
- $text = '' . $text . '';
- } else {
- if ($hasinline) {
- // If the default inline TeX delimiters \( \) are present, wrap each pair in nolink.
- $text = preg_replace('/\\\\\\([\S\s]*?\\\\\\)/u',
- '\0', $text);
- }
- if ($hasdisplay) {
- // If default display TeX is used, wrap $$ $$ or \[ \] individually.
- $text = preg_replace('/\$\$[\S\s]*?\$\$|\\\\\\[[\S\s]*?\\\\\\]/u',
- '\0', $text);
- }
- }
return '' . $text . '';
}
return $text;
}
+
+ /**
+ * Find math environments in the $text and wrap them in no link spans
+ * (). If math environments are nested, only
+ * the outer environment is wrapped in the span.
+ *
+ * The recognized math environments are \[ \] and $$ $$ for display
+ * mathematics and \( \) for inline mathematics.
+ *
+ * @param string $text The text to filter.
+ * @return array An array containing the potentially modified text and
+ * a boolean that is true if any changes were made to the text.
+ */
+ protected function wrap_math_in_nolink($text) {
+ $i = 1;
+ $len = strlen($text);
+ $displaystart = -1;
+ $displaybracket = false;
+ $displaydollar = false;
+ $inlinestart = -1;
+ $changesdone = false;
+ // Loop over the $text once.
+ while ($i < $len) {
+ if ($displaystart === -1) {
+ // No display math has started yet.
+ if ($text[$i - 1] === '\\' && $text[$i] === '[') {
+ // Display mode \[ begins.
+ $displaystart = $i - 1;
+ $displaybracket = true;
+ } else if ($text[$i - 1] === '$' && $text[$i] === '$') {
+ // Display mode $$ begins.
+ $displaystart = $i - 1;
+ $displaydollar = true;
+ } else if ($text[$i - 1] === '\\' && $text[$i] === '(') {
+ // Inline math \( begins, not nested inside display math.
+ $inlinestart = $i - 1;
+ } else if ($text[$i - 1] === '\\' && $text[$i] === ')' && $inlinestart > -1) {
+ // Inline math ends, not nested inside display math.
+ // Wrap the span around it.
+ $text = $this->insert_span($text, $inlinestart, $i);
+
+ $inlinestart = -1; // Reset.
+ $i += 28; // The $text length changed due to the .
+ $len += 28;
+ $changesdone = true;
+ }
+ } else {
+ // Display math open.
+ if (($text[$i - 1] === '\\' && $text[$i] === ']' && $displaybracket) ||
+ ($text[$i - 1] === '$' && $text[$i] === '$' && $displaydollar)) {
+ // Display math ends, wrap the span around it.
+ $text = $this->insert_span($text, $displaystart, $i);
+
+ $displaystart = -1; // Reset.
+ $displaybracket = false;
+ $displaydollar = false;
+ $i += 28; // The $text length changed due to the .
+ $len += 28;
+ $changesdone = true;
+ }
+ }
+
+ ++$i;
+ }
+ return array($text, $changesdone);
+ }
+
+ /**
+ * Wrap a portion of the $text inside a no link span
+ * (). The whole text is then returned.
+ *
+ * @param string $text The text to modify.
+ * @param int $start The start index of the substring in $text that should
+ * be wrapped in the span.
+ * @param int $end The end index of the substring in $text that should be
+ * wrapped in the span.
+ * @return string The whole $text with the span inserted around
+ * the defined substring.
+ */
+ protected function insert_span($text, $start, $end) {
+ return substr_replace($text,
+ ''. substr($text, $start, $end - $start + 1) .'',
+ $start,
+ $end - $start + 1);
+ }
}