TYPO3 CMS  TYPO3_7-6
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 
19 
46 {
52  const AND_Constraint = 'AND';
53 
59  const OR_Constraint = 'OR';
60 
66  public $debugOutput = false;
67 
73  public $debug_lastBuiltQuery = '';
74 
80  public $store_lastBuiltQuery = false;
81 
88  public $explainOutput = 0;
89 
93  protected $databaseHost = '';
94 
98  protected $databasePort = 3306;
99 
103  protected $databaseSocket = null;
104 
108  protected $databaseName = '';
109 
113  protected $databaseUsername = '';
114 
118  protected $databaseUserPassword = '';
119 
124  protected $persistentDatabaseConnection = false;
125 
129  protected $connectionCompression = false;
130 
137  protected $connectionCharset = 'utf8';
138 
143 
147  protected $isConnected = false;
148 
152  protected $link = null;
153 
159  public $default_charset = 'utf8';
160 
164  protected $preProcessHookObjects = [];
165 
169  protected $postProcessHookObjects = [];
170 
176  protected static $dateTimeFormats = [
177  'date' => [
178  'empty' => '0000-00-00',
179  'format' => 'Y-m-d'
180  ],
181  'datetime' => [
182  'empty' => '0000-00-00 00:00:00',
183  'format' => 'Y-m-d H:i:s'
184  ]
185  ];
186 
192  public function initialize()
193  {
194  // Intentionally blank as this will be overloaded by DBAL
195  }
196 
197  /************************************
198  *
199  * Query execution
200  *
201  * These functions are the RECOMMENDED DBAL functions for use in your applications
202  * Using these functions will allow the DBAL to use alternative ways of accessing data (contrary to if a query is returned!)
203  * They compile a query AND execute it immediately and then return the result
204  * This principle heightens our ability to create various forms of DBAL of the functions.
205  * Generally: We want to return a result pointer/object, never queries.
206  * Also, having the table name together with the actual query execution allows us to direct the request to other databases.
207  *
208  **************************************/
209 
219  public function exec_INSERTquery($table, $fields_values, $no_quote_fields = false)
220  {
221  $res = $this->query($this->INSERTquery($table, $fields_values, $no_quote_fields));
222  if ($this->debugOutput) {
223  $this->debug('exec_INSERTquery');
224  }
225  foreach ($this->postProcessHookObjects as $hookObject) {
227  $hookObject->exec_INSERTquery_postProcessAction($table, $fields_values, $no_quote_fields, $this);
228  }
229  return $res;
230  }
231 
241  public function exec_INSERTmultipleRows($table, array $fields, array $rows, $no_quote_fields = false)
242  {
243  $res = $this->query($this->INSERTmultipleRows($table, $fields, $rows, $no_quote_fields));
244  if ($this->debugOutput) {
245  $this->debug('exec_INSERTmultipleRows');
246  }
247  foreach ($this->postProcessHookObjects as $hookObject) {
249  $hookObject->exec_INSERTmultipleRows_postProcessAction($table, $fields, $rows, $no_quote_fields, $this);
250  }
251  return $res;
252  }
253 
264  public function exec_UPDATEquery($table, $where, $fields_values, $no_quote_fields = false)
265  {
266  $res = $this->query($this->UPDATEquery($table, $where, $fields_values, $no_quote_fields));
267  if ($this->debugOutput) {
268  $this->debug('exec_UPDATEquery');
269  }
270  foreach ($this->postProcessHookObjects as $hookObject) {
272  $hookObject->exec_UPDATEquery_postProcessAction($table, $where, $fields_values, $no_quote_fields, $this);
273  }
274  return $res;
275  }
276 
284  public function exec_DELETEquery($table, $where)
285  {
286  $res = $this->query($this->DELETEquery($table, $where));
287  if ($this->debugOutput) {
288  $this->debug('exec_DELETEquery');
289  }
290  foreach ($this->postProcessHookObjects as $hookObject) {
292  $hookObject->exec_DELETEquery_postProcessAction($table, $where, $this);
293  }
294  return $res;
295  }
296 
309  public function exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '')
310  {
311  $query = $this->SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
312  $res = $this->query($query);
313  if ($this->debugOutput) {
314  $this->debug('exec_SELECTquery');
315  }
316  if ($this->explainOutput) {
317  $this->explain($query, $from_table, $res->num_rows);
318  }
319  foreach ($this->postProcessHookObjects as $hookObject) {
321  $hookObject->exec_SELECTquery_postProcessAction($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', $this);
322  }
323  return $res;
324  }
325 
343  public function exec_SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
344  {
345  $queryParts = $this->getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause, $groupBy, $orderBy, $limit);
346  return $this->exec_SELECT_queryArray($queryParts);
347  }
348 
364  public function exec_SELECT_queryArray($queryParts)
365  {
366  $queryPartsFallback = [
367  'WHERE' => '',
368  'GROUPBY' => '',
369  'ORDERBY' => '',
370  'LIMIT' => ''
371  ];
372  $queryParts = array_merge($queryPartsFallback, $queryParts);
373  return $this->exec_SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT']);
374  }
375 
390  public function exec_SELECTgetRows($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', $uidIndexField = '')
391  {
392  $res = $this->exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
393  if ($this->sql_error()) {
394  $this->sql_free_result($res);
395  return null;
396  }
397  $output = [];
398  $firstRecord = true;
399  while ($record = $this->sql_fetch_assoc($res)) {
400  if ($uidIndexField) {
401  if ($firstRecord) {
402  $firstRecord = false;
403  if (!array_key_exists($uidIndexField, $record)) {
404  $this->sql_free_result($res);
405  throw new \InvalidArgumentException('The given $uidIndexField "' . $uidIndexField . '" is not available in the result.', 1432933855);
406  }
407  }
408  $output[$record[$uidIndexField]] = $record;
409  } else {
410  $output[] = $record;
411  }
412  }
413  $this->sql_free_result($res);
414  return $output;
415  }
416 
429  public function exec_SELECTgetSingleRow($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $numIndex = false)
430  {
431  $res = $this->exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, '1');
432  $output = null;
433  if ($res !== false) {
434  if ($numIndex) {
435  $output = $this->sql_fetch_row($res);
436  } else {
437  $output = $this->sql_fetch_assoc($res);
438  }
439  $this->sql_free_result($res);
440  }
441  return $output;
442  }
443 
452  public function exec_SELECTcountRows($field, $table, $where = '1=1')
453  {
454  $count = false;
455  $resultSet = $this->exec_SELECTquery('COUNT(' . $field . ')', $table, $where);
456  if ($resultSet !== false) {
457  list($count) = $this->sql_fetch_row($resultSet);
458  $count = (int)$count;
459  $this->sql_free_result($resultSet);
460  }
461  return $count;
462  }
463 
470  public function exec_TRUNCATEquery($table)
471  {
472  $res = $this->query($this->TRUNCATEquery($table));
473  if ($this->debugOutput) {
474  $this->debug('exec_TRUNCATEquery');
475  }
476  foreach ($this->postProcessHookObjects as $hookObject) {
478  $hookObject->exec_TRUNCATEquery_postProcessAction($table, $this);
479  }
480  return $res;
481  }
482 
490  protected function query($query)
491  {
492  if (!$this->isConnected) {
493  $this->connectDB();
494  }
495  return $this->link->query($query);
496  }
497 
498  /**************************************
499  *
500  * Query building
501  *
502  **************************************/
511  public function INSERTquery($table, $fields_values, $no_quote_fields = false)
512  {
513  // Table and fieldnames should be "SQL-injection-safe" when supplied to this
514  // function (contrary to values in the arrays which may be insecure).
515  if (!is_array($fields_values) || empty($fields_values)) {
516  return null;
517  }
518  foreach ($this->preProcessHookObjects as $hookObject) {
519  $hookObject->INSERTquery_preProcessAction($table, $fields_values, $no_quote_fields, $this);
520  }
521  // Quote and escape values
522  $fields_values = $this->fullQuoteArray($fields_values, $table, $no_quote_fields, true);
523  // Build query
524  $query = 'INSERT INTO ' . $table . ' (' . implode(',', array_keys($fields_values)) . ') VALUES ' . '(' . implode(',', $fields_values) . ')';
525  // Return query
526  if ($this->debugOutput || $this->store_lastBuiltQuery) {
527  $this->debug_lastBuiltQuery = $query;
528  }
529  return $query;
530  }
531 
541  public function INSERTmultipleRows($table, array $fields, array $rows, $no_quote_fields = false)
542  {
543  // Table and fieldnames should be "SQL-injection-safe" when supplied to this
544  // function (contrary to values in the arrays which may be insecure).
545  if (empty($rows)) {
546  return null;
547  }
548  foreach ($this->preProcessHookObjects as $hookObject) {
550  $hookObject->INSERTmultipleRows_preProcessAction($table, $fields, $rows, $no_quote_fields, $this);
551  }
552  // Build query
553  $query = 'INSERT INTO ' . $table . ' (' . implode(', ', $fields) . ') VALUES ';
554  $rowSQL = [];
555  foreach ($rows as $row) {
556  // Quote and escape values
557  $row = $this->fullQuoteArray($row, $table, $no_quote_fields);
558  $rowSQL[] = '(' . implode(', ', $row) . ')';
559  }
560  $query .= implode(', ', $rowSQL);
561  // Return query
562  if ($this->debugOutput || $this->store_lastBuiltQuery) {
563  $this->debug_lastBuiltQuery = $query;
564  }
565  return $query;
566  }
567 
579  public function UPDATEquery($table, $where, $fields_values, $no_quote_fields = false)
580  {
581  // Table and fieldnames should be "SQL-injection-safe" when supplied to this
582  // function (contrary to values in the arrays which may be insecure).
583  if (is_string($where)) {
584  foreach ($this->preProcessHookObjects as $hookObject) {
586  $hookObject->UPDATEquery_preProcessAction($table, $where, $fields_values, $no_quote_fields, $this);
587  }
588  $fields = [];
589  if (is_array($fields_values) && !empty($fields_values)) {
590  // Quote and escape values
591  $nArr = $this->fullQuoteArray($fields_values, $table, $no_quote_fields, true);
592  foreach ($nArr as $k => $v) {
593  $fields[] = $k . '=' . $v;
594  }
595  }
596  // Build query
597  $query = 'UPDATE ' . $table . ' SET ' . implode(',', $fields) . ((string)$where !== '' ? ' WHERE ' . $where : '');
598  if ($this->debugOutput || $this->store_lastBuiltQuery) {
599  $this->debug_lastBuiltQuery = $query;
600  }
601  return $query;
602  } else {
603  throw new \InvalidArgumentException('TYPO3 Fatal Error: "Where" clause argument for UPDATE query was not a string in $this->UPDATEquery() !', 1270853880);
604  }
605  }
606 
615  public function DELETEquery($table, $where)
616  {
617  if (is_string($where)) {
618  foreach ($this->preProcessHookObjects as $hookObject) {
620  $hookObject->DELETEquery_preProcessAction($table, $where, $this);
621  }
622  // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
623  $query = 'DELETE FROM ' . $table . ((string)$where !== '' ? ' WHERE ' . $where : '');
624  if ($this->debugOutput || $this->store_lastBuiltQuery) {
625  $this->debug_lastBuiltQuery = $query;
626  }
627  return $query;
628  } else {
629  throw new \InvalidArgumentException('TYPO3 Fatal Error: "Where" clause argument for DELETE query was not a string in $this->DELETEquery() !', 1270853881);
630  }
631  }
632 
644  public function SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '')
645  {
646  foreach ($this->preProcessHookObjects as $hookObject) {
648  $hookObject->SELECTquery_preProcessAction($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit, $this);
649  }
650  // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
651  // Build basic query
652  $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
653  // Group by
654  $query .= (string)$groupBy !== '' ? ' GROUP BY ' . $groupBy : '';
655  // Order by
656  $query .= (string)$orderBy !== '' ? ' ORDER BY ' . $orderBy : '';
657  // Group by
658  $query .= (string)$limit !== '' ? ' LIMIT ' . $limit : '';
659  // Return query
660  if ($this->debugOutput || $this->store_lastBuiltQuery) {
661  $this->debug_lastBuiltQuery = $query;
662  }
663  return $query;
664  }
665 
675  public function SELECTsubquery($select_fields, $from_table, $where_clause)
676  {
677  // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
678  // Build basic query:
679  $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
680  // Return query
681  if ($this->debugOutput || $this->store_lastBuiltQuery) {
682  $this->debug_lastBuiltQuery = $query;
683  }
684  return $query;
685  }
686 
704  public function SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
705  {
706  $queryParts = $this->getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause, $groupBy, $orderBy, $limit);
707  return $this->SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT']);
708  }
709 
716  public function TRUNCATEquery($table)
717  {
718  foreach ($this->preProcessHookObjects as $hookObject) {
720  $hookObject->TRUNCATEquery_preProcessAction($table, $this);
721  }
722  // Table should be "SQL-injection-safe" when supplied to this function
723  // Build basic query:
724  $query = 'TRUNCATE TABLE ' . $table;
725  // Return query:
726  if ($this->debugOutput || $this->store_lastBuiltQuery) {
727  $this->debug_lastBuiltQuery = $query;
728  }
729  return $query;
730  }
731 
747  public function listQuery($field, $value, $table)
748  {
749  $value = (string)$value;
750  if (strpos($value, ',') !== false) {
751  throw new \InvalidArgumentException('$value must not contain a comma (,) in $this->listQuery() !', 1294585862);
752  }
753  $pattern = $this->quoteStr($value, $table);
754  $where = 'FIND_IN_SET(\'' . $pattern . '\',' . $field . ')';
755  return $where;
756  }
757 
767  public function searchQuery($searchWords, $fields, $table, $constraint = self::AND_Constraint)
768  {
769  switch ($constraint) {
770  case self::OR_Constraint:
771  $constraint = 'OR';
772  break;
773  default:
774  $constraint = 'AND';
775  }
776 
777  $queryParts = [];
778  foreach ($searchWords as $sw) {
779  $like = ' LIKE \'%' . $this->quoteStr($this->escapeStrForLike($sw, $table), $table) . '%\'';
780  $queryParts[] = $table . '.' . implode(($like . ' OR ' . $table . '.'), $fields) . $like;
781  }
782  $query = '(' . implode(') ' . $constraint . ' (', $queryParts) . ')';
783 
784  return $query;
785  }
786 
787  /**************************************
788  *
789  * Prepared Query Support
790  *
791  **************************************/
804  public function prepare_SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', array $input_parameters = [])
805  {
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  return $this->prepare_SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT'], $input_parameters);
827  }
828 
837  public function prepare_PREPAREDquery($query, array $queryComponents)
838  {
839  if (!$this->isConnected) {
840  $this->connectDB();
841  }
842  $stmt = $this->link->stmt_init();
843  $success = $stmt->prepare($query);
844  if ($this->debugOutput) {
845  $this->debug('stmt_execute', $query);
846  }
847  return $success ? $stmt : null;
848  }
849 
850  /**************************************
851  *
852  * Various helper functions
853  *
854  * Functions recommended to be used for
855  * - escaping values,
856  * - cleaning lists of values,
857  * - stripping of excess ORDER BY/GROUP BY keywords
858  *
859  **************************************/
869  public function fullQuoteStr($str, $table, $allowNull = false)
870  {
871  if (!$this->isConnected) {
872  $this->connectDB();
873  }
874  if ($allowNull && $str === null) {
875  return 'NULL';
876  }
877  if (is_bool($str)) {
878  $str = (int)$str;
879  }
880 
881  return '\'' . $this->link->real_escape_string($str) . '\'';
882  }
883 
894  public function fullQuoteArray($arr, $table, $noQuote = false, $allowNull = false)
895  {
896  if (is_string($noQuote)) {
897  $noQuote = explode(',', $noQuote);
898  } elseif (!is_array($noQuote)) {
899  $noQuote = false;
900  }
901  foreach ($arr as $k => $v) {
902  if ($noQuote === false || !in_array($k, $noQuote)) {
903  $arr[$k] = $this->fullQuoteStr($v, $table, $allowNull);
904  }
905  }
906  return $arr;
907  }
908 
919  public function quoteStr($str, $table)
920  {
921  if (!$this->isConnected) {
922  $this->connectDB();
923  }
924  return $this->link->real_escape_string($str);
925  }
926 
935  public function escapeStrForLike($str, $table)
936  {
937  return addcslashes($str, '_%');
938  }
939 
948  public function cleanIntArray($arr)
949  {
950  return array_map('intval', $arr);
951  }
952 
961  public function cleanIntList($list)
962  {
963  return implode(',', GeneralUtility::intExplode(',', $list));
964  }
965 
975  public function stripOrderBy($str)
976  {
977  return preg_replace('/^(?:ORDER[[:space:]]*BY[[:space:]]*)+/i', '', trim($str));
978  }
979 
989  public function stripGroupBy($str)
990  {
991  return preg_replace('/^(?:GROUP[[:space:]]*BY[[:space:]]*)+/i', '', trim($str));
992  }
993 
1002  public function splitGroupOrderLimit($str)
1003  {
1005  // Prepending a space to make sure "[[:space:]]+" will find a space there
1006  // for the first element.
1007  $str = ' ' . $str;
1008  // Init output array:
1009  $wgolParts = [
1010  'WHERE' => '',
1011  'GROUPBY' => '',
1012  'ORDERBY' => '',
1013  'LIMIT' => ''
1014  ];
1015  // Find LIMIT
1016  $reg = [];
1017  if (preg_match('/^(.*)[[:space:]]+LIMIT[[:space:]]+([[:alnum:][:space:],._]+)$/i', $str, $reg)) {
1018  $wgolParts['LIMIT'] = trim($reg[2]);
1019  $str = $reg[1];
1020  }
1021  // Find ORDER BY
1022  $reg = [];
1023  if (preg_match('/^(.*)[[:space:]]+ORDER[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $str, $reg)) {
1024  $wgolParts['ORDERBY'] = trim($reg[2]);
1025  $str = $reg[1];
1026  }
1027  // Find GROUP BY
1028  $reg = [];
1029  if (preg_match('/^(.*)[[:space:]]+GROUP[[:space:]]+BY[[:space:]]+([[:alnum:][:space:],._]+)$/i', $str, $reg)) {
1030  $wgolParts['GROUPBY'] = trim($reg[2]);
1031  $str = $reg[1];
1032  }
1033  // Rest is assumed to be "WHERE" clause
1034  $wgolParts['WHERE'] = $str;
1035  return $wgolParts;
1036  }
1037 
1044  public function getDateTimeFormats($table)
1045  {
1046  return self::$dateTimeFormats;
1047  }
1048 
1065  protected function getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
1066  {
1067  $foreign_table_as = $foreign_table == $local_table ? $foreign_table . StringUtility::getUniqueId('_join') : '';
1068  $mmWhere = $local_table ? $local_table . '.uid=' . $mm_table . '.uid_local' : '';
1069  $mmWhere .= ($local_table and $foreign_table) ? ' AND ' : '';
1070  $tables = ($local_table ? $local_table . ',' : '') . $mm_table;
1071  if ($foreign_table) {
1072  $mmWhere .= ($foreign_table_as ?: $foreign_table) . '.uid=' . $mm_table . '.uid_foreign';
1073  $tables .= ',' . $foreign_table . ($foreign_table_as ? ' AS ' . $foreign_table_as : '');
1074  }
1075  return [
1076  'SELECT' => $select,
1077  'FROM' => $tables,
1078  'WHERE' => $mmWhere . ' ' . $whereClause,
1079  'GROUPBY' => $groupBy,
1080  'ORDERBY' => $orderBy,
1081  'LIMIT' => $limit
1082  ];
1083  }
1084 
1085  /**************************************
1086  *
1087  * MySQL(i) wrapper functions
1088  * (For use in your applications)
1089  *
1090  **************************************/
1100  public function sql_query($query)
1101  {
1102  $res = $this->query($query);
1103  if ($this->debugOutput) {
1104  $this->debug('sql_query', $query);
1105  }
1106  return $res;
1107  }
1108 
1114  public function sql_error()
1115  {
1116  return $this->link->error;
1117  }
1118 
1124  public function sql_errno()
1125  {
1126  return $this->link->errno;
1127  }
1128 
1135  public function sql_num_rows($res)
1136  {
1137  if ($this->debug_check_recordset($res)) {
1138  return $res->num_rows;
1139  } else {
1140  return false;
1141  }
1142  }
1143 
1151  public function sql_fetch_assoc($res)
1152  {
1153  if ($this->debug_check_recordset($res)) {
1154  $result = $res->fetch_assoc();
1155  if ($result === null) {
1156  // Needed for compatibility
1157  $result = false;
1158  }
1159  return $result;
1160  } else {
1161  return false;
1162  }
1163  }
1164 
1173  public function sql_fetch_row($res)
1174  {
1175  if ($this->debug_check_recordset($res)) {
1176  $result = $res->fetch_row();
1177  if ($result === null) {
1178  // Needed for compatibility
1179  $result = false;
1180  }
1181  return $result;
1182  } else {
1183  return false;
1184  }
1185  }
1186 
1194  public function sql_free_result($res)
1195  {
1196  if ($this->debug_check_recordset($res) && is_object($res)) {
1197  $res->free();
1198  return true;
1199  } else {
1200  return false;
1201  }
1202  }
1203 
1209  public function sql_insert_id()
1210  {
1211  return $this->link->insert_id;
1212  }
1213 
1219  public function sql_affected_rows()
1220  {
1221  return $this->link->affected_rows;
1222  }
1223 
1231  public function sql_data_seek($res, $seek)
1232  {
1233  if ($this->debug_check_recordset($res)) {
1234  return $res->data_seek($seek);
1235  } else {
1236  return false;
1237  }
1238  }
1239 
1248  public function sql_field_type($res, $pointer)
1249  {
1250  // mysql_field_type compatibility map
1251  // taken from: http://www.php.net/manual/en/mysqli-result.fetch-field-direct.php#89117
1252  // Constant numbers see http://php.net/manual/en/mysqli.constants.php
1253  $mysql_data_type_hash = [
1254  1=>'tinyint',
1255  2=>'smallint',
1256  3=>'int',
1257  4=>'float',
1258  5=>'double',
1259  7=>'timestamp',
1260  8=>'bigint',
1261  9=>'mediumint',
1262  10=>'date',
1263  11=>'time',
1264  12=>'datetime',
1265  13=>'year',
1266  16=>'bit',
1267  //252 is currently mapped to all text and blob types (MySQL 5.0.51a)
1268  253=>'varchar',
1269  254=>'char',
1270  246=>'decimal'
1271  ];
1272  if ($this->debug_check_recordset($res)) {
1273  $metaInfo = $res->fetch_field_direct($pointer);
1274  if ($metaInfo === false) {
1275  return false;
1276  }
1277  return $mysql_data_type_hash[$metaInfo->type];
1278  } else {
1279  return false;
1280  }
1281  }
1282 
1289  public function sql_pconnect()
1290  {
1291  if ($this->isConnected) {
1292  return $this->link;
1293  }
1294 
1295  if (!extension_loaded('mysqli')) {
1296  throw new \RuntimeException(
1297  'Database Error: PHP mysqli extension not loaded. This is a must have for TYPO3 CMS!',
1298  1271492607
1299  );
1300  }
1301 
1302  $host = $this->persistentDatabaseConnection
1303  ? 'p:' . $this->databaseHost
1305 
1306  $this->link = mysqli_init();
1307  $connected = $this->link->real_connect(
1308  $host,
1309  $this->databaseUsername,
1310  $this->databaseUserPassword,
1311  null,
1312  (int)$this->databasePort,
1313  $this->databaseSocket,
1314  $this->connectionCompression ? MYSQLI_CLIENT_COMPRESS : 0
1315  );
1316 
1317  if ($connected) {
1318  $this->isConnected = true;
1319 
1320  if ($this->link->set_charset($this->connectionCharset) === false) {
1321  GeneralUtility::sysLog(
1322  'Error setting connection charset to "' . $this->connectionCharset . '"',
1323  'core',
1325  );
1326  }
1327 
1328  foreach ($this->initializeCommandsAfterConnect as $command) {
1329  if ($this->query($command) === false) {
1330  GeneralUtility::sysLog(
1331  'Could not initialize DB connection with query "' . $command . '": ' . $this->sql_error(),
1332  'core',
1334  );
1335  }
1336  }
1337  $this->checkConnectionCharset();
1338  } else {
1339  // @todo This should raise an exception. Would be useful especially to work during installation.
1340  $error_msg = $this->link->connect_error;
1341  $this->link = null;
1342  GeneralUtility::sysLog(
1343  'Could not connect to MySQL server ' . $host . ' with user ' . $this->databaseUsername . ': ' . $error_msg,
1344  'core',
1346  );
1347  }
1348  return $this->link;
1349  }
1350 
1356  public function sql_select_db()
1357  {
1358  if (!$this->isConnected) {
1359  $this->connectDB();
1360  }
1361 
1362  $ret = $this->link->select_db($this->databaseName);
1363  if (!$ret) {
1364  GeneralUtility::sysLog(
1365  'Could not select MySQL database ' . $this->databaseName . ': ' . $this->sql_error(),
1366  'core',
1368  );
1369  }
1370  return $ret;
1371  }
1372 
1373  /**************************************
1374  *
1375  * SQL admin functions
1376  * (For use in the Install Tool and Extension Manager)
1377  *
1378  **************************************/
1388  public function admin_get_dbs()
1389  {
1390  $dbArr = [];
1391  $db_list = $this->query('SELECT SCHEMA_NAME FROM information_schema.SCHEMATA');
1392  if ($db_list === false) {
1393  throw new \RuntimeException(
1394  'MySQL Error: Cannot get tablenames: "' . $this->sql_error() . '"!',
1395  1378457171
1396  );
1397  } else {
1398  while ($row = $db_list->fetch_object()) {
1399  try {
1400  $this->setDatabaseName($row->SCHEMA_NAME);
1401  if ($this->sql_select_db()) {
1402  $dbArr[] = $row->SCHEMA_NAME;
1403  }
1404  } catch (\RuntimeException $exception) {
1405  // The exception happens if we cannot connect to the database
1406  // (usually due to missing permissions). This is ok here.
1407  // We catch the exception, skip the database and continue.
1408  }
1409  }
1410  }
1411  return $dbArr;
1412  }
1413 
1421  public function admin_get_tables()
1422  {
1423  $whichTables = [];
1424  $tables_result = $this->query('SHOW TABLE STATUS FROM `' . $this->databaseName . '`');
1425  if ($tables_result !== false) {
1426  while ($theTable = $tables_result->fetch_assoc()) {
1427  $whichTables[$theTable['Name']] = $theTable;
1428  }
1429  $tables_result->free();
1430  }
1431  return $whichTables;
1432  }
1433 
1445  public function admin_get_fields($tableName)
1446  {
1447  $output = [];
1448  $columns_res = $this->query('SHOW FULL COLUMNS FROM `' . $tableName . '`');
1449  if ($columns_res !== false) {
1450  while ($fieldRow = $columns_res->fetch_assoc()) {
1451  $output[$fieldRow['Field']] = $fieldRow;
1452  }
1453  $columns_res->free();
1454  }
1455  return $output;
1456  }
1457 
1465  public function admin_get_keys($tableName)
1466  {
1467  $output = [];
1468  $keyRes = $this->query('SHOW KEYS FROM `' . $tableName . '`');
1469  if ($keyRes !== false) {
1470  while ($keyRow = $keyRes->fetch_assoc()) {
1471  $output[] = $keyRow;
1472  }
1473  $keyRes->free();
1474  }
1475  return $output;
1476  }
1477 
1490  public function admin_get_charsets()
1491  {
1492  $output = [];
1493  $columns_res = $this->query('SHOW CHARACTER SET');
1494  if ($columns_res !== false) {
1495  while ($row = $columns_res->fetch_assoc()) {
1496  $output[$row['Charset']] = $row;
1497  }
1498  $columns_res->free();
1499  }
1500  return $output;
1501  }
1502 
1509  public function admin_query($query)
1510  {
1511  $res = $this->query($query);
1512  if ($this->debugOutput) {
1513  $this->debug('admin_query', $query);
1514  }
1515  return $res;
1516  }
1517 
1518  /******************************
1519  *
1520  * Connect handling
1521  *
1522  ******************************/
1523 
1529  public function setDatabaseHost($host = 'localhost')
1530  {
1531  $this->disconnectIfConnected();
1532  $this->databaseHost = $host;
1533  }
1534 
1540  public function setDatabasePort($port = 3306)
1541  {
1542  $this->disconnectIfConnected();
1543  $this->databasePort = (int)$port;
1544  }
1545 
1551  public function setDatabaseSocket($socket = null)
1552  {
1553  $this->disconnectIfConnected();
1554  $this->databaseSocket = $socket;
1555  }
1556 
1562  public function setDatabaseName($name)
1563  {
1564  $this->disconnectIfConnected();
1565  $this->databaseName = $name;
1566  }
1567 
1573  public function setDatabaseUsername($username)
1574  {
1575  $this->disconnectIfConnected();
1576  $this->databaseUsername = $username;
1577  }
1578 
1584  public function setDatabasePassword($password)
1585  {
1586  $this->disconnectIfConnected();
1587  $this->databaseUserPassword = $password;
1588  }
1589 
1597  {
1598  $this->disconnectIfConnected();
1599  $this->persistentDatabaseConnection = (bool)$persistentDatabaseConnection;
1600  }
1601 
1608  {
1609  $this->disconnectIfConnected();
1610  $this->connectionCompression = (bool)$connectionCompression;
1611  }
1612 
1618  public function setInitializeCommandsAfterConnect(array $commands)
1619  {
1620  $this->disconnectIfConnected();
1621  $this->initializeCommandsAfterConnect = $commands;
1622  }
1623 
1633  public function setConnectionCharset($connectionCharset = 'utf8')
1634  {
1635  $this->disconnectIfConnected();
1636  $this->connectionCharset = $connectionCharset;
1637  }
1638 
1646  public function connectDB()
1647  {
1648  // Early return if connected already
1649  if ($this->isConnected) {
1650  return;
1651  }
1652 
1653  if (!$this->databaseName) {
1654  throw new \RuntimeException(
1655  'TYPO3 Fatal Error: No database selected!',
1656  1270853882
1657  );
1658  }
1659 
1660  if ($this->sql_pconnect()) {
1661  if (!$this->sql_select_db()) {
1662  throw new \RuntimeException(
1663  'TYPO3 Fatal Error: Cannot connect to the current database, "' . $this->databaseName . '"!',
1664  1270853883
1665  );
1666  }
1667  } else {
1668  throw new \RuntimeException(
1669  'TYPO3 Fatal Error: The current username, password or host was not accepted when the connection to the database was attempted to be established!',
1670  1270853884
1671  );
1672  }
1673 
1674  // Prepare user defined objects (if any) for hooks which extend query methods
1675  $this->preProcessHookObjects = [];
1676  $this->postProcessHookObjects = [];
1677  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'])) {
1678  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'] as $classRef) {
1679  $hookObject = GeneralUtility::getUserObj($classRef);
1680  if (!(
1681  $hookObject instanceof PreProcessQueryHookInterface
1682  || $hookObject instanceof PostProcessQueryHookInterface
1683  )) {
1684  throw new \UnexpectedValueException(
1685  '$hookObject must either implement interface TYPO3\\CMS\\Core\\Database\\PreProcessQueryHookInterface or interface TYPO3\\CMS\\Core\\Database\\PostProcessQueryHookInterface',
1686  1299158548
1687  );
1688  }
1689  if ($hookObject instanceof PreProcessQueryHookInterface) {
1690  $this->preProcessHookObjects[] = $hookObject;
1691  }
1692  if ($hookObject instanceof PostProcessQueryHookInterface) {
1693  $this->postProcessHookObjects[] = $hookObject;
1694  }
1695  }
1696  }
1697  }
1698 
1704  public function isConnected()
1705  {
1706  // We think we're still connected
1707  if ($this->isConnected) {
1708  // Check if this is really the case or if the database server has gone away for some reason
1709  // Using mysqlnd ping() does not reconnect (which we would not want anyway since charset etc would not be reinitialized that way)
1710  $this->isConnected = $this->link->ping();
1711  }
1712  return $this->isConnected;
1713  }
1714 
1729  protected function checkConnectionCharset()
1730  {
1731  $sessionResult = $this->sql_query('SHOW SESSION VARIABLES LIKE \'character_set%\'');
1732 
1733  if ($sessionResult === false) {
1734  GeneralUtility::sysLog(
1735  'Error while retrieving the current charset session variables from the database: ' . $this->sql_error(),
1736  'core',
1738  );
1739  throw new \RuntimeException(
1740  'TYPO3 Fatal Error: Could not determine the current charset of the database.',
1741  1381847136
1742  );
1743  }
1744 
1745  $charsetVariables = [];
1746  while (($row = $this->sql_fetch_row($sessionResult)) !== false) {
1747  $variableName = $row[0];
1748  $variableValue = $row[1];
1749  $charsetVariables[$variableName] = $variableValue;
1750  }
1751  $this->sql_free_result($sessionResult);
1752 
1753  // These variables are set with the "Set names" command which was
1754  // used in the past. This is why we check them.
1755  $charsetRequiredVariables = [
1756  'character_set_client',
1757  'character_set_results',
1758  'character_set_connection',
1759  ];
1760 
1761  $hasValidCharset = true;
1762  foreach ($charsetRequiredVariables as $variableName) {
1763  if (empty($charsetVariables[$variableName])) {
1764  GeneralUtility::sysLog(
1765  'A required session variable is missing in the current MySQL connection: ' . $variableName,
1766  'core',
1768  );
1769  throw new \RuntimeException(
1770  'TYPO3 Fatal Error: Could not determine the value of the database session variable: ' . $variableName,
1771  1381847779
1772  );
1773  }
1774 
1775  if ($charsetVariables[$variableName] !== $this->connectionCharset) {
1776  $hasValidCharset = false;
1777  break;
1778  }
1779  }
1780 
1781  if (!$hasValidCharset) {
1782  throw new \RuntimeException(
1783  'It looks like the character set ' . $this->connectionCharset . ' is not used for this connection even though it is configured as connection charset. ' .
1784  'This TYPO3 installation is using the $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'setDBinit\'] property with the following value: "' .
1785  $GLOBALS['TYPO3_CONF_VARS']['SYS']['setDBinit'] . '". Please make sure that this command does not overwrite the configured charset. ' .
1786  'Please note that for the TYPO3 database everything other than utf8 is unsupported since version 4.7.',
1787  1389697515
1788  );
1789  }
1790  }
1791 
1797  protected function disconnectIfConnected()
1798  {
1799  if ($this->isConnected) {
1800  $this->link->close();
1801  $this->isConnected = false;
1802  }
1803  }
1804 
1810  public function getDatabaseHandle()
1811  {
1812  return $this->link;
1813  }
1814 
1820  public function setDatabaseHandle($handle)
1821  {
1822  $this->link = $handle;
1823  }
1824 
1830  public function getServerVersion()
1831  {
1832  return $this->link->server_info;
1833  }
1834 
1835  /******************************
1836  *
1837  * Debugging
1838  *
1839  ******************************/
1847  public function debug($func, $query = '')
1848  {
1849  $error = $this->sql_error();
1850  if ($error || (int)$this->debugOutput === 2) {
1852  [
1853  'caller' => \TYPO3\CMS\Core\Database\DatabaseConnection::class . '::' . $func,
1854  'ERROR' => $error,
1855  'lastBuiltQuery' => $query ? $query : $this->debug_lastBuiltQuery,
1856  'debug_backtrace' => \TYPO3\CMS\Core\Utility\DebugUtility::debugTrail()
1857  ],
1858  $func,
1859  is_object($GLOBALS['error']) && @is_callable([$GLOBALS['error'], 'debug'])
1860  ? ''
1861  : 'DB Error'
1862  );
1863  }
1864  }
1865 
1872  public function debug_check_recordset($res)
1873  {
1874  if ($res !== false && $res !== null) {
1875  return true;
1876  }
1877  $trace = debug_backtrace(0);
1878  array_shift($trace);
1879  $msg = 'Invalid database result detected: function TYPO3\\CMS\\Core\\Database\\DatabaseConnection->'
1880  . $trace[0]['function'] . ' called from file ' . substr($trace[0]['file'], (strlen(PATH_site) + 2))
1881  . ' in line ' . $trace[0]['line'] . '.';
1882  GeneralUtility::sysLog(
1883  $msg . ' Use a devLog extension to get more details.',
1884  'core',
1886  );
1887  // Send to devLog if enabled
1888  if (TYPO3_DLOG) {
1889  $debugLogData = [
1890  'SQL Error' => $this->sql_error(),
1891  'Backtrace' => $trace
1892  ];
1893  if ($this->debug_lastBuiltQuery) {
1894  $debugLogData = ['SQL Query' => $this->debug_lastBuiltQuery] + $debugLogData;
1895  }
1896  GeneralUtility::devLog($msg, 'Core/t3lib_db', 3, $debugLogData);
1897  }
1898  return false;
1899  }
1900 
1913  protected function explain($query, $from_table, $row_count)
1914  {
1915  $debugAllowedForIp = GeneralUtility::cmpIP(
1916  GeneralUtility::getIndpEnv('REMOTE_ADDR'),
1917  $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
1918  );
1919  if (
1920  (int)$this->explainOutput == 1
1921  || ((int)$this->explainOutput == 2 && $debugAllowedForIp)
1922  ) {
1923  // Raw HTML output
1924  $explainMode = 1;
1925  } elseif ((int)$this->explainOutput == 3 && is_object($GLOBALS['TT'])) {
1926  // Embed the output into the TS admin panel
1927  $explainMode = 2;
1928  } else {
1929  return false;
1930  }
1931  $error = $this->sql_error();
1933  $explain_tables = [];
1934  $explain_output = [];
1935  $res = $this->sql_query('EXPLAIN ' . $query, $this->link);
1936  if (is_a($res, '\\mysqli_result')) {
1937  while ($tempRow = $this->sql_fetch_assoc($res)) {
1938  $explain_output[] = $tempRow;
1939  $explain_tables[] = $tempRow['table'];
1940  }
1941  $this->sql_free_result($res);
1942  }
1943  $indices_output = [];
1944  // Notice: Rows are skipped if there is only one result, or if no conditions are set
1945  if ($explain_output[0]['rows'] > 1 || $explain_output[0]['type'] === 'ALL') {
1946  // Only enable output if it's really useful
1947  $debug = true;
1948  foreach ($explain_tables as $table) {
1949  $tableRes = $this->sql_query('SHOW TABLE STATUS LIKE \'' . $table . '\'');
1950  $isTable = $this->sql_num_rows($tableRes);
1951  if ($isTable) {
1952  $res = $this->sql_query('SHOW INDEX FROM ' . $table, $this->link);
1953  if (is_a($res, '\\mysqli_result')) {
1954  while ($tempRow = $this->sql_fetch_assoc($res)) {
1955  $indices_output[] = $tempRow;
1956  }
1957  $this->sql_free_result($res);
1958  }
1959  }
1960  $this->sql_free_result($tableRes);
1961  }
1962  } else {
1963  $debug = false;
1964  }
1965  if ($debug) {
1966  if ($explainMode) {
1967  $data = [];
1968  $data['query'] = $query;
1969  $data['trail'] = $trail;
1970  $data['row_count'] = $row_count;
1971  if ($error) {
1972  $data['error'] = $error;
1973  }
1974  if (!empty($explain_output)) {
1975  $data['explain'] = $explain_output;
1976  }
1977  if (!empty($indices_output)) {
1978  $data['indices'] = $indices_output;
1979  }
1980  if ($explainMode == 1) {
1981  \TYPO3\CMS\Core\Utility\DebugUtility::debug($data, 'Tables: ' . $from_table, 'DB SQL EXPLAIN');
1982  } elseif ($explainMode == 2) {
1983  $GLOBALS['TT']->setTSselectQuery($data);
1984  }
1985  }
1986  return true;
1987  }
1988  return false;
1989  }
1990 
1996  public function __sleep()
1997  {
1998  $this->disconnectIfConnected();
1999  return [
2000  'debugOutput',
2001  'explainOutput',
2002  'databaseHost',
2003  'databasePort',
2004  'databaseSocket',
2005  'databaseName',
2006  'databaseUsername',
2007  'databaseUserPassword',
2008  'persistentDatabaseConnection',
2009  'connectionCompression',
2010  'initializeCommandsAfterConnect',
2011  'default_charset',
2012  ];
2013  }
2014 }
static devLog($msg, $extKey, $severity=0, $dataVar=false)
exec_SELECTcountRows($field, $table, $where='1=1')
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
prepare_PREPAREDquery($query, array $queryComponents)
static debug($var='', $header='', $group='Debug')
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='')
exec_SELECTgetSingleRow($select_fields, $from_table, $where_clause, $groupBy='', $orderBy='', $numIndex=false)
fullQuoteStr($str, $table, $allowNull=false)
explain($query, $from_table, $row_count)
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)
$host
Definition: server.php:37
prepare_SELECTqueryArray(array $queryParts, array $input_parameters=[])
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']