Merge branch 'MDL-80548' of https://github.com/marinaglancy/moodle
[moodle.git] / filter / tex / lib.php
blob5c4d4af2ff42aa013ca8c506ad8df24d59ba8f4a
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.
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/>.
17 /**
18 * TeX filter library functions.
20 * @package filter
21 * @subpackage tex
22 * @copyright 2004 Zbigniew Fiedorowicz fiedorow@math.ohio-state.edu
23 * Originally based on code provided by Bruno Vernier bruno@vsbeducation.ca
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 defined('MOODLE_INTERNAL') || die();
29 function filter_tex_get_executable($debug=false) {
30 global $CFG;
32 if ((PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows")) {
33 return "$CFG->dirroot/filter/tex/mimetex.exe";
36 if ($pathmimetex = get_config('filter_tex', 'pathmimetex')) {
37 if (is_executable($pathmimetex)) {
38 return $pathmimetex;
39 } else {
40 throw new \moodle_exception('mimetexnotexecutable', 'error');
44 $custom_commandpath = "$CFG->dirroot/filter/tex/mimetex";
45 if (file_exists($custom_commandpath)) {
46 if (is_executable($custom_commandpath)) {
47 return $custom_commandpath;
48 } else {
49 throw new \moodle_exception('mimetexnotexecutable', 'error');
53 switch (PHP_OS) {
54 case "Darwin": return "$CFG->dirroot/filter/tex/mimetex.darwin";
55 case "FreeBSD": return "$CFG->dirroot/filter/tex/mimetex.freebsd";
56 case "Linux":
57 if (php_uname('m') == 'aarch64') {
58 return "$CFG->dirroot/filter/tex/mimetex.linux.aarch64";
61 return "$CFG->dirroot/filter/tex/mimetex.linux";
64 throw new \moodle_exception('mimetexisnotexist', 'error');
67 /**
68 * Check the formula expression against the list of denied keywords.
70 * List of allowed could be more complete but also harder to maintain.
72 * @param string $texexp Formula expression to check.
73 * @return string Formula expression with denied keywords replaced with 'forbiddenkeyword'.
75 function filter_tex_sanitize_formula(string $texexp): string {
77 $denylist = [
78 'include', 'command', 'loop', 'repeat', 'open', 'toks', 'output',
79 'input', 'catcode', 'name', '^^',
80 '\def', '\edef', '\gdef', '\xdef',
81 '\every', '\errhelp', '\errorstopmode', '\scrollmode', '\nonstopmode',
82 '\batchmode', '\read', '\write', 'csname', '\newhelp', '\uppercase',
83 '\lowercase', '\relax', '\aftergroup',
84 '\afterassignment', '\expandafter', '\noexpand', '\special',
85 '\let', '\futurelet', '\else', '\fi', '\chardef', '\makeatletter', '\afterground',
86 '\noexpand', '\line', '\mathcode', '\item', '\section', '\mbox', '\declarerobustcommand',
87 '\ExplSyntaxOn',
90 $allowlist = ['inputenc'];
92 // Prepare the denylist for regular expression.
93 $denylist = array_map(function($value){
94 return '/' . preg_quote($value, '/') . '/i';
95 }, $denylist);
97 // Prepare the allowlist for regular expression.
98 $allowlist = array_map(function($value){
99 return '/\bforbiddenkeyword_(' . preg_quote($value, '/') . ')\b/i';
100 }, $allowlist);
102 // First, mangle all denied words.
103 $texexp = preg_replace_callback($denylist,
104 function($matches) {
105 return 'forbiddenkeyword_' . $matches[0];
107 $texexp
110 // Then, change back the allowed words.
111 $texexp = preg_replace_callback($allowlist,
112 function($matches) {
113 return $matches[1];
115 $texexp
118 return $texexp;
121 function filter_tex_get_cmd($pathname, $texexp) {
122 $texexp = filter_tex_sanitize_formula($texexp);
123 $texexp = escapeshellarg($texexp);
124 $executable = filter_tex_get_executable(false);
126 if ((PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows")) {
127 $executable = str_replace(' ', '^ ', $executable);
128 return "$executable ++ -e \"$pathname\" -- $texexp";
130 } else {
131 return "\"$executable\" -e \"$pathname\" -- $texexp";
136 * Purge all caches when settings changed.
138 function filter_tex_updatedcallback($name) {
139 global $CFG, $DB;
140 reset_text_filters_cache();
142 if (file_exists("$CFG->dataroot/filter/tex")) {
143 remove_dir("$CFG->dataroot/filter/tex");
145 if (file_exists("$CFG->dataroot/filter/algebra")) {
146 remove_dir("$CFG->dataroot/filter/algebra");
148 if (file_exists("$CFG->tempdir/latex")) {
149 remove_dir("$CFG->tempdir/latex");
152 $DB->delete_records('cache_filters', array('filter'=>'tex'));
153 $DB->delete_records('cache_filters', array('filter'=>'algebra'));
155 $pathlatex = get_config('filter_tex', 'pathlatex');
156 if ($pathlatex === false) {
157 // detailed settings not present yet
158 return;
161 $pathlatex = trim($pathlatex, " '\"");
162 $pathdvips = trim(get_config('filter_tex', 'pathdvips'), " '\"");
163 $pathconvert = trim(get_config('filter_tex', 'pathconvert'), " '\"");
164 $pathdvisvgm = trim(get_config('filter_tex', 'pathdvisvgm'), " '\"");
166 $supportedformats = array('gif');
167 if ((is_file($pathlatex) && is_executable($pathlatex)) &&
168 (is_file($pathdvips) && is_executable($pathdvips))) {
169 if (is_file($pathconvert) && is_executable($pathconvert)) {
170 $supportedformats[] = 'png';
172 if (is_file($pathdvisvgm) && is_executable($pathdvisvgm)) {
173 $supportedformats[] = 'svg';
176 if (!in_array(get_config('filter_tex', 'convertformat'), $supportedformats)) {
177 set_config('convertformat', array_pop($supportedformats), 'filter_tex');