2009-05-26 03:57:03 +00:00
< ? php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
2005-01-31 07:39:03 +00:00
2009-05-26 03:57:03 +00:00
/**
2010-07-25 13:35:05 +00:00
* @ package core
* @ subpackage search
* @ copyright 1999 onwards Martin Dougiamas { @ link http :// moodle . com }
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
2009-05-26 03:57:03 +00:00
*/
2010-07-25 13:35:05 +00:00
defined ( 'MOODLE_INTERNAL' ) || die ();
2009-05-26 03:57:03 +00:00
/** @see lexer.php */
2005-01-31 07:39:03 +00:00
require_once ( $CFG -> libdir . '/lexer.php' );
2009-05-26 03:57:03 +00:00
/** Constants for the various types of tokens */
2005-01-31 07:39:03 +00:00
2005-02-12 04:12:24 +00:00
define ( " TOKEN_USER " , " 0 " );
define ( " TOKEN_META " , " 1 " );
define ( " TOKEN_EXACT " , " 2 " );
define ( " TOKEN_NEGATE " , " 3 " );
define ( " TOKEN_STRING " , " 4 " );
2005-02-27 09:57:31 +00:00
define ( " TOKEN_USERID " , " 5 " );
2005-02-28 12:40:29 +00:00
define ( " TOKEN_DATEFROM " , " 6 " );
define ( " TOKEN_DATETO " , " 7 " );
2005-03-03 12:22:13 +00:00
define ( " TOKEN_INSTANCE " , " 8 " );
2005-01-31 07:39:03 +00:00
2009-05-26 03:57:03 +00:00
/**
* Class to hold token / value pairs after they ' re parsed .
*
* @ package moodlecore
* @ copyright 1999 onwards Martin Dougiamas { @ link http :// moodle . com }
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
*/
2005-01-31 07:39:03 +00:00
class search_token {
2008-06-15 10:12:27 +00:00
private $value ;
private $type ;
2015-11-26 10:49:58 +08:00
public function __construct ( $type , $value ){
2005-01-31 07:39:03 +00:00
$this -> type = $type ;
$this -> value = $this -> sanitize ( $value );
2009-11-01 11:31:16 +00:00
2005-01-31 07:39:03 +00:00
}
2015-12-11 13:52:17 +08:00
/**
* Old syntax of class constructor . Deprecated in PHP7 .
*
* @ deprecated since Moodle 3.1
*/
public function search_token ( $type , $value ) {
debugging ( 'Use of class name as constructor is deprecated' , DEBUG_DEVELOPER );
self :: __construct ( $type , $value );
}
2005-01-31 07:39:03 +00:00
// Try to clean up user input to avoid potential security issues.
2009-11-01 11:31:16 +00:00
// Need to think about this some more.
2005-01-31 07:39:03 +00:00
function sanitize ( $userstring ){
2008-06-09 16:53:30 +00:00
return htmlspecialchars ( $userstring );
2005-01-31 07:39:03 +00:00
}
2009-11-01 11:31:16 +00:00
function getValue (){
2005-01-31 07:39:03 +00:00
return $this -> value ;
}
function getType (){
return $this -> type ;
}
}
2008-06-15 10:12:27 +00:00
/**
* This class does the heavy lifting of lexing the search string into tokens .
2009-11-01 11:31:16 +00:00
* Using a full - blown lexer is probably overkill for this application , but
2008-06-15 10:12:27 +00:00
* might be useful for other tasks .
2009-05-26 03:57:03 +00:00
*
* @ package moodlecore
* @ copyright 1999 onwards Martin Dougiamas { @ link http :// moodle . com }
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
2008-06-15 10:12:27 +00:00
*/
2005-01-31 07:39:03 +00:00
class search_lexer extends Lexer {
2015-11-26 10:49:58 +08:00
public function __construct ( & $parser ){
2005-01-31 07:39:03 +00:00
// Call parent constructor.
2015-11-26 10:49:58 +08:00
parent :: __construct ( $parser );
2005-01-31 07:39:03 +00:00
//Set up the state machine and pattern matches for transitions.
2005-02-28 12:40:29 +00:00
// Patterns to handle strings of the form datefrom:foo
// If we see the string datefrom: while in the base accept state, start
// parsing a username and go to the indatefrom state.
$this -> addEntryPattern ( " datefrom: \ S+ " , " accept " , " indatefrom " );
// Snarf everything into the username until we see whitespace, then exit
// back to the base accept state.
$this -> addExitPattern ( " \ s " , " indatefrom " );
// Patterns to handle strings of the form dateto:foo
// If we see the string dateto: while in the base accept state, start
// parsing a username and go to the indateto state.
$this -> addEntryPattern ( " dateto: \ S+ " , " accept " , " indateto " );
// Snarf everything into the username until we see whitespace, then exit
// back to the base accept state.
$this -> addExitPattern ( " \ s " , " indateto " );
2005-03-03 12:22:13 +00:00
// Patterns to handle strings of the form instance:foo
// If we see the string instance: while in the base accept state, start
// parsing for instance number and go to the ininstance state.
$this -> addEntryPattern ( " instance: \ S+ " , " accept " , " ininstance " );
// Snarf everything into the username until we see whitespace, then exit
// back to the base accept state.
$this -> addExitPattern ( " \ s " , " ininstance " );
2005-02-27 09:57:31 +00:00
// Patterns to handle strings of the form userid:foo
2005-02-28 12:40:29 +00:00
// If we see the string userid: while in the base accept state, start
2005-02-27 09:57:31 +00:00
// parsing a username and go to the inuserid state.
$this -> addEntryPattern ( " userid: \ S+ " , " accept " , " inuserid " );
// Snarf everything into the username until we see whitespace, then exit
// back to the base accept state.
$this -> addExitPattern ( " \ s " , " inuserid " );
2005-01-31 07:39:03 +00:00
// Patterns to handle strings of the form user:foo
// If we see the string user: while in the base accept state, start
// parsing a username and go to the inusername state.
$this -> addEntryPattern ( " user: \ S+ " , " accept " , " inusername " );
// Snarf everything into the username until we see whitespace, then exit
// back to the base accept state.
$this -> addExitPattern ( " \ s " , " inusername " );
2005-02-27 09:57:31 +00:00
2005-01-31 07:39:03 +00:00
// Patterns to handle strings of the form meta:foo
2009-11-01 11:31:16 +00:00
2005-01-31 07:39:03 +00:00
// If we see the string meta: while in the base accept state, start
// parsing a username and go to the inmeta state.
$this -> addEntryPattern ( " subject: \ S+ " , " accept " , " inmeta " );
// Snarf everything into the meta token until we see whitespace, then exit
// back to the base accept state.
$this -> addExitPattern ( " \ s " , " inmeta " );
2009-11-01 11:31:16 +00:00
2005-01-31 07:39:03 +00:00
// Patterns to handle required exact match strings (+foo) .
// If we see a + sign while in the base accept state, start
// parsing an exact match string and enter the inrequired state
$this -> addEntryPattern ( " \ + \ S+ " , " accept " , " inrequired " );
// When we see white space, exit back to accept state.
$this -> addExitPattern ( " \ s " , " inrequired " );
// Handle excluded strings (-foo)
// If we see a - sign while in the base accept state, start
// parsing an excluded string and enter the inexcluded state
$this -> addEntryPattern ( " \ - \ S+ " , " accept " , " inexcluded " );
// When we see white space, exit back to accept state.
$this -> addExitPattern ( " \ s " , " inexcluded " );
// Patterns to handle quoted strings.
// If we see a quote while in the base accept state, start
// parsing a quoted string and enter the inquotedstring state.
// Grab everything until we see the closing quote.
2009-11-01 11:31:16 +00:00
2005-01-31 07:39:03 +00:00
$this -> addEntryPattern ( " \" [^ \" ]+ " , " accept " , " inquotedstring " );
// When we see a closing quote, reenter the base accept state.
$this -> addExitPattern ( " \" " , " inquotedstring " );
2009-11-01 11:31:16 +00:00
2005-01-31 07:39:03 +00:00
// Patterns to handle ordinary, nonquoted words.
2009-11-01 11:31:16 +00:00
2005-01-31 07:39:03 +00:00
// When we see non-whitespace, snarf everything into the nonquoted word
// until we see whitespace again.
$this -> addEntryPattern ( " \ S+ " , " accept " , " plainstring " );
// Once we see whitespace, reenter the base accept state.
$this -> addExitPattern ( " \ s " , " plainstring " );
2009-11-01 11:31:16 +00:00
2005-01-31 07:39:03 +00:00
}
2015-12-11 13:52:17 +08:00
/**
* Old syntax of class constructor . Deprecated in PHP7 .
*
* @ deprecated since Moodle 3.1
*/
public function search_lexer ( & $parser ) {
debugging ( 'Use of class name as constructor is deprecated' , DEBUG_DEVELOPER );
self :: __construct ( $parser );
}
2009-11-01 11:31:16 +00:00
}
2005-01-31 07:39:03 +00:00
2008-06-15 10:12:27 +00:00
/**
* This class takes care of sticking the proper token type / value pairs into
* the parsed token array .
* Most functions in this class should only be called by the lexer , the
* one exception being getParseArray () which returns the result .
2009-05-26 03:57:03 +00:00
*
* @ package moodlecore
* @ copyright 1999 onwards Martin Dougiamas { @ link http :// moodle . com }
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
2008-06-15 10:12:27 +00:00
*/
2005-01-31 07:39:03 +00:00
class search_parser {
2008-06-15 10:12:27 +00:00
private $tokens ;
2005-01-31 07:39:03 +00:00
2005-02-12 04:12:24 +00:00
// This function is called by the code that's interested in the result of the parse operation.
function get_parsed_array (){
return $this -> tokens ;
}
2005-01-31 07:39:03 +00:00
2005-02-12 04:12:24 +00:00
/*
* Functions below this are part of the state machine for the parse
* operation and should not be called directly .
*/
2005-01-31 07:39:03 +00:00
2005-02-12 04:12:24 +00:00
// Base state. No output emitted.
function accept () {
return true ;
}
2005-01-31 07:39:03 +00:00
2005-02-28 12:40:29 +00:00
// State for handling datefrom:foo constructs. Potentially emits a token.
function indatefrom ( $content ){
if ( strlen ( $content ) < 10 ) { // State exit or missing parameter.
return true ;
}
// Strip off the datefrom: part and add the reminder to the parsed token array
$param = trim ( substr ( $content , 9 ));
$this -> tokens [] = new search_token ( TOKEN_DATEFROM , $param );
return true ;
}
// State for handling dateto:foo constructs. Potentially emits a token.
function indateto ( $content ){
if ( strlen ( $content ) < 8 ) { // State exit or missing parameter.
return true ;
}
// Strip off the dateto: part and add the reminder to the parsed token array
$param = trim ( substr ( $content , 7 ));
$this -> tokens [] = new search_token ( TOKEN_DATETO , $param );
return true ;
}
2005-03-03 12:22:13 +00:00
// State for handling instance:foo constructs. Potentially emits a token.
function ininstance ( $content ){
if ( strlen ( $content ) < 10 ) { // State exit or missing parameter.
return true ;
}
// Strip off the instance: part and add the reminder to the parsed token array
$param = trim ( substr ( $content , 9 ));
$this -> tokens [] = new search_token ( TOKEN_INSTANCE , $param );
return true ;
}
2005-02-27 09:57:31 +00:00
// State for handling userid:foo constructs. Potentially emits a token.
function inuserid ( $content ){
if ( strlen ( $content ) < 8 ) { // State exit or missing parameter.
return true ;
}
// Strip off the userid: part and add the reminder to the parsed token array
$param = trim ( substr ( $content , 7 ));
$this -> tokens [] = new search_token ( TOKEN_USERID , $param );
return true ;
}
2005-01-31 07:39:03 +00:00
2005-02-12 04:12:24 +00:00
// State for handling user:foo constructs. Potentially emits a token.
function inusername ( $content ){
if ( strlen ( $content ) < 6 ) { // State exit or missing parameter.
return true ;
}
// Strip off the user: part and add the reminder to the parsed token array
$param = trim ( substr ( $content , 5 ));
$this -> tokens [] = new search_token ( TOKEN_USER , $param );
return true ;
}
// State for handling meta:foo constructs. Potentially emits a token.
2009-11-01 11:31:16 +00:00
function inmeta ( $content ){
2005-02-12 04:12:24 +00:00
if ( strlen ( $content ) < 9 ) { // Missing parameter.
return true ;
}
// Strip off the meta: part and add the reminder to the parsed token array.
$param = trim ( substr ( $content , 8 ));
$this -> tokens [] = new search_token ( TOKEN_META , $param );
return true ;
}
// State entered when we've seen a required string (+foo). Potentially
// emits a token.
function inrequired ( $content ){
if ( strlen ( $content ) < 2 ) { // State exit or missing parameter, don't emit.
return true ;
}
// Strip off the + sign and add the reminder to the parsed token array.
$this -> tokens [] = new search_token ( TOKEN_EXACT , substr ( $content , 1 ));
return true ;
2009-11-01 11:31:16 +00:00
}
2005-02-12 04:12:24 +00:00
2009-11-01 11:31:16 +00:00
// State entered when we've seen an excluded string (-foo). Potentially
2005-02-12 04:12:24 +00:00
// emits a token.
function inexcluded ( $content ){
if ( strlen ( $content ) < 2 ) { // State exit or missing parameter.
return true ;
}
// Strip off the -sign and add the reminder to the parsed token array.
$this -> tokens [] = new search_token ( TOKEN_NEGATE , substr ( $content , 1 ));
return true ;
2009-11-01 11:31:16 +00:00
}
2005-01-31 07:39:03 +00:00
2005-02-12 04:12:24 +00:00
// State entered when we've seen a quoted string. Potentially emits a token.
function inquotedstring ( $content ){
if ( strlen ( $content ) < 2 ) { // State exit or missing parameter.
return true ;
}
// Strip off the opening quote and add the reminder to the parsed token array.
$this -> tokens [] = new search_token ( TOKEN_STRING , substr ( $content , 1 ));
return true ;
2009-11-01 11:31:16 +00:00
}
2005-01-31 07:39:03 +00:00
2005-02-12 04:12:24 +00:00
// State entered when we've seen an ordinary, non-quoted word. Potentially
// emits a token.
function plainstring ( $content ){
2008-04-16 03:37:45 +00:00
if ( trim ( $content ) === '' ) { // State exit
2005-02-12 04:12:24 +00:00
return true ;
}
// Add the string to the parsed token array.
$this -> tokens [] = new search_token ( TOKEN_STRING , $content );
return true ;
2009-11-01 11:31:16 +00:00
}
2005-01-31 07:39:03 +00:00
}
2008-06-15 10:12:27 +00:00
/**
* Primitive function to generate a SQL string from a parse tree
* using TEXT indexes . If searches aren ' t suitable to use TEXT
* this function calls the default search_generate_SQL () one .
2009-11-01 11:31:16 +00:00
*
2015-01-22 15:10:00 +08:00
* @ deprecated since Moodle 2.9 MDL - 48939
* @ todo MDL - 48940 This will be deleted in Moodle 3.2
* @ see search_generate_SQL ()
2008-06-15 10:12:27 +00:00
*/
2007-03-09 18:21:06 +00:00
function search_generate_text_SQL ( $parsetree , $datafield , $metafield , $mainidfield , $useridfield ,
$userfirstnamefield , $userlastnamefield , $timefield , $instancefield ) {
2015-01-22 15:10:00 +08:00
debugging ( 'search_generate_text_SQL() is deprecated, please use search_generate_SQL() instead.' , DEBUG_DEVELOPER );
2007-03-09 18:21:06 +00:00
2015-01-22 15:10:00 +08:00
return search_generate_SQL ( $parsetree , $datafield , $metafield , $mainidfield , $useridfield ,
$userfirstnamefield , $userlastnamefield , $timefield , $instancefield );
2007-03-09 18:21:06 +00:00
}
2005-01-31 07:39:03 +00:00
2008-06-15 10:12:27 +00:00
/**
2009-11-01 11:31:16 +00:00
* Primitive function to generate a SQL string from a parse tree .
* Parameters :
2008-06-15 10:12:27 +00:00
*
2009-11-01 11:31:16 +00:00
* $parsetree should be a parse tree generated by a
* search_lexer / search_parser combination .
2008-06-15 10:12:27 +00:00
* Other fields are database table names to search .
2009-05-26 03:57:03 +00:00
*
* @ global object
* @ global object
2008-06-15 10:12:27 +00:00
*/
2005-02-12 04:12:24 +00:00
function search_generate_SQL ( $parsetree , $datafield , $metafield , $mainidfield , $useridfield ,
2005-03-03 12:22:13 +00:00
$userfirstnamefield , $userlastnamefield , $timefield , $instancefield ) {
2008-06-02 21:56:06 +00:00
global $CFG , $DB ;
2008-06-15 10:12:27 +00:00
static $p = 0 ;
2005-02-12 04:12:24 +00:00
2008-06-15 10:12:27 +00:00
if ( $DB -> sql_regex_supported ()) {
$REGEXP = $DB -> sql_regex ( true );
$NOTREGEXP = $DB -> sql_regex ( false );
2005-01-31 07:39:03 +00:00
}
2008-06-15 10:12:27 +00:00
$params = array ();
2005-01-31 07:39:03 +00:00
2005-02-12 04:12:24 +00:00
$ntokens = count ( $parsetree );
if ( $ntokens == 0 ) {
return " " ;
}
$SQLString = '' ;
for ( $i = 0 ; $i < $ntokens ; $i ++ ){
if ( $i > 0 ) { // We have more than one clause, need to tack on AND
$SQLString .= ' AND ' ;
}
$type = $parsetree [ $i ] -> getType ();
$value = $parsetree [ $i ] -> getValue ();
2006-10-31 20:17:03 +00:00
/// Under Oracle and MSSQL, transform TOKEN searches into STRING searches and trim +- chars
2008-06-15 10:12:27 +00:00
if ( ! $DB -> sql_regex_supported ()) {
2006-10-31 20:17:03 +00:00
$value = trim ( $value , '+-' );
if ( $type == TOKEN_EXACT ) {
$type = TOKEN_STRING ;
}
}
2008-06-15 10:12:27 +00:00
$name1 = 'sq' . $p ++ ;
$name2 = 'sq' . $p ++ ;
2005-02-12 04:12:24 +00:00
switch ( $type ){
2009-11-01 11:31:16 +00:00
case TOKEN_STRING :
2010-09-04 12:50:23 +00:00
$SQLString .= " (( " . $DB -> sql_like ( $datafield , " : $name1 " , false ) . " ) OR ( " . $DB -> sql_like ( $metafield , " : $name2 " , false ) . " )) " ;
2008-06-15 10:12:27 +00:00
$params [ $name1 ] = " % $value % " ;
$params [ $name2 ] = " % $value % " ;
2005-02-12 04:12:24 +00:00
break ;
2009-11-01 11:31:16 +00:00
case TOKEN_EXACT :
2008-06-15 10:12:27 +00:00
$SQLString .= " (( $datafield $REGEXP : $name1 ) OR ( $metafield $REGEXP : $name2 )) " ;
$params [ $name1 ] = " [[:<:]] " . $value . " [[:>:]] " ;
$params [ $name2 ] = " [[:<:]] " . $value . " [[:>:]] " ;
2009-11-01 11:31:16 +00:00
break ;
case TOKEN_META :
2005-02-12 04:12:24 +00:00
if ( $metafield != '' ) {
2010-09-04 12:50:23 +00:00
$SQLString .= " ( " . $DB -> sql_like ( $metafield , " : $name1 " , false ) . " ) " ;
2008-06-15 10:12:27 +00:00
$params [ $name1 ] = " % $value % " ;
2005-02-12 04:12:24 +00:00
}
break ;
2009-11-01 11:31:16 +00:00
case TOKEN_USER :
2010-09-04 12:50:23 +00:00
$SQLString .= " (( $mainidfield = $useridfield ) AND (( " . $DB -> sql_like ( $userfirstnamefield , " : $name1 " , false ) . " ) OR ( " . $DB -> sql_like ( $userlastnamefield , " : $name2 " , false ) . " ))) " ;
2008-06-15 10:12:27 +00:00
$params [ $name1 ] = " % $value % " ;
$params [ $name2 ] = " % $value % " ;
2009-11-01 11:31:16 +00:00
break ;
case TOKEN_USERID :
2008-06-15 10:12:27 +00:00
$SQLString .= " ( $useridfield = : $name1 ) " ;
$params [ $name1 ] = $value ;
2009-11-01 11:31:16 +00:00
break ;
case TOKEN_INSTANCE :
2008-06-15 10:12:27 +00:00
$SQLString .= " ( $instancefield = : $name1 ) " ;
$params [ $name1 ] = $value ;
2009-11-01 11:31:16 +00:00
break ;
case TOKEN_DATETO :
2008-06-15 10:12:27 +00:00
$SQLString .= " ( $timefield <= : $name1 ) " ;
$params [ $name1 ] = $value ;
2009-11-01 11:31:16 +00:00
break ;
case TOKEN_DATEFROM :
2008-06-15 10:12:27 +00:00
$SQLString .= " ( $timefield >= : $name1 ) " ;
$params [ $name1 ] = $value ;
2009-11-01 11:31:16 +00:00
break ;
case TOKEN_NEGATE :
2010-09-04 12:50:23 +00:00
$SQLString .= " (NOT (( " . $DB -> sql_like ( $datafield , " : $name1 " , false ) . " ) OR ( " . $DB -> sql_like ( $metafield , " : $name2 " , false ) . " ))) " ;
2008-06-15 10:12:27 +00:00
$params [ $name1 ] = " % $value % " ;
$params [ $name2 ] = " % $value % " ;
2009-11-01 11:31:16 +00:00
break ;
2005-02-12 04:12:24 +00:00
default :
return '' ;
2009-11-01 11:31:16 +00:00
}
}
2008-06-15 10:12:27 +00:00
return array ( $SQLString , $params );
2005-01-31 07:39:03 +00:00
}