MDL-69911 core: Clean content passed through TeX filter
[moodle.git] / filter / tex / filter.php
CommitLineData
35716b86 1<?php
35716b86
PS
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 * Moodle - Filter for converting TeX expressions to cached gif images
f7f0909c
PS
19 *
20 * This Moodle text filter converts TeX expressions delimited
21 * by either $$...$$ or by <tex...>...</tex> tags to gif images using
22 * mimetex.cgi obtained from http: *www.forkosh.com/mimetex.html authored by
23 * John Forkosh john@forkosh.com. Several binaries of this areincluded with
24 * this distribution.
25 * Note that there may be patent restrictions on the production of gif images
26 * in Canada and some parts of Western Europe and Japan until July 2004.
27 *
35716b86
PS
28 * @package filter
29 * @subpackage tex
30 * @copyright 2004 Zbigniew Fiedorowicz fiedorow@math.ohio-state.edu
31 * Originally based on code provided by Bruno Vernier bruno@vsbeducation.ca
32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33 */
34
f7f0909c
PS
35defined('MOODLE_INTERNAL') || die;
36
a57a336f
DT
37require_once($CFG->libdir . '/classes/useragent.php');
38
f7f0909c
PS
39/**
40 * Create TeX image link.
41 *
42 * @param string $imagefile name of file
43 * @param string $tex TeX notation (html entities already decoded)
44 * @param int $height O means automatic
45 * @param int $width O means automatic
46 * @param string $align
47 * @param string $alt
48 * @return string HTML markup
49 */
50function filter_text_image($imagefile, $tex, $height, $width, $align, $alt) {
e4cd4de2 51 global $CFG, $OUTPUT;
c49dede8 52
f7f0909c
PS
53 if (!$imagefile) {
54 throw new coding_exception('image file argument empty in filter_text_image()');
c1d9eeb9 55 }
56
57 // Work out any necessary inline style.
58 $rules = array();
e47d5ade 59 if ($align !== 'middle') {
c1d9eeb9 60 $rules[] = 'vertical-align:' . $align . ';';
e47d5ade 61 }
62 if ($height) {
c1d9eeb9 63 $rules[] = 'height:' . $height . 'px;';
e47d5ade 64 }
65 if ($width) {
c1d9eeb9 66 $rules[] = 'width:' . $width . 'px;';
e47d5ade 67 }
c1d9eeb9 68 if (!empty($rules)) {
69 $style = ' style="' . implode('', $rules) . '" ';
70 } else {
71 $style = '';
72 }
73
74 // Prepare the title attribute.
f7f0909c
PS
75 // Note that we retain the title tag as TeX format rather than using
76 // the alt text, even if supplied. The alt text is intended for blind
77 // users (to provide a text equivalent to the equation) while the title
78 // is there as a convenience for sighted users who want to see the TeX
79 // code.
80 $title = 'title="'.s($tex).'"';
81
82 if ($alt === '') {
83 $alt = s($tex);
84 } else {
85 $alt = s(html_entity_decode($tex, ENT_QUOTES, 'UTF-8'));
9d49b430 86 }
c1d9eeb9 87
88 // Build the output.
f7f0909c
PS
89 $anchorcontents = "<img class=\"texrender\" $title alt=\"$alt\" src=\"";
90 if ($CFG->slasharguments) { // Use this method if possible for better caching
91 $anchorcontents .= "$CFG->wwwroot/filter/tex/pix.php/$imagefile";
c49dede8 92 } else {
f7f0909c 93 $anchorcontents .= "$CFG->wwwroot/filter/tex/pix.php?file=$imagefile";
c49dede8 94 }
f7f0909c
PS
95 $anchorcontents .= "\" $style/>";
96
6ca657a7 97 if (!file_exists("$CFG->dataroot/filter/tex/$imagefile") && has_capability('moodle/site:config', context_system::instance())) {
f7f0909c
PS
98 $link = '/filter/tex/texdebug.php';
99 $action = null;
100 } else {
101 $link = new moodle_url('/filter/tex/displaytex.php', array('texexp'=>$tex));
102 $action = new popup_action('click', $link, 'popup', array('width'=>320,'height'=>240));
103 }
104 $output = $OUTPUT->action_link($link, $anchorcontents, $action, array('title'=>'TeX')); //TODO: the popups do not work when text caching is enabled!!
a57a336f 105 $output = "<span class=\"MathJax_Preview\">$output</span><script type=\"math/tex\">$tex</script>";
f7f0909c 106
9d49b430 107 return $output;
c49dede8 108}
109
f7f0909c
PS
110
111/**
112 * TeX filtering class.
113 */
35716b86 114class filter_tex extends moodle_text_filter {
f7f0909c 115 function filter($text, array $options = array()) {
9e3f34d1 116
117 global $CFG, $DB;
118
119 /// Do a quick check using stripos to avoid unnecessary work
f8a8610b
DW
120 if ((!preg_match('/<tex/i', $text)) &&
121 (strpos($text,'$$') === false) &&
122 (strpos($text,'\\[') === false) &&
123 (strpos($text, '\\(') === false) &&
124 (!preg_match('/\[tex/i',$text))) {
9e3f34d1 125 return $text;
126 }
c49dede8 127
9d49b430 128# //restrict filtering to forum 130 (Maths Tools on moodle.org)
129# $scriptname = $_SERVER['SCRIPT_NAME'];
130# if (!strstr($scriptname,'/forum/')) {
131# return $text;
132# }
133# if (strstr($scriptname,'post.php')) {
134# $parent = forum_get_post_full($_GET['reply']);
82a5323c 135# $discussion = $DB->get_record("forum_discussions", array("id"=>$parent->discussion));
9d49b430 136# } else if (strstr($scriptname,'discuss.php')) {
82a5323c 137# $discussion = $DB->get_record("forum_discussions", array("id"=>$_GET['d']));
9d49b430 138# } else {
139# return $text;
140# }
141# if ($discussion->forum != 130) {
142# return $text;
143# }
9e3f34d1 144 $text .= ' ';
145 preg_match_all('/\$(\$\$+?)([^\$])/s',$text,$matches);
f7f0909c
PS
146 for ($i=0; $i<count($matches[0]); $i++) {
147 $replacement = str_replace('$','&#x00024;', $matches[1][$i]).$matches[2][$i];
148 $text = str_replace($matches[0][$i], $replacement, $text);
f352814a 149 }
9e3f34d1 150
151 // <tex> TeX expression </tex>
152 // or <tex alt="My alternative text to be used instead of the TeX form"> TeX expression </tex>
153 // or $$ TeX expression $$
154 // or \[ TeX expression \] // original tag of MathType and TeXaide (dlnsk)
155 // or [tex] TeX expression [/tex] // somtime it's more comfortable than <tex> (dlnsk)
f8a8610b
DW
156 $rules = array(
157 '<tex(?:\s+alt=["\'](.*?)["\'])?>(.+?)<\/tex>',
158 '\$\$(.+?)\$\$',
159 '\\\\\[(.+?)\\\\\]',
160 '\\\\\((.+?)\\\\\)',
161 '\\[tex\\](.+?)\\[\/tex\\]'
162 );
ee439d01 163 $megarule = '/' . implode('|', $rules) . '/is';
f8a8610b 164 preg_match_all($megarule, $text, $matches);
9e3f34d1 165 for ($i=0; $i<count($matches[0]); $i++) {
f8a8610b
DW
166 $texexp = '';
167 for ($j = 0; $j < count($rules); $j++) {
168 $texexp .= $matches[$j + 2][$i];
169 }
9e3f34d1 170 $alt = $matches[1][$i];
171 $texexp = str_replace('<nolink>','',$texexp);
172 $texexp = str_replace('</nolink>','',$texexp);
173 $texexp = str_replace('<span class="nolink">','',$texexp);
174 $texexp = str_replace('</span>','',$texexp);
c78a948e 175 $texexp = preg_replace("/<br[[:space:]]*\/?>/i", '', $texexp); //dlnsk
9e3f34d1 176 $align = "middle";
177 if (preg_match('/^align=bottom /',$texexp)) {
178 $align = "text-bottom";
179 $texexp = preg_replace('/^align=bottom /','',$texexp);
180 } else if (preg_match('/^align=top /',$texexp)) {
181 $align = "text-top";
182 $texexp = preg_replace('/^align=top /','',$texexp);
183 }
f7f0909c
PS
184
185 // decode entities encoded by editor, luckily there is very little chance of double decoding
186 $texexp = html_entity_decode($texexp, ENT_QUOTES, 'UTF-8');
187
188 if ($texexp === '') {
962360fa 189 continue;
f7f0909c
PS
190 }
191
11e13d16
MH
192 // Sanitize the decoded string, because filter_text_image() injects the final string between script tags.
193 $texexp = clean_param($texexp, PARAM_TEXT);
194
9e3f34d1 195 $md5 = md5($texexp);
f7f0909c
PS
196 if (!$DB->record_exists("cache_filters", array("filter"=>"tex", "md5key"=>$md5))) {
197 $texcache = new stdClass();
9e3f34d1 198 $texcache->filter = 'tex';
199 $texcache->version = 1;
200 $texcache->md5key = $md5;
201 $texcache->rawtext = $texexp;
202 $texcache->timemodified = time();
203 $DB->insert_record("cache_filters", $texcache, false);
204 }
7a372321 205 $convertformat = get_config('filter_tex', 'convertformat');
a57a336f
DT
206 if ($convertformat == 'svg' && !core_useragent::supports_svg()) {
207 $convertformat = 'png';
208 }
7a372321 209 $filename = $md5.".{$convertformat}";
f7f0909c 210 $text = str_replace( $matches[0][$i], filter_text_image($filename, $texexp, 0, 0, $align, $alt), $text);
c49dede8 211 }
9e3f34d1 212 return $text;
c49dede8 213 }
9d49b430 214}
c49dede8 215
4317f92f 216