0byt3m1n1
Path:
/
data
/
applications
/
aps
/
typo3
/
4.5.5-0
/
standard
/
htdocs
/
t3lib
/
[
Home
]
File: class.t3lib_sqlparser.php
<?php /*************************************************************** * Copyright notice * * (c) 2004-2011 Kasper Skårhøj (kasperYYYY@typo3.com) * All rights reserved * * This script is part of the TYPO3 project. The TYPO3 project 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 2 of the License, or * (at your option) any later version. * * The GNU General Public License can be found at * http://www.gnu.org/copyleft/gpl.html. * A copy is found in the textfile GPL.txt and important notices to the license * from the author is found in LICENSE.txt distributed with these scripts. * * * This script 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. * * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ /** * TYPO3 SQL parser * * $Id$ * * @author Kasper Skårhøj <kasperYYYY@typo3.com> */ /** * [CLASS/FUNCTION INDEX of SCRIPT] * * * * 107: class t3lib_sqlparser * * SECTION: SQL Parsing, full queries * 129: function parseSQL($parseString) * 192: function parseSELECT($parseString) * 261: function parseUPDATE($parseString) * 315: function parseINSERT($parseString) * 375: function parseDELETE($parseString) * 413: function parseEXPLAIN($parseString) * 435: function parseCREATETABLE($parseString) * 514: function parseALTERTABLE($parseString) * 583: function parseDROPTABLE($parseString) * 616: function parseCREATEDATABASE($parseString) * * SECTION: SQL Parsing, helper functions for parts of queries * 670: function parseFieldList(&$parseString, $stopRegex='') * 791: function parseFromTables(&$parseString, $stopRegex='') * 882: function parseWhereClause(&$parseString, $stopRegex='') * 990: function parseFieldDef(&$parseString, $stopRegex='') * * SECTION: Parsing: Helper functions * 1053: function nextPart(&$parseString,$regex,$trimAll=FALSE) * 1068: function getValue(&$parseString,$comparator='') * 1127: function getValueInQuotes(&$parseString,$quote) * 1153: function parseStripslashes($str) * 1167: function compileAddslashes($str) * 1182: function parseError($msg,$restQuery) * 1196: function trimSQL($str) * * SECTION: Compiling queries * 1225: function compileSQL($components) * 1263: function compileSELECT($components) * 1294: function compileUPDATE($components) * 1322: function compileINSERT($components) * 1362: function compileDELETE($components) * 1382: function compileCREATETABLE($components) * 1415: function compileALTERTABLE($components) * * SECTION: Compiling queries, helper functions for parts of queries * 1468: function compileFieldList($selectFields) * 1510: function compileFromTables($tablesArray) * 1551: function compileWhereClause($clauseArray) * 1605: function compileFieldCfg($fieldCfg) * * SECTION: Debugging * 1654: function debug_parseSQLpart($part,$str) * 1679: function debug_parseSQLpartCompare($str,$newStr,$caseInsensitive=FALSE) * 1712: function debug_testSQL($SQLquery) * * TOTAL FUNCTIONS: 35 * (This index is automatically created/updated by the extension "extdeveval") * */ /** * TYPO3 SQL parser class. * * @author Kasper Skårhøj <kasperYYYY@typo3.com> * @package TYPO3 * @subpackage t3lib */ class t3lib_sqlparser { // Parser: var $parse_error = ''; // Parsing error string var $lastStopKeyWord = ''; // Last stop keyword used. /************************************* * * SQL Parsing, full queries * **************************************/ /** * Parses any single SQL query * * @param string SQL query * @return array Result array with all the parts in - or error message string * @see compileSQL(), debug_testSQL() */ public function parseSQL($parseString) { // Prepare variables: $parseString = $this->trimSQL($parseString); $this->parse_error = ''; $result = array(); // Finding starting keyword of string: $_parseString = $parseString; // Protecting original string... $keyword = $this->nextPart($_parseString, '^(SELECT|UPDATE|INSERT[[:space:]]+INTO|DELETE[[:space:]]+FROM|EXPLAIN|DROP[[:space:]]+TABLE|CREATE[[:space:]]+TABLE|CREATE[[:space:]]+DATABASE|ALTER[[:space:]]+TABLE|TRUNCATE[[:space:]]+TABLE)[[:space:]]+'); $keyword = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $keyword)); switch ($keyword) { case 'SELECT': // Parsing SELECT query: $result = $this->parseSELECT($parseString); break; case 'UPDATE': // Parsing UPDATE query: $result = $this->parseUPDATE($parseString); break; case 'INSERTINTO': // Parsing INSERT query: $result = $this->parseINSERT($parseString); break; case 'DELETEFROM': // Parsing DELETE query: $result = $this->parseDELETE($parseString); break; case 'EXPLAIN': // Parsing EXPLAIN SELECT query: $result = $this->parseEXPLAIN($parseString); break; case 'DROPTABLE': // Parsing DROP TABLE query: $result = $this->parseDROPTABLE($parseString); break; case 'ALTERTABLE': // Parsing ALTER TABLE query: $result = $this->parseALTERTABLE($parseString); break; case 'CREATETABLE': // Parsing CREATE TABLE query: $result = $this->parseCREATETABLE($parseString); break; case 'CREATEDATABASE': // Parsing CREATE DATABASE query: $result = $this->parseCREATEDATABASE($parseString); break; case 'TRUNCATETABLE': // Parsing TRUNCATE TABLE query: $result = $this->parseTRUNCATETABLE($parseString); break; default: $result = $this->parseError('"' . $keyword . '" is not a keyword', $parseString); break; } return $result; } /** * Parsing SELECT query * * @param string SQL string with SELECT query to parse * @param array Array holding references to either named (:name) or question mark (?) parameters found * @return mixed Returns array with components of SELECT query on success, otherwise an error message string. * @see compileSELECT() */ protected function parseSELECT($parseString, &$parameterReferences = NULL) { // Removing SELECT: $parseString = $this->trimSQL($parseString); $parseString = ltrim(substr($parseString, 6)); // Init output variable: $result = array(); if ($parameterReferences === NULL) { $result['parameters'] = array(); $parameterReferences = &$result['parameters']; } $result['type'] = 'SELECT'; // Looking for STRAIGHT_JOIN keyword: $result['STRAIGHT_JOIN'] = $this->nextPart($parseString, '^(STRAIGHT_JOIN)[[:space:]]+'); // Select fields: $result['SELECT'] = $this->parseFieldList($parseString, '^(FROM)[[:space:]]+'); if ($this->parse_error) { return $this->parse_error; } // Continue if string is not ended: if ($parseString) { // Get table list: $result['FROM'] = $this->parseFromTables($parseString, '^(WHERE)[[:space:]]+'); if ($this->parse_error) { return $this->parse_error; } // If there are more than just the tables (a WHERE clause that would be...) if ($parseString) { // Get WHERE clause: $result['WHERE'] = $this->parseWhereClause($parseString, '^(GROUP[[:space:]]+BY|ORDER[[:space:]]+BY|LIMIT)[[:space:]]+', $parameterReferences); if ($this->parse_error) { return $this->parse_error; } // If the WHERE clause parsing was stopped by GROUP BY, ORDER BY or LIMIT, then proceed with parsing: if ($this->lastStopKeyWord) { // GROUP BY parsing: if ($this->lastStopKeyWord == 'GROUPBY') { $result['GROUPBY'] = $this->parseFieldList($parseString, '^(ORDER[[:space:]]+BY|LIMIT)[[:space:]]+'); if ($this->parse_error) { return $this->parse_error; } } // ORDER BY parsing: if ($this->lastStopKeyWord == 'ORDERBY') { $result['ORDERBY'] = $this->parseFieldList($parseString, '^(LIMIT)[[:space:]]+'); if ($this->parse_error) { return $this->parse_error; } } // LIMIT parsing: if ($this->lastStopKeyWord == 'LIMIT') { if (preg_match('/^([0-9]+|[0-9]+[[:space:]]*,[[:space:]]*[0-9]+)$/', trim($parseString))) { $result['LIMIT'] = $parseString; } else { return $this->parseError('No value for limit!', $parseString); } } } } } else { return $this->parseError('No table to select from!', $parseString); } // Store current parseString in the result array for possible further processing (e.g., subquery support by DBAL) $result['parseString'] = $parseString; // Return result: return $result; } /** * Parsing UPDATE query * * @param string SQL string with UPDATE query to parse * @return mixed Returns array with components of UPDATE query on success, otherwise an error message string. * @see compileUPDATE() */ protected function parseUPDATE($parseString) { // Removing UPDATE $parseString = $this->trimSQL($parseString); $parseString = ltrim(substr($parseString, 6)); // Init output variable: $result = array(); $result['type'] = 'UPDATE'; // Get table: $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+'); // Continue if string is not ended: if ($result['TABLE']) { if ($parseString && $this->nextPart($parseString, '^(SET)[[:space:]]+')) { $comma = TRUE; // Get field/value pairs: while ($comma) { if ($fieldName = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]*=')) { $this->nextPart($parseString, '^(=)'); // Strip of "=" sign. $value = $this->getValue($parseString); $result['FIELDS'][$fieldName] = $value; } else { return $this->parseError('No fieldname found', $parseString); } $comma = $this->nextPart($parseString, '^(,)'); } // WHERE if ($this->nextPart($parseString, '^(WHERE)')) { $result['WHERE'] = $this->parseWhereClause($parseString); if ($this->parse_error) { return $this->parse_error; } } } else { return $this->parseError('Query missing SET...', $parseString); } } else { return $this->parseError('No table found!', $parseString); } // Should be no more content now: if ($parseString) { return $this->parseError('Still content in clause after parsing!', $parseString); } // Return result: return $result; } /** * Parsing INSERT query * * @param string SQL string with INSERT query to parse * @return mixed Returns array with components of INSERT query on success, otherwise an error message string. * @see compileINSERT() */ protected function parseINSERT($parseString) { // Removing INSERT $parseString = $this->trimSQL($parseString); $parseString = ltrim(substr(ltrim(substr($parseString, 6)), 4)); // Init output variable: $result = array(); $result['type'] = 'INSERT'; // Get table: $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)([[:space:]]+|\()'); if ($result['TABLE']) { if ($this->nextPart($parseString, '^(VALUES)([[:space:]]+|\()')) { // In this case there are no field names mentioned in the SQL! // Get values/fieldnames (depending...) $result['VALUES_ONLY'] = $this->getValue($parseString, 'IN'); if ($this->parse_error) { return $this->parse_error; } if (preg_match('/^,/', $parseString)) { $result['VALUES_ONLY'] = array($result['VALUES_ONLY']); $result['EXTENDED'] = '1'; while ($this->nextPart($parseString, '^(,)') === ',') { $result['VALUES_ONLY'][] = $this->getValue($parseString, 'IN'); if ($this->parse_error) { return $this->parse_error; } } } } else { // There are apparently fieldnames listed: $fieldNames = $this->getValue($parseString, '_LIST'); if ($this->parse_error) { return $this->parse_error; } if ($this->nextPart($parseString, '^(VALUES)([[:space:]]+|\()')) { // "VALUES" keyword binds the fieldnames to values: $result['FIELDS'] = array(); do { $values = $this->getValue($parseString, 'IN'); // Using the "getValue" function to get the field list... if ($this->parse_error) { return $this->parse_error; } $insertValues = array(); foreach ($fieldNames as $k => $fN) { if (preg_match('/^[[:alnum:]_]+$/', $fN)) { if (isset($values[$k])) { if (!isset($insertValues[$fN])) { $insertValues[$fN] = $values[$k]; } else { return $this->parseError('Fieldname ("' . $fN . '") already found in list!', $parseString); } } else { return $this->parseError('No value set!', $parseString); } } else { return $this->parseError('Invalid fieldname ("' . $fN . '")', $parseString); } } if (isset($values[$k + 1])) { return $this->parseError('Too many values in list!', $parseString); } $result['FIELDS'][] = $insertValues; } while ($this->nextPart($parseString, '^(,)') === ','); if (count($result['FIELDS']) === 1) { $result['FIELDS'] = $result['FIELDS'][0]; } else { $result['EXTENDED'] = '1'; } } else { return $this->parseError('VALUES keyword expected', $parseString); } } } else { return $this->parseError('No table found!', $parseString); } // Should be no more content now: if ($parseString) { return $this->parseError('Still content after parsing!', $parseString); } // Return result return $result; } /** * Parsing DELETE query * * @param string SQL string with DELETE query to parse * @return mixed Returns array with components of DELETE query on success, otherwise an error message string. * @see compileDELETE() */ protected function parseDELETE($parseString) { // Removing DELETE $parseString = $this->trimSQL($parseString); $parseString = ltrim(substr(ltrim(substr($parseString, 6)), 4)); // Init output variable: $result = array(); $result['type'] = 'DELETE'; // Get table: $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+'); if ($result['TABLE']) { // WHERE if ($this->nextPart($parseString, '^(WHERE)')) { $result['WHERE'] = $this->parseWhereClause($parseString); if ($this->parse_error) { return $this->parse_error; } } } else { return $this->parseError('No table found!', $parseString); } // Should be no more content now: if ($parseString) { return $this->parseError('Still content in clause after parsing!', $parseString); } // Return result: return $result; } /** * Parsing EXPLAIN query * * @param string SQL string with EXPLAIN query to parse * @return mixed Returns array with components of EXPLAIN query on success, otherwise an error message string. * @see parseSELECT() */ protected function parseEXPLAIN($parseString) { // Removing EXPLAIN $parseString = $this->trimSQL($parseString); $parseString = ltrim(substr($parseString, 6)); // Init output variable: $result = $this->parseSELECT($parseString); if (is_array($result)) { $result['type'] = 'EXPLAIN'; } return $result; } /** * Parsing CREATE TABLE query * * @param string SQL string starting with CREATE TABLE * @return mixed Returns array with components of CREATE TABLE query on success, otherwise an error message string. * @see compileCREATETABLE() */ protected function parseCREATETABLE($parseString) { // Removing CREATE TABLE $parseString = $this->trimSQL($parseString); $parseString = ltrim(substr(ltrim(substr($parseString, 6)), 5)); // Init output variable: $result = array(); $result['type'] = 'CREATETABLE'; // Get table: $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]*\(', TRUE); if ($result['TABLE']) { // While the parseString is not yet empty: while (strlen($parseString) > 0) { if ($key = $this->nextPart($parseString, '^(KEY|PRIMARY KEY|UNIQUE KEY|UNIQUE)([[:space:]]+|\()')) { // Getting key $key = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $key)); switch ($key) { case 'PRIMARYKEY': $result['KEYS']['PRIMARYKEY'] = $this->getValue($parseString, '_LIST'); if ($this->parse_error) { return $this->parse_error; } break; case 'UNIQUE': case 'UNIQUEKEY': if ($keyName = $this->nextPart($parseString, '^([[:alnum:]_]+)([[:space:]]+|\()')) { $result['KEYS']['UNIQUE'] = array($keyName => $this->getValue($parseString, '_LIST')); if ($this->parse_error) { return $this->parse_error; } } else { return $this->parseError('No keyname found', $parseString); } break; case 'KEY': if ($keyName = $this->nextPart($parseString, '^([[:alnum:]_]+)([[:space:]]+|\()')) { $result['KEYS'][$keyName] = $this->getValue($parseString, '_LIST', 'INDEX'); if ($this->parse_error) { return $this->parse_error; } } else { return $this->parseError('No keyname found', $parseString); } break; } } elseif ($fieldName = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+')) { // Getting field: $result['FIELDS'][$fieldName]['definition'] = $this->parseFieldDef($parseString); if ($this->parse_error) { return $this->parse_error; } } // Finding delimiter: $delim = $this->nextPart($parseString, '^(,|\))'); if (!$delim) { return $this->parseError('No delimiter found', $parseString); } elseif ($delim == ')') { break; } } // Finding what is after the table definition - table type in MySQL if ($delim == ')') { if ($this->nextPart($parseString, '^((ENGINE|TYPE)[[:space:]]*=)')) { $result['tableType'] = $parseString; $parseString = ''; } } else { return $this->parseError('No fieldname found!', $parseString); } // Getting table type } else { return $this->parseError('No table found!', $parseString); } // Should be no more content now: if ($parseString) { return $this->parseError('Still content in clause after parsing!', $parseString); } return $result; } /** * Parsing ALTER TABLE query * * @param string SQL string starting with ALTER TABLE * @return mixed Returns array with components of ALTER TABLE query on success, otherwise an error message string. * @see compileALTERTABLE() */ protected function parseALTERTABLE($parseString) { // Removing ALTER TABLE $parseString = $this->trimSQL($parseString); $parseString = ltrim(substr(ltrim(substr($parseString, 5)), 5)); // Init output variable: $result = array(); $result['type'] = 'ALTERTABLE'; // Get table: $hasBackquote = ($this->nextPart($parseString, '^(`)') === '`'); $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)' . ($hasBackquote ? '`' : '') . '[[:space:]]+'); if ($hasBackquote && $this->nextPart($parseString, '^(`)') !== '`') { return $this->parseError('No end backquote found!', $parseString); } if ($result['TABLE']) { if ($result['action'] = $this->nextPart($parseString, '^(CHANGE|DROP[[:space:]]+KEY|DROP[[:space:]]+PRIMARY[[:space:]]+KEY|ADD[[:space:]]+KEY|ADD[[:space:]]+PRIMARY[[:space:]]+KEY|ADD[[:space:]]+UNIQUE|DROP|ADD|RENAME|DEFAULT[[:space:]]+CHARACTER[[:space:]]+SET|ENGINE)([[:space:]]+|\(|=)')) { $actionKey = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $result['action'])); // Getting field: if (t3lib_div::inList('ADDPRIMARYKEY,DROPPRIMARYKEY,ENGINE', $actionKey) || $fieldKey = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+')) { switch ($actionKey) { case 'ADD': $result['FIELD'] = $fieldKey; $result['definition'] = $this->parseFieldDef($parseString); if ($this->parse_error) { return $this->parse_error; } break; case 'DROP': case 'RENAME': $result['FIELD'] = $fieldKey; break; case 'CHANGE': $result['FIELD'] = $fieldKey; if ($result['newField'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+')) { $result['definition'] = $this->parseFieldDef($parseString); if ($this->parse_error) { return $this->parse_error; } } else { return $this->parseError('No NEW field name found', $parseString); } break; case 'ADDKEY': case 'ADDPRIMARYKEY': case 'ADDUNIQUE': $result['KEY'] = $fieldKey; $result['fields'] = $this->getValue($parseString, '_LIST', 'INDEX'); if ($this->parse_error) { return $this->parse_error; } break; case 'DROPKEY': $result['KEY'] = $fieldKey; break; case 'DROPPRIMARYKEY': // ??? todo! break; case 'DEFAULTCHARACTERSET': $result['charset'] = $fieldKey; break; case 'ENGINE': $result['engine'] = $this->nextPart($parseString, '^=[[:space:]]*([[:alnum:]]+)[[:space:]]+', TRUE); break; } } else { return $this->parseError('No field name found', $parseString); } } else { return $this->parseError('No action CHANGE, DROP or ADD found!', $parseString); } } else { return $this->parseError('No table found!', $parseString); } // Should be no more content now: if ($parseString) { return $this->parseError('Still content in clause after parsing!', $parseString); } return $result; } /** * Parsing DROP TABLE query * * @param string SQL string starting with DROP TABLE * @return mixed Returns array with components of DROP TABLE query on success, otherwise an error message string. */ protected function parseDROPTABLE($parseString) { // Removing DROP TABLE $parseString = $this->trimSQL($parseString); $parseString = ltrim(substr(ltrim(substr($parseString, 4)), 5)); // Init output variable: $result = array(); $result['type'] = 'DROPTABLE'; // IF EXISTS $result['ifExists'] = $this->nextPart($parseString, '^(IF[[:space:]]+EXISTS[[:space:]]+)'); // Get table: $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+'); if ($result['TABLE']) { // Should be no more content now: if ($parseString) { return $this->parseError('Still content in clause after parsing!', $parseString); } return $result; } else { return $this->parseError('No table found!', $parseString); } } /** * Parsing CREATE DATABASE query * * @param string SQL string starting with CREATE DATABASE * @return mixed Returns array with components of CREATE DATABASE query on success, otherwise an error message string. */ protected function parseCREATEDATABASE($parseString) { // Removing CREATE DATABASE $parseString = $this->trimSQL($parseString); $parseString = ltrim(substr(ltrim(substr($parseString, 6)), 8)); // Init output variable: $result = array(); $result['type'] = 'CREATEDATABASE'; // Get table: $result['DATABASE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+'); if ($result['DATABASE']) { // Should be no more content now: if ($parseString) { return $this->parseError('Still content in clause after parsing!', $parseString); } return $result; } else { return $this->parseError('No database found!', $parseString); } } /** * Parsing TRUNCATE TABLE query * * @param string SQL string starting with TRUNCATE TABLE * @return mixed Returns array with components of TRUNCATE TABLE query on success, otherwise an error message string. */ protected function parseTRUNCATETABLE($parseString) { // Removing TRUNCATE TABLE $parseString = $this->trimSQL($parseString); $parseString = ltrim(substr(ltrim(substr($parseString, 8)), 5)); // Init output variable: $result = array(); $result['type'] = 'TRUNCATETABLE'; // Get table: $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+'); if ($result['TABLE']) { // Should be no more content now: if ($parseString) { return $this->parseError('Still content in clause after parsing!', $parseString); } return $result; } else { return $this->parseError('No table found!', $parseString); } } /************************************** * * SQL Parsing, helper functions for parts of queries * **************************************/ /** * Parsing the fields in the "SELECT [$selectFields] FROM" part of a query into an array. * The output from this function can be compiled back into a field list with ->compileFieldList() * Will detect the keywords "DESC" and "ASC" after the table name; thus is can be used for parsing the more simply ORDER BY and GROUP BY field lists as well! * * @param string The string with fieldnames, eg. "title, uid AS myUid, max(tstamp), count(*)" etc. NOTICE: passed by reference! * @param string Regular expressing to STOP parsing, eg. '^(FROM)([[:space:]]*)' * @return array If successful parsing, returns an array, otherwise an error string. * @see compileFieldList() */ public function parseFieldList(&$parseString, $stopRegex = '') { $stack = array(); // Contains the parsed content if (strlen($parseString) == 0) { return $stack; } // FIXME - should never happen, why does it? $pnt = 0; // Pointer to positions in $stack $level = 0; // Indicates the parenthesis level we are at. $loopExit = 0; // Recursivity brake. // Prepare variables: $parseString = $this->trimSQL($parseString); $this->lastStopKeyWord = ''; $this->parse_error = ''; // Parse any SQL hint / comments $stack[$pnt]['comments'] = $this->nextPart($parseString, '^(\/\*.*\*\/)'); // $parseString is continously shortend by the process and we keep parsing it till it is zero: while (strlen($parseString)) { // Checking if we are inside / outside parenthesis (in case of a function like count(), max(), min() etc...): if ($level > 0) { // Inside parenthesis here (does NOT detect if values in quotes are used, the only token is ")" or "("): // Accumulate function content until next () parenthesis: $funcContent = $this->nextPart($parseString, '^([^()]*.)'); $stack[$pnt]['func_content.'][] = array( 'level' => $level, 'func_content' => substr($funcContent, 0, -1) ); $stack[$pnt]['func_content'] .= $funcContent; // Detecting ( or ) switch (substr($stack[$pnt]['func_content'], -1)) { case '(': $level++; break; case ')': $level--; if (!$level) { // If this was the last parenthesis: $stack[$pnt]['func_content'] = substr($stack[$pnt]['func_content'], 0, -1); $parseString = ltrim($parseString); // Remove any whitespace after the parenthesis. } break; } } else { // Outside parenthesis, looking for next field: // Looking for a flow-control construct (only known constructs supported) if (preg_match('/^case([[:space:]][[:alnum:]\*._]+)?[[:space:]]when/i', $parseString)) { $stack[$pnt]['type'] = 'flow-control'; $stack[$pnt]['flow-control'] = $this->parseCaseStatement($parseString); // Looking for "AS" alias: if ($as = $this->nextPart($parseString, '^(AS)[[:space:]]+')) { $stack[$pnt]['as'] = $this->nextPart($parseString, '^([[:alnum:]_]+)(,|[[:space:]]+)'); $stack[$pnt]['as_keyword'] = $as; } } else { // Looking for a known function (only known functions supported) $func = $this->nextPart($parseString, '^(count|max|min|floor|sum|avg)[[:space:]]*\('); if ($func) { $parseString = trim(substr($parseString, 1)); // Strip of "(" $stack[$pnt]['type'] = 'function'; $stack[$pnt]['function'] = $func; $level++; // increse parenthesis level counter. } else { $stack[$pnt]['distinct'] = $this->nextPart($parseString, '^(distinct[[:space:]]+)'); // Otherwise, look for regular fieldname: if (($fieldName = $this->nextPart($parseString, '^([[:alnum:]\*._]+)(,|[[:space:]]+)')) !== '') { $stack[$pnt]['type'] = 'field'; // Explode fieldname into field and table: $tableField = explode('.', $fieldName, 2); if (count($tableField) == 2) { $stack[$pnt]['table'] = $tableField[0]; $stack[$pnt]['field'] = $tableField[1]; } else { $stack[$pnt]['table'] = ''; $stack[$pnt]['field'] = $tableField[0]; } } else { return $this->parseError('No field name found as expected in parseFieldList()', $parseString); } } } } // After a function or field we look for "AS" alias and a comma to separate to the next field in the list: if (!$level) { // Looking for "AS" alias: if ($as = $this->nextPart($parseString, '^(AS)[[:space:]]+')) { $stack[$pnt]['as'] = $this->nextPart($parseString, '^([[:alnum:]_]+)(,|[[:space:]]+)'); $stack[$pnt]['as_keyword'] = $as; } // Looking for "ASC" or "DESC" keywords (for ORDER BY) if ($sDir = $this->nextPart($parseString, '^(ASC|DESC)([[:space:]]+|,)')) { $stack[$pnt]['sortDir'] = $sDir; } // Looking for stop-keywords: if ($stopRegex && $this->lastStopKeyWord = $this->nextPart($parseString, $stopRegex)) { $this->lastStopKeyWord = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $this->lastStopKeyWord)); return $stack; } // Looking for comma (since the stop-keyword did not trigger a return...) if (strlen($parseString) && !$this->nextPart($parseString, '^(,)')) { return $this->parseError('No comma found as expected in parseFieldList()', $parseString); } // Increasing pointer: $pnt++; } // Check recursivity brake: $loopExit++; if ($loopExit > 500) { return $this->parseError('More than 500 loops, exiting prematurely in parseFieldList()...', $parseString); } } // Return result array: return $stack; } /** * Parsing a CASE ... WHEN flow-control construct. * The output from this function can be compiled back with ->compileCaseStatement() * * @param string The string with the CASE ... WHEN construct, eg. "CASE field WHEN 1 THEN 0 ELSE ..." etc. NOTICE: passed by reference! * @return array If successful parsing, returns an array, otherwise an error string. * @see compileCaseConstruct() */ protected function parseCaseStatement(&$parseString) { $result = array(); $result['type'] = $this->nextPart($parseString, '^(case)[[:space:]]+'); if (!preg_match('/^when[[:space:]]+/i', $parseString)) { $value = $this->getValue($parseString); if (!(isset($value[1]) || is_numeric($value[0]))) { $result['case_field'] = $value[0]; } else { $result['case_value'] = $value; } } $result['when'] = array(); while ($this->nextPart($parseString, '^(when)[[:space:]]')) { $when = array(); $when['when_value'] = $this->parseWhereClause($parseString, '^(then)[[:space:]]+'); $when['then_value'] = $this->getValue($parseString); $result['when'][] = $when; } if ($this->nextPart($parseString, '^(else)[[:space:]]+')) { $result['else'] = $this->getValue($parseString); } if (!$this->nextPart($parseString, '^(end)[[:space:]]+')) { return $this->parseError('No "end" keyword found as expected in parseCaseStatement()', $parseString); } return $result; } /** * Parsing the tablenames in the "FROM [$parseString] WHERE" part of a query into an array. * The success of this parsing determines if that part of the query is supported by TYPO3. * * @param string list of tables, eg. "pages, tt_content" or "pages A, pages B". NOTICE: passed by reference! * @param string Regular expressing to STOP parsing, eg. '^(WHERE)([[:space:]]*)' * @return array If successful parsing, returns an array, otherwise an error string. * @see compileFromTables() */ public function parseFromTables(&$parseString, $stopRegex = '') { // Prepare variables: $parseString = $this->trimSQL($parseString); $this->lastStopKeyWord = ''; $this->parse_error = ''; $stack = array(); // Contains the parsed content $pnt = 0; // Pointer to positions in $stack $loopExit = 0; // Recursivity brake. // $parseString is continously shortend by the process and we keep parsing it till it is zero: while (strlen($parseString)) { // Looking for the table: if ($stack[$pnt]['table'] = $this->nextPart($parseString, '^([[:alnum:]_]+)(,|[[:space:]]+)')) { // Looking for stop-keywords before fetching potential table alias: if ($stopRegex && ($this->lastStopKeyWord = $this->nextPart($parseString, $stopRegex))) { $this->lastStopKeyWord = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $this->lastStopKeyWord)); return $stack; } if (!preg_match('/^(LEFT|RIGHT|JOIN|INNER)[[:space:]]+/i', $parseString)) { $stack[$pnt]['as_keyword'] = $this->nextPart($parseString, '^(AS[[:space:]]+)'); $stack[$pnt]['as'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]*'); } } else { return $this->parseError('No table name found as expected in parseFromTables()!', $parseString); } // Looking for JOIN $joinCnt = 0; while ($join = $this->nextPart($parseString, '^(LEFT[[:space:]]+JOIN|LEFT[[:space:]]+OUTER[[:space:]]+JOIN|RIGHT[[:space:]]+JOIN|RIGHT[[:space:]]+OUTER[[:space:]]+JOIN|INNER[[:space:]]+JOIN|JOIN)[[:space:]]+')) { $stack[$pnt]['JOIN'][$joinCnt]['type'] = $join; if ($stack[$pnt]['JOIN'][$joinCnt]['withTable'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+', 1)) { if (!preg_match('/^ON[[:space:]]+/i', $parseString)) { $stack[$pnt]['JOIN'][$joinCnt]['as_keyword'] = $this->nextPart($parseString, '^(AS[[:space:]]+)'); $stack[$pnt]['JOIN'][$joinCnt]['as'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+'); } if (!$this->nextPart($parseString, '^(ON[[:space:]]+)')) { return $this->parseError('No join condition found in parseFromTables()!', $parseString); } $stack[$pnt]['JOIN'][$joinCnt]['ON'] = array(); $condition = array('operator' => ''); $parseCondition = TRUE; while ($parseCondition) { if (($fieldName = $this->nextPart($parseString, '^([[:alnum:]._]+)[[:space:]]*(<=|>=|<|>|=|!=)')) !== '') { // Parse field name into field and table: $tableField = explode('.', $fieldName, 2); $condition['left'] = array(); if (count($tableField) == 2) { $condition['left']['table'] = $tableField[0]; $condition['left']['field'] = $tableField[1]; } else { $condition['left']['table'] = ''; $condition['left']['field'] = $tableField[0]; } } else { return $this->parseError('No join field found in parseFromTables()!', $parseString); } // Find "comparator": $condition['comparator'] = $this->nextPart($parseString, '^(<=|>=|<|>|=|!=)'); if (($fieldName = $this->nextPart($parseString, '^([[:alnum:]._]+)')) !== '') { // Parse field name into field and table: $tableField = explode('.', $fieldName, 2); $condition['right'] = array(); if (count($tableField) == 2) { $condition['right']['table'] = $tableField[0]; $condition['right']['field'] = $tableField[1]; } else { $condition['right']['table'] = ''; $condition['right']['field'] = $tableField[0]; } } else { return $this->parseError('No join field found in parseFromTables()!', $parseString); } $stack[$pnt]['JOIN'][$joinCnt]['ON'][] = $condition; if (($operator = $this->nextPart($parseString, '^(AND|OR)')) !== '') { $condition = array('operator' => $operator); } else { $parseCondition = FALSE; } } $joinCnt++; } else { return $this->parseError('No join table found in parseFromTables()!', $parseString); } } // Looking for stop-keywords: if ($stopRegex && $this->lastStopKeyWord = $this->nextPart($parseString, $stopRegex)) { $this->lastStopKeyWord = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $this->lastStopKeyWord)); return $stack; } // Looking for comma: if (strlen($parseString) && !$this->nextPart($parseString, '^(,)')) { return $this->parseError('No comma found as expected in parseFromTables()', $parseString); } // Increasing pointer: $pnt++; // Check recursivity brake: $loopExit++; if ($loopExit > 500) { return $this->parseError('More than 500 loops, exiting prematurely in parseFromTables()...', $parseString); } } // Return result array: return $stack; } /** * Parsing the WHERE clause fields in the "WHERE [$parseString] ..." part of a query into a multidimensional array. * The success of this parsing determines if that part of the query is supported by TYPO3. * * @param string WHERE clause to parse. NOTICE: passed by reference! * @param string Regular expressing to STOP parsing, eg. '^(GROUP BY|ORDER BY|LIMIT)([[:space:]]*)' * @param array Array holding references to either named (:name) or question mark (?) parameters found * @return mixed If successful parsing, returns an array, otherwise an error string. */ public function parseWhereClause(&$parseString, $stopRegex = '', array &$parameterReferences = array()) { // Prepare variables: $parseString = $this->trimSQL($parseString); $this->lastStopKeyWord = ''; $this->parse_error = ''; $stack = array(0 => array()); // Contains the parsed content $pnt = array(0 => 0); // Pointer to positions in $stack $level = 0; // Determines parenthesis level $loopExit = 0; // Recursivity brake. // $parseString is continously shortend by the process and we keep parsing it till it is zero: while (strlen($parseString)) { // Look for next parenthesis level: $newLevel = $this->nextPart($parseString, '^([(])'); if ($newLevel == '(') { // If new level is started, manage stack/pointers: $level++; // Increase level $pnt[$level] = 0; // Reset pointer for this level $stack[$level] = array(); // Reset stack for this level } else { // If no new level is started, just parse the current level: // Find "modifier", eg. "NOT or !" $stack[$level][$pnt[$level]]['modifier'] = trim($this->nextPart($parseString, '^(!|NOT[[:space:]]+)')); // See if condition is EXISTS with a subquery if (preg_match('/^EXISTS[[:space:]]*[(]/i', $parseString)) { $stack[$level][$pnt[$level]]['func']['type'] = $this->nextPart($parseString, '^(EXISTS)[[:space:]]*'); $parseString = trim(substr($parseString, 1)); // Strip of "(" $stack[$level][$pnt[$level]]['func']['subquery'] = $this->parseSELECT($parseString, $parameterReferences); // Seek to new position in parseString after parsing of the subquery $parseString = $stack[$level][$pnt[$level]]['func']['subquery']['parseString']; unset($stack[$level][$pnt[$level]]['func']['subquery']['parseString']); if (!$this->nextPart($parseString, '^([)])')) { return 'No ) parenthesis at end of subquery'; } } else { // See if LOCATE function is found if (preg_match('/^LOCATE[[:space:]]*[(]/i', $parseString)) { $stack[$level][$pnt[$level]]['func']['type'] = $this->nextPart($parseString, '^(LOCATE)[[:space:]]*'); $parseString = trim(substr($parseString, 1)); // Strip of "(" $stack[$level][$pnt[$level]]['func']['substr'] = $this->getValue($parseString); if (!$this->nextPart($parseString, '^(,)')) { return $this->parseError('No comma found as expected in parseWhereClause()', $parseString); } if ($fieldName = $this->nextPart($parseString, '^([[:alnum:]\*._]+)[[:space:]]*')) { // Parse field name into field and table: $tableField = explode('.', $fieldName, 2); if (count($tableField) == 2) { $stack[$level][$pnt[$level]]['func']['table'] = $tableField[0]; $stack[$level][$pnt[$level]]['func']['field'] = $tableField[1]; } else { $stack[$level][$pnt[$level]]['func']['table'] = ''; $stack[$level][$pnt[$level]]['func']['field'] = $tableField[0]; } } else { return $this->parseError('No field name found as expected in parseWhereClause()', $parseString); } if ($this->nextPart($parseString, '^(,)')) { $stack[$level][$pnt[$level]]['func']['pos'] = $this->getValue($parseString); } if (!$this->nextPart($parseString, '^([)])')) { return $this->parseError('No ) parenthesis at end of function', $parseString); } } elseif (preg_match('/^IFNULL[[:space:]]*[(]/i', $parseString)) { $stack[$level][$pnt[$level]]['func']['type'] = $this->nextPart($parseString, '^(IFNULL)[[:space:]]*'); $parseString = trim(substr($parseString, 1)); // Strip of "(" if ($fieldName = $this->nextPart($parseString, '^([[:alnum:]\*._]+)[[:space:]]*')) { // Parse field name into field and table: $tableField = explode('.', $fieldName, 2); if (count($tableField) == 2) { $stack[$level][$pnt[$level]]['func']['table'] = $tableField[0]; $stack[$level][$pnt[$level]]['func']['field'] = $tableField[1]; } else { $stack[$level][$pnt[$level]]['func']['table'] = ''; $stack[$level][$pnt[$level]]['func']['field'] = $tableField[0]; } } else { return $this->parseError('No field name found as expected in parseWhereClause()', $parseString); } if ($this->nextPart($parseString, '^(,)')) { $stack[$level][$pnt[$level]]['func']['default'] = $this->getValue($parseString); } if (!$this->nextPart($parseString, '^([)])')) { return $this->parseError('No ) parenthesis at end of function', $parseString); } } elseif (preg_match('/^FIND_IN_SET[[:space:]]*[(]/i', $parseString)) { $stack[$level][$pnt[$level]]['func']['type'] = $this->nextPart($parseString, '^(FIND_IN_SET)[[:space:]]*'); $parseString = trim(substr($parseString, 1)); // Strip of "(" if ($str = $this->getValue($parseString)) { $stack[$level][$pnt[$level]]['func']['str'] = $str; if ($fieldName = $this->nextPart($parseString, '^,[[:space:]]*([[:alnum:]._]+)[[:space:]]*', TRUE)) { // Parse field name into field and table: $tableField = explode('.', $fieldName, 2); if (count($tableField) == 2) { $stack[$level][$pnt[$level]]['func']['table'] = $tableField[0]; $stack[$level][$pnt[$level]]['func']['field'] = $tableField[1]; } else { $stack[$level][$pnt[$level]]['func']['table'] = ''; $stack[$level][$pnt[$level]]['func']['field'] = $tableField[0]; } } else { return $this->parseError('No field name found as expected in parseWhereClause()', $parseString); } if (!$this->nextPart($parseString, '^([)])')) { return $this->parseError('No ) parenthesis at end of function', $parseString); } } else { return $this->parseError('No item to look for found as expected in parseWhereClause()', $parseString); } } else { // Support calculated value only for: // - "&" (boolean AND) // - "+" (addition) // - "-" (substraction) // - "*" (multiplication) // - "/" (division) // - "%" (modulo) $calcOperators = '&|\+|-|\*|\/|%'; // Fieldname: if (($fieldName = $this->nextPart($parseString, '^([[:alnum:]._]+)([[:space:]]+|' . $calcOperators . '|<=|>=|<|>|=|!=|IS)')) !== '') { // Parse field name into field and table: $tableField = explode('.', $fieldName, 2); if (count($tableField) == 2) { $stack[$level][$pnt[$level]]['table'] = $tableField[0]; $stack[$level][$pnt[$level]]['field'] = $tableField[1]; } else { $stack[$level][$pnt[$level]]['table'] = ''; $stack[$level][$pnt[$level]]['field'] = $tableField[0]; } } else { return $this->parseError('No field name found as expected in parseWhereClause()', $parseString); } // See if the value is calculated: $stack[$level][$pnt[$level]]['calc'] = $this->nextPart($parseString, '^(' . $calcOperators . ')'); if (strlen($stack[$level][$pnt[$level]]['calc'])) { // Finding value for calculation: $calc_value = $this->getValue($parseString); $stack[$level][$pnt[$level]]['calc_value'] = $calc_value; if (count($calc_value) == 1 && is_string($calc_value[0])) { // Value is a field, store it to allow DBAL to post-process it (quoting, remapping) $tableField = explode('.', $calc_value[0], 2); if (count($tableField) == 2) { $stack[$level][$pnt[$level]]['calc_table'] = $tableField[0]; $stack[$level][$pnt[$level]]['calc_field'] = $tableField[1]; } else { $stack[$level][$pnt[$level]]['calc_table'] = ''; $stack[$level][$pnt[$level]]['calc_field'] = $tableField[0]; } } } } // Find "comparator": $comparatorPatterns = array( '<=', '>=', '<', '>', '=', '!=', 'NOT[[:space:]]+IN', 'IN', 'NOT[[:space:]]+LIKE[[:space:]]+BINARY', 'LIKE[[:space:]]+BINARY', 'NOT[[:space:]]+LIKE', 'LIKE', 'IS[[:space:]]+NOT', 'IS', 'BETWEEN', 'NOT[[:space]]+BETWEEN', ); $stack[$level][$pnt[$level]]['comparator'] = $this->nextPart($parseString, '^(' . implode('|', $comparatorPatterns) . ')'); if (strlen($stack[$level][$pnt[$level]]['comparator'])) { if (preg_match('/^CONCAT[[:space:]]*\(/', $parseString)) { $this->nextPart($parseString, '^(CONCAT[[:space:]]?[(])'); $values = array( 'operator' => 'CONCAT', 'args' => array(), ); $cnt = 0; while ($fieldName = $this->nextPart($parseString, '^([[:alnum:]._]+)')) { // Parse field name into field and table: $tableField = explode('.', $fieldName, 2); if (count($tableField) == 2) { $values['args'][$cnt]['table'] = $tableField[0]; $values['args'][$cnt]['field'] = $tableField[1]; } else { $values['args'][$cnt]['table'] = ''; $values['args'][$cnt]['field'] = $tableField[0]; } // Looking for comma: $this->nextPart($parseString, '^(,)'); $cnt++; } // Look for ending parenthesis: $this->nextPart($parseString, '([)])'); $stack[$level][$pnt[$level]]['value'] = $values; } else { if (t3lib_div::inList('IN,NOT IN', $stack[$level][$pnt[$level]]['comparator']) && preg_match('/^[(][[:space:]]*SELECT[[:space:]]+/', $parseString)) { $this->nextPart($parseString, '^([(])'); $stack[$level][$pnt[$level]]['subquery'] = $this->parseSELECT($parseString, $parameterReferences); // Seek to new position in parseString after parsing of the subquery $parseString = $stack[$level][$pnt[$level]]['subquery']['parseString']; unset($stack[$level][$pnt[$level]]['subquery']['parseString']); if (!$this->nextPart($parseString, '^([)])')) { return 'No ) parenthesis at end of subquery'; } } else { if (t3lib_div::inList('BETWEEN,NOT BETWEEN', $stack[$level][$pnt[$level]]['comparator'])) { $stack[$level][$pnt[$level]]['values'] = array(); $stack[$level][$pnt[$level]]['values'][0] = $this->getValue($parseString); if (!$this->nextPart($parseString, '^(AND)')) { return $this->parseError('No AND operator found as expected in parseWhereClause()', $parseString); } $stack[$level][$pnt[$level]]['values'][1] = $this->getValue($parseString); } else { // Finding value for comparator: $stack[$level][$pnt[$level]]['value'] = &$this->getValueOrParameter($parseString, $stack[$level][$pnt[$level]]['comparator'], '', $parameterReferences); if ($this->parse_error) { return $this->parse_error; } } } } } } // Finished, increase pointer: $pnt[$level]++; // Checking if we are back to level 0 and we should still decrease level, // meaning we were probably parsing as subquery and should return here: if ($level === 0 && preg_match('/^[)]/', $parseString)) { // Return the stacks lowest level: return $stack[0]; } // Checking if we are back to level 0 and we should still decrease level, // meaning we were probably parsing a subquery and should return here: if ($level === 0 && preg_match('/^[)]/', $parseString)) { // Return the stacks lowest level: return $stack[0]; } // Checking if the current level is ended, in that case do stack management: while ($this->nextPart($parseString, '^([)])')) { $level--; // Decrease level: $stack[$level][$pnt[$level]]['sub'] = $stack[$level + 1]; // Copy stack $pnt[$level]++; // Increase pointer of the new level // Make recursivity check: $loopExit++; if ($loopExit > 500) { return $this->parseError('More than 500 loops (in search for exit parenthesis), exiting prematurely in parseWhereClause()...', $parseString); } } // Detecting the operator for the next level: $op = $this->nextPart($parseString, '^(AND[[:space:]]+NOT|&&[[:space:]]+NOT|OR[[:space:]]+NOT|OR[[:space:]]+NOT|\|\|[[:space:]]+NOT|AND|&&|OR|\|\|)(\(|[[:space:]]+)'); if ($op) { // Normalize boolean operator $op = str_replace(array('&&', '||'), array('AND', 'OR'), $op); $stack[$level][$pnt[$level]]['operator'] = $op; } elseif (strlen($parseString)) { // Looking for stop-keywords: if ($stopRegex && $this->lastStopKeyWord = $this->nextPart($parseString, $stopRegex)) { $this->lastStopKeyWord = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $this->lastStopKeyWord)); return $stack[0]; } else { return $this->parseError('No operator, but parsing not finished in parseWhereClause().', $parseString); } } } // Make recursivity check: $loopExit++; if ($loopExit > 500) { return $this->parseError('More than 500 loops, exiting prematurely in parseWhereClause()...', $parseString); } } // Return the stacks lowest level: return $stack[0]; } /** * Parsing the WHERE clause fields in the "WHERE [$parseString] ..." part of a query into a multidimensional array. * The success of this parsing determines if that part of the query is supported by TYPO3. * * @param string WHERE clause to parse. NOTICE: passed by reference! * @param string Regular expressing to STOP parsing, eg. '^(GROUP BY|ORDER BY|LIMIT)([[:space:]]*)' * @return mixed If successful parsing, returns an array, otherwise an error string. */ public function parseFieldDef(&$parseString, $stopRegex = '') { // Prepare variables: $parseString = $this->trimSQL($parseString); $this->lastStopKeyWord = ''; $this->parse_error = ''; $result = array(); // Field type: if ($result['fieldType'] = $this->nextPart($parseString, '^(int|smallint|tinyint|mediumint|bigint|double|numeric|decimal|float|varchar|char|text|tinytext|mediumtext|longtext|blob|tinyblob|mediumblob|longblob)([[:space:],]+|\()')) { // Looking for value: if (substr($parseString, 0, 1) == '(') { $parseString = substr($parseString, 1); if ($result['value'] = $this->nextPart($parseString, '^([^)]*)')) { $parseString = ltrim(substr($parseString, 1)); } else { return $this->parseError('No end-parenthesis for value found in parseFieldDef()!', $parseString); } } // Looking for keywords while ($keyword = $this->nextPart($parseString, '^(DEFAULT|NOT[[:space:]]+NULL|AUTO_INCREMENT|UNSIGNED)([[:space:]]+|,|\))')) { $keywordCmp = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $keyword)); $result['featureIndex'][$keywordCmp]['keyword'] = $keyword; switch ($keywordCmp) { case 'DEFAULT': $result['featureIndex'][$keywordCmp]['value'] = $this->getValue($parseString); break; } } } else { return $this->parseError('Field type unknown in parseFieldDef()!', $parseString); } return $result; } /************************************ * * Parsing: Helper functions * ************************************/ /** * Strips off a part of the parseString and returns the matching part. * Helper function for the parsing methods. * * @param string Parse string; if $regex finds anything the value of the first () level will be stripped of the string in the beginning. Further $parseString is left-trimmed (on success). Notice; parsestring is passed by reference. * @param string Regex to find a matching part in the beginning of the string. Rules: You MUST start the regex with "^" (finding stuff in the beginning of string) and the result of the first parenthesis is what will be returned to you (and stripped of the string). Eg. '^(AND|OR|&&)[[:space:]]+' will return AND, OR or && if found and having one of more whitespaces after it, plus shorten $parseString with that match and any space after (by ltrim()) * @param boolean If set the full match of the regex is stripped of the beginning of the string! * @return string The value of the first parenthesis level of the REGEX. */ protected function nextPart(&$parseString, $regex, $trimAll = FALSE) { $reg = array(); if (preg_match('/' . $regex . '/i', $parseString . ' ', $reg)) { // Adding space char because [[:space:]]+ is often a requirement in regex's $parseString = ltrim(substr($parseString, strlen($reg[$trimAll ? 0 : 1]))); return $reg[1]; } // No match found return ''; } /** * Finds value or either named (:name) or question mark (?) parameter markers at the beginning * of $parseString, returns result and strips it of parseString. * This method returns a pointer to the parameter or value that was found. In case of a parameter * the pointer is a reference to the corresponding item in array $parameterReferences. * * @param string $parseString The parseString * @param string $comparator The comparator used before. * @param string $mode The mode, e.g., "INDEX" * @param mixed The value (string/integer) or parameter (:name/?). Otherwise an array with error message in first key (0) */ protected function &getValueOrParameter(&$parseString, $comparator = '', $mode = '', array &$parameterReferences = array()) { $parameter = $this->nextPart($parseString, '^(\\:[[:alnum:]_]+|\\?)'); if ($parameter === '?') { if (!isset($parameterReferences['?'])) { $parameterReferences['?'] = array(); } $value = array('?'); $parameterReferences['?'][] = &$value; } elseif ($parameter !== '') { // named parameter if (isset($parameterReferences[$parameter])) { // Use the same reference as last time we encountered this parameter $value = &$parameterReferences[$parameter]; } else { $value = array($parameter); $parameterReferences[$parameter] = &$value; } } else { $value = $this->getValue($parseString, $comparator, $mode); } return $value; } /** * Finds value in beginning of $parseString, returns result and strips it of parseString * * @param string The parseString, eg. "(0,1,2,3) ..." or "('asdf','qwer') ..." or "1234 ..." or "'My string value here' ..." * @param string The comparator used before. If "NOT IN" or "IN" then the value is expected to be a list of values. Otherwise just an integer (un-quoted) or string (quoted) * @param string The mode, eg. "INDEX" * @return mixed The value (string/integer). Otherwise an array with error message in first key (0) */ protected function getValue(&$parseString, $comparator = '', $mode = '') { $value = ''; if (t3lib_div::inList('NOTIN,IN,_LIST', strtoupper(str_replace(array(' ', LF, CR, TAB), '', $comparator)))) { // List of values: if ($this->nextPart($parseString, '^([(])')) { $listValues = array(); $comma = ','; while ($comma == ',') { $listValues[] = $this->getValue($parseString); if ($mode === 'INDEX') { // Remove any length restriction on INDEX definition $this->nextPart($parseString, '^([(]\d+[)])'); } $comma = $this->nextPart($parseString, '^([,])'); } $out = $this->nextPart($parseString, '^([)])'); if ($out) { if ($comparator == '_LIST') { $kVals = array(); foreach ($listValues as $vArr) { $kVals[] = $vArr[0]; } return $kVals; } else { return $listValues; } } else { return array($this->parseError('No ) parenthesis in list', $parseString)); } } else { return array($this->parseError('No ( parenthesis starting the list', $parseString)); } } else { // Just plain string value, in quotes or not: // Quote? $firstChar = substr($parseString, 0, 1); switch ($firstChar) { case '"': $value = array($this->getValueInQuotes($parseString, '"'), '"'); break; case "'": $value = array($this->getValueInQuotes($parseString, "'"), "'"); break; default: $reg = array(); if (preg_match('/^([[:alnum:]._-]+)/i', $parseString, $reg)) { $parseString = ltrim(substr($parseString, strlen($reg[0]))); $value = array($reg[1]); } break; } } return $value; } /** * Get value in quotes from $parseString. * NOTICE: If a query being parsed was prepared for another database than MySQL this function should probably be changed * * @param string String from which to find value in quotes. Notice that $parseString is passed by reference and is shortend by the output of this function. * @param string The quote used; input either " or ' * @return string The value, passed through stripslashes() ! */ protected function getValueInQuotes(&$parseString, $quote) { $parts = explode($quote, substr($parseString, 1)); $buffer = ''; foreach ($parts as $k => $v) { $buffer .= $v; $reg = array(); preg_match('/\\\\$/', $v, $reg); if ($reg AND strlen($reg[0]) % 2) { $buffer .= $quote; } else { $parseString = ltrim(substr($parseString, strlen($buffer) + 2)); return $this->parseStripslashes($buffer); } } } /** * Strip slashes function used for parsing * NOTICE: If a query being parsed was prepared for another database than MySQL this function should probably be changed * * @param string Input string * @return string Output string */ protected function parseStripslashes($str) { $search = array('\\\\', '\\\'', '\\"', '\0', '\n', '\r', '\Z'); $replace = array('\\', '\'', '"', "\x00", "\x0a", "\x0d", "\x1a"); return str_replace($search, $replace, $str); } /** * Add slashes function used for compiling queries * NOTICE: If a query being parsed was prepared for another database than MySQL this function should probably be changed * * @param string Input string * @return string Output string */ protected function compileAddslashes($str) { $search = array('\\', '\'', '"', "\x00", "\x0a", "\x0d", "\x1a"); $replace = array('\\\\', '\\\'', '\\"', '\0', '\n', '\r', '\Z'); return str_replace($search, $replace, $str); } /** * Setting the internal error message value, $this->parse_error and returns that value. * * @param string Input error message * @param string Remaining query to parse. * @return string Error message. */ protected function parseError($msg, $restQuery) { $this->parse_error = 'SQL engine parse ERROR: ' . $msg . ': near "' . substr($restQuery, 0, 50) . '"'; return $this->parse_error; } /** * Trimming SQL as preparation for parsing. * ";" in the end is stripped of. * White space is trimmed away around the value * A single space-char is added in the end * * @param string Input string * @return string Output string */ protected function trimSQL($str) { return trim(rtrim($str, "; \r\n\t")) . ' '; } /************************* * * Compiling queries * *************************/ /** * Compiles an SQL query from components * * @param array Array of SQL query components * @return string SQL query * @see parseSQL() */ public function compileSQL($components) { switch ($components['type']) { case 'SELECT': $query = $this->compileSELECT($components); break; case 'UPDATE': $query = $this->compileUPDATE($components); break; case 'INSERT': $query = $this->compileINSERT($components); break; case 'DELETE': $query = $this->compileDELETE($components); break; case 'EXPLAIN': $query = 'EXPLAIN ' . $this->compileSELECT($components); break; case 'DROPTABLE': $query = 'DROP TABLE' . ($components['ifExists'] ? ' IF EXISTS' : '') . ' ' . $components['TABLE']; break; case 'CREATETABLE': $query = $this->compileCREATETABLE($components); break; case 'ALTERTABLE': $query = $this->compileALTERTABLE($components); break; case 'TRUNCATETABLE': $query = $this->compileTRUNCATETABLE($components); break; } return $query; } /** * Compiles a SELECT statement from components array * * @param array Array of SQL query components * @return string SQL SELECT query * @see parseSELECT() */ protected function compileSELECT($components) { // Initialize: $where = $this->compileWhereClause($components['WHERE']); $groupBy = $this->compileFieldList($components['GROUPBY']); $orderBy = $this->compileFieldList($components['ORDERBY']); $limit = $components['LIMIT']; // Make query: $query = 'SELECT ' . ($components['STRAIGHT_JOIN'] ? $components['STRAIGHT_JOIN'] . '' : '') . ' ' . $this->compileFieldList($components['SELECT']) . ' FROM ' . $this->compileFromTables($components['FROM']) . (strlen($where) ? ' WHERE ' . $where : '') . (strlen($groupBy) ? ' GROUP BY ' . $groupBy : '') . (strlen($orderBy) ? ' ORDER BY ' . $orderBy : '') . (strlen($limit) ? ' LIMIT ' . $limit : ''); return $query; } /** * Compiles an UPDATE statement from components array * * @param array Array of SQL query components * @return string SQL UPDATE query * @see parseUPDATE() */ protected function compileUPDATE($components) { // Where clause: $where = $this->compileWhereClause($components['WHERE']); // Fields $fields = array(); foreach ($components['FIELDS'] as $fN => $fV) { $fields[] = $fN . '=' . $fV[1] . $this->compileAddslashes($fV[0]) . $fV[1]; } // Make query: $query = 'UPDATE ' . $components['TABLE'] . ' SET ' . implode(', ', $fields) . ' ' . (strlen($where) ? ' WHERE ' . $where : ''); return $query; } /** * Compiles an INSERT statement from components array * * @param array Array of SQL query components * @return string SQL INSERT query * @see parseINSERT() */ protected function compileINSERT($components) { $values = array(); if (isset($components['VALUES_ONLY']) && is_array($components['VALUES_ONLY'])) { $valuesComponents = $components['EXTENDED'] === '1' ? $components['VALUES_ONLY'] : array($components['VALUES_ONLY']); $tableFields = array(); } else { $valuesComponents = $components['EXTENDED'] === '1' ? $components['FIELDS'] : array($components['FIELDS']); $tableFields = array_keys($valuesComponents[0]); } foreach ($valuesComponents as $valuesComponent) { $fields = array(); foreach ($valuesComponent as $fV) { $fields[] = $fV[1] . $this->compileAddslashes($fV[0]) . $fV[1]; } $values[] = '(' . implode(', ', $fields) . ')'; } // Make query: $query = 'INSERT INTO ' . $components['TABLE']; if (count($tableFields)) { $query .= ' (' . implode(', ', $tableFields) . ')'; } $query .= ' VALUES ' . implode(', ', $values); return $query; } /** * Compiles an DELETE statement from components array * * @param array Array of SQL query components * @return string SQL DELETE query * @see parseDELETE() */ protected function compileDELETE($components) { // Where clause: $where = $this->compileWhereClause($components['WHERE']); // Make query: $query = 'DELETE FROM ' . $components['TABLE'] . (strlen($where) ? ' WHERE ' . $where : ''); return $query; } /** * Compiles a CREATE TABLE statement from components array * * @param array Array of SQL query components * @return string SQL CREATE TABLE query * @see parseCREATETABLE() */ protected function compileCREATETABLE($components) { // Create fields and keys: $fieldsKeys = array(); foreach ($components['FIELDS'] as $fN => $fCfg) { $fieldsKeys[] = $fN . ' ' . $this->compileFieldCfg($fCfg['definition']); } foreach ($components['KEYS'] as $kN => $kCfg) { if ($kN === 'PRIMARYKEY') { $fieldsKeys[] = 'PRIMARY KEY (' . implode(',', $kCfg) . ')'; } elseif ($kN === 'UNIQUE') { $key = key($kCfg); $fields = current($kCfg); $fieldsKeys[] = 'UNIQUE KEY ' . $key . ' (' . implode(',', $fields) . ')'; } else { $fieldsKeys[] = 'KEY ' . $kN . ' (' . implode(',', $kCfg) . ')'; } } // Make query: $query = 'CREATE TABLE ' . $components['TABLE'] . ' ( ' . implode(', ', $fieldsKeys) . ' )' . ($components['tableType'] ? ' TYPE=' . $components['tableType'] : ''); return $query; } /** * Compiles an ALTER TABLE statement from components array * * @param array Array of SQL query components * @return string SQL ALTER TABLE query * @see parseALTERTABLE() */ protected function compileALTERTABLE($components) { // Make query: $query = 'ALTER TABLE ' . $components['TABLE'] . ' ' . $components['action'] . ' ' . ($components['FIELD'] ? $components['FIELD'] : $components['KEY']); // Based on action, add the final part: switch (strtoupper(str_replace(array(' ', TAB, CR, LF), '', $components['action']))) { case 'ADD': $query .= ' ' . $this->compileFieldCfg($components['definition']); break; case 'CHANGE': $query .= ' ' . $components['newField'] . ' ' . $this->compileFieldCfg($components['definition']); break; case 'DROP': case 'DROPKEY': break; case 'ADDKEY': case 'ADDPRIMARYKEY': case 'ADDUNIQUE': $query .= ' (' . implode(',', $components['fields']) . ')'; break; case 'DEFAULTCHARACTERSET': $query .= $components['charset']; break; case 'ENGINE': $query .= '= ' . $components['engine']; break; } // Return query return $query; } /** * Compiles a TRUNCATE TABLE statement from components array * * @param array Array of SQL query components * @return string SQL TRUNCATE TABLE query * @see parseTRUNCATETABLE() */ protected function compileTRUNCATETABLE(array $components) { // Make query: $query = 'TRUNCATE TABLE ' . $components['TABLE']; // Return query return $query; } /************************************** * * Compiling queries, helper functions for parts of queries * **************************************/ /** * Compiles a "SELECT [output] FROM..:" field list based on input array (made with ->parseFieldList()) * Can also compile field lists for ORDER BY and GROUP BY. * * @param array Array of select fields, (made with ->parseFieldList()) * @param boolean Whether comments should be compiled * @return string Select field string * @see parseFieldList() */ public function compileFieldList($selectFields, $compileComments = TRUE) { // Prepare buffer variable: $fields = ''; // Traverse the selectFields if any: if (is_array($selectFields)) { $outputParts = array(); foreach ($selectFields as $k => $v) { // Detecting type: switch ($v['type']) { case 'function': $outputParts[$k] = $v['function'] . '(' . $v['func_content'] . ')'; break; case 'flow-control': if ($v['flow-control']['type'] === 'CASE') { $outputParts[$k] = $this->compileCaseStatement($v['flow-control']); } break; case 'field': $outputParts[$k] = ($v['distinct'] ? $v['distinct'] : '') . ($v['table'] ? $v['table'] . '.' : '') . $v['field']; break; } // Alias: if ($v['as']) { $outputParts[$k] .= ' ' . $v['as_keyword'] . ' ' . $v['as']; } // Specifically for ORDER BY and GROUP BY field lists: if ($v['sortDir']) { $outputParts[$k] .= ' ' . $v['sortDir']; } } if ($compileComments && $selectFields[0]['comments']) { $fields = $selectFields[0]['comments'] . ' '; } $fields .= implode(', ', $outputParts); } return $fields; } /** * Compiles a CASE ... WHEN flow-control construct based on input array (made with ->parseCaseStatement()) * * @param array Array of case components, (made with ->parseCaseStatement()) * @return string case when string * @see parseCaseStatement() */ protected function compileCaseStatement(array $components) { $statement = 'CASE'; if (isset($components['case_field'])) { $statement .= ' ' . $components['case_field']; } elseif (isset($components['case_value'])) { $statement .= ' ' . $components['case_value'][1] . $components['case_value'][0] . $components['case_value'][1]; } foreach ($components['when'] as $when) { $statement .= ' WHEN '; $statement .= $this->compileWhereClause($when['when_value']); $statement .= ' THEN '; $statement .= $when['then_value'][1] . $when['then_value'][0] . $when['then_value'][1]; } if (isset($components['else'])) { $statement .= ' ELSE '; $statement .= $components['else'][1] . $components['else'][0] . $components['else'][1]; } $statement .= ' END'; return $statement; } /** * Compiles a "FROM [output] WHERE..:" table list based on input array (made with ->parseFromTables()) * * @param array Array of table names, (made with ->parseFromTables()) * @return string Table name string * @see parseFromTables() */ public function compileFromTables($tablesArray) { // Prepare buffer variable: $outputParts = array(); // Traverse the table names: if (is_array($tablesArray)) { foreach ($tablesArray as $k => $v) { // Set table name: $outputParts[$k] = $v['table']; // Add alias AS if there: if ($v['as']) { $outputParts[$k] .= ' ' . $v['as_keyword'] . ' ' . $v['as']; } if (is_array($v['JOIN'])) { foreach ($v['JOIN'] as $join) { $outputParts[$k] .= ' ' . $join['type'] . ' ' . $join['withTable']; // Add alias AS if there: if (isset($join['as']) && $join['as']) { $outputParts[$k] .= ' ' . $join['as_keyword'] . ' ' . $join['as']; } $outputParts[$k] .= ' ON '; foreach ($join['ON'] as $condition) { if ($condition['operator'] !== '') { $outputParts[$k] .= ' ' . $condition['operator'] . ' '; } $outputParts[$k] .= ($condition['left']['table']) ? $condition['left']['table'] . '.' : ''; $outputParts[$k] .= $condition['left']['field']; $outputParts[$k] .= $condition['comparator']; $outputParts[$k] .= ($condition['right']['table']) ? $condition['right']['table'] . '.' : ''; $outputParts[$k] .= $condition['right']['field']; } } } } } // Return imploded buffer: return implode(', ', $outputParts); } /** * Implodes an array of WHERE clause configuration into a WHERE clause. * * @param array WHERE clause configuration * @return string WHERE clause as string. * @see explodeWhereClause() */ public function compileWhereClause($clauseArray) { // Prepare buffer variable: $output = ''; // Traverse clause array: if (is_array($clauseArray)) { foreach ($clauseArray as $k => $v) { // Set operator: $output .= $v['operator'] ? ' ' . $v['operator'] : ''; // Look for sublevel: if (is_array($v['sub'])) { $output .= ' (' . trim($this->compileWhereClause($v['sub'])) . ')'; } elseif (isset($v['func']) && $v['func']['type'] === 'EXISTS') { $output .= ' ' . trim($v['modifier']) . ' EXISTS (' . $this->compileSELECT($v['func']['subquery']) . ')'; } else { if (isset($v['func']) && $v['func']['type'] === 'LOCATE') { $output .= ' ' . trim($v['modifier']) . ' LOCATE('; $output .= $v['func']['substr'][1] . $v['func']['substr'][0] . $v['func']['substr'][1]; $output .= ', ' . ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field']; $output .= isset($v['func']['pos']) ? ', ' . $v['func']['pos'][0] : ''; $output .= ')'; } elseif (isset($v['func']) && $v['func']['type'] === 'IFNULL') { $output .= ' ' . trim($v['modifier']) . ' IFNULL('; $output .= ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field']; $output .= ', ' . $v['func']['default'][1] . $this->compileAddslashes($v['func']['default'][0]) . $v['func']['default'][1]; $output .= ')'; } elseif (isset($v['func']) && $v['func']['type'] === 'FIND_IN_SET') { $output .= ' ' . trim($v['modifier']) . ' FIND_IN_SET('; $output .= $v['func']['str'][1] . $v['func']['str'][0] . $v['func']['str'][1]; $output .= ', ' . ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field']; $output .= ')'; } else { // Set field/table with modifying prefix if any: $output .= ' ' . trim($v['modifier'] . ' ' . ($v['table'] ? $v['table'] . '.' : '') . $v['field']); // Set calculation, if any: if ($v['calc']) { $output .= $v['calc'] . $v['calc_value'][1] . $this->compileAddslashes($v['calc_value'][0]) . $v['calc_value'][1]; } } // Set comparator: if ($v['comparator']) { $output .= ' ' . $v['comparator']; // Detecting value type; list or plain: if (t3lib_div::inList('NOTIN,IN', strtoupper(str_replace(array(' ', TAB, CR, LF), '', $v['comparator'])))) { if (isset($v['subquery'])) { $output .= ' (' . $this->compileSELECT($v['subquery']) . ')'; } else { $valueBuffer = array(); foreach ($v['value'] as $realValue) { $valueBuffer[] = $realValue[1] . $this->compileAddslashes($realValue[0]) . $realValue[1]; } $output .= ' (' . trim(implode(',', $valueBuffer)) . ')'; } } else { if (t3lib_div::inList('BETWEEN,NOT BETWEEN', $v['comparator'])) { $lbound = $v['values'][0]; $ubound = $v['values'][1]; $output .= ' ' . $lbound[1] . $this->compileAddslashes($lbound[0]) . $lbound[1]; $output .= ' AND '; $output .= $ubound[1] . $this->compileAddslashes($ubound[0]) . $ubound[1]; } else { if (isset($v['value']['operator'])) { $values = array(); foreach ($v['value']['args'] as $fieldDef) { $values[] = ($fieldDef['table'] ? $fieldDef['table'] . '.' : '') . $fieldDef['field']; } $output .= ' ' . $v['value']['operator'] . '(' . implode(',', $values) . ')'; } else { $output .= ' ' . $v['value'][1] . $this->compileAddslashes($v['value'][0]) . $v['value'][1]; } } } } } } } // Return output buffer: return $output; } /** * Compile field definition * * @param array Field definition parts * @return string Field definition string */ public function compileFieldCfg($fieldCfg) { // Set type: $cfg = $fieldCfg['fieldType']; // Add value, if any: if (strlen($fieldCfg['value'])) { $cfg .= '(' . $fieldCfg['value'] . ')'; } // Add additional features: if (is_array($fieldCfg['featureIndex'])) { foreach ($fieldCfg['featureIndex'] as $featureDef) { $cfg .= ' ' . $featureDef['keyword']; // Add value if found: if (is_array($featureDef['value'])) { $cfg .= ' ' . $featureDef['value'][1] . $this->compileAddslashes($featureDef['value'][0]) . $featureDef['value'][1]; } } } // Return field definition string: return $cfg; } /************************* * * Debugging * *************************/ /** * Check parsability of input SQL part string; Will parse and re-compile after which it is compared * * @param string Part definition of string; "SELECT" = fieldlist (also ORDER BY and GROUP BY), "FROM" = table list, "WHERE" = Where clause. * @param string SQL string to verify parsability of * @return mixed Returns array with string 1 and 2 if error, otherwise false */ public function debug_parseSQLpart($part, $str) { $retVal = FALSE; switch ($part) { case 'SELECT': $retVal = $this->debug_parseSQLpartCompare($str, $this->compileFieldList($this->parseFieldList($str))); break; case 'FROM': $retVal = $this->debug_parseSQLpartCompare($str, $this->compileFromTables($this->parseFromTables($str))); break; case 'WHERE': $retVal = $this->debug_parseSQLpartCompare($str, $this->compileWhereClause($this->parseWhereClause($str))); break; } return $retVal; } /** * Compare two query strins by stripping away whitespace. * * @param string SQL String 1 * @param string SQL string 2 * @param boolean If true, the strings are compared insensitive to case * @return mixed Returns array with string 1 and 2 if error, otherwise false */ public function debug_parseSQLpartCompare($str, $newStr, $caseInsensitive = FALSE) { if ($caseInsensitive) { $str1 = strtoupper($str); $str2 = strtoupper($newStr); } else { $str1 = $str; $str2 = $newStr; } // Fixing escaped chars: $search = array('\0', '\n', '\r', '\Z'); $replace = array("\x00", "\x0a", "\x0d", "\x1a"); $str1 = str_replace($search, $replace, $str1); $str2 = str_replace($search, $replace, $str2); # Normally, commented out since they are needed only in tricky cases... # $str1 = stripslashes($str1); # $str2 = stripslashes($str2); if (strcmp(str_replace(array(' ', TAB, CR, LF), '', $this->trimSQL($str1)), str_replace(array(' ', TAB, CR, LF), '', $this->trimSQL($str2)))) { return array( str_replace(array(' ', TAB, CR, LF), ' ', $str), str_replace(array(' ', TAB, CR, LF), ' ', $newStr), ); } } /** * Performs the ultimate test of the parser: Direct a SQL query in; You will get it back (through the parsed and re-compiled) if no problems, otherwise the script will print the error and exit * * @param string SQL query * @return string Query if all is well, otherwise exit. */ public function debug_testSQL($SQLquery) { // Getting result array: $parseResult = $this->parseSQL($SQLquery); // If result array was returned, proceed. Otherwise show error and exit. if (is_array($parseResult)) { // Re-compile query: $newQuery = $this->compileSQL($parseResult); // TEST the new query: $testResult = $this->debug_parseSQLpartCompare($SQLquery, $newQuery); // Return new query if OK, otherwise show error and exit: if (!is_array($testResult)) { return $newQuery; } else { debug(array('ERROR MESSAGE' => 'Input query did not match the parsed and recompiled query exactly (not observing whitespace)', 'TEST result' => $testResult), 'SQL parsing failed:'); exit; } } else { debug(array('query' => $SQLquery, 'ERROR MESSAGE' => $parseResult), 'SQL parsing failed:'); exit; } } } if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_sqlparser.php'])) { include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_sqlparser.php']); } ?>