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