[BUGFIX] Fix return value of DatabaseConnection::sql_free_result()
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Database / DatabaseConnection.php
1 <?php
2 namespace TYPO3\CMS\Core\Database;
3
4 /**
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18
19 /**
20 * Contains the class "DatabaseConnection" containing functions for building SQL queries
21 * and mysqli wrappers, thus providing a foundational API to all database
22 * interaction.
23 * This class is instantiated globally as $TYPO3_DB in TYPO3 scripts.
24 *
25 * TYPO3 "database wrapper" class (new in 3.6.0)
26 * This class contains
27 * - abstraction functions for executing INSERT/UPDATE/DELETE/SELECT queries ("Query execution"; These are REQUIRED for all future connectivity to the database, thus ensuring DBAL compliance!)
28 * - functions for building SQL queries (INSERT/UPDATE/DELETE/SELECT) ("Query building"); These are transitional functions for building SQL queries in a more automated way. Use these to build queries instead of doing it manually in your code!
29 * - mysqli wrapper functions; These are transitional functions. By a simple search/replace you should be able to substitute all mysql*() calls with $GLOBALS['TYPO3_DB']->sql*() and your application will work out of the box. YOU CANNOT (legally) use any mysqli functions not found as wrapper functions in this class!
30 * See the Project Coding Guidelines (doc_core_cgl) for more instructions on best-practise
31 *
32 * This class is not in itself a complete database abstraction layer but can be extended to be a DBAL (by extensions, see "dbal" for example)
33 * ALL connectivity to the database in TYPO3 must be done through this class!
34 * The points of this class are:
35 * - To direct all database calls through this class so it becomes possible to implement DBAL with extensions.
36 * - To keep it very easy to use for developers used to MySQL in PHP - and preserve as much performance as possible when TYPO3 is used with MySQL directly...
37 * - To create an interface for DBAL implemented by extensions; (Eg. making possible escaping characters, clob/blob handling, reserved words handling)
38 * - Benchmarking the DB bottleneck queries will become much easier; Will make it easier to find optimization possibilities.
39 *
40 * USE:
41 * In all TYPO3 scripts the global variable $TYPO3_DB is an instance of this class. Use that.
42 * Eg. $GLOBALS['TYPO3_DB']->sql_fetch_assoc()
43 *
44 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
45 */
46 class DatabaseConnection {
47
48 /**
49 * The AND constraint in where clause
50 *
51 * @var string
52 */
53 const AND_Constraint = 'AND';
54
55 /**
56 * The OR constraint in where clause
57 *
58 * @var string
59 */
60 const OR_Constraint = 'OR';
61
62 // Set "TRUE" or "1" if you want database errors outputted. Set to "2" if you also want successful database actions outputted.
63 /**
64 * @todo Define visibility
65 */
66 public $debugOutput = FALSE;
67
68 // Internally: Set to last built query (not necessarily executed...)
69 /**
70 * @todo Define visibility
71 */
72 public $debug_lastBuiltQuery = '';
73
74 // Set "TRUE" if you want the last built query to be stored in $debug_lastBuiltQuery independent of $this->debugOutput
75 /**
76 * @todo Define visibility
77 */
78 public $store_lastBuiltQuery = FALSE;
79
80 // Set this to 1 to get queries explained (devIPmask must match). Set the value to 2 to the same but disregarding the devIPmask.
81 // There is an alternative option to enable explain output in the admin panel under "TypoScript", which will produce much nicer output, but only works in FE.
82 /**
83 * @todo Define visibility
84 */
85 public $explainOutput = 0;
86
87 /**
88 * @var string Database host to connect to
89 */
90 protected $databaseHost = '';
91
92 /**
93 * @var integer Database port to connect to
94 */
95 protected $databasePort = 3306;
96
97 /**
98 * @var string|NULL Database socket to connect to
99 */
100 protected $databaseSocket = NULL;
101
102 /**
103 * @var string Database name to connect to
104 */
105 protected $databaseName = '';
106
107 /**
108 * @var string Database user to connect with
109 */
110 protected $databaseUsername = '';
111
112 /**
113 * @var string Database password to connect with
114 */
115 protected $databaseUserPassword = '';
116
117 /**
118 * @var boolean TRUE if database connection should be persistent
119 * @see http://php.net/manual/de/mysqli.persistconns.php
120 */
121 protected $persistentDatabaseConnection = FALSE;
122
123 /**
124 * @var boolean TRUE if connection between client and sql server is compressed
125 */
126 protected $connectionCompression = FALSE;
127
128 /**
129 * The charset for the connection; will be passed on to
130 * mysqli_set_charset during connection initialization.
131 *
132 * @var string
133 */
134 protected $connectionCharset = 'utf8';
135
136 /**
137 * @var array List of commands executed after connection was established
138 */
139 protected $initializeCommandsAfterConnect = array();
140
141 /**
142 * @var boolean TRUE if database connection is established
143 */
144 protected $isConnected = FALSE;
145
146 /**
147 * @var \mysqli $link Default database link object
148 */
149 protected $link = NULL;
150
151 // Default character set, applies unless character set or collation are explicitly set
152 /**
153 * @todo Define visibility
154 */
155 public $default_charset = 'utf8';
156
157 /**
158 * @var array<PostProcessQueryHookInterface>
159 */
160 protected $preProcessHookObjects = array();
161
162 /**
163 * @var array<PreProcessQueryHookInterface>
164 */
165 protected $postProcessHookObjects = array();
166
167
168 /**
169 * Initialize the database connection
170 *
171 * @return void
172 */
173 public function initialize() {
174 // Intentionally blank as this will be overloaded by DBAL
175 }
176
177 /************************************
178 *
179 * Query execution
180 *
181 * These functions are the RECOMMENDED DBAL functions for use in your applications
182 * Using these functions will allow the DBAL to use alternative ways of accessing data (contrary to if a query is returned!)
183 * They compile a query AND execute it immediately and then return the result
184 * This principle heightens our ability to create various forms of DBAL of the functions.
185 * Generally: We want to return a result pointer/object, never queries.
186 * Also, having the table name together with the actual query execution allows us to direct the request to other databases.
187 *
188 **************************************/
189
190 /**
191 * Creates and executes an INSERT SQL-statement for $table from the array with field/value pairs $fields_values.
192 * Using this function specifically allows us to handle BLOB and CLOB fields depending on DB
193 *
194 * @param string $table Table name
195 * @param array $fields_values Field values as key=>value pairs. Values will be escaped internally. Typically you would fill an array like "$insertFields" with 'fieldname'=>'value' and pass it to this function as argument.
196 * @param boolean $no_quote_fields See fullQuoteArray()
197 * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
198 */
199 public function exec_INSERTquery($table, $fields_values, $no_quote_fields = FALSE) {
200 $res = $this->query($this->INSERTquery($table, $fields_values, $no_quote_fields));
201 if ($this->debugOutput) {
202 $this->debug('exec_INSERTquery');
203 }
204 foreach ($this->postProcessHookObjects as $hookObject) {
205 /** @var $hookObject PostProcessQueryHookInterface */
206 $hookObject->exec_INSERTquery_postProcessAction($table, $fields_values, $no_quote_fields, $this);
207 }
208 return $res;
209 }
210
211 /**
212 * Creates and executes an INSERT SQL-statement for $table with multiple rows.
213 *
214 * @param string $table Table name
215 * @param array $fields Field names
216 * @param array $rows Table rows. Each row should be an array with field values mapping to $fields
217 * @param boolean $no_quote_fields See fullQuoteArray()
218 * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
219 */
220 public function exec_INSERTmultipleRows($table, array $fields, array $rows, $no_quote_fields = FALSE) {
221 $res = $this->query($this->INSERTmultipleRows($table, $fields, $rows, $no_quote_fields));
222 if ($this->debugOutput) {
223 $this->debug('exec_INSERTmultipleRows');
224 }
225 foreach ($this->postProcessHookObjects as $hookObject) {
226 /** @var $hookObject PostProcessQueryHookInterface */
227 $hookObject->exec_INSERTmultipleRows_postProcessAction($table, $fields, $rows, $no_quote_fields, $this);
228 }
229 return $res;
230 }
231
232 /**
233 * Creates and executes an UPDATE SQL-statement for $table where $where-clause (typ. 'uid=...') from the array with field/value pairs $fields_values.
234 * Using this function specifically allow us to handle BLOB and CLOB fields depending on DB
235 *
236 * @param string $table Database tablename
237 * @param string $where WHERE clause, eg. "uid=1". NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself!
238 * @param array $fields_values Field values as key=>value pairs. Values will be escaped internally. Typically you would fill an array like "$updateFields" with 'fieldname'=>'value' and pass it to this function as argument.
239 * @param boolean $no_quote_fields See fullQuoteArray()
240 * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
241 */
242 public function exec_UPDATEquery($table, $where, $fields_values, $no_quote_fields = FALSE) {
243 $res = $this->query($this->UPDATEquery($table, $where, $fields_values, $no_quote_fields));
244 if ($this->debugOutput) {
245 $this->debug('exec_UPDATEquery');
246 }
247 foreach ($this->postProcessHookObjects as $hookObject) {
248 /** @var $hookObject PostProcessQueryHookInterface */
249 $hookObject->exec_UPDATEquery_postProcessAction($table, $where, $fields_values, $no_quote_fields, $this);
250 }
251 return $res;
252 }
253
254 /**
255 * Creates and executes a DELETE SQL-statement for $table where $where-clause
256 *
257 * @param string $table Database tablename
258 * @param string $where WHERE clause, eg. "uid=1". NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself!
259 * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
260 */
261 public function exec_DELETEquery($table, $where) {
262 $res = $this->query($this->DELETEquery($table, $where));
263 if ($this->debugOutput) {
264 $this->debug('exec_DELETEquery');
265 }
266 foreach ($this->postProcessHookObjects as $hookObject) {
267 /** @var $hookObject PostProcessQueryHookInterface */
268 $hookObject->exec_DELETEquery_postProcessAction($table, $where, $this);
269 }
270 return $res;
271 }
272
273 /**
274 * Creates and executes a SELECT SQL-statement
275 * Using this function specifically allow us to handle the LIMIT feature independently of DB.
276 *
277 * @param string $select_fields List of fields to select from the table. This is what comes right after "SELECT ...". Required value.
278 * @param string $from_table Table(s) from which to select. This is what comes right after "FROM ...". Required value.
279 * @param string $where_clause Additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself! DO NOT PUT IN GROUP BY, ORDER BY or LIMIT!
280 * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
281 * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
282 * @param string $limit Optional LIMIT value ([begin,]max), if none, supply blank string.
283 * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
284 */
285 public function exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '') {
286 $query = $this->SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
287 $res = $this->query($query);
288 if ($this->debugOutput) {
289 $this->debug('exec_SELECTquery');
290 }
291 if ($this->explainOutput) {
292 $this->explain($query, $from_table, $res->num_rows);
293 }
294 foreach ($this->postProcessHookObjects as $hookObject) {
295 /** @var $hookObject PostProcessQueryHookInterface */
296 $hookObject->exec_SELECTquery_postProcessAction($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', $this);
297 }
298 return $res;
299 }
300
301 /**
302 * Creates and executes a SELECT query, selecting fields ($select) from two/three tables joined
303 * Use $mm_table together with $local_table or $foreign_table to select over two tables. Or use all three tables to select the full MM-relation.
304 * The JOIN is done with [$local_table].uid <--> [$mm_table].uid_local / [$mm_table].uid_foreign <--> [$foreign_table].uid
305 * The function is very useful for selecting MM-relations between tables adhering to the MM-format used by TCE (TYPO3 Core Engine). See the section on $GLOBALS['TCA'] in Inside TYPO3 for more details.
306 *
307 * @param string $select Field list for SELECT
308 * @param string $local_table Tablename, local table
309 * @param string $mm_table Tablename, relation table
310 * @param string $foreign_table Tablename, foreign table
311 * @param string $whereClause Optional additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself! DO NOT PUT IN GROUP BY, ORDER BY or LIMIT! You have to prepend 'AND ' to this parameter yourself!
312 * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
313 * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
314 * @param string $limit Optional LIMIT value ([begin,]max), if none, supply blank string.
315 * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
316 * @see exec_SELECTquery()
317 */
318 public function exec_SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '') {
319 $foreign_table_as = $foreign_table == $local_table ? $foreign_table . uniqid('_join') : '';
320 $mmWhere = $local_table ? $local_table . '.uid=' . $mm_table . '.uid_local' : '';
321 $mmWhere .= ($local_table and $foreign_table) ? ' AND ' : '';
322 $tables = ($local_table ? $local_table . ',' : '') . $mm_table;
323 if ($foreign_table) {
324 $mmWhere .= ($foreign_table_as ?: $foreign_table) . '.uid=' . $mm_table . '.uid_foreign';
325 $tables .= ',' . $foreign_table . ($foreign_table_as ? ' AS ' . $foreign_table_as : '');
326 }
327 return $this->exec_SELECTquery($select, $tables, $mmWhere . ' ' . $whereClause, $groupBy, $orderBy, $limit);
328 }
329
330 /**
331 * Executes a select based on input query parts array
332 *
333 * @param array $queryParts Query parts array
334 * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
335 * @see exec_SELECTquery()
336 */
337 public function exec_SELECT_queryArray($queryParts) {
338 return $this->exec_SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT']);
339 }
340
341 /**
342 * Creates and executes a SELECT SQL-statement AND traverse result set and returns array with records in.
343 *
344 * @param string $select_fields See exec_SELECTquery()
345 * @param string $from_table See exec_SELECTquery()
346 * @param string $where_clause See exec_SELECTquery()
347 * @param string $groupBy See exec_SELECTquery()
348 * @param string $orderBy See exec_SELECTquery()
349 * @param string $limit See exec_SELECTquery()
350 * @param string $uidIndexField If set, the result array will carry this field names value as index. Requires that field to be selected of course!
351 * @return array|NULL Array of rows, or NULL in case of SQL error
352 */
353 public function exec_SELECTgetRows($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', $uidIndexField = '') {
354 $res = $this->exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
355 if ($this->debugOutput) {
356 $this->debug('exec_SELECTquery');
357 }
358 if (!$this->sql_error()) {
359 $output = array();
360 if ($uidIndexField) {
361 while ($tempRow = $this->sql_fetch_assoc($res)) {
362 $output[$tempRow[$uidIndexField]] = $tempRow;
363 }
364 } else {
365 while ($output[] = $this->sql_fetch_assoc($res)) {
366
367 }
368 array_pop($output);
369 }
370 $this->sql_free_result($res);
371 } else {
372 $output = NULL;
373 }
374 return $output;
375 }
376
377 /**
378 * Creates and executes a SELECT SQL-statement AND gets a result set and returns an array with a single record in.
379 * LIMIT is automatically set to 1 and can not be overridden.
380 *
381 * @param string $select_fields List of fields to select from the table.
382 * @param string $from_table Table(s) from which to select.
383 * @param string $where_clause Optional additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself!
384 * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
385 * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
386 * @param boolean $numIndex If set, the result will be fetched with sql_fetch_row, otherwise sql_fetch_assoc will be used.
387 * @return array|FALSE|NULL Single row, FALSE on empty result, NULL on error
388 */
389 public function exec_SELECTgetSingleRow($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $numIndex = FALSE) {
390 $res = $this->exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, '1');
391 if ($this->debugOutput) {
392 $this->debug('exec_SELECTquery');
393 }
394 $output = NULL;
395 if ($res !== FALSE) {
396 if ($numIndex) {
397 $output = $this->sql_fetch_row($res);
398 } else {
399 $output = $this->sql_fetch_assoc($res);
400 }
401 $this->sql_free_result($res);
402 }
403 return $output;
404 }
405
406 /**
407 * Counts the number of rows in a table.
408 *
409 * @param string $field Name of the field to use in the COUNT() expression (e.g. '*')
410 * @param string $table Name of the table to count rows for
411 * @param string $where (optional) WHERE statement of the query
412 * @return mixed Number of rows counter (integer) or FALSE if something went wrong (boolean)
413 */
414 public function exec_SELECTcountRows($field, $table, $where = '') {
415 $count = FALSE;
416 $resultSet = $this->exec_SELECTquery('COUNT(' . $field . ')', $table, $where);
417 if ($resultSet !== FALSE) {
418 list($count) = $this->sql_fetch_row($resultSet);
419 $count = (int)$count;
420 $this->sql_free_result($resultSet);
421 }
422 return $count;
423 }
424
425 /**
426 * Truncates a table.
427 *
428 * @param string $table Database tablename
429 * @return mixed Result from handler
430 */
431 public function exec_TRUNCATEquery($table) {
432 $res = $this->query($this->TRUNCATEquery($table));
433 if ($this->debugOutput) {
434 $this->debug('exec_TRUNCATEquery');
435 }
436 foreach ($this->postProcessHookObjects as $hookObject) {
437 /** @var $hookObject PostProcessQueryHookInterface */
438 $hookObject->exec_TRUNCATEquery_postProcessAction($table, $this);
439 }
440 return $res;
441 }
442
443 /**
444 * Central query method. Also checks if there is a database connection.
445 * Use this to execute database queries instead of directly calling $this->link->query()
446 *
447 * @param string $query The query to send to the database
448 * @return bool|\mysqli_result
449 */
450 protected function query($query) {
451 if (!$this->isConnected) {
452 $this->connectDB();
453 }
454 return $this->link->query($query);
455 }
456
457 /**************************************
458 *
459 * Query building
460 *
461 **************************************/
462 /**
463 * Creates an INSERT SQL-statement for $table from the array with field/value pairs $fields_values.
464 *
465 * @param string $table See exec_INSERTquery()
466 * @param array $fields_values See exec_INSERTquery()
467 * @param boolean $no_quote_fields See fullQuoteArray()
468 * @return string|NULL Full SQL query for INSERT, NULL if $fields_values is empty
469 */
470 public function INSERTquery($table, $fields_values, $no_quote_fields = FALSE) {
471 // Table and fieldnames should be "SQL-injection-safe" when supplied to this
472 // function (contrary to values in the arrays which may be insecure).
473 if (!is_array($fields_values) || count($fields_values) === 0) {
474 return NULL;
475 }
476 foreach ($this->preProcessHookObjects as $hookObject) {
477 $hookObject->INSERTquery_preProcessAction($table, $fields_values, $no_quote_fields, $this);
478 }
479 // Quote and escape values
480 $fields_values = $this->fullQuoteArray($fields_values, $table, $no_quote_fields, TRUE);
481 // Build query
482 $query = 'INSERT INTO ' . $table . ' (' . implode(',', array_keys($fields_values)) . ') VALUES ' . '(' . implode(',', $fields_values) . ')';
483 // Return query
484 if ($this->debugOutput || $this->store_lastBuiltQuery) {
485 $this->debug_lastBuiltQuery = $query;
486 }
487 return $query;
488 }
489
490 /**
491 * Creates an INSERT SQL-statement for $table with multiple rows.
492 *
493 * @param string $table Table name
494 * @param array $fields Field names
495 * @param array $rows Table rows. Each row should be an array with field values mapping to $fields
496 * @param boolean $no_quote_fields See fullQuoteArray()
497 * @return string|NULL Full SQL query for INSERT, NULL if $rows is empty
498 */
499 public function INSERTmultipleRows($table, array $fields, array $rows, $no_quote_fields = FALSE) {
500 // Table and fieldnames should be "SQL-injection-safe" when supplied to this
501 // function (contrary to values in the arrays which may be insecure).
502 if (count($rows) === 0) {
503 return NULL;
504 }
505 foreach ($this->preProcessHookObjects as $hookObject) {
506 /** @var $hookObject PreProcessQueryHookInterface */
507 $hookObject->INSERTmultipleRows_preProcessAction($table, $fields, $rows, $no_quote_fields, $this);
508 }
509 // Build query
510 $query = 'INSERT INTO ' . $table . ' (' . implode(', ', $fields) . ') VALUES ';
511 $rowSQL = array();
512 foreach ($rows as $row) {
513 // Quote and escape values
514 $row = $this->fullQuoteArray($row, $table, $no_quote_fields);
515 $rowSQL[] = '(' . implode(', ', $row) . ')';
516 }
517 $query .= implode(', ', $rowSQL);
518 // Return query
519 if ($this->debugOutput || $this->store_lastBuiltQuery) {
520 $this->debug_lastBuiltQuery = $query;
521 }
522 return $query;
523 }
524
525 /**
526 * Creates an UPDATE SQL-statement for $table where $where-clause (typ. 'uid=...') from the array with field/value pairs $fields_values.
527 *
528 *
529 * @param string $table See exec_UPDATEquery()
530 * @param string $where See exec_UPDATEquery()
531 * @param array $fields_values See exec_UPDATEquery()
532 * @param boolean $no_quote_fields
533 * @throws \InvalidArgumentException
534 * @return string Full SQL query for UPDATE
535 */
536 public function UPDATEquery($table, $where, $fields_values, $no_quote_fields = FALSE) {
537 // Table and fieldnames should be "SQL-injection-safe" when supplied to this
538 // function (contrary to values in the arrays which may be insecure).
539 if (is_string($where)) {
540 foreach ($this->preProcessHookObjects as $hookObject) {
541 /** @var $hookObject PreProcessQueryHookInterface */
542 $hookObject->UPDATEquery_preProcessAction($table, $where, $fields_values, $no_quote_fields, $this);
543 }
544 $fields = array();
545 if (is_array($fields_values) && count($fields_values)) {
546 // Quote and escape values
547 $nArr = $this->fullQuoteArray($fields_values, $table, $no_quote_fields, TRUE);
548 foreach ($nArr as $k => $v) {
549 $fields[] = $k . '=' . $v;
550 }
551 }
552 // Build query
553 $query = 'UPDATE ' . $table . ' SET ' . implode(',', $fields) . ((string)$where !== '' ? ' WHERE ' . $where : '');
554 if ($this->debugOutput || $this->store_lastBuiltQuery) {
555 $this->debug_lastBuiltQuery = $query;
556 }
557 return $query;
558 } else {
559 throw new \InvalidArgumentException('TYPO3 Fatal Error: "Where" clause argument for UPDATE query was not a string in $this->UPDATEquery() !', 1270853880);
560 }
561 }
562
563 /**
564 * Creates a DELETE SQL-statement for $table where $where-clause
565 *
566 * @param string $table See exec_DELETEquery()
567 * @param string $where See exec_DELETEquery()
568 * @return string Full SQL query for DELETE
569 * @throws \InvalidArgumentException
570 */
571 public function DELETEquery($table, $where) {
572 if (is_string($where)) {
573 foreach ($this->preProcessHookObjects as $hookObject) {
574 /** @var $hookObject PreProcessQueryHookInterface */
575 $hookObject->DELETEquery_preProcessAction($table, $where, $this);
576 }
577 // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
578 $query = 'DELETE FROM ' . $table . ((string)$where !== '' ? ' WHERE ' . $where : '');
579 if ($this->debugOutput || $this->store_lastBuiltQuery) {
580 $this->debug_lastBuiltQuery = $query;
581 }
582 return $query;
583 } else {
584 throw new \InvalidArgumentException('TYPO3 Fatal Error: "Where" clause argument for DELETE query was not a string in $this->DELETEquery() !', 1270853881);
585 }
586 }
587
588 /**
589 * Creates a SELECT SQL-statement
590 *
591 * @param string $select_fields See exec_SELECTquery()
592 * @param string $from_table See exec_SELECTquery()
593 * @param string $where_clause See exec_SELECTquery()
594 * @param string $groupBy See exec_SELECTquery()
595 * @param string $orderBy See exec_SELECTquery()
596 * @param string $limit See exec_SELECTquery()
597 * @return string Full SQL query for SELECT
598 */
599 public function SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '') {
600 foreach ($this->preProcessHookObjects as $hookObject) {
601 /** @var $hookObject PreProcessQueryHookInterface */
602 $hookObject->SELECTquery_preProcessAction($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit, $this);
603 }
604 // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
605 // Build basic query
606 $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
607 // Group by
608 $query .= (string)$groupBy !== '' ? ' GROUP BY ' . $groupBy : '';
609 // Order by
610 $query .= (string)$orderBy !== '' ? ' ORDER BY ' . $orderBy : '';
611 // Group by
612 $query .= (string)$limit !== '' ? ' LIMIT ' . $limit : '';
613 // Return query
614 if ($this->debugOutput || $this->store_lastBuiltQuery) {
615 $this->debug_lastBuiltQuery = $query;
616 }
617 return $query;
618 }
619
620 /**
621 * Creates a SELECT SQL-statement to be used as subquery within another query.
622 * BEWARE: This method should not be overriden within DBAL to prevent quoting from happening.
623 *
624 * @param string $select_fields List of fields to select from the table.
625 * @param string $from_table Table from which to select.
626 * @param string $where_clause Conditional WHERE statement
627 * @return string Full SQL query for SELECT
628 */
629 public function SELECTsubquery($select_fields, $from_table, $where_clause) {
630 // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
631 // Build basic query:
632 $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
633 // Return query
634 if ($this->debugOutput || $this->store_lastBuiltQuery) {
635 $this->debug_lastBuiltQuery = $query;
636 }
637 return $query;
638 }
639
640 /**
641 * Creates a TRUNCATE TABLE SQL-statement
642 *
643 * @param string $table See exec_TRUNCATEquery()
644 * @return string Full SQL query for TRUNCATE TABLE
645 */
646 public function TRUNCATEquery($table) {
647 foreach ($this->preProcessHookObjects as $hookObject) {
648 /** @var $hookObject PreProcessQueryHookInterface */
649 $hookObject->TRUNCATEquery_preProcessAction($table, $this);
650 }
651 // Table should be "SQL-injection-safe" when supplied to this function
652 // Build basic query:
653 $query = 'TRUNCATE TABLE ' . $table;
654 // Return query:
655 if ($this->debugOutput || $this->store_lastBuiltQuery) {
656 $this->debug_lastBuiltQuery = $query;
657 }
658 return $query;
659 }
660
661 /**
662 * Returns a WHERE clause that can find a value ($value) in a list field ($field)
663 * For instance a record in the database might contain a list of numbers,
664 * "34,234,5" (with no spaces between). This query would be able to select that
665 * record based on the value "34", "234" or "5" regardless of their position in
666 * the list (left, middle or right).
667 * The value must not contain a comma (,)
668 * Is nice to look up list-relations to records or files in TYPO3 database tables.
669 *
670 * @param string $field Field name
671 * @param string $value Value to find in list
672 * @param string $table Table in which we are searching (for DBAL detection of quoteStr() method)
673 * @return string WHERE clause for a query
674 * @throws \InvalidArgumentException
675 */
676 public function listQuery($field, $value, $table) {
677 $value = (string)$value;
678 if (strpos($value, ',') !== FALSE) {
679 throw new \InvalidArgumentException('$value must not contain a comma (,) in $this->listQuery() !', 1294585862);
680 }
681 $pattern = $this->quoteStr($value, $table);
682 $where = 'FIND_IN_SET(\'' . $pattern . '\',' . $field . ')';
683 return $where;
684 }
685
686 /**
687 * Returns a WHERE clause which will make an AND or OR search for the words in the $searchWords array in any of the fields in array $fields.
688 *
689 * @param array $searchWords Array of search words
690 * @param array $fields Array of fields
691 * @param string $table Table in which we are searching (for DBAL detection of quoteStr() method)
692 * @param string $constraint How multiple search words have to match ('AND' or 'OR')
693 * @return string WHERE clause for search
694 */
695 public function searchQuery($searchWords, $fields, $table, $constraint = self::AND_Constraint) {
696 switch ($constraint) {
697 case self::OR_Constraint:
698 $constraint = 'OR';
699 break;
700 default:
701 $constraint = 'AND';
702 }
703
704 $queryParts = array();
705 foreach ($searchWords as $sw) {
706 $like = ' LIKE \'%' . $this->quoteStr($sw, $table) . '%\'';
707 $queryParts[] = $table . '.' . implode(($like . ' OR ' . $table . '.'), $fields) . $like;
708 }
709 $query = '(' . implode(') ' . $constraint . ' (', $queryParts) . ')';
710
711 return $query;
712 }
713
714 /**************************************
715 *
716 * Prepared Query Support
717 *
718 **************************************/
719 /**
720 * Creates a SELECT prepared SQL statement.
721 *
722 * @param string $select_fields See exec_SELECTquery()
723 * @param string $from_table See exec_SELECTquery()
724 * @param string $where_clause See exec_SELECTquery()
725 * @param string $groupBy See exec_SELECTquery()
726 * @param string $orderBy See exec_SELECTquery()
727 * @param string $limit See exec_SELECTquery()
728 * @param array $input_parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. All values are treated as \TYPO3\CMS\Core\Database\PreparedStatement::PARAM_AUTOTYPE.
729 * @return \TYPO3\CMS\Core\Database\PreparedStatement Prepared statement
730 */
731 public function prepare_SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', array $input_parameters = array()) {
732 $query = $this->SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
733 /** @var $preparedStatement \TYPO3\CMS\Core\Database\PreparedStatement */
734 $preparedStatement = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\PreparedStatement', $query, $from_table, array());
735 // Bind values to parameters
736 foreach ($input_parameters as $key => $value) {
737 $preparedStatement->bindValue($key, $value, PreparedStatement::PARAM_AUTOTYPE);
738 }
739 // Return prepared statement
740 return $preparedStatement;
741 }
742
743 /**
744 * Creates a SELECT prepared SQL statement based on input query parts array
745 *
746 * @param array $queryParts Query parts array
747 * @param array $input_parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. All values are treated as \TYPO3\CMS\Core\Database\PreparedStatement::PARAM_AUTOTYPE.
748 * @return \TYPO3\CMS\Core\Database\PreparedStatement Prepared statement
749 */
750 public function prepare_SELECTqueryArray(array $queryParts, array $input_parameters = array()) {
751 return $this->prepare_SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT'], $input_parameters);
752 }
753
754 /**
755 * Prepares a prepared query.
756 *
757 * @param string $query The query to execute
758 * @param array $queryComponents The components of the query to execute
759 * @return \mysqli_stmt|object MySQLi statement / DBAL object
760 * @internal This method may only be called by \TYPO3\CMS\Core\Database\PreparedStatement
761 */
762 public function prepare_PREPAREDquery($query, array $queryComponents) {
763 if (!$this->isConnected) {
764 $this->connectDB();
765 }
766 $stmt = $this->link->stmt_init();
767 $success = $stmt->prepare($query);
768 if ($this->debugOutput) {
769 $this->debug('stmt_execute', $query);
770 }
771 return $success ? $stmt : NULL;
772 }
773
774 /**************************************
775 *
776 * Various helper functions
777 *
778 * Functions recommended to be used for
779 * - escaping values,
780 * - cleaning lists of values,
781 * - stripping of excess ORDER BY/GROUP BY keywords
782 *
783 **************************************/
784 /**
785 * Escaping and quoting values for SQL statements.
786 *
787 * @param string $str Input string
788 * @param string $table Table name for which to quote string. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how to quote the string!).
789 * @param boolean $allowNull Whether to allow NULL values
790 * @return string Output string; Wrapped in single quotes and quotes in the string (" / ') and \ will be backslashed (or otherwise based on DBAL handler)
791 * @see quoteStr()
792 */
793 public function fullQuoteStr($str, $table, $allowNull = FALSE) {
794 if (!$this->isConnected) {
795 $this->connectDB();
796 }
797 if ($allowNull && $str === NULL) {
798 return 'NULL';
799 }
800
801 return '\'' . $this->link->real_escape_string($str) . '\'';
802 }
803
804 /**
805 * Will fullquote all values in the one-dimensional array so they are ready to "implode" for an sql query.
806 *
807 * @param array $arr Array with values (either associative or non-associative array)
808 * @param string $table Table name for which to quote
809 * @param boolean|array $noQuote List/array of keys NOT to quote (eg. SQL functions) - ONLY for associative arrays
810 * @param boolean $allowNull Whether to allow NULL values
811 * @return array The input array with the values quoted
812 * @see cleanIntArray()
813 */
814 public function fullQuoteArray($arr, $table, $noQuote = FALSE, $allowNull = FALSE) {
815 if (is_string($noQuote)) {
816 $noQuote = explode(',', $noQuote);
817 } elseif (!is_array($noQuote)) {
818 $noQuote = FALSE;
819 }
820 foreach ($arr as $k => $v) {
821 if ($noQuote === FALSE || !in_array($k, $noQuote)) {
822 $arr[$k] = $this->fullQuoteStr($v, $table, $allowNull);
823 }
824 }
825 return $arr;
826 }
827
828 /**
829 * Substitution for PHP function "addslashes()"
830 * Use this function instead of the PHP addslashes() function when you build queries - this will prepare your code for DBAL.
831 * NOTICE: You must wrap the output of this function in SINGLE QUOTES to be DBAL compatible. Unless you have to apply the single quotes yourself you should rather use ->fullQuoteStr()!
832 *
833 * @param string $str Input string
834 * @param string $table Table name for which to quote string. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how to quote the string!).
835 * @return string Output string; Quotes (" / ') and \ will be backslashed (or otherwise based on DBAL handler)
836 * @see quoteStr()
837 */
838 public function quoteStr($str, $table) {
839 if (!$this->isConnected) {
840 $this->connectDB();
841 }
842 return $this->link->real_escape_string($str);
843 }
844
845 /**
846 * Escaping values for SQL LIKE statements.
847 *
848 * @param string $str Input string
849 * @param string $table Table name for which to escape string. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how to quote the string!).
850 * @return string Output string; % and _ will be escaped with \ (or otherwise based on DBAL handler)
851 * @see quoteStr()
852 */
853 public function escapeStrForLike($str, $table) {
854 return addcslashes($str, '_%');
855 }
856
857 /**
858 * Will convert all values in the one-dimensional array to integers.
859 * Useful when you want to make sure an array contains only integers before imploding them in a select-list.
860 *
861 * @param array $arr Array with values
862 * @return array The input array with all values cast to (int)
863 * @see cleanIntList()
864 */
865 public function cleanIntArray($arr) {
866 return array_map('intval', $arr);
867 }
868
869 /**
870 * Will force all entries in the input comma list to integers
871 * Useful when you want to make sure a commalist of supposed integers really contain only integers; You want to know that when you don't trust content that could go into an SQL statement.
872 *
873 * @param string $list List of comma-separated values which should be integers
874 * @return string The input list but with every value cast to (int)
875 * @see cleanIntArray()
876 */
877 public function cleanIntList($list) {
878 return implode(',', GeneralUtility::intExplode(',', $list));
879 }
880
881 /**
882 * Removes the prefix "ORDER BY" from the input string.
883 * This function is used when you call the exec_SELECTquery() function and want to pass the ORDER BY parameter by can't guarantee that "ORDER BY" is not prefixed.
884 * Generally; This function provides a work-around to the situation where you cannot pass only the fields by which to order the result.
885 *
886 * @param string $str eg. "ORDER BY title, uid
887 * @return string eg. "title, uid
888 * @see exec_SELECTquery(), stripGroupBy()
889 */
890 public function stripOrderBy($str) {
891 return preg_replace('/^(?:ORDER[[:space:]]*BY[[:space:]]*)+/i', '', trim($str));
892 }
893
894 /**
895 * Removes the prefix "GROUP BY" from the input string.
896 * This function is used when you call the SELECTquery() function and want to pass the GROUP BY parameter by can't guarantee that "GROUP BY" is not prefixed.
897 * Generally; This function provides a work-around to the situation where you cannot pass only the fields by which to order the result.
898 *
899 * @param string $str eg. "GROUP BY title, uid
900 * @return string eg. "title, uid
901 * @see exec_SELECTquery(), stripOrderBy()
902 */
903 public function stripGroupBy($str) {
904 return preg_replace('/^(?:GROUP[[:space:]]*BY[[:space:]]*)+/i', '', trim($str));
905 }
906
907 /**
908 * Takes the last part of a query, eg. "... uid=123 GROUP BY title ORDER BY title LIMIT 5,2" and splits each part into a table (WHERE, GROUPBY, ORDERBY, LIMIT)
909 * Work-around function for use where you know some userdefined end to an SQL clause is supplied and you need to separate these factors.
910 *
911 * @param string $str Input string
912 * @return array
913 */
914 public function splitGroupOrderLimit($str) {
915 // Prepending a space to make sure "[[:space:]]+" will find a space there
916 // for the first element.
917 $str = ' ' . $str;
918 // Init output array:
919 $wgolParts = array(
920 'WHERE' => '',
921 'GROUPBY' => '',
922 'ORDERBY' => '',
923 'LIMIT' => ''
924 );
925 // Find LIMIT
926 $reg = array();
927 if (preg_match('/^(.*)[[:space:]]+LIMIT[[:space:]]+([[:alnum:][:space:],._]+)$/i', $str, $reg)) {
928 $wgolParts['LIMIT'] = trim($reg[2]);
929 $str = $reg[1];
930 }
931 // Find ORDER BY
932 $reg = array();
933 if (preg_match('/^(.*)[[:space:]]+ORDER[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $str, $reg)) {
934 $wgolParts['ORDERBY'] = trim($reg[2]);
935 $str = $reg[1];
936 }
937 // Find GROUP BY
938 $reg = array();
939 if (preg_match('/^(.*)[[:space:]]+GROUP[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $str, $reg)) {
940 $wgolParts['GROUPBY'] = trim($reg[2]);
941 $str = $reg[1];
942 }
943 // Rest is assumed to be "WHERE" clause
944 $wgolParts['WHERE'] = $str;
945 return $wgolParts;
946 }
947
948 /**
949 * Returns the date and time formats compatible with the given database table.
950 *
951 * @param string $table Table name for which to return an empty date. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how date and time should be formatted).
952 * @return array
953 */
954 public function getDateTimeFormats($table) {
955 return array(
956 'date' => array(
957 'empty' => '0000-00-00',
958 'format' => 'Y-m-d'
959 ),
960 'datetime' => array(
961 'empty' => '0000-00-00 00:00:00',
962 'format' => 'Y-m-d H:i:s'
963 )
964 );
965 }
966
967 /**************************************
968 *
969 * MySQL(i) wrapper functions
970 * (For use in your applications)
971 *
972 **************************************/
973 /**
974 * Executes query
975 * MySQLi query() wrapper function
976 * Beware: Use of this method should be avoided as it is experimentally supported by DBAL. You should consider
977 * using exec_SELECTquery() and similar methods instead.
978 *
979 * @param string $query Query to execute
980 * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
981 */
982 public function sql_query($query) {
983 $res = $this->query($query);
984 if ($this->debugOutput) {
985 $this->debug('sql_query', $query);
986 }
987 return $res;
988 }
989
990 /**
991 * Returns the error status on the last query() execution
992 *
993 * @return string MySQLi error string.
994 */
995 public function sql_error() {
996 return $this->link->error;
997 }
998
999 /**
1000 * Returns the error number on the last query() execution
1001 *
1002 * @return integer MySQLi error number
1003 */
1004 public function sql_errno() {
1005 return $this->link->errno;
1006 }
1007
1008 /**
1009 * Returns the number of selected rows.
1010 *
1011 * @param boolean|\mysqli_result|object $res MySQLi result object / DBAL object
1012 * @return integer Number of resulting rows
1013 */
1014 public function sql_num_rows($res) {
1015 if ($this->debug_check_recordset($res)) {
1016 return $res->num_rows;
1017 } else {
1018 return FALSE;
1019 }
1020 }
1021
1022 /**
1023 * Returns an associative array that corresponds to the fetched row, or FALSE if there are no more rows.
1024 * MySQLi fetch_assoc() wrapper function
1025 *
1026 * @param boolean|\mysqli_result|object $res MySQLi result object / DBAL object
1027 * @return array|boolean Associative array of result row.
1028 */
1029 public function sql_fetch_assoc($res) {
1030 if ($this->debug_check_recordset($res)) {
1031 $result = $res->fetch_assoc();
1032 if ($result === NULL) {
1033 // Needed for compatibility
1034 $result = FALSE;
1035 }
1036 return $result;
1037 } else {
1038 return FALSE;
1039 }
1040 }
1041
1042 /**
1043 * Returns an array that corresponds to the fetched row, or FALSE if there are no more rows.
1044 * The array contains the values in numerical indices.
1045 * MySQLi fetch_row() wrapper function
1046 *
1047 * @param boolean|\mysqli_result|object $res MySQLi result object / DBAL object
1048 * @return array|boolean Array with result rows.
1049 */
1050 public function sql_fetch_row($res) {
1051 if ($this->debug_check_recordset($res)) {
1052 $result = $res->fetch_row();
1053 if ($result === NULL) {
1054 // Needed for compatibility
1055 $result = FALSE;
1056 }
1057 return $result;
1058 } else {
1059 return FALSE;
1060 }
1061 }
1062
1063 /**
1064 * Free result memory
1065 * free_result() wrapper function
1066 *
1067 * @param boolean|\mysqli_result|object $res MySQLi result object / DBAL object
1068 * @return boolean Returns TRUE on success or FALSE on failure.
1069 */
1070 public function sql_free_result($res) {
1071 if ($this->debug_check_recordset($res) && is_object($res)) {
1072 $res->free();
1073 return TRUE;
1074 } else {
1075 return FALSE;
1076 }
1077 }
1078
1079 /**
1080 * Get the ID generated from the previous INSERT operation
1081 *
1082 * @return integer The uid of the last inserted record.
1083 */
1084 public function sql_insert_id() {
1085 return $this->link->insert_id;
1086 }
1087
1088 /**
1089 * Returns the number of rows affected by the last INSERT, UPDATE or DELETE query
1090 *
1091 * @return integer Number of rows affected by last query
1092 */
1093 public function sql_affected_rows() {
1094 return $this->link->affected_rows;
1095 }
1096
1097 /**
1098 * Move internal result pointer
1099 *
1100 * @param boolean|\mysqli_result|object $res MySQLi result object / DBAL object
1101 * @param integer $seek Seek result number.
1102 * @return boolean Returns TRUE on success or FALSE on failure.
1103 */
1104 public function sql_data_seek($res, $seek) {
1105 if ($this->debug_check_recordset($res)) {
1106 return $res->data_seek($seek);
1107 } else {
1108 return FALSE;
1109 }
1110 }
1111
1112 /**
1113 * Get the type of the specified field in a result
1114 * mysql_field_type() wrapper function
1115 *
1116 * @param boolean|\mysqli_result|object $res MySQLi result object / DBAL object
1117 * @param integer $pointer Field index.
1118 * @return string Returns the name of the specified field index, or FALSE on error
1119 */
1120 public function sql_field_type($res, $pointer) {
1121 // mysql_field_type compatibility map
1122 // taken from: http://www.php.net/manual/en/mysqli-result.fetch-field-direct.php#89117
1123 // Constant numbers see http://php.net/manual/en/mysqli.constants.php
1124 $mysql_data_type_hash = array(
1125 1=>'tinyint',
1126 2=>'smallint',
1127 3=>'int',
1128 4=>'float',
1129 5=>'double',
1130 7=>'timestamp',
1131 8=>'bigint',
1132 9=>'mediumint',
1133 10=>'date',
1134 11=>'time',
1135 12=>'datetime',
1136 13=>'year',
1137 16=>'bit',
1138 //252 is currently mapped to all text and blob types (MySQL 5.0.51a)
1139 253=>'varchar',
1140 254=>'char',
1141 246=>'decimal'
1142 );
1143 if ($this->debug_check_recordset($res)) {
1144 $metaInfo = $res->fetch_field_direct($pointer);
1145 if ($metaInfo === FALSE) {
1146 return FALSE;
1147 }
1148 return $mysql_data_type_hash[$metaInfo->type];
1149 } else {
1150 return FALSE;
1151 }
1152 }
1153
1154 /**
1155 * Open a (persistent) connection to a MySQL server
1156 *
1157 * @param string $host Deprecated since 6.1, will be removed in two versions. Database host IP/domain[:port]
1158 * @param string $username Deprecated since 6.1, will be removed in two versions. Username to connect with.
1159 * @param string $password Deprecated since 6.1, will be removed in two versions. Password to connect with.
1160 * @return boolean|void
1161 * @throws \RuntimeException
1162 */
1163 public function sql_pconnect($host = NULL, $username = NULL, $password = NULL) {
1164 if ($this->isConnected) {
1165 return $this->link;
1166 }
1167
1168 if (!extension_loaded('mysqli')) {
1169 throw new \RuntimeException(
1170 'Database Error: PHP mysqli extension not loaded. This is a must have for TYPO3 CMS!',
1171 1271492607
1172 );
1173 }
1174
1175 if ($host || $username || $password) {
1176 $this->handleDeprecatedConnectArguments($host, $username, $password);
1177 }
1178
1179 $host = $this->persistentDatabaseConnection
1180 ? 'p:' . $this->databaseHost
1181 : $this->databaseHost;
1182
1183 $this->link = mysqli_init();
1184 $connected = $this->link->real_connect(
1185 $host,
1186 $this->databaseUsername,
1187 $this->databaseUserPassword,
1188 NULL,
1189 (int)$this->databasePort,
1190 $this->databaseSocket,
1191 $this->connectionCompression ? MYSQLI_CLIENT_COMPRESS : 0
1192 );
1193
1194 if ($connected) {
1195 $this->isConnected = TRUE;
1196
1197 if ($this->link->set_charset($this->connectionCharset) === FALSE) {
1198 GeneralUtility::sysLog(
1199 'Error setting connection charset to "' . $this->connectionCharset . '"',
1200 'Core',
1201 GeneralUtility::SYSLOG_SEVERITY_ERROR
1202 );
1203 }
1204
1205 foreach ($this->initializeCommandsAfterConnect as $command) {
1206 if ($this->query($command) === FALSE) {
1207 GeneralUtility::sysLog(
1208 'Could not initialize DB connection with query "' . $command . '": ' . $this->sql_error(),
1209 'Core',
1210 GeneralUtility::SYSLOG_SEVERITY_ERROR
1211 );
1212 }
1213 }
1214 $this->setSqlMode();
1215 $this->checkConnectionCharset();
1216 } else {
1217 // @TODO: This should raise an exception. Would be useful especially to work during installation.
1218 $error_msg = $this->link->connect_error;
1219 $this->link = NULL;
1220 GeneralUtility::sysLog(
1221 'Could not connect to MySQL server ' . $host . ' with user ' . $username . ': ' . $error_msg,
1222 'Core',
1223 GeneralUtility::SYSLOG_SEVERITY_FATAL
1224 );
1225 }
1226 return $this->link;
1227 }
1228
1229 /**
1230 * Fixes the SQL mode by unsetting NO_BACKSLASH_ESCAPES if found.
1231 *
1232 * @return void
1233 */
1234 protected function setSqlMode() {
1235 $resource = $this->sql_query('SELECT @@SESSION.sql_mode;');
1236 if ($resource) {
1237 $result = $this->sql_fetch_row($resource);
1238 if (isset($result[0]) && $result[0] && strpos($result[0], 'NO_BACKSLASH_ESCAPES') !== FALSE) {
1239 $modes = array_diff(GeneralUtility::trimExplode(',', $result[0]), array('NO_BACKSLASH_ESCAPES'));
1240 $query = 'SET sql_mode=\'' . $this->link->real_escape_string(implode(',', $modes)) . '\';';
1241 $this->sql_query($query);
1242 GeneralUtility::sysLog(
1243 'NO_BACKSLASH_ESCAPES could not be removed from SQL mode: ' . $this->sql_error(),
1244 'Core',
1245 GeneralUtility::SYSLOG_SEVERITY_ERROR
1246 );
1247 }
1248 }
1249 }
1250
1251 /**
1252 * Select a SQL database
1253 *
1254 * @param string $TYPO3_db Deprecated since 6.1, will be removed in two versions. Database to connect to.
1255 * @return boolean Returns TRUE on success or FALSE on failure.
1256 */
1257 public function sql_select_db($TYPO3_db = NULL) {
1258 if (!$this->isConnected) {
1259 $this->connectDB();
1260 }
1261
1262 if ($TYPO3_db) {
1263 GeneralUtility::deprecationLog(
1264 'DatabaseConnection->sql_select_db() should be called without arguments.' .
1265 ' Use the setDatabaseName() before. Will be removed two versions after 6.1.'
1266 );
1267 } else {
1268 $TYPO3_db = $this->databaseName;
1269 }
1270
1271 $ret = $this->link->select_db($TYPO3_db);
1272 if (!$ret) {
1273 GeneralUtility::sysLog(
1274 'Could not select MySQL database ' . $TYPO3_db . ': ' . $this->sql_error(),
1275 'Core',
1276 GeneralUtility::SYSLOG_SEVERITY_FATAL
1277 );
1278 }
1279 return $ret;
1280 }
1281
1282 /**************************************
1283 *
1284 * SQL admin functions
1285 * (For use in the Install Tool and Extension Manager)
1286 *
1287 **************************************/
1288 /**
1289 * Listing databases from current MySQL connection. NOTICE: It WILL try to select those databases and thus break selection of current database.
1290 * This is only used as a service function in the (1-2-3 process) of the Install Tool.
1291 * In any case a lookup should be done in the _DEFAULT handler DBMS then.
1292 * Use in Install Tool only!
1293 *
1294 * @return array Each entry represents a database name
1295 * @throws \RuntimeException
1296 */
1297 public function admin_get_dbs() {
1298 $dbArr = array();
1299 $db_list = $this->query("SELECT SCHEMA_NAME FROM information_schema.SCHEMATA");
1300 if ($db_list === FALSE) {
1301 throw new \RuntimeException(
1302 'MySQL Error: Cannot get tablenames: "' . $this->sql_error() . '"!',
1303 1378457171
1304 );
1305 } else {
1306 while ($row = $db_list->fetch_object()) {
1307 try {
1308 $this->setDatabaseName($row->SCHEMA_NAME);
1309 if ($this->sql_select_db()) {
1310 $dbArr[] = $row->SCHEMA_NAME;
1311 }
1312 } catch (\RuntimeException $exception) {
1313 // The exception happens if we cannot connect to the database
1314 // (usually due to missing permissions). This is ok here.
1315 // We catch the exception, skip the database and continue.
1316 }
1317 }
1318 }
1319 return $dbArr;
1320 }
1321
1322 /**
1323 * Returns the list of tables from the default database, TYPO3_db (quering the DBMS)
1324 * In a DBAL this method should 1) look up all tables from the DBMS of
1325 * the _DEFAULT handler and then 2) add all tables *configured* to be managed by other handlers
1326 *
1327 * @return array Array with tablenames as key and arrays with status information as value
1328 */
1329 public function admin_get_tables() {
1330 $whichTables = array();
1331 $tables_result = $this->query('SHOW TABLE STATUS FROM `' . $this->databaseName . '`');
1332 if ($tables_result !== FALSE) {
1333 while ($theTable = $tables_result->fetch_assoc()) {
1334 $whichTables[$theTable['Name']] = $theTable;
1335 }
1336 $tables_result->free();
1337 }
1338 return $whichTables;
1339 }
1340
1341 /**
1342 * Returns information about each field in the $table (quering the DBMS)
1343 * In a DBAL this should look up the right handler for the table and return compatible information
1344 * This function is important not only for the Install Tool but probably for
1345 * DBALs as well since they might need to look up table specific information
1346 * in order to construct correct queries. In such cases this information should
1347 * probably be cached for quick delivery.
1348 *
1349 * @param string $tableName Table name
1350 * @return array Field information in an associative array with fieldname => field row
1351 */
1352 public function admin_get_fields($tableName) {
1353 $output = array();
1354 $columns_res = $this->query('SHOW COLUMNS FROM `' . $tableName . '`');
1355 if ($columns_res !== FALSE) {
1356 while ($fieldRow = $columns_res->fetch_assoc()) {
1357 $output[$fieldRow['Field']] = $fieldRow;
1358 }
1359 $columns_res->free();
1360 }
1361 return $output;
1362 }
1363
1364 /**
1365 * Returns information about each index key in the $table (quering the DBMS)
1366 * In a DBAL this should look up the right handler for the table and return compatible information
1367 *
1368 * @param string $tableName Table name
1369 * @return array Key information in a numeric array
1370 */
1371 public function admin_get_keys($tableName) {
1372 $output = array();
1373 $keyRes = $this->query('SHOW KEYS FROM `' . $tableName . '`');
1374 if ($keyRes !== FALSE) {
1375 while ($keyRow = $keyRes->fetch_assoc()) {
1376 $output[] = $keyRow;
1377 }
1378 $keyRes->free();
1379 }
1380 return $output;
1381 }
1382
1383 /**
1384 * Returns information about the character sets supported by the current DBM
1385 * This function is important not only for the Install Tool but probably for
1386 * DBALs as well since they might need to look up table specific information
1387 * in order to construct correct queries. In such cases this information should
1388 * probably be cached for quick delivery.
1389 *
1390 * This is used by the Install Tool to convert tables with non-UTF8 charsets
1391 * Use in Install Tool only!
1392 *
1393 * @return array Array with Charset as key and an array of "Charset", "Description", "Default collation", "Maxlen" as values
1394 */
1395 public function admin_get_charsets() {
1396 $output = array();
1397 $columns_res = $this->query('SHOW CHARACTER SET');
1398 if ($columns_res !== FALSE) {
1399 while ($row = $columns_res->fetch_assoc()) {
1400 $output[$row['Charset']] = $row;
1401 }
1402 $columns_res->free();
1403 }
1404 return $output;
1405 }
1406
1407 /**
1408 * mysqli() wrapper function, used by the Install Tool and EM for all queries regarding management of the database!
1409 *
1410 * @param string $query Query to execute
1411 * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
1412 */
1413 public function admin_query($query) {
1414 $res = $this->query($query);
1415 if ($this->debugOutput) {
1416 $this->debug('admin_query', $query);
1417 }
1418 return $res;
1419 }
1420
1421 /******************************
1422 *
1423 * Connect handling
1424 *
1425 ******************************/
1426
1427 /**
1428 * Set database host
1429 *
1430 * @param string $host
1431 */
1432 public function setDatabaseHost($host = 'localhost') {
1433 $this->disconnectIfConnected();
1434 $this->databaseHost = $host;
1435 }
1436
1437 /**
1438 * Set database port
1439 *
1440 * @param integer $port
1441 */
1442 public function setDatabasePort($port = 3306) {
1443 $this->disconnectIfConnected();
1444 $this->databasePort = (int)$port;
1445 }
1446
1447 /**
1448 * Set database socket
1449 *
1450 * @param string|NULL $socket
1451 */
1452 public function setDatabaseSocket($socket = NULL) {
1453 $this->disconnectIfConnected();
1454 $this->databaseSocket = $socket;
1455 }
1456
1457 /**
1458 * Set database name
1459 *
1460 * @param string $name
1461 */
1462 public function setDatabaseName($name) {
1463 $this->disconnectIfConnected();
1464 $this->databaseName = $name;
1465 }
1466
1467 /**
1468 * Set database username
1469 *
1470 * @param string $username
1471 */
1472 public function setDatabaseUsername($username) {
1473 $this->disconnectIfConnected();
1474 $this->databaseUsername = $username;
1475 }
1476
1477 /**
1478 * Set database password
1479 *
1480 * @param string $password
1481 */
1482 public function setDatabasePassword($password) {
1483 $this->disconnectIfConnected();
1484 $this->databaseUserPassword = $password;
1485 }
1486
1487 /**
1488 * Set persistent database connection
1489 *
1490 * @param boolean $persistentDatabaseConnection
1491 * @see http://php.net/manual/de/mysqli.persistconns.php
1492 */
1493 public function setPersistentDatabaseConnection($persistentDatabaseConnection) {
1494 $this->disconnectIfConnected();
1495 $this->persistentDatabaseConnection = (bool)$persistentDatabaseConnection;
1496 }
1497
1498 /**
1499 * Set connection compression. Might be an advantage, if SQL server is not on localhost
1500 *
1501 * @param bool $connectionCompression TRUE if connection should be compressed
1502 */
1503 public function setConnectionCompression($connectionCompression) {
1504 $this->disconnectIfConnected();
1505 $this->connectionCompression = (bool)$connectionCompression;
1506 }
1507
1508 /**
1509 * Set commands to be fired after connection was established
1510 *
1511 * @param array $commands List of SQL commands to be executed after connect
1512 */
1513 public function setInitializeCommandsAfterConnect(array $commands) {
1514 $this->disconnectIfConnected();
1515 $this->initializeCommandsAfterConnect = $commands;
1516 }
1517
1518 /**
1519 * Set the charset that should be used for the MySQL connection.
1520 * The given value will be passed on to mysqli_set_charset().
1521 *
1522 * The default value of this setting is utf8.
1523 *
1524 * @param string $connectionCharset The connection charset that will be passed on to mysqli_set_charset() when connecting the database. Default is utf8.
1525 * @return void
1526 */
1527 public function setConnectionCharset($connectionCharset = 'utf8') {
1528 $this->disconnectIfConnected();
1529 $this->connectionCharset = $connectionCharset;
1530 }
1531
1532 /**
1533 * Connects to database for TYPO3 sites:
1534 *
1535 * @param string $host Deprecated since 6.1, will be removed in two versions Database. host IP/domain[:port]
1536 * @param string $username Deprecated since 6.1, will be removed in two versions. Username to connect with
1537 * @param string $password Deprecated since 6.1, will be removed in two versions. Password to connect with
1538 * @param string $db Deprecated since 6.1, will be removed in two versions. Database name to connect to
1539 * @throws \RuntimeException
1540 * @throws \UnexpectedValueException
1541 * @internal param string $user Username to connect with.
1542 * @return void
1543 */
1544 public function connectDB($host = NULL, $username = NULL, $password = NULL, $db = NULL) {
1545 // Early return if connected already
1546 if ($this->isConnected) {
1547 return;
1548 }
1549
1550 if (!$this->databaseName && !$db) {
1551 throw new \RuntimeException(
1552 'TYPO3 Fatal Error: No database selected!',
1553 1270853882
1554 );
1555 }
1556
1557 if ($host || $username || $password || $db) {
1558 $this->handleDeprecatedConnectArguments($host, $username, $password, $db);
1559 }
1560
1561 if ($this->sql_pconnect()) {
1562 if (!$this->sql_select_db()) {
1563 throw new \RuntimeException(
1564 'TYPO3 Fatal Error: Cannot connect to the current database, "' . $this->databaseName . '"!',
1565 1270853883
1566 );
1567 }
1568 } else {
1569 throw new \RuntimeException(
1570 'TYPO3 Fatal Error: The current username, password or host was not accepted when the connection to the database was attempted to be established!',
1571 1270853884
1572 );
1573 }
1574
1575 // Prepare user defined objects (if any) for hooks which extend query methods
1576 $this->preProcessHookObjects = array();
1577 $this->postProcessHookObjects = array();
1578 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'])) {
1579 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'] as $classRef) {
1580 $hookObject = GeneralUtility::getUserObj($classRef);
1581 if (!(
1582 $hookObject instanceof PreProcessQueryHookInterface
1583 || $hookObject instanceof PostProcessQueryHookInterface
1584 )) {
1585 throw new \UnexpectedValueException(
1586 '$hookObject must either implement interface TYPO3\\CMS\\Core\\Database\\PreProcessQueryHookInterface or interface TYPO3\\CMS\\Core\\Database\\PostProcessQueryHookInterface',
1587 1299158548
1588 );
1589 }
1590 if ($hookObject instanceof PreProcessQueryHookInterface) {
1591 $this->preProcessHookObjects[] = $hookObject;
1592 }
1593 if ($hookObject instanceof PostProcessQueryHookInterface) {
1594 $this->postProcessHookObjects[] = $hookObject;
1595 }
1596 }
1597 }
1598 }
1599
1600 /**
1601 * Checks if database is connected
1602 *
1603 * @return boolean
1604 */
1605 public function isConnected() {
1606 // We think we're still connected
1607 if ($this->isConnected) {
1608 // Check if this is really the case or if the database server has gone away for some reason
1609 $this->isConnected = $this->link->ping();
1610 }
1611 return $this->isConnected;
1612 }
1613
1614 /**
1615 * Checks if the current connection character set has the same value
1616 * as the connectionCharset variable.
1617 *
1618 * To determine the character set these MySQL session variables are
1619 * checked: character_set_client, character_set_results and
1620 * character_set_connection.
1621 *
1622 * If the character set does not match or if the session variables
1623 * can not be read a RuntimeException is thrown.
1624 *
1625 * @return void
1626 * @throws \RuntimeException
1627 */
1628 protected function checkConnectionCharset() {
1629 $sessionResult = $this->sql_query('SHOW SESSION VARIABLES LIKE \'character_set%\'');
1630
1631 if ($sessionResult === FALSE) {
1632 GeneralUtility::sysLog(
1633 'Error while retrieving the current charset session variables from the database: ' . $this->sql_error(),
1634 'Core',
1635 GeneralUtility::SYSLOG_SEVERITY_ERROR
1636 );
1637 throw new \RuntimeException(
1638 'TYPO3 Fatal Error: Could not determine the current charset of the database.',
1639 1381847136
1640 );
1641 }
1642
1643 $charsetVariables = array();
1644 while (($row = $this->sql_fetch_row($sessionResult)) !== FALSE) {
1645 $variableName = $row[0];
1646 $variableValue = $row[1];
1647 $charsetVariables[$variableName] = $variableValue;
1648 }
1649 $this->sql_free_result($sessionResult);
1650
1651 // These variables are set with the "Set names" command which was
1652 // used in the past. This is why we check them.
1653 $charsetRequiredVariables = array(
1654 'character_set_client',
1655 'character_set_results',
1656 'character_set_connection',
1657 );
1658
1659 $hasValidCharset = TRUE;
1660 foreach ($charsetRequiredVariables as $variableName) {
1661 if (empty($charsetVariables[$variableName])) {
1662 GeneralUtility::sysLog(
1663 'A required session variable is missing in the current MySQL connection: ' . $variableName,
1664 'Core',
1665 GeneralUtility::SYSLOG_SEVERITY_ERROR
1666 );
1667 throw new \RuntimeException(
1668 'TYPO3 Fatal Error: Could not determine the value of the database session variable: ' . $variableName,
1669 1381847779
1670 );
1671 }
1672
1673 if ($charsetVariables[$variableName] !== $this->connectionCharset) {
1674 $hasValidCharset = FALSE;
1675 break;
1676 }
1677 }
1678
1679 if (!$hasValidCharset) {
1680 throw new \RuntimeException(
1681 'It looks like the character set ' . $this->connectionCharset . ' is not used for this connection even though it is configured as connection charset. ' .
1682 'This TYPO3 installation is using the $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'setDBinit\'] property with the following value: "' .
1683 $GLOBALS['TYPO3_CONF_VARS']['SYS']['setDBinit'] . '". Please make sure that this command does not overwrite the configured charset. ' .
1684 'Please note that for the TYPO3 database everything other than utf8 is unsupported since version 4.7.',
1685 1389697515
1686 );
1687 }
1688 }
1689
1690 /**
1691 * Disconnect from database if connected
1692 *
1693 * @return void
1694 */
1695 protected function disconnectIfConnected() {
1696 if ($this->isConnected) {
1697 $this->link->close();
1698 $this->isConnected = FALSE;
1699 }
1700 }
1701
1702 /**
1703 * Returns current database handle
1704 *
1705 * @return \mysqli|NULL
1706 */
1707 public function getDatabaseHandle() {
1708 return $this->link;
1709 }
1710
1711 /**
1712 * Set current database handle, usually \mysqli
1713 *
1714 * @param \mysqli $handle
1715 */
1716 public function setDatabaseHandle($handle) {
1717 $this->link = $handle;
1718 }
1719
1720 /**
1721 * Handle deprecated arguments for sql_pconnect() and connectDB()
1722 *
1723 * @param string|null $host Database host[:port]
1724 * @param string|null $username Database user name
1725 * @param string|null $password User password
1726 * @param string|null $db Database
1727 */
1728 protected function handleDeprecatedConnectArguments($host = NULL, $username = NULL, $password = NULL, $db = NULL) {
1729 GeneralUtility::deprecationLog(
1730 'DatabaseConnection->sql_pconnect() and DatabaseConnection->connectDB() should be ' .
1731 'called without arguments. Use the setters instead.'
1732 );
1733 if ($host) {
1734 if (strpos($host, ':') > 0) {
1735 list($databaseHost, $databasePort) = explode(':', $host);
1736 $this->setDatabaseHost($databaseHost);
1737 $this->setDatabasePort($databasePort);
1738 } else {
1739 $this->setDatabaseHost($host);
1740 }
1741 }
1742 if ($username) {
1743 $this->setDatabaseUsername($username);
1744 }
1745 if ($password) {
1746 $this->setDatabasePassword($password);
1747 }
1748 if ($db) {
1749 $this->setDatabaseName($db);
1750 }
1751 }
1752
1753 /******************************
1754 *
1755 * Debugging
1756 *
1757 ******************************/
1758 /**
1759 * Debug function: Outputs error if any
1760 *
1761 * @param string $func Function calling debug()
1762 * @param string $query Last query if not last built query
1763 * @return void
1764 * @todo Define visibility
1765 */
1766 public function debug($func, $query = '') {
1767 $error = $this->sql_error();
1768 if ($error || (int)$this->debugOutput === 2) {
1769 \TYPO3\CMS\Core\Utility\DebugUtility::debug(
1770 array(
1771 'caller' => 'TYPO3\\CMS\\Core\\Database\\DatabaseConnection::' . $func,
1772 'ERROR' => $error,
1773 'lastBuiltQuery' => $query ? $query : $this->debug_lastBuiltQuery,
1774 'debug_backtrace' => \TYPO3\CMS\Core\Utility\DebugUtility::debugTrail()
1775 ),
1776 $func,
1777 is_object($GLOBALS['error']) && @is_callable(array($GLOBALS['error'], 'debug'))
1778 ? ''
1779 : 'DB Error'
1780 );
1781 }
1782 }
1783
1784 /**
1785 * Checks if record set is valid and writes debugging information into devLog if not.
1786 *
1787 * @param boolean|\mysqli_result|object MySQLi result object / DBAL object
1788 * @return boolean TRUE if the record set is valid, FALSE otherwise
1789 * @todo Define visibility
1790 */
1791 public function debug_check_recordset($res) {
1792 if ($res !== FALSE) {
1793 return TRUE;
1794 }
1795 $msg = 'Invalid database result detected';
1796 $trace = debug_backtrace();
1797 array_shift($trace);
1798 $cnt = count($trace);
1799 for ($i = 0; $i < $cnt; $i++) {
1800 // Complete objects are too large for the log
1801 if (isset($trace['object'])) {
1802 unset($trace['object']);
1803 }
1804 }
1805 $msg .= ': function TYPO3\\CMS\\Core\\Database\\DatabaseConnection->' . $trace[0]['function'] . ' called from file ' . substr($trace[0]['file'], (strlen(PATH_site) + 2)) . ' in line ' . $trace[0]['line'];
1806 GeneralUtility::sysLog(
1807 $msg . '. Use a devLog extension to get more details.',
1808 'Core/t3lib_db',
1809 GeneralUtility::SYSLOG_SEVERITY_ERROR
1810 );
1811 // Send to devLog if enabled
1812 if (TYPO3_DLOG) {
1813 $debugLogData = array(
1814 'SQL Error' => $this->sql_error(),
1815 'Backtrace' => $trace
1816 );
1817 if ($this->debug_lastBuiltQuery) {
1818 $debugLogData = array('SQL Query' => $this->debug_lastBuiltQuery) + $debugLogData;
1819 }
1820 GeneralUtility::devLog($msg . '.', 'Core/t3lib_db', 3, $debugLogData);
1821 }
1822 return FALSE;
1823 }
1824
1825 /**
1826 * Explain select queries
1827 * If $this->explainOutput is set, SELECT queries will be explained here. Only queries with more than one possible result row will be displayed.
1828 * The output is either printed as raw HTML output or embedded into the TS admin panel (checkbox must be enabled!)
1829 *
1830 * TODO: Feature is not DBAL-compliant
1831 *
1832 * @param string $query SQL query
1833 * @param string $from_table Table(s) from which to select. This is what comes right after "FROM ...". Required value.
1834 * @param integer $row_count Number of resulting rows
1835 * @return boolean TRUE if explain was run, FALSE otherwise
1836 */
1837 protected function explain($query, $from_table, $row_count) {
1838 $debugAllowedForIp = GeneralUtility::cmpIP(
1839 GeneralUtility::getIndpEnv('REMOTE_ADDR'),
1840 $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
1841 );
1842 if (
1843 (int)$this->explainOutput == 1
1844 || ((int)$this->explainOutput == 2 && $debugAllowedForIp)
1845 ) {
1846 // Raw HTML output
1847 $explainMode = 1;
1848 } elseif ((int)$this->explainOutput == 3 && is_object($GLOBALS['TT'])) {
1849 // Embed the output into the TS admin panel
1850 $explainMode = 2;
1851 } else {
1852 return FALSE;
1853 }
1854 $error = $this->sql_error();
1855 $trail = \TYPO3\CMS\Core\Utility\DebugUtility::debugTrail();
1856 $explain_tables = array();
1857 $explain_output = array();
1858 $res = $this->sql_query('EXPLAIN ' . $query, $this->link);
1859 if (is_a($res, '\\mysqli_result')) {
1860 while ($tempRow = $this->sql_fetch_assoc($res)) {
1861 $explain_output[] = $tempRow;
1862 $explain_tables[] = $tempRow['table'];
1863 }
1864 $this->sql_free_result($res);
1865 }
1866 $indices_output = array();
1867 // Notice: Rows are skipped if there is only one result, or if no conditions are set
1868 if (
1869 $explain_output[0]['rows'] > 1
1870 || GeneralUtility::inList('ALL', $explain_output[0]['type'])
1871 ) {
1872 // Only enable output if it's really useful
1873 $debug = TRUE;
1874 foreach ($explain_tables as $table) {
1875 $tableRes = $this->sql_query('SHOW TABLE STATUS LIKE \'' . $table . '\'');
1876 $isTable = $this->sql_num_rows($tableRes);
1877 if ($isTable) {
1878 $res = $this->sql_query('SHOW INDEX FROM ' . $table, $this->link);
1879 if (is_a($res, '\\mysqli_result')) {
1880 while ($tempRow = $this->sql_fetch_assoc($res)) {
1881 $indices_output[] = $tempRow;
1882 }
1883 $this->sql_free_result($res);
1884 }
1885 }
1886 $this->sql_free_result($tableRes);
1887 }
1888 } else {
1889 $debug = FALSE;
1890 }
1891 if ($debug) {
1892 if ($explainMode) {
1893 $data = array();
1894 $data['query'] = $query;
1895 $data['trail'] = $trail;
1896 $data['row_count'] = $row_count;
1897 if ($error) {
1898 $data['error'] = $error;
1899 }
1900 if (count($explain_output)) {
1901 $data['explain'] = $explain_output;
1902 }
1903 if (count($indices_output)) {
1904 $data['indices'] = $indices_output;
1905 }
1906 if ($explainMode == 1) {
1907 \TYPO3\CMS\Core\Utility\DebugUtility::debug($data, 'Tables: ' . $from_table, 'DB SQL EXPLAIN');
1908 } elseif ($explainMode == 2) {
1909 $GLOBALS['TT']->setTSselectQuery($data);
1910 }
1911 }
1912 return TRUE;
1913 }
1914 return FALSE;
1915 }
1916
1917 /**
1918 * Serialize destructs current connection
1919 *
1920 * @return array All protected properties that should be saved
1921 */
1922 public function __sleep() {
1923 $this->disconnectIfConnected();
1924 return array(
1925 'debugOutput',
1926 'explainOutput',
1927 'databaseHost',
1928 'databasePort',
1929 'databaseSocket',
1930 'databaseName',
1931 'databaseUsername',
1932 'databaseUserPassword',
1933 'persistentDatabaseConnection',
1934 'connectionCompression',
1935 'initializeCommandsAfterConnect',
1936 'default_charset',
1937 );
1938 }
1939 }