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