Differences Between: [Versions 311 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * This filter provides automatic support for MathJax 19 * 20 * @package filter_mathjaxloader 21 * @copyright 2013 Damyon Wiese (damyon@moodle.com) 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 /** 28 * Mathjax filtering 29 */ 30 class filter_mathjaxloader extends moodle_text_filter { 31 32 /* 33 * Perform a mapping of the moodle language code to the equivalent for MathJax. 34 * 35 * @param string $moodlelangcode - The moodle language code - e.g. en_pirate 36 * @return string The MathJax language code. 37 */ 38 public function map_language_code($moodlelangcode) { 39 40 // List of language codes found in the MathJax/localization/ directory. 41 $mathjaxlangcodes = [ 42 'ar', 'ast', 'bcc', 'bg', 'br', 'ca', 'cdo', 'ce', 'cs', 'cy', 'da', 'de', 'diq', 'en', 'eo', 'es', 'fa', 43 'fi', 'fr', 'gl', 'he', 'ia', 'it', 'ja', 'kn', 'ko', 'lb', 'lki', 'lt', 'mk', 'nl', 'oc', 'pl', 'pt', 44 'pt-br', 'qqq', 'ru', 'scn', 'sco', 'sk', 'sl', 'sv', 'th', 'tr', 'uk', 'vi', 'zh-hans', 'zh-hant' 45 ]; 46 47 // List of explicit mappings and known exceptions (moodle => mathjax). 48 $explicit = [ 49 'cz' => 'cs', 50 'pt_br' => 'pt-br', 51 'zh_tw' => 'zh-hant', 52 'zh_cn' => 'zh-hans', 53 ]; 54 55 // If defined, explicit mapping takes the highest precedence. 56 if (isset($explicit[$moodlelangcode])) { 57 return $explicit[$moodlelangcode]; 58 } 59 60 // If there is exact match, it will be probably right. 61 if (in_array($moodlelangcode, $mathjaxlangcodes)) { 62 return $moodlelangcode; 63 } 64 65 // Finally try to find the best matching mathjax pack. 66 $parts = explode('_', $moodlelangcode, 2); 67 if (in_array($parts[0], $mathjaxlangcodes)) { 68 return $parts[0]; 69 } 70 71 // No more guessing, use English. 72 return 'en'; 73 } 74 75 /* 76 * Add the javascript to enable mathjax processing on this page. 77 * 78 * @param moodle_page $page The current page. 79 * @param context $context The current context. 80 */ 81 public function setup($page, $context) { 82 83 if ($page->requires->should_create_one_time_item_now('filter_mathjaxloader-scripts')) { 84 $url = get_config('filter_mathjaxloader', 'httpsurl'); 85 $lang = $this->map_language_code(current_language()); 86 $url = new moodle_url($url, array('delayStartupUntil' => 'configured')); 87 88 $moduleconfig = array( 89 'name' => 'mathjax', 90 'fullpath' => $url 91 ); 92 93 $page->requires->js_module($moduleconfig); 94 95 $config = get_config('filter_mathjaxloader', 'mathjaxconfig'); 96 $wwwroot = new moodle_url('/'); 97 98 $config = str_replace('{wwwroot}', $wwwroot->out(true), $config); 99 100 $params = array('mathjaxconfig' => $config, 'lang' => $lang); 101 102 $page->requires->yui_module('moodle-filter_mathjaxloader-loader', 'M.filter_mathjaxloader.configure', array($params)); 103 } 104 } 105 106 /* 107 * This function wraps the filtered text in a span, that mathjaxloader is configured to process. 108 * 109 * @param string $text The text to filter. 110 * @param array $options The filter options. 111 */ 112 public function filter($text, array $options = array()) { 113 global $PAGE; 114 115 $legacy = get_config('filter_mathjaxloader', 'texfiltercompatibility'); 116 $extradelimiters = explode(',', get_config('filter_mathjaxloader', 'additionaldelimiters')); 117 if ($legacy) { 118 // This replaces any of the tex filter maths delimiters with the default for inline maths in MathJAX "\( blah \)". 119 // E.g. "<tex.*> blah </tex>". 120 $text = preg_replace('|<(/?) *tex( [^>]*)?>|u', '[\1tex]', $text); 121 // E.g. "[tex.*] blah [/tex]". 122 $text = str_replace('[tex]', '\\(', $text); 123 $text = str_replace('[/tex]', '\\)', $text); 124 // E.g. "$$ blah $$". 125 $text = preg_replace('|\$\$([\S\s]*?)\$\$|u', '\\(\1\\)', $text); 126 // E.g. "\[ blah \]". 127 $text = str_replace('\\[', '\\(', $text); 128 $text = str_replace('\\]', '\\)', $text); 129 } 130 131 $hasextra = false; 132 foreach ($extradelimiters as $extra) { 133 if ($extra && strpos($text, $extra) !== false) { 134 $hasextra = true; 135 break; 136 } 137 } 138 139 $hasdisplayorinline = false; 140 if ($hasextra) { 141 // If custom dilimeters are used, wrap whole text to prevent autolinking. 142 $text = '<span class="nolink">' . $text . '</span>'; 143 } else if (preg_match('/\\\\[[(]/', $text) || preg_match('/\$\$/', $text)) { 144 // Only parse the text if there are mathjax symbols in it. The recognized 145 // math environments are \[ \] and $$ $$ for display mathematics and \( \) 146 // for inline mathematics. 147 // Note: 2 separate regexes seems to perform better here than using a single 148 // regex with groupings. 149 150 // Wrap display and inline math environments in nolink spans. 151 // Do not wrap nested environments, i.e., if inline math is nested 152 // inside display math, only the outer display math is wrapped in 153 // a span. The span HTML inside a LaTex math environment would break 154 // MathJax. See MDL-61981. 155 list($text, $hasdisplayorinline) = $this->wrap_math_in_nolink($text); 156 } 157 158 if ($hasdisplayorinline || $hasextra) { 159 $PAGE->requires->yui_module('moodle-filter_mathjaxloader-loader', 'M.filter_mathjaxloader.typeset'); 160 return '<span class="filter_mathjaxloader_equation">' . $text . '</span>'; 161 } 162 return $text; 163 } 164 165 /** 166 * Find math environments in the $text and wrap them in no link spans 167 * (<span class="nolink"></span>). If math environments are nested, only 168 * the outer environment is wrapped in the span. 169 * 170 * The recognized math environments are \[ \] and $$ $$ for display 171 * mathematics and \( \) for inline mathematics. 172 * 173 * @param string $text The text to filter. 174 * @return array An array containing the potentially modified text and 175 * a boolean that is true if any changes were made to the text. 176 */ 177 protected function wrap_math_in_nolink($text) { 178 $i = 1; 179 $len = strlen($text); 180 $displaystart = -1; 181 $displaybracket = false; 182 $displaydollar = false; 183 $inlinestart = -1; 184 $changesdone = false; 185 // Loop over the $text once. 186 while ($i < $len) { 187 if ($displaystart === -1) { 188 // No display math has started yet. 189 if ($text[$i - 1] === '\\' && $text[$i] === '[') { 190 // Display mode \[ begins. 191 $displaystart = $i - 1; 192 $displaybracket = true; 193 } else if ($text[$i - 1] === '$' && $text[$i] === '$') { 194 // Display mode $$ begins. 195 $displaystart = $i - 1; 196 $displaydollar = true; 197 } else if ($text[$i - 1] === '\\' && $text[$i] === '(') { 198 // Inline math \( begins, not nested inside display math. 199 $inlinestart = $i - 1; 200 } else if ($text[$i - 1] === '\\' && $text[$i] === ')' && $inlinestart > -1) { 201 // Inline math ends, not nested inside display math. 202 // Wrap the span around it. 203 $text = $this->insert_span($text, $inlinestart, $i); 204 205 $inlinestart = -1; // Reset. 206 $i += 28; // The $text length changed due to the <span>. 207 $len += 28; 208 $changesdone = true; 209 } 210 } else { 211 // Display math open. 212 if (($text[$i - 1] === '\\' && $text[$i] === ']' && $displaybracket) || 213 ($text[$i - 1] === '$' && $text[$i] === '$' && $displaydollar)) { 214 // Display math ends, wrap the span around it. 215 $text = $this->insert_span($text, $displaystart, $i); 216 217 $displaystart = -1; // Reset. 218 $displaybracket = false; 219 $displaydollar = false; 220 $i += 28; // The $text length changed due to the <span>. 221 $len += 28; 222 $changesdone = true; 223 } 224 } 225 226 ++$i; 227 } 228 return array($text, $changesdone); 229 } 230 231 /** 232 * Wrap a portion of the $text inside a no link span 233 * (<span class="nolink"></span>). The whole text is then returned. 234 * 235 * @param string $text The text to modify. 236 * @param int $start The start index of the substring in $text that should 237 * be wrapped in the span. 238 * @param int $end The end index of the substring in $text that should be 239 * wrapped in the span. 240 * @return string The whole $text with the span inserted around 241 * the defined substring. 242 */ 243 protected function insert_span($text, $start, $end) { 244 return substr_replace($text, 245 '<span class="nolink">'. substr($text, $start, $end - $start + 1) .'</span>', 246 $start, 247 $end - $start + 1); 248 } 249 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body