TYPO3 CMS  TYPO3_8-7
DatabaseConnection.php
Go to the documentation of this file.
1 <?php
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 
21 
51 {
57  const AND_Constraint = 'AND';
58 
64  const OR_Constraint = 'OR';
65 
71  public $debugOutput = false;
72 
78  public $debug_lastBuiltQuery = '';
79 
85  public $store_lastBuiltQuery = false;
86 
93  public $explainOutput = 0;
94 
98  protected $databaseHost = '';
99 
103  protected $databasePort = 3306;
104 
108  protected $databaseSocket = null;
109 
113  protected $databaseName = '';
114 
118  protected $databaseUsername = '';
119 
123  protected $databaseUserPassword = '';
124 
129  protected $persistentDatabaseConnection = false;
130 
134  protected $connectionCompression = false;
135 
142  protected $connectionCharset = 'utf8';
143 
148 
152  protected $isConnected = false;
153 
157  protected $link = null;
158 
164  public $default_charset = 'utf8';
165 
169  protected $preProcessHookObjects = [];
170 
174  protected $postProcessHookObjects = [];
175 
181  protected $deprecationWarningThrown = false;
182 
186  public function initialize()
187  {
188  // Intentionally blank as this will be overloaded by DBAL
189  }
190 
191  /************************************
192  *
193  * Query execution
194  *
195  * These functions are the RECOMMENDED DBAL functions for use in your applications
196  * Using these functions will allow the DBAL to use alternative ways of accessing data (contrary to if a query is returned!)
197  * They compile a query AND execute it immediately and then return the result
198  * This principle heightens our ability to create various forms of DBAL of the functions.
199  * Generally: We want to return a result pointer/object, never queries.
200  * Also, having the table name together with the actual query execution allows us to direct the request to other databases.
201  *
202  **************************************/
203 
213  public function exec_INSERTquery($table, $fields_values, $no_quote_fields = false)
214  {
215  $this->logDeprecation();
216  $res = $this->query($this->INSERTquery($table, $fields_values, $no_quote_fields));
217  if ($this->debugOutput) {
218  $this->debug('exec_INSERTquery');
219  }
220  foreach ($this->postProcessHookObjects as $hookObject) {
222  $hookObject->exec_INSERTquery_postProcessAction($table, $fields_values, $no_quote_fields, $this);
223  }
224  return $res;
225  }
226 
236  public function exec_INSERTmultipleRows($table, array $fields, array $rows, $no_quote_fields = false)
237  {
238  $this->logDeprecation();
239  $res = $this->query($this->INSERTmultipleRows($table, $fields, $rows, $no_quote_fields));
240  if ($this->debugOutput) {
241  $this->debug('exec_INSERTmultipleRows');
242  }
243  foreach ($this->postProcessHookObjects as $hookObject) {
245  $hookObject->exec_INSERTmultipleRows_postProcessAction($table, $fields, $rows, $no_quote_fields, $this);
246  }
247  return $res;
248  }
249 
260  public function exec_UPDATEquery($table, $where, $fields_values, $no_quote_fields = false)
261  {
262  $this->logDeprecation();
263  $res = $this->query($this->UPDATEquery($table, $where, $fields_values, $no_quote_fields));
264  if ($this->debugOutput) {
265  $this->debug('exec_UPDATEquery');
266  }
267  foreach ($this->postProcessHookObjects as $hookObject) {
269  $hookObject->exec_UPDATEquery_postProcessAction($table, $where, $fields_values, $no_quote_fields, $this);
270  }
271  return $res;
272  }
273 
281  public function exec_DELETEquery($table, $where)
282  {
283  $this->logDeprecation();
284  $res = $this->query($this->DELETEquery($table, $where));
285  if ($this->debugOutput) {
286  $this->debug('exec_DELETEquery');
287  }
288  foreach ($this->postProcessHookObjects as $hookObject) {
290  $hookObject->exec_DELETEquery_postProcessAction($table, $where, $this);
291  }
292  return $res;
293  }
294 
307  public function exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '')
308  {
309  $this->logDeprecation();
310  $query = $this->SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
311  $res = $this->query($query);
312  if ($this->debugOutput) {
313  $this->debug('exec_SELECTquery');
314  }
315  if ($this->explainOutput) {
316  $this->explain($query, $from_table, $res->num_rows);
317  }
318  foreach ($this->postProcessHookObjects as $hookObject) {
320  $hookObject->exec_SELECTquery_postProcessAction($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', $this);
321  }
322  return $res;
323  }
324 
342  public function exec_SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
343  {
344  $this->logDeprecation();
345  $queryParts = $this->getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause, $groupBy, $orderBy, $limit);
346  return $this->exec_SELECT_queryArray($queryParts);
347  }
348 
356  public function exec_SELECT_queryArray($queryParts)
357  {
358  $this->logDeprecation();
359  return $this->exec_SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT']);
360  }
361 
376  public function exec_SELECTgetRows($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', $uidIndexField = '')
377  {
378  $this->logDeprecation();
379  $res = $this->exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
380  if ($this->sql_error()) {
381  $this->sql_free_result($res);
382  return null;
383  }
384  $output = [];
385  $firstRecord = true;
386  while ($record = $this->sql_fetch_assoc($res)) {
387  if ($uidIndexField) {
388  if ($firstRecord) {
389  $firstRecord = false;
390  if (!array_key_exists($uidIndexField, $record)) {
391  $this->sql_free_result($res);
392  throw new \InvalidArgumentException('The given $uidIndexField "' . $uidIndexField . '" is not available in the result.', 1432933855);
393  }
394  }
395  $output[$record[$uidIndexField]] = $record;
396  } else {
397  $output[] = $record;
398  }
399  }
400  $this->sql_free_result($res);
401  return $output;
402  }
403 
416  public function exec_SELECTgetSingleRow($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $numIndex = false)
417  {
418  $this->logDeprecation();
419  $res = $this->exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, '1');
420  $output = null;
421  if ($res !== false) {
422  if ($numIndex) {
423  $output = $this->sql_fetch_row($res);
424  } else {
425  $output = $this->sql_fetch_assoc($res);
426  }
427  $this->sql_free_result($res);
428  }
429  return $output;
430  }
431 
440  public function exec_SELECTcountRows($field, $table, $where = '1=1')
441  {
442  $this->logDeprecation();
443  $count = false;
444  $resultSet = $this->exec_SELECTquery('COUNT(' . $field . ')', $table, $where);
445  if ($resultSet !== false) {
446  list($count) = $this->sql_fetch_row($resultSet);
447  $count = (int)$count;
448  $this->sql_free_result($resultSet);
449  }
450  return $count;
451  }
452 
459  public function exec_TRUNCATEquery($table)
460  {
461  $this->logDeprecation();
462  $res = $this->query($this->TRUNCATEquery($table));
463  if ($this->debugOutput) {
464  $this->debug('exec_TRUNCATEquery');
465  }
466  foreach ($this->postProcessHookObjects as $hookObject) {
468  $hookObject->exec_TRUNCATEquery_postProcessAction($table, $this);
469  }
470  return $res;
471  }
472 
480  protected function query($query)
481  {
482  $this->logDeprecation();
483  if (!$this->isConnected) {
484  $this->connectDB();
485  }
486  return $this->link->query($query);
487  }
488 
489  /**************************************
490  *
491  * Query building
492  *
493  **************************************/
502  public function INSERTquery($table, $fields_values, $no_quote_fields = false)
503  {
504  $this->logDeprecation();
505  // Table and fieldnames should be "SQL-injection-safe" when supplied to this
506  // function (contrary to values in the arrays which may be insecure).
507  if (!is_array($fields_values) || empty($fields_values)) {
508  return null;
509  }
510  foreach ($this->preProcessHookObjects as $hookObject) {
511  $hookObject->INSERTquery_preProcessAction($table, $fields_values, $no_quote_fields, $this);
512  }
513  // Quote and escape values
514  $fields_values = $this->fullQuoteArray($fields_values, $table, $no_quote_fields, true);
515  // Build query
516  $query = 'INSERT INTO ' . $table . ' (' . implode(',', array_keys($fields_values)) . ') VALUES ' . '(' . implode(',', $fields_values) . ')';
517  // Return query
518  if ($this->debugOutput || $this->store_lastBuiltQuery) {
519  $this->debug_lastBuiltQuery = $query;
520  }
521  return $query;
522  }
523 
533  public function INSERTmultipleRows($table, array $fields, array $rows, $no_quote_fields = false)
534  {
535  $this->logDeprecation();
536  // Table and fieldnames should be "SQL-injection-safe" when supplied to this
537  // function (contrary to values in the arrays which may be insecure).
538  if (empty($rows)) {
539  return null;
540  }
541  foreach ($this->preProcessHookObjects as $hookObject) {
543  $hookObject->INSERTmultipleRows_preProcessAction($table, $fields, $rows, $no_quote_fields, $this);
544  }
545  // Build query
546  $query = 'INSERT INTO ' . $table . ' (' . implode(', ', $fields) . ') VALUES ';
547  $rowSQL = [];
548  foreach ($rows as $row) {
549  // Quote and escape values
550  $row = $this->fullQuoteArray($row, $table, $no_quote_fields);
551  $rowSQL[] = '(' . implode(', ', $row) . ')';
552  }
553  $query .= implode(', ', $rowSQL);
554  // Return query
555  if ($this->debugOutput || $this->store_lastBuiltQuery) {
556  $this->debug_lastBuiltQuery = $query;
557  }
558  return $query;
559  }
560 
572  public function UPDATEquery($table, $where, $fields_values, $no_quote_fields = false)
573  {
574  $this->logDeprecation();
575  // Table and fieldnames should be "SQL-injection-safe" when supplied to this
576  // function (contrary to values in the arrays which may be insecure).
577  if (is_string($where)) {
578  foreach ($this->preProcessHookObjects as $hookObject) {
580  $hookObject->UPDATEquery_preProcessAction($table, $where, $fields_values, $no_quote_fields, $this);
581  }
582  $fields = [];
583  if (is_array($fields_values) && !empty($fields_values)) {
584  // Quote and escape values
585  $nArr = $this->fullQuoteArray($fields_values, $table, $no_quote_fields, true);
586  foreach ($nArr as $k => $v) {
587  $fields[] = $k . '=' . $v;
588  }
589  }
590  // Build query
591  $query = 'UPDATE ' . $table . ' SET ' . implode(',', $fields) . ((string)$where !== '' ? ' WHERE ' . $where : '');
592  if ($this->debugOutput || $this->store_lastBuiltQuery) {
593  $this->debug_lastBuiltQuery = $query;
594  }
595  return $query;
596  }
597  throw new \InvalidArgumentException('TYPO3 Fatal Error: "Where" clause argument for UPDATE query was not a string in $this->UPDATEquery() !', 1270853880);
598  }
599 
608  public function DELETEquery($table, $where)
609  {
610  $this->logDeprecation();
611  if (is_string($where)) {
612  foreach ($this->preProcessHookObjects as $hookObject) {
614  $hookObject->DELETEquery_preProcessAction($table, $where, $this);
615  }
616  // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
617  $query = 'DELETE FROM ' . $table . ((string)$where !== '' ? ' WHERE ' . $where : '');
618  if ($this->debugOutput || $this->store_lastBuiltQuery) {
619  $this->debug_lastBuiltQuery = $query;
620  }
621  return $query;
622  }
623  throw new \InvalidArgumentException('TYPO3 Fatal Error: "Where" clause argument for DELETE query was not a string in $this->DELETEquery() !', 1270853881);
624  }
625 
637  public function SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '')
638  {
639  $this->logDeprecation();
640  foreach ($this->preProcessHookObjects as $hookObject) {
642  $hookObject->SELECTquery_preProcessAction($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit, $this);
643  }
644  // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
645  // Build basic query
646  $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
647  // Group by
648  $query .= (string)$groupBy !== '' ? ' GROUP BY ' . $groupBy : '';
649  // Order by
650  $query .= (string)$orderBy !== '' ? ' ORDER BY ' . $orderBy : '';
651  // Group by
652  $query .= (string)$limit !== '' ? ' LIMIT ' . $limit : '';
653  // Return query
654  if ($this->debugOutput || $this->store_lastBuiltQuery) {
655  $this->debug_lastBuiltQuery = $query;
656  }
657  return $query;
658  }
659 
669  public function SELECTsubquery($select_fields, $from_table, $where_clause)
670  {
671  $this->logDeprecation();
672  // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
673  // Build basic query:
674  $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
675  // Return query
676  if ($this->debugOutput || $this->store_lastBuiltQuery) {
677  $this->debug_lastBuiltQuery = $query;
678  }
679  return $query;
680  }
681 
699  public function SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
700  {
701  $this->logDeprecation();
702  $queryParts = $this->getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause, $groupBy, $orderBy, $limit);
703  return $this->SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT']);
704  }
705 
712  public function TRUNCATEquery($table)
713  {
714  $this->logDeprecation();
715  foreach ($this->preProcessHookObjects as $hookObject) {
717  $hookObject->TRUNCATEquery_preProcessAction($table, $this);
718  }
719  // Table should be "SQL-injection-safe" when supplied to this function
720  // Build basic query:
721  $query = 'TRUNCATE TABLE ' . $table;
722  // Return query:
723  if ($this->debugOutput || $this->store_lastBuiltQuery) {
724  $this->debug_lastBuiltQuery = $query;
725  }
726  return $query;
727  }
728 
744  public function listQuery($field, $value, $table)
745  {
746  $this->logDeprecation();
747  $value = (string)$value;
748  if (strpos($value, ',') !== false) {
749  throw new \InvalidArgumentException('$value must not contain a comma (,) in $this->listQuery() !', 1294585862);
750  }
751  $pattern = $this->quoteStr($value, $table);
752  $where = 'FIND_IN_SET(\'' . $pattern . '\',' . $field . ')';
753  return $where;
754  }
755 
765  public function searchQuery($searchWords, $fields, $table, $constraint = self::AND_Constraint)
766  {
767  $this->logDeprecation();
768  switch ($constraint) {
769  case self::OR_Constraint:
770  $constraint = 'OR';
771  break;
772  default:
773  $constraint = 'AND';
774  }
775 
776  $queryParts = [];
777  foreach ($searchWords as $sw) {
778  $like = ' LIKE \'%' . $this->quoteStr($this->escapeStrForLike($sw, $table), $table) . '%\'';
779  $queryParts[] = $table . '.' . implode(($like . ' OR ' . $table . '.'), $fields) . $like;
780  }
781  $query = '(' . implode(') ' . $constraint . ' (', $queryParts) . ')';
782 
783  return $query;
784  }
785 
786  /**************************************
787  *
788  * Prepared Query Support
789  *
790  **************************************/
803  public function prepare_SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', array $input_parameters = [])
804  {
805  $this->logDeprecation();
806  $query = $this->SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
808  $preparedStatement = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\PreparedStatement::class, $query, $from_table, []);
809  // Bind values to parameters
810  foreach ($input_parameters as $key => $value) {
811  $preparedStatement->bindValue($key, $value, PreparedStatement::PARAM_AUTOTYPE);
812  }
813  // Return prepared statement
814  return $preparedStatement;
815  }
816 
824  public function prepare_SELECTqueryArray(array $queryParts, array $input_parameters = [])
825  {
826  $this->logDeprecation();
827  return $this->prepare_SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT'], $input_parameters);
828  }
829 
838  public function prepare_PREPAREDquery($query, array $queryComponents)
839  {
840  $this->logDeprecation();
841  if (!$this->isConnected) {
842  $this->connectDB();
843  }
844  $stmt = $this->link->stmt_init();
845  $success = $stmt->prepare($query);
846  if ($this->debugOutput) {
847  $this->debug('stmt_execute', $query);
848  }
849  return $success ? $stmt : null;
850  }
851 
852  /**************************************
853  *
854  * Various helper functions
855  *
856  * Functions recommended to be used for
857  * - escaping values,
858  * - cleaning lists of values,
859  * - stripping of excess ORDER BY/GROUP BY keywords
860  *
861  **************************************/
871  public function fullQuoteStr($str, $table, $allowNull = false)
872  {
873  $this->logDeprecation();
874  if (!$this->isConnected) {
875  $this->connectDB();
876  }
877  if ($allowNull && $str === null) {
878  return 'NULL';
879  }
880  if (is_bool($str)) {
881  $str = (int)$str;
882  }
883 
884  return '\'' . $this->link->real_escape_string($str) . '\'';
885  }
886 
897  public function fullQuoteArray($arr, $table, $noQuote = false, $allowNull = false)
898  {
899  $this->logDeprecation();
900  if (is_string($noQuote)) {
901  $noQuote = explode(',', $noQuote);
902  } elseif (!is_array($noQuote)) {
903  $noQuote = (bool)$noQuote;
904  }
905  if ($noQuote === true) {
906  return $arr;
907  }
908  foreach ($arr as $k => $v) {
909  if ($noQuote === false || !in_array($k, $noQuote)) {
910  $arr[$k] = $this->fullQuoteStr($v, $table, $allowNull);
911  }
912  }
913  return $arr;
914  }
915 
926  public function quoteStr($str, $table)
927  {
928  $this->logDeprecation();
929  if (!$this->isConnected) {
930  $this->connectDB();
931  }
932  return $this->link->real_escape_string($str);
933  }
934 
943  public function escapeStrForLike($str, $table)
944  {
945  $this->logDeprecation();
946  return addcslashes($str, '_%');
947  }
948 
957  public function cleanIntArray($arr)
958  {
959  $this->logDeprecation();
960  return array_map('intval', $arr);
961  }
962 
971  public function cleanIntList($list)
972  {
973  $this->logDeprecation();
974  return implode(',', GeneralUtility::intExplode(',', $list));
975  }
976 
986  public function stripOrderBy($str)
987  {
988  $this->logDeprecation();
989  return preg_replace('/^(?:ORDER[[:space:]]*BY[[:space:]]*)+/i', '', trim($str));
990  }
991 
1001  public function stripGroupBy($str)
1002  {
1003  $this->logDeprecation();
1004  return preg_replace('/^(?:GROUP[[:space:]]*BY[[:space:]]*)+/i', '', trim($str));
1005  }
1006 
1013  public function getDateTimeFormats($table)
1014  {
1015  $this->logDeprecation();
1017  }
1018 
1035  protected function getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
1036  {
1037  $foreign_table_as = $foreign_table == $local_table ? $foreign_table . StringUtility::getUniqueId('_join') : '';
1038  $mmWhere = $local_table ? $local_table . '.uid=' . $mm_table . '.uid_local' : '';
1039  $mmWhere .= ($local_table and $foreign_table) ? ' AND ' : '';
1040  $tables = ($local_table ? $local_table . ',' : '') . $mm_table;
1041  if ($foreign_table) {
1042  $mmWhere .= ($foreign_table_as ?: $foreign_table) . '.uid=' . $mm_table . '.uid_foreign';
1043  $tables .= ',' . $foreign_table . ($foreign_table_as ? ' AS ' . $foreign_table_as : '');
1044  }
1045  return [
1046  'SELECT' => $select,
1047  'FROM' => $tables,
1048  'WHERE' => $mmWhere . ' ' . $whereClause,
1049  'GROUPBY' => $groupBy,
1050  'ORDERBY' => $orderBy,
1051  'LIMIT' => $limit
1052  ];
1053  }
1054 
1055  /**************************************
1056  *
1057  * MySQL(i) wrapper functions
1058  * (For use in your applications)
1059  *
1060  **************************************/
1070  public function sql_query($query)
1071  {
1072  $this->logDeprecation();
1073  $res = $this->query($query);
1074  if ($this->debugOutput) {
1075  $this->debug('sql_query', $query);
1076  }
1077  return $res;
1078  }
1079 
1085  public function sql_error()
1086  {
1087  $this->logDeprecation();
1088  return $this->link->error;
1089  }
1090 
1096  public function sql_errno()
1097  {
1098  $this->logDeprecation();
1099  return $this->link->errno;
1100  }
1101 
1108  public function sql_num_rows($res)
1109  {
1110  $this->logDeprecation();
1111  if ($this->debug_check_recordset($res)) {
1112  return $res->num_rows;
1113  }
1114  return false;
1115  }
1116 
1124  public function sql_fetch_assoc($res)
1125  {
1126  $this->logDeprecation();
1127  if ($this->debug_check_recordset($res)) {
1128  $result = $res->fetch_assoc();
1129  if ($result === null) {
1130  // Needed for compatibility
1131  $result = false;
1132  }
1133  return $result;
1134  }
1135  return false;
1136  }
1137 
1146  public function sql_fetch_row($res)
1147  {
1148  $this->logDeprecation();
1149  if ($this->debug_check_recordset($res)) {
1150  $result = $res->fetch_row();
1151  if ($result === null) {
1152  // Needed for compatibility
1153  $result = false;
1154  }
1155  return $result;
1156  }
1157  return false;
1158  }
1159 
1167  public function sql_free_result($res)
1168  {
1169  $this->logDeprecation();
1170  if ($this->debug_check_recordset($res) && is_object($res)) {
1171  $res->free();
1172  return true;
1173  }
1174  return false;
1175  }
1176 
1182  public function sql_insert_id()
1183  {
1184  $this->logDeprecation();
1185  return $this->link->insert_id;
1186  }
1187 
1193  public function sql_affected_rows()
1194  {
1195  return $this->link->affected_rows;
1196  }
1197 
1205  public function sql_data_seek($res, $seek)
1206  {
1207  $this->logDeprecation();
1208  if ($this->debug_check_recordset($res)) {
1209  return $res->data_seek($seek);
1210  }
1211  return false;
1212  }
1213 
1222  public function sql_field_type($res, $pointer)
1223  {
1224  // mysql_field_type compatibility map
1225  // taken from: http://www.php.net/manual/en/mysqli-result.fetch-field-direct.php#89117
1226  // Constant numbers see http://php.net/manual/en/mysqli.constants.php
1227  $mysql_data_type_hash = [
1228  1=>'tinyint',
1229  2=>'smallint',
1230  3=>'int',
1231  4=>'float',
1232  5=>'double',
1233  7=>'timestamp',
1234  8=>'bigint',
1235  9=>'mediumint',
1236  10=>'date',
1237  11=>'time',
1238  12=>'datetime',
1239  13=>'year',
1240  16=>'bit',
1241  //252 is currently mapped to all text and blob types (MySQL 5.0.51a)
1242  253=>'varchar',
1243  254=>'char',
1244  246=>'decimal'
1245  ];
1246  if ($this->debug_check_recordset($res)) {
1247  $metaInfo = $res->fetch_field_direct($pointer);
1248  if ($metaInfo === false) {
1249  return false;
1250  }
1251  return $mysql_data_type_hash[$metaInfo->type];
1252  }
1253  return false;
1254  }
1255 
1262  public function sql_pconnect()
1263  {
1264  if ($this->isConnected) {
1265  return $this->link;
1266  }
1267 
1268  if (!extension_loaded('mysqli')) {
1269  throw new \RuntimeException(
1270  'Database Error: PHP mysqli extension not loaded. This is a must have for TYPO3 CMS!',
1271  1271492607
1272  );
1273  }
1274 
1275  $host = $this->persistentDatabaseConnection
1276  ? 'p:' . $this->databaseHost
1278 
1279  // We are not using the TYPO3 CMS shim here as the database parameters in this class
1280  // are settable externally. This requires building the connection parameter array
1281  // just in time when establishing the connection.
1282  $connection = \Doctrine\DBAL\DriverManager::getConnection([
1283  'driver' => 'mysqli',
1284  'wrapperClass' => Connection::class,
1285  'host' => $host,
1286  'port' => (int)$this->databasePort,
1287  'unix_socket' => $this->databaseSocket,
1288  'user' => $this->databaseUsername,
1289  'password' => $this->databaseUserPassword,
1290  'charset' => $this->connectionCharset,
1291  ]);
1292 
1293  // Mimic the previous behavior of returning false on connection errors
1294  try {
1296  $mysqliConnection = $connection->getWrappedConnection();
1297  $this->link = $mysqliConnection->getWrappedResourceHandle();
1298  } catch (\Doctrine\DBAL\Exception\ConnectionException $exception) {
1299  return false;
1300  }
1301 
1302  if ($connection->isConnected()) {
1303  $this->isConnected = true;
1304 
1305  foreach ($this->initializeCommandsAfterConnect as $command) {
1306  if ($this->query($command) === false) {
1307  GeneralUtility::sysLog(
1308  'Could not initialize DB connection with query "' . $command . '": ' . $this->sql_error(),
1309  'core',
1311  );
1312  }
1313  }
1314  $this->checkConnectionCharset();
1315  } else {
1316  // @todo This should raise an exception. Would be useful especially to work during installation.
1317  $error_msg = $this->link->connect_error;
1318  $this->link = null;
1319  GeneralUtility::sysLog(
1320  'Could not connect to MySQL server ' . $host . ' with user ' . $this->databaseUsername . ': '
1321  . $error_msg,
1322  'core',
1324  );
1325  }
1326 
1327  return $this->link;
1328  }
1329 
1335  public function sql_select_db()
1336  {
1337  if (!$this->isConnected) {
1338  $this->connectDB();
1339  }
1340 
1341  $ret = $this->link->select_db($this->databaseName);
1342  if (!$ret) {
1343  GeneralUtility::sysLog(
1344  'Could not select MySQL database ' . $this->databaseName . ': ' . $this->sql_error(),
1345  'core',
1347  );
1348  }
1349  return $ret;
1350  }
1351 
1352  /**************************************
1353  *
1354  * SQL admin functions
1355  * (For use in the Install Tool and Extension Manager)
1356  *
1357  **************************************/
1367  public function admin_get_dbs()
1368  {
1369  $this->logDeprecation();
1370  $dbArr = [];
1371  $db_list = $this->query('SELECT SCHEMA_NAME FROM information_schema.SCHEMATA');
1372  if ($db_list === false) {
1373  throw new \RuntimeException(
1374  'MySQL Error: Cannot get tablenames: "' . $this->sql_error() . '"!',
1375  1378457171
1376  );
1377  }
1378  while ($row = $db_list->fetch_object()) {
1379  try {
1380  $this->setDatabaseName($row->SCHEMA_NAME);
1381  if ($this->sql_select_db()) {
1382  $dbArr[] = $row->SCHEMA_NAME;
1383  }
1384  } catch (\RuntimeException $exception) {
1385  // The exception happens if we cannot connect to the database
1386  // (usually due to missing permissions). This is ok here.
1387  // We catch the exception, skip the database and continue.
1388  }
1389  }
1390 
1391  return $dbArr;
1392  }
1393 
1401  public function admin_get_tables()
1402  {
1403  $this->logDeprecation();
1404  $whichTables = [];
1405  $tables_result = $this->query('SHOW TABLE STATUS FROM `' . $this->databaseName . '`');
1406  if ($tables_result !== false) {
1407  while ($theTable = $tables_result->fetch_assoc()) {
1408  $whichTables[$theTable['Name']] = $theTable;
1409  }
1410  $tables_result->free();
1411  }
1412  return $whichTables;
1413  }
1414 
1426  public function admin_get_fields($tableName)
1427  {
1428  $this->logDeprecation();
1429  $output = [];
1430  $columns_res = $this->query('SHOW FULL COLUMNS FROM `' . $tableName . '`');
1431  if ($columns_res !== false) {
1432  while ($fieldRow = $columns_res->fetch_assoc()) {
1433  $output[$fieldRow['Field']] = $fieldRow;
1434  }
1435  $columns_res->free();
1436  }
1437  return $output;
1438  }
1439 
1447  public function admin_get_keys($tableName)
1448  {
1449  $this->logDeprecation();
1450  $output = [];
1451  $keyRes = $this->query('SHOW KEYS FROM `' . $tableName . '`');
1452  if ($keyRes !== false) {
1453  while ($keyRow = $keyRes->fetch_assoc()) {
1454  $output[] = $keyRow;
1455  }
1456  $keyRes->free();
1457  }
1458  return $output;
1459  }
1460 
1473  public function admin_get_charsets()
1474  {
1475  $this->logDeprecation();
1476  $output = [];
1477  $columns_res = $this->query('SHOW CHARACTER SET');
1478  if ($columns_res !== false) {
1479  while ($row = $columns_res->fetch_assoc()) {
1480  $output[$row['Charset']] = $row;
1481  }
1482  $columns_res->free();
1483  }
1484  return $output;
1485  }
1486 
1493  public function admin_query($query)
1494  {
1495  $this->logDeprecation();
1496  $res = $this->query($query);
1497  if ($this->debugOutput) {
1498  $this->debug('admin_query', $query);
1499  }
1500  return $res;
1501  }
1502 
1503  /******************************
1504  *
1505  * Connect handling
1506  *
1507  ******************************/
1508 
1514  public function setDatabaseHost($host = 'localhost')
1515  {
1516  $this->disconnectIfConnected();
1517  $this->databaseHost = $host;
1518  }
1519 
1525  public function setDatabasePort($port = 3306)
1526  {
1527  $this->disconnectIfConnected();
1528  $this->databasePort = (int)$port;
1529  }
1530 
1536  public function setDatabaseSocket($socket = null)
1537  {
1538  $this->disconnectIfConnected();
1539  $this->databaseSocket = $socket;
1540  }
1541 
1547  public function setDatabaseName($name)
1548  {
1549  $this->disconnectIfConnected();
1550  $this->databaseName = $name;
1551  }
1552 
1558  public function setDatabaseUsername($username)
1559  {
1560  $this->disconnectIfConnected();
1561  $this->databaseUsername = $username;
1562  }
1563 
1569  public function setDatabasePassword($password)
1570  {
1571  $this->disconnectIfConnected();
1572  $this->databaseUserPassword = $password;
1573  }
1574 
1582  {
1583  $this->disconnectIfConnected();
1584  $this->persistentDatabaseConnection = (bool)$persistentDatabaseConnection;
1585  }
1586 
1593  {
1594  $this->disconnectIfConnected();
1595  $this->connectionCompression = (bool)$connectionCompression;
1596  }
1597 
1603  public function setInitializeCommandsAfterConnect(array $commands)
1604  {
1605  $this->disconnectIfConnected();
1606  $this->initializeCommandsAfterConnect = $commands;
1607  }
1608 
1617  public function setConnectionCharset($connectionCharset = 'utf8')
1618  {
1619  $this->disconnectIfConnected();
1620  $this->connectionCharset = $connectionCharset;
1621  }
1622 
1629  public function connectDB()
1630  {
1631  $this->logDeprecation();
1632  // Early return if connected already
1633  if ($this->isConnected) {
1634  return;
1635  }
1636 
1637  if (!$this->databaseName) {
1638  throw new \RuntimeException(
1639  'TYPO3 Fatal Error: No database selected!',
1640  1270853882
1641  );
1642  }
1643 
1644  if ($this->sql_pconnect()) {
1645  if (!$this->sql_select_db()) {
1646  throw new \RuntimeException(
1647  'TYPO3 Fatal Error: Cannot connect to the current database, "' . $this->databaseName . '"!',
1648  1270853883
1649  );
1650  }
1651  } else {
1652  throw new \RuntimeException(
1653  'TYPO3 Fatal Error: The current username, password or host was not accepted when the connection to the database was attempted to be established!',
1654  1270853884
1655  );
1656  }
1657 
1658  // Prepare user defined objects (if any) for hooks which extend query methods
1659  $this->preProcessHookObjects = [];
1660  $this->postProcessHookObjects = [];
1661  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'])) {
1662  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'] as $classRef) {
1663  $hookObject = GeneralUtility::getUserObj($classRef);
1664  if (!(
1665  $hookObject instanceof PreProcessQueryHookInterface
1666  || $hookObject instanceof PostProcessQueryHookInterface
1667  )) {
1668  throw new \UnexpectedValueException(
1669  '$hookObject must either implement interface TYPO3\\CMS\\Core\\Database\\PreProcessQueryHookInterface or interface TYPO3\\CMS\\Core\\Database\\PostProcessQueryHookInterface',
1670  1299158548
1671  );
1672  }
1673  if ($hookObject instanceof PreProcessQueryHookInterface) {
1674  $this->preProcessHookObjects[] = $hookObject;
1675  }
1676  if ($hookObject instanceof PostProcessQueryHookInterface) {
1677  $this->postProcessHookObjects[] = $hookObject;
1678  }
1679  }
1680  }
1681  }
1682 
1688  public function isConnected()
1689  {
1690  // We think we're still connected
1691  if ($this->isConnected) {
1692  // Check if this is really the case or if the database server has gone away for some reason
1693  // Using mysqlnd ping() does not reconnect (which we would not want anyway since charset etc would not be reinitialized that way)
1694  $this->isConnected = $this->link->ping();
1695  }
1696  return $this->isConnected;
1697  }
1698 
1712  protected function checkConnectionCharset()
1713  {
1714  $sessionResult = $this->sql_query('SHOW SESSION VARIABLES LIKE \'character_set%\'');
1715 
1716  if ($sessionResult === false) {
1717  GeneralUtility::sysLog(
1718  'Error while retrieving the current charset session variables from the database: ' . $this->sql_error(),
1719  'core',
1721  );
1722  throw new \RuntimeException(
1723  'TYPO3 Fatal Error: Could not determine the current charset of the database.',
1724  1381847136
1725  );
1726  }
1727 
1728  $charsetVariables = [];
1729  while (($row = $this->sql_fetch_row($sessionResult)) !== false) {
1730  $variableName = $row[0];
1731  $variableValue = $row[1];
1732  $charsetVariables[$variableName] = $variableValue;
1733  }
1734  $this->sql_free_result($sessionResult);
1735 
1736  // These variables are set with the "Set names" command which was
1737  // used in the past. This is why we check them.
1738  $charsetRequiredVariables = [
1739  'character_set_client',
1740  'character_set_results',
1741  'character_set_connection',
1742  ];
1743 
1744  $hasValidCharset = true;
1745  foreach ($charsetRequiredVariables as $variableName) {
1746  if (empty($charsetVariables[$variableName])) {
1747  GeneralUtility::sysLog(
1748  'A required session variable is missing in the current MySQL connection: ' . $variableName,
1749  'core',
1751  );
1752  throw new \RuntimeException(
1753  'TYPO3 Fatal Error: Could not determine the value of the database session variable: ' . $variableName,
1754  1381847779
1755  );
1756  }
1757 
1758  if ($charsetVariables[$variableName] !== $this->connectionCharset) {
1759  $hasValidCharset = false;
1760  break;
1761  }
1762  }
1763 
1764  if (!$hasValidCharset) {
1765  throw new \RuntimeException(
1766  'It looks like the character set ' . $this->connectionCharset . ' is not used for this connection even though it is configured as connection charset. ' .
1767  'This TYPO3 installation is using the $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'setDBinit\'] property with the following value: "' .
1768  $GLOBALS['TYPO3_CONF_VARS']['SYS']['setDBinit'] . '". Please make sure that this command does not overwrite the configured charset. ' .
1769  'Please note that for the TYPO3 database everything other than utf8 is unsupported since version 4.7.',
1770  1389697515
1771  );
1772  }
1773  }
1774 
1778  protected function disconnectIfConnected()
1779  {
1780  if ($this->isConnected) {
1781  $this->link->close();
1782  $this->isConnected = false;
1783  }
1784  }
1785 
1791  public function getDatabaseHandle()
1792  {
1793  $this->logDeprecation();
1794  return $this->link;
1795  }
1796 
1802  public function setDatabaseHandle($handle)
1803  {
1804  $this->link = $handle;
1805  }
1806 
1812  public function getServerVersion()
1813  {
1814  $this->logDeprecation();
1815  return $this->link->server_info;
1816  }
1817 
1818  /******************************
1819  *
1820  * Debugging
1821  *
1822  ******************************/
1829  public function debug($func, $query = '')
1830  {
1831  $this->logDeprecation();
1832  $error = $this->sql_error();
1833  if ($error || (int)$this->debugOutput === 2) {
1835  [
1836  'caller' => \TYPO3\CMS\Core\Database\DatabaseConnection::class . '::' . $func,
1837  'ERROR' => $error,
1838  'lastBuiltQuery' => $query ? $query : $this->debug_lastBuiltQuery,
1839  'debug_backtrace' => \TYPO3\CMS\Core\Utility\DebugUtility::debugTrail()
1840  ],
1841  $func,
1842  is_object($GLOBALS['error']) && @is_callable([$GLOBALS['error'], 'debug'])
1843  ? ''
1844  : 'DB Error'
1845  );
1846  }
1847  }
1848 
1855  public function debug_check_recordset($res)
1856  {
1857  $this->logDeprecation();
1858  if ($res !== false && $res !== null) {
1859  return true;
1860  }
1861  $trace = debug_backtrace(0);
1862  array_shift($trace);
1863  $msg = 'Invalid database result detected: function TYPO3\\CMS\\Core\\Database\\DatabaseConnection->'
1864  . $trace[0]['function'] . ' called from file ' . substr($trace[0]['file'], (strlen(PATH_site) + 2))
1865  . ' in line ' . $trace[0]['line'] . '.';
1866  GeneralUtility::sysLog(
1867  $msg . ' Use a devLog extension to get more details.',
1868  'core',
1870  );
1871  // Send to devLog if enabled
1872  if (TYPO3_DLOG) {
1873  $debugLogData = [
1874  'SQL Error' => $this->sql_error(),
1875  'Backtrace' => $trace
1876  ];
1877  if ($this->debug_lastBuiltQuery) {
1878  $debugLogData = ['SQL Query' => $this->debug_lastBuiltQuery] + $debugLogData;
1879  }
1880  GeneralUtility::devLog($msg, 'Core/t3lib_db', 3, $debugLogData);
1881  }
1882  return false;
1883  }
1884 
1897  protected function explain($query, $from_table, $row_count)
1898  {
1899  $debugAllowedForIp = GeneralUtility::cmpIP(
1900  GeneralUtility::getIndpEnv('REMOTE_ADDR'),
1901  $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
1902  );
1903  if (
1904  (int)$this->explainOutput == 1
1905  || ((int)$this->explainOutput == 2 && $debugAllowedForIp)
1906  ) {
1907  // Raw HTML output
1908  $explainMode = 1;
1909  } elseif ((int)$this->explainOutput == 3) {
1910  // Embed the output into the TS admin panel
1911  $explainMode = 2;
1912  } else {
1913  return false;
1914  }
1915  $error = $this->sql_error();
1917  $explain_tables = [];
1918  $explain_output = [];
1919  $res = $this->sql_query('EXPLAIN ' . $query, $this->link);
1920  if (is_a($res, '\\mysqli_result')) {
1921  while ($tempRow = $this->sql_fetch_assoc($res)) {
1922  $explain_output[] = $tempRow;
1923  $explain_tables[] = $tempRow['table'];
1924  }
1925  $this->sql_free_result($res);
1926  }
1927  $indices_output = [];
1928  // Notice: Rows are skipped if there is only one result, or if no conditions are set
1929  if ($explain_output[0]['rows'] > 1 || $explain_output[0]['type'] === 'ALL') {
1930  // Only enable output if it's really useful
1931  $debug = true;
1932  foreach ($explain_tables as $table) {
1933  $tableRes = $this->sql_query('SHOW TABLE STATUS LIKE \'' . $table . '\'');
1934  $isTable = $this->sql_num_rows($tableRes);
1935  if ($isTable) {
1936  $res = $this->sql_query('SHOW INDEX FROM ' . $table, $this->link);
1937  if (is_a($res, '\\mysqli_result')) {
1938  while ($tempRow = $this->sql_fetch_assoc($res)) {
1939  $indices_output[] = $tempRow;
1940  }
1941  $this->sql_free_result($res);
1942  }
1943  }
1944  $this->sql_free_result($tableRes);
1945  }
1946  } else {
1947  $debug = false;
1948  }
1949  if ($debug) {
1950  if ($explainMode) {
1951  $data = [];
1952  $data['query'] = $query;
1953  $data['trail'] = $trail;
1954  $data['row_count'] = $row_count;
1955  if ($error) {
1956  $data['error'] = $error;
1957  }
1958  if (!empty($explain_output)) {
1959  $data['explain'] = $explain_output;
1960  }
1961  if (!empty($indices_output)) {
1962  $data['indices'] = $indices_output;
1963  }
1964  if ($explainMode == 1) {
1965  \TYPO3\CMS\Core\Utility\DebugUtility::debug($data, 'Tables: ' . $from_table, 'DB SQL EXPLAIN');
1966  } elseif ($explainMode == 2) {
1968  $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
1969  $timeTracker->setTSselectQuery($data);
1970  }
1971  }
1972  return true;
1973  }
1974  return false;
1975  }
1976 
1982  public function __sleep()
1983  {
1984  $this->disconnectIfConnected();
1985  return [
1986  'debugOutput',
1987  'explainOutput',
1988  'databaseHost',
1989  'databasePort',
1990  'databaseSocket',
1991  'databaseName',
1992  'databaseUsername',
1993  'databaseUserPassword',
1994  'persistentDatabaseConnection',
1995  'connectionCompression',
1996  'initializeCommandsAfterConnect',
1997  'default_charset',
1998  ];
1999  }
2000 
2004  protected function logDeprecation()
2005  {
2006  if (!$this->deprecationWarningThrown) {
2007  $this->deprecationWarningThrown = true;
2008  GeneralUtility::deprecationLog('DatabaseConnection a.k.a. $["TYPO3_DB"] has been marked as deprecated in'
2009  . ' TYPO3 v8 and will be removed in TYPO3 v9. Please use the newly available ConnectionPool and QueryBuilder'
2010  . ' classes.');
2011  }
2012  }
2013 }
static devLog($msg, $extKey, $severity=0, $dataVar=false)
prepare_PREPAREDquery($query, array $queryComponents)
exec_SELECTcountRows($field, $table, $where='1=1')
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
exec_SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause='', $groupBy='', $orderBy='', $limit='')
INSERTquery($table, $fields_values, $no_quote_fields=false)
exec_SELECTgetRows($select_fields, $from_table, $where_clause, $groupBy='', $orderBy='', $limit='', $uidIndexField='')
setPersistentDatabaseConnection($persistentDatabaseConnection)
getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause='', $groupBy='', $orderBy='', $limit='')
static makeInstance($className,... $constructorArguments)
$fields
Definition: pages.php:4
static debug($var='', $header='Debug', $group='Debug')
exec_SELECTgetSingleRow($select_fields, $from_table, $where_clause, $groupBy='', $orderBy='', $numIndex=false)
static debugTrail($prependFileNames=false)
fullQuoteStr($str, $table, $allowNull=false)
SELECTsubquery($select_fields, $from_table, $where_clause)
SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause='', $groupBy='', $orderBy='', $limit='')
fullQuoteArray($arr, $table, $noQuote=false, $allowNull=false)
prepare_SELECTqueryArray(array $queryParts, array $input_parameters=[])
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']