Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * @package core 20 * @subpackage search 21 * @copyright 1999 onwards Martin Dougiamas {@link http://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 /** @see lexer.php */ 28 require_once($CFG->libdir.'/lexer.php'); 29 30 /** Constants for the various types of tokens */ 31 32 define("TOKEN_USER","0"); 33 define("TOKEN_META","1"); 34 define("TOKEN_EXACT","2"); 35 define("TOKEN_NEGATE","3"); 36 define("TOKEN_STRING","4"); 37 define("TOKEN_USERID","5"); 38 define("TOKEN_DATEFROM","6"); 39 define("TOKEN_DATETO","7"); 40 define("TOKEN_INSTANCE","8"); 41 define("TOKEN_TAGS","9"); 42 43 /** 44 * Class to hold token/value pairs after they're parsed. 45 * 46 * @package moodlecore 47 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 48 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 49 */ 50 class search_token { 51 private $value; 52 private $type; 53 54 public function __construct($type,$value){ 55 $this->type = $type; 56 $this->value = $this->sanitize($value); 57 58 } 59 60 /** 61 * Old syntax of class constructor. Deprecated in PHP7. 62 * 63 * @deprecated since Moodle 3.1 64 */ 65 public function search_token($type, $value) { 66 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 67 self::__construct($type, $value); 68 } 69 70 // Try to clean up user input to avoid potential security issues. 71 // Need to think about this some more. 72 73 function sanitize($userstring){ 74 return htmlspecialchars($userstring); 75 } 76 function getValue(){ 77 return $this->value; 78 } 79 function getType(){ 80 return $this->type; 81 } 82 } 83 84 85 /** 86 * This class does the heavy lifting of lexing the search string into tokens. 87 * Using a full-blown lexer is probably overkill for this application, but 88 * might be useful for other tasks. 89 * 90 * @package moodlecore 91 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 92 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 93 */ 94 class search_lexer extends Lexer{ 95 96 public function __construct(&$parser){ 97 98 // Call parent constructor. 99 parent::__construct($parser); 100 101 //Set up the state machine and pattern matches for transitions. 102 103 // Patterns to handle strings of the form datefrom:foo 104 105 // If we see the string datefrom: while in the base accept state, start 106 // parsing a username and go to the indatefrom state. 107 $this->addEntryPattern("datefrom:\S+","accept","indatefrom"); 108 109 // Snarf everything into the username until we see whitespace, then exit 110 // back to the base accept state. 111 $this->addExitPattern("\s","indatefrom"); 112 113 114 // If we see the string tags: while in the base accept state, start 115 // parsing tags and go to the intags state. 116 $this->addEntryPattern("tags:\S+","accept","intags"); 117 118 // Snarf everything into the tags until we see whitespace, then exit 119 // back to the base accept state. 120 $this->addExitPattern("\s","intags"); 121 122 // Patterns to handle strings of the form dateto:foo 123 124 // If we see the string dateto: while in the base accept state, start 125 // parsing a username and go to the indateto state. 126 $this->addEntryPattern("dateto:\S+","accept","indateto"); 127 128 // Snarf everything into the username until we see whitespace, then exit 129 // back to the base accept state. 130 $this->addExitPattern("\s","indateto"); 131 132 133 // Patterns to handle strings of the form instance:foo 134 135 // If we see the string instance: while in the base accept state, start 136 // parsing for instance number and go to the ininstance state. 137 $this->addEntryPattern("instance:\S+","accept","ininstance"); 138 139 // Snarf everything into the username until we see whitespace, then exit 140 // back to the base accept state. 141 $this->addExitPattern("\s","ininstance"); 142 143 144 // Patterns to handle strings of the form userid:foo 145 146 // If we see the string userid: while in the base accept state, start 147 // parsing a username and go to the inuserid state. 148 $this->addEntryPattern("userid:\S+","accept","inuserid"); 149 150 // Snarf everything into the username until we see whitespace, then exit 151 // back to the base accept state. 152 $this->addExitPattern("\s","inuserid"); 153 154 155 // Patterns to handle strings of the form user:foo 156 157 // If we see the string user: while in the base accept state, start 158 // parsing a username and go to the inusername state. 159 $this->addEntryPattern("user:\S+","accept","inusername"); 160 161 // Snarf everything into the username until we see whitespace, then exit 162 // back to the base accept state. 163 $this->addExitPattern("\s","inusername"); 164 165 166 // Patterns to handle strings of the form meta:foo 167 168 // If we see the string meta: while in the base accept state, start 169 // parsing a username and go to the inmeta state. 170 $this->addEntryPattern("subject:\S+","accept","inmeta"); 171 172 // Snarf everything into the meta token until we see whitespace, then exit 173 // back to the base accept state. 174 $this->addExitPattern("\s","inmeta"); 175 176 177 // Patterns to handle required exact match strings (+foo) . 178 179 // If we see a + sign while in the base accept state, start 180 // parsing an exact match string and enter the inrequired state 181 $this->addEntryPattern("\+\S+","accept","inrequired"); 182 // When we see white space, exit back to accept state. 183 $this->addExitPattern("\s","inrequired"); 184 185 // Handle excluded strings (-foo) 186 187 // If we see a - sign while in the base accept state, start 188 // parsing an excluded string and enter the inexcluded state 189 $this->addEntryPattern("\-\S+","accept","inexcluded"); 190 // When we see white space, exit back to accept state. 191 $this->addExitPattern("\s","inexcluded"); 192 193 194 // Patterns to handle quoted strings. 195 196 // If we see a quote while in the base accept state, start 197 // parsing a quoted string and enter the inquotedstring state. 198 // Grab everything until we see the closing quote. 199 200 $this->addEntryPattern("\"[^\"]+","accept","inquotedstring"); 201 202 // When we see a closing quote, reenter the base accept state. 203 $this->addExitPattern("\"","inquotedstring"); 204 205 // Patterns to handle ordinary, nonquoted words. 206 207 // When we see non-whitespace, snarf everything into the nonquoted word 208 // until we see whitespace again. 209 $this->addEntryPattern("\S+","accept","plainstring"); 210 211 // Once we see whitespace, reenter the base accept state. 212 $this->addExitPattern("\s","plainstring"); 213 214 } 215 216 /** 217 * Old syntax of class constructor. Deprecated in PHP7. 218 * 219 * @deprecated since Moodle 3.1 220 */ 221 public function search_lexer(&$parser) { 222 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 223 self::__construct($parser); 224 } 225 226 } 227 228 229 230 /** 231 * This class takes care of sticking the proper token type/value pairs into 232 * the parsed token array. 233 * Most functions in this class should only be called by the lexer, the 234 * one exception being getParseArray() which returns the result. 235 * 236 * @package moodlecore 237 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 238 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 239 */ 240 class search_parser { 241 private $tokens; 242 243 // This function is called by the code that's interested in the result of the parse operation. 244 function get_parsed_array(){ 245 return $this->tokens; 246 } 247 248 /* 249 * Functions below this are part of the state machine for the parse 250 * operation and should not be called directly. 251 */ 252 253 // Base state. No output emitted. 254 function accept() { 255 return true; 256 } 257 258 // State for handling datefrom:foo constructs. Potentially emits a token. 259 function indatefrom($content){ 260 if (strlen($content) < 10) { // State exit or missing parameter. 261 return true; 262 } 263 // Strip off the datefrom: part and add the reminder to the parsed token array 264 $param = trim(substr($content,9)); 265 $this->tokens[] = new search_token(TOKEN_DATEFROM,$param); 266 return true; 267 } 268 269 // State for handling dateto:foo constructs. Potentially emits a token. 270 function indateto($content){ 271 if (strlen($content) < 8) { // State exit or missing parameter. 272 return true; 273 } 274 // Strip off the dateto: part and add the reminder to the parsed token array 275 $param = trim(substr($content,7)); 276 $this->tokens[] = new search_token(TOKEN_DATETO,$param); 277 return true; 278 } 279 280 // State for handling tags:tagname,tagname constructs. Potentially emits a token. 281 function intags($content){ 282 if (strlen($content) < 5) { // State exit or missing parameter. 283 return true; 284 } 285 // Strip off the tags: part and add the reminder to the parsed token array 286 $param = trim(substr($content,5)); 287 $this->tokens[] = new search_token(TOKEN_TAGS,$param); 288 return true; 289 } 290 291 // State for handling instance:foo constructs. Potentially emits a token. 292 function ininstance($content){ 293 if (strlen($content) < 10) { // State exit or missing parameter. 294 return true; 295 } 296 // Strip off the instance: part and add the reminder to the parsed token array 297 $param = trim(substr($content,9)); 298 $this->tokens[] = new search_token(TOKEN_INSTANCE,$param); 299 return true; 300 } 301 302 303 // State for handling userid:foo constructs. Potentially emits a token. 304 function inuserid($content){ 305 if (strlen($content) < 8) { // State exit or missing parameter. 306 return true; 307 } 308 // Strip off the userid: part and add the reminder to the parsed token array 309 $param = trim(substr($content,7)); 310 $this->tokens[] = new search_token(TOKEN_USERID,$param); 311 return true; 312 } 313 314 315 // State for handling user:foo constructs. Potentially emits a token. 316 function inusername($content){ 317 if (strlen($content) < 6) { // State exit or missing parameter. 318 return true; 319 } 320 // Strip off the user: part and add the reminder to the parsed token array 321 $param = trim(substr($content,5)); 322 $this->tokens[] = new search_token(TOKEN_USER,$param); 323 return true; 324 } 325 326 327 // State for handling meta:foo constructs. Potentially emits a token. 328 function inmeta($content){ 329 if (strlen($content) < 9) { // Missing parameter. 330 return true; 331 } 332 // Strip off the meta: part and add the reminder to the parsed token array. 333 $param = trim(substr($content,8)); 334 $this->tokens[] = new search_token(TOKEN_META,$param); 335 return true; 336 } 337 338 339 // State entered when we've seen a required string (+foo). Potentially 340 // emits a token. 341 function inrequired($content){ 342 if (strlen($content) < 2) { // State exit or missing parameter, don't emit. 343 return true; 344 } 345 // Strip off the + sign and add the reminder to the parsed token array. 346 $this->tokens[] = new search_token(TOKEN_EXACT,substr($content,1)); 347 return true; 348 } 349 350 // State entered when we've seen an excluded string (-foo). Potentially 351 // emits a token. 352 function inexcluded($content){ 353 if (strlen($content) < 2) { // State exit or missing parameter. 354 return true; 355 } 356 // Strip off the -sign and add the reminder to the parsed token array. 357 $this->tokens[] = new search_token(TOKEN_NEGATE,substr($content,1)); 358 return true; 359 } 360 361 362 // State entered when we've seen a quoted string. Potentially emits a token. 363 function inquotedstring($content){ 364 if (strlen($content) < 2) { // State exit or missing parameter. 365 return true; 366 } 367 // Strip off the opening quote and add the reminder to the parsed token array. 368 $this->tokens[] = new search_token(TOKEN_STRING,substr($content,1)); 369 return true; 370 } 371 372 // State entered when we've seen an ordinary, non-quoted word. Potentially 373 // emits a token. 374 function plainstring($content){ 375 if (trim($content) === '') { // State exit 376 return true; 377 } 378 // Add the string to the parsed token array. 379 $this->tokens[] = new search_token(TOKEN_STRING,$content); 380 return true; 381 } 382 } 383 384 /** 385 * Primitive function to generate a SQL string from a parse tree 386 * using TEXT indexes. If searches aren't suitable to use TEXT 387 * this function calls the default search_generate_SQL() one. 388 * 389 * @deprecated since Moodle 2.9 MDL-48939 390 * @todo MDL-48940 This will be deleted in Moodle 3.2 391 * @see search_generate_SQL() 392 */ 393 function search_generate_text_SQL($parsetree, $datafield, $metafield, $mainidfield, $useridfield, 394 $userfirstnamefield, $userlastnamefield, $timefield, $instancefield) { 395 debugging('search_generate_text_SQL() is deprecated, please use search_generate_SQL() instead.', DEBUG_DEVELOPER); 396 397 return search_generate_SQL($parsetree, $datafield, $metafield, $mainidfield, $useridfield, 398 $userfirstnamefield, $userlastnamefield, $timefield, $instancefield); 399 } 400 401 /** 402 * Primitive function to generate a SQL string from a parse tree. 403 * Parameters: 404 * 405 * $parsetree should be a parse tree generated by a 406 * search_lexer/search_parser combination. 407 * Other fields are database table names to search. 408 * 409 * @global object 410 * @global object 411 */ 412 function search_generate_SQL($parsetree, $datafield, $metafield, $mainidfield, $useridfield, 413 $userfirstnamefield, $userlastnamefield, $timefield, $instancefield, 414 $tagfields = []) { 415 global $CFG, $DB; 416 static $p = 0; 417 418 if ($DB->sql_regex_supported()) { 419 $REGEXP = $DB->sql_regex(true); 420 $NOTREGEXP = $DB->sql_regex(false); 421 $regexwordbegin = $DB->sql_regex_get_word_beginning_boundary_marker(); 422 $regexwordend = $DB->sql_regex_get_word_end_boundary_marker(); 423 } 424 425 $params = array(); 426 427 $ntokens = count($parsetree); 428 if ($ntokens == 0) { 429 return ""; 430 } 431 432 $SQLString = ''; 433 $nexttagfield = 0; 434 for ($i=0; $i<$ntokens; $i++){ 435 if ($i > 0) {// We have more than one clause, need to tack on AND 436 $SQLString .= ' AND '; 437 } 438 439 $type = $parsetree[$i]->getType(); 440 $value = $parsetree[$i]->getValue(); 441 442 /// Under Oracle and MSSQL, transform TOKEN searches into STRING searches and trim +- chars 443 if (!$DB->sql_regex_supported()) { 444 $value = trim($value, '+-'); 445 if ($type == TOKEN_EXACT) { 446 $type = TOKEN_STRING; 447 } 448 } 449 450 $name1 = 'sq'.$p++; 451 $name2 = 'sq'.$p++; 452 453 switch($type){ 454 case TOKEN_STRING: 455 $SQLString .= "((".$DB->sql_like($datafield, ":$name1", false).") OR (".$DB->sql_like($metafield, ":$name2", false)."))"; 456 $params[$name1] = "%$value%"; 457 $params[$name2] = "%$value%"; 458 break; 459 case TOKEN_EXACT: 460 $SQLString .= "(($datafield $REGEXP :$name1) OR ($metafield $REGEXP :$name2))"; 461 $params[$name1] = $regexwordbegin.$value.$regexwordend; 462 $params[$name2] = $regexwordbegin.$value.$regexwordend; 463 break; 464 case TOKEN_META: 465 if ($metafield != '') { 466 $SQLString .= "(".$DB->sql_like($metafield, ":$name1", false).")"; 467 $params[$name1] = "%$value%"; 468 } 469 break; 470 case TOKEN_USER: 471 $SQLString .= "(($mainidfield = $useridfield) AND ((".$DB->sql_like($userfirstnamefield, ":$name1", false).") OR (".$DB->sql_like($userlastnamefield, ":$name2", false).")))"; 472 $params[$name1] = "%$value%"; 473 $params[$name2] = "%$value%"; 474 break; 475 case TOKEN_USERID: 476 $SQLString .= "($useridfield = :$name1)"; 477 $params[$name1] = $value; 478 break; 479 case TOKEN_INSTANCE: 480 $SQLString .= "($instancefield = :$name1)"; 481 $params[$name1] = $value; 482 break; 483 case TOKEN_DATETO: 484 $SQLString .= "($timefield <= :$name1)"; 485 $params[$name1] = $value; 486 break; 487 case TOKEN_DATEFROM: 488 $SQLString .= "($timefield >= :$name1)"; 489 $params[$name1] = $value; 490 break; 491 case TOKEN_TAGS: 492 $sqlstrings = []; 493 foreach (explode(',', $value) as $tag) { 494 $paramname = $name1 . '_' . $nexttagfield; 495 if (isset($tagfields[$nexttagfield])) { 496 $sqlstrings[] = "($tagfields[$nexttagfield] = :$paramname)"; 497 $params[$paramname] = $tag; 498 } else if (!isset($tagfields[$nexttagfield]) && !isset($stoppedprocessingtags)) { 499 // Show a debugging message the first time we hit this. 500 $stoppedprocessingtags = true; 501 \core\notification::add(get_string('toomanytags'), \core\notification::WARNING); 502 } 503 $nexttagfield++; 504 } 505 $SQLString .= implode(' AND ', $sqlstrings); 506 break; 507 case TOKEN_NEGATE: 508 $SQLString .= "(NOT ((".$DB->sql_like($datafield, ":$name1", false).") OR (".$DB->sql_like($metafield, ":$name2", false).")))"; 509 $params[$name1] = "%$value%"; 510 $params[$name2] = "%$value%"; 511 break; 512 default: 513 return ''; 514 515 } 516 } 517 return array($SQLString, $params); 518 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body