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 * Mlang PHP based on David Mudrak phpparser for local_amos. 19 * 20 * @package tool_customlang 21 * @copyright 2020 Ferran Recio <ferran@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace tool_customlang\local\mlang; 26 27 use coding_exception; 28 use moodle_exception; 29 30 /** 31 * Parser of Moodle strings defined as associative array. 32 * 33 * Moodle core just includes this file format directly as normal PHP code. However 34 * for security reasons, we must not do this for files uploaded by anonymous users. 35 * This parser reconstructs the associative $string array without actually including 36 * the file. 37 */ 38 class phpparser { 39 40 /** @var holds the singleton instance of self */ 41 private static $instance = null; 42 43 /** 44 * Prevents direct creation of object 45 */ 46 private function __construct() { 47 } 48 49 /** 50 * Prevent from cloning the instance 51 */ 52 public function __clone() { 53 throw new coding_exception('Cloning os singleton is not allowed'); 54 } 55 56 /** 57 * Get the singleton instance fo this class 58 * 59 * @return phpparser singleton instance of phpparser 60 */ 61 public static function get_instance(): phpparser { 62 if (is_null(self::$instance)) { 63 self::$instance = new phpparser(); 64 } 65 return self::$instance; 66 } 67 68 /** 69 * Parses the given data in Moodle PHP string format 70 * 71 * Note: This method is adapted from local_amos as it is highly tested and robust. 72 * The priority is keeping it similar to the original one to make it easier to mantain. 73 * 74 * @param string $data definition of the associative array 75 * @param int $format the data format on the input, defaults to the one used since 2.0 76 * @return langstring[] array of langstrings of this file 77 */ 78 public function parse(string $data, int $format = 2): array { 79 $result = []; 80 $strings = $this->extract_strings($data); 81 foreach ($strings as $id => $text) { 82 $cleaned = clean_param($id, PARAM_STRINGID); 83 if ($cleaned !== $id) { 84 continue; 85 } 86 $text = langstring::fix_syntax($text, 2, $format); 87 $result[] = new langstring($id, $text); 88 } 89 return $result; 90 } 91 92 /** 93 * Low level parsing method 94 * 95 * Note: This method is adapted from local_amos as it is highly tested and robust. 96 * The priority is keeping it similar to the original one to make it easier to mantain. 97 * 98 * @param string $data 99 * @return string[] the data strings 100 */ 101 protected function extract_strings(string $data): array { 102 103 $strings = []; // To be returned. 104 105 if (empty($data)) { 106 return $strings; 107 } 108 109 // Tokenize data - we expect valid PHP code. 110 $tokens = token_get_all($data); 111 112 // Get rid of all non-relevant tokens. 113 $rubbish = [T_WHITESPACE, T_INLINE_HTML, T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG, T_CLOSE_TAG]; 114 foreach ($tokens as $i => $token) { 115 if (is_array($token)) { 116 if (in_array($token[0], $rubbish)) { 117 unset($tokens[$i]); 118 } 119 } 120 } 121 122 $id = null; 123 $text = null; 124 $line = 0; 125 $expect = 'STRING_VAR'; // The first expected token is '$string'. 126 127 // Iterate over tokens and look for valid $string array assignment patterns. 128 foreach ($tokens as $token) { 129 $foundtype = null; 130 $founddata = null; 131 if (is_array($token)) { 132 $foundtype = $token[0]; 133 $founddata = $token[1]; 134 if (!empty($token[2])) { 135 $line = $token[2]; 136 } 137 138 } else { 139 $foundtype = 'char'; 140 $founddata = $token; 141 } 142 143 if ($expect == 'STRING_VAR') { 144 if ($foundtype === T_VARIABLE and $founddata === '$string') { 145 $expect = 'LEFT_BRACKET'; 146 continue; 147 } else { 148 // Allow other code at the global level. 149 continue; 150 } 151 } 152 153 if ($expect == 'LEFT_BRACKET') { 154 if ($foundtype === 'char' and $founddata === '[') { 155 $expect = 'STRING_ID'; 156 continue; 157 } else { 158 throw new moodle_exception('Parsing error. Expected character [ at line '.$line); 159 } 160 } 161 162 if ($expect == 'STRING_ID') { 163 if ($foundtype === T_CONSTANT_ENCAPSED_STRING) { 164 $id = $this->decapsulate($founddata); 165 $expect = 'RIGHT_BRACKET'; 166 continue; 167 } else { 168 throw new moodle_exception('Parsing error. Expected T_CONSTANT_ENCAPSED_STRING array key at line '.$line); 169 } 170 } 171 172 if ($expect == 'RIGHT_BRACKET') { 173 if ($foundtype === 'char' and $founddata === ']') { 174 $expect = 'ASSIGNMENT'; 175 continue; 176 } else { 177 throw new moodle_exception('Parsing error. Expected character ] at line '.$line); 178 } 179 } 180 181 if ($expect == 'ASSIGNMENT') { 182 if ($foundtype === 'char' and $founddata === '=') { 183 $expect = 'STRING_TEXT'; 184 continue; 185 } else { 186 throw new moodle_exception('Parsing error. Expected character = at line '.$line); 187 } 188 } 189 190 if ($expect == 'STRING_TEXT') { 191 if ($foundtype === T_CONSTANT_ENCAPSED_STRING) { 192 $text = $this->decapsulate($founddata); 193 $expect = 'SEMICOLON'; 194 continue; 195 } else { 196 throw new moodle_exception( 197 'Parsing error. Expected T_CONSTANT_ENCAPSED_STRING array item value at line '.$line 198 ); 199 } 200 } 201 202 if ($expect == 'SEMICOLON') { 203 if (is_null($id) or is_null($text)) { 204 throw new moodle_exception('Parsing error. NULL string id or value at line '.$line); 205 } 206 if ($foundtype === 'char' and $founddata === ';') { 207 if (!empty($id)) { 208 $strings[$id] = $text; 209 } 210 $id = null; 211 $text = null; 212 $expect = 'STRING_VAR'; 213 continue; 214 } else { 215 throw new moodle_exception('Parsing error. Expected character ; at line '.$line); 216 } 217 } 218 219 } 220 221 return $strings; 222 } 223 224 /** 225 * Given one T_CONSTANT_ENCAPSED_STRING, return its value without quotes 226 * 227 * Also processes escaped quotes inside the text. 228 * 229 * Note: This method is taken directly from local_amos as it is highly tested and robust. 230 * 231 * @param string $text value obtained by token_get_all() 232 * @return string value without quotes 233 */ 234 protected function decapsulate(string $text): string { 235 236 if (strlen($text) < 2) { 237 throw new moodle_exception('Parsing error. Expected T_CONSTANT_ENCAPSED_STRING in decapsulate()'); 238 } 239 240 if (substr($text, 0, 1) == "'" and substr($text, -1) == "'") { 241 // Single quoted string. 242 $text = trim($text, "'"); 243 $text = str_replace("\'", "'", $text); 244 $text = str_replace('\\\\', '\\', $text); 245 return $text; 246 247 } else if (substr($text, 0, 1) == '"' and substr($text, -1) == '"') { 248 // Double quoted string. 249 $text = trim($text, '"'); 250 $text = str_replace('\"', '"', $text); 251 $text = str_replace('\\\\', '\\', $text); 252 return $text; 253 254 } else { 255 throw new moodle_exception( 256 'Parsing error. Unexpected quotation in T_CONSTANT_ENCAPSED_STRING in decapsulate(): '.$text 257 ); 258 } 259 } 260 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body