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