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