[BUGFIX] Unused TDParams in ColumnsContentObject()
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Database / DatabaseConnection.php
index a2dd865..23fc313 100644 (file)
@@ -15,7 +15,7 @@ namespace TYPO3\CMS\Core\Database;
  *
  *  The GNU General Public License can be found at
  *  http://www.gnu.org/copyleft/gpl.html.
- *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  A copy is found in the text file GPL.txt and important notices to the license
  *  from the author is found in LICENSE.txt distributed with these scripts.
  *
  *
@@ -106,6 +106,11 @@ class DatabaseConnection {
        protected $databasePort = 3306;
 
        /**
+        * @var string|NULL Database socket to connect to
+        */
+       protected $databaseSocket = NULL;
+
+       /**
         * @var string Database name to connect to
         */
        protected $databaseName = '';
@@ -132,6 +137,14 @@ class DatabaseConnection {
        protected $connectionCompression = FALSE;
 
        /**
+        * The charset for the connection; will be passed on to
+        * mysqli_set_charset during connection initialization.
+        *
+        * @var string
+        */
+       protected $connectionCharset = 'utf8';
+
+       /**
         * @var array List of commands executed after connection was established
         */
        protected $initializeCommandsAfterConnect = array();
@@ -315,7 +328,7 @@ class DatabaseConnection {
         * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
         * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
         * @param string $limit Optional LIMIT value ([begin,]max), if none, supply blank string.
-        * @return resource MySQLi result object / DBAL object
+        * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
         * @see exec_SELECTquery()
         */
        public function exec_SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '') {
@@ -326,7 +339,7 @@ class DatabaseConnection {
                $mmWhere .= ($local_table and $foreign_table) ? ' AND ' : '';
                $tables = ($local_table ? $local_table . ',' : '') . $mm_table;
                if ($foreign_table) {
-                       $mmWhere .= ($foreign_table_as ? $foreign_table_as : $foreign_table) . '.uid=' . $mm_table . '.uid_foreign';
+                       $mmWhere .= ($foreign_table_as ?: $foreign_table) . '.uid=' . $mm_table . '.uid_foreign';
                        $tables .= ',' . $foreign_table . ($foreign_table_as ? ' AS ' . $foreign_table_as : '');
                }
                return $this->exec_SELECTquery($select, $tables, $mmWhere . ' ' . $whereClause, $groupBy, $orderBy, $limit);
@@ -336,7 +349,7 @@ class DatabaseConnection {
         * Executes a select based on input query parts array
         *
         * @param array $queryParts Query parts array
-        * @return resource MySQLi select result object / DBAL object
+        * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
         * @see exec_SELECTquery()
         */
        public function exec_SELECT_queryArray($queryParts) {
@@ -421,7 +434,7 @@ class DatabaseConnection {
                $resultSet = $this->exec_SELECTquery('COUNT(' . $field . ')', $table, $where);
                if ($resultSet !== FALSE) {
                        list($count) = $this->sql_fetch_row($resultSet);
-                       $count = intval($count);
+                       $count = (int)$count;
                        $this->sql_free_result($resultSet);
                }
                return $count;
@@ -469,7 +482,7 @@ class DatabaseConnection {
                                $hookObject->INSERTquery_preProcessAction($table, $fields_values, $no_quote_fields, $this);
                        }
                        // Quote and escape values
-                       $fields_values = $this->fullQuoteArray($fields_values, $table, $no_quote_fields);
+                       $fields_values = $this->fullQuoteArray($fields_values, $table, $no_quote_fields, TRUE);
                        // Build query
                        $query = 'INSERT INTO ' . $table . ' (' . implode(',', array_keys($fields_values)) . ') VALUES ' . '(' . implode(',', $fields_values) . ')';
                        // Return query
@@ -542,7 +555,7 @@ class DatabaseConnection {
                                }
                        }
                        // Build query
-                       $query = 'UPDATE ' . $table . ' SET ' . implode(',', $fields) . (strlen($where) > 0 ? ' WHERE ' . $where : '');
+                       $query = 'UPDATE ' . $table . ' SET ' . implode(',', $fields) . ((string)$where !== '' ? ' WHERE ' . $where : '');
                        if ($this->debugOutput || $this->store_lastBuiltQuery) {
                                $this->debug_lastBuiltQuery = $query;
                        }
@@ -567,7 +580,7 @@ class DatabaseConnection {
                                $hookObject->DELETEquery_preProcessAction($table, $where, $this);
                        }
                        // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
-                       $query = 'DELETE FROM ' . $table . (strlen($where) > 0 ? ' WHERE ' . $where : '');
+                       $query = 'DELETE FROM ' . $table . ((string)$where !== '' ? ' WHERE ' . $where : '');
                        if ($this->debugOutput || $this->store_lastBuiltQuery) {
                                $this->debug_lastBuiltQuery = $query;
                        }
@@ -595,13 +608,13 @@ class DatabaseConnection {
                }
                // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
                // Build basic query
-               $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . (strlen($where_clause) > 0 ? ' WHERE ' . $where_clause : '');
+               $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
                // Group by
-               $query .= strlen($groupBy) > 0 ? ' GROUP BY ' . $groupBy : '';
+               $query .= (string)$groupBy !== '' ? ' GROUP BY ' . $groupBy : '';
                // Order by
-               $query .= strlen($orderBy) > 0 ? ' ORDER BY ' . $orderBy : '';
+               $query .= (string)$orderBy !== '' ? ' ORDER BY ' . $orderBy : '';
                // Group by
-               $query .= strlen($limit) > 0 ? ' LIMIT ' . $limit : '';
+               $query .= (string)$limit !== '' ? ' LIMIT ' . $limit : '';
                // Return query
                if ($this->debugOutput || $this->store_lastBuiltQuery) {
                        $this->debug_lastBuiltQuery = $query;
@@ -621,7 +634,7 @@ class DatabaseConnection {
        public function SELECTsubquery($select_fields, $from_table, $where_clause) {
                // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
                // Build basic query:
-               $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . (strlen($where_clause) > 0 ? ' WHERE ' . $where_clause : '');
+               $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
                // Return query
                if ($this->debugOutput || $this->store_lastBuiltQuery) {
                        $this->debug_lastBuiltQuery = $query;
@@ -691,7 +704,6 @@ class DatabaseConnection {
                                break;
                        default:
                                $constraint = 'AND';
-                               break;
                }
 
                $queryParts = array();
@@ -750,7 +762,7 @@ class DatabaseConnection {
         *
         * @param string $query The query to execute
         * @param array $queryComponents The components of the query to execute
-        * @return resource MySQL result object / DBAL object
+        * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
         */
        public function exec_PREPAREDquery($query, array $queryComponents) {
                if (!$this->isConnected) {
@@ -851,12 +863,12 @@ class DatabaseConnection {
         * Useful when you want to make sure an array contains only integers before imploding them in a select-list.
         *
         * @param array $arr Array with values
-        * @return array The input array with all values passed through intval()
+        * @return array The input array with all values cast to (int)
         * @see cleanIntList()
         */
        public function cleanIntArray($arr) {
                foreach ($arr as $k => $v) {
-                       $arr[$k] = intval($arr[$k]);
+                       $arr[$k] = (int)$arr[$k];
                }
                return $arr;
        }
@@ -866,7 +878,7 @@ class DatabaseConnection {
         * 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.
         *
         * @param string $list List of comma-separated values which should be integers
-        * @return string The input list but with every value passed through intval()
+        * @return string The input list but with every value cast to (int)
         * @see cleanIntArray()
         */
        public function cleanIntList($list) {
@@ -1179,17 +1191,26 @@ class DatabaseConnection {
 
                $this->link = mysqli_init();
                $connected = $this->link->real_connect(
-                       $this->databaseHost,
+                       $host,
                        $this->databaseUsername,
                        $this->databaseUserPassword,
                        NULL,
-                       $this->databasePort,
-                       NULL,
+                       (int)$this->databasePort,
+                       $this->databaseSocket,
                        $this->connectionCompression ? MYSQLI_CLIENT_COMPRESS : 0
                );
 
                if ($connected) {
                        $this->isConnected = TRUE;
+
+                       if ($this->link->set_charset($this->connectionCharset) === FALSE) {
+                               \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog(
+                                       'Error setting connection charset to "' . $this->connectionCharset . '"',
+                                       'Core',
+                                       \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_ERROR
+                               );
+                       }
+
                        foreach ($this->initializeCommandsAfterConnect as $command) {
                                if ($this->link->query($command) === FALSE) {
                                        \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog(
@@ -1200,6 +1221,7 @@ class DatabaseConnection {
                                }
                        }
                        $this->setSqlMode();
+                       $this->checkConnectionCharset();
                } else {
                        // @TODO: This should raise an exception. Would be useful especially to work during installation.
                        $error_msg = $this->link->connect_error;
@@ -1285,11 +1307,24 @@ class DatabaseConnection {
                        $this->connectDB();
                }
                $dbArr = array();
-               $db_list = $this->link->query("SHOW DATABASES");
-               while ($row = $db_list->fetch_object()) {
-                       $this->setDatabaseName($row->Database);
-                       if ($this->sql_select_db()) {
-                               $dbArr[] = $row->Database;
+               $db_list = $this->link->query("SELECT SCHEMA_NAME FROM information_schema.SCHEMATA");
+               if ($db_list === FALSE) {
+                       throw new \RuntimeException(
+                               'MySQL Error: Cannot get tablenames: "' . $this->sql_error() . '"!',
+                               1378457171
+                       );
+               } else {
+                       while ($row = $db_list->fetch_object()) {
+                               try {
+                                       $this->setDatabaseName($row->SCHEMA_NAME);
+                                       if ($this->sql_select_db()) {
+                                               $dbArr[] = $row->SCHEMA_NAME;
+                                       }
+                               } catch (\RuntimeException $exception) {
+                                       // The exception happens if we cannot connect to the database
+                                       // (usually due to missing permissions). This is ok here.
+                                       // We catch the exception, skip the database and continue.
+                               }
                        }
                }
                return $dbArr;
@@ -1307,7 +1342,7 @@ class DatabaseConnection {
                        $this->connectDB();
                }
                $whichTables = array();
-               $tables_result = $this->link->query('SHOW TABLE STATUS FROM `' . TYPO3_db . '`');
+               $tables_result = $this->link->query('SHOW TABLE STATUS FROM `' . $this->databaseName . '`');
                if ($tables_result !== FALSE) {
                        while ($theTable = $tables_result->fetch_assoc()) {
                                $whichTables[$theTable['Name']] = $theTable;
@@ -1334,10 +1369,12 @@ class DatabaseConnection {
                }
                $output = array();
                $columns_res = $this->link->query('SHOW COLUMNS FROM `' . $tableName . '`');
-               while ($fieldRow = $columns_res->fetch_assoc()) {
-                       $output[$fieldRow['Field']] = $fieldRow;
+               if ($columns_res !== FALSE) {
+                       while ($fieldRow = $columns_res->fetch_assoc()) {
+                               $output[$fieldRow['Field']] = $fieldRow;
+                       }
+                       $columns_res->free();
                }
-               $columns_res->free();
                return $output;
        }
 
@@ -1354,10 +1391,12 @@ class DatabaseConnection {
                }
                $output = array();
                $keyRes = $this->link->query('SHOW KEYS FROM `' . $tableName . '`');
-               while ($keyRow = $keyRes->fetch_assoc()) {
-                       $output[] = $keyRow;
+               if ($keyRes !== FALSE) {
+                       while ($keyRow = $keyRes->fetch_assoc()) {
+                               $output[] = $keyRow;
+                       }
+                       $keyRes->free();
                }
-               $keyRes->free();
                return $output;
        }
 
@@ -1392,7 +1431,7 @@ class DatabaseConnection {
         * mysqli() wrapper function, used by the Install Tool and EM for all queries regarding management of the database!
         *
         * @param string $query Query to execute
-        * @return resource Result pointer (MySQLi result object)
+        * @return boolean|\mysqli_result|object MySQLi result object / DBAL object
         */
        public function admin_query($query) {
                if (!$this->isConnected) {
@@ -1432,6 +1471,16 @@ class DatabaseConnection {
        }
 
        /**
+        * Set database socket
+        *
+        * @param string|NULL $socket
+        */
+       public function setDatabaseSocket($socket = NULL) {
+               $this->disconnectIfConnected();
+               $this->databaseSocket = $socket;
+       }
+
+       /**
         * Set database name
         *
         * @param string $name
@@ -1493,6 +1542,20 @@ class DatabaseConnection {
        }
 
        /**
+        * Set the charset that should be used for the MySQL connection.
+        * The given value will be passed on to mysqli_set_charset().
+        *
+        * The default value of this setting is utf8.
+        *
+        * @param string $connectionCharset The connection charset that will be passed on to mysqli_set_charset() when connecting the database. Default is utf8.
+        * @return void
+        */
+       public function setConnectionCharset($connectionCharset = 'utf8') {
+               $this->disconnectIfConnected();
+               $this->connectionCharset = $connectionCharset;
+       }
+
+       /**
         * Connects to database for TYPO3 sites:
         *
         * @param string $host Deprecated since 6.1, will be removed in two versions Database. host IP/domain[:port]
@@ -1570,6 +1633,82 @@ class DatabaseConnection {
        }
 
        /**
+        * Checks if the current connection character set has the same value
+        * as the connectionCharset variable.
+        *
+        * To determine the character set these MySQL session variables are
+        * checked: character_set_client, character_set_results and
+        * character_set_connection.
+        *
+        * If the character set does not match or if the session variables
+        * can not be read a RuntimeException is thrown.
+        *
+        * @return void
+        * @throws \RuntimeException
+        */
+       protected function checkConnectionCharset() {
+               $sessionResult = $this->sql_query('SHOW SESSION VARIABLES LIKE \'character_set%\'');
+
+               if ($sessionResult === FALSE) {
+                       \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog(
+                               'Error while retrieving the current charset session variables from the database: ' . $this->sql_error(),
+                               'Core',
+                               \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_ERROR
+                       );
+                       throw new \RuntimeException(
+                               'TYPO3 Fatal Error: Could not determine the current charset of the database.',
+                               1381847136
+                       );
+               }
+
+               $charsetVariables = array();
+               while (($row = $this->sql_fetch_row($sessionResult)) !== FALSE) {
+                       $variableName = $row[0];
+                       $variableValue = $row[1];
+                       $charsetVariables[$variableName] = $variableValue;
+               }
+               $this->sql_free_result($sessionResult);
+
+               // These variables are set with the "Set names" command which was
+               // used in the past. This is why we check them.
+               $charsetRequiredVariables = array(
+                       'character_set_client',
+                       'character_set_results',
+                       'character_set_connection',
+               );
+
+               $hasValidCharset = TRUE;
+               foreach ($charsetRequiredVariables as $variableName) {
+                       if (empty($charsetVariables[$variableName])) {
+                               \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog(
+                                       'A required session variable is missing in the current MySQL connection: ' . $variableName,
+                                       'Core',
+                                       \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_ERROR
+                               );
+                               throw new \RuntimeException(
+                                       'TYPO3 Fatal Error: Could not determine the value of the database session variable: ' . $variableName,
+                                       1381847779
+                               );
+                       }
+
+                       if ($charsetVariables[$variableName] !== $this->connectionCharset) {
+                               $hasValidCharset = FALSE;
+                               break;
+                       }
+               }
+
+               if (!$hasValidCharset) {
+                       throw new \RuntimeException(
+                               'It looks like the character set ' . $this->connectionCharset . ' is not used for this connection even though it is configured as connection charset. ' .
+                               'This TYPO3 installation is using the $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'setDBinit\'] property with the following value: "' .
+                               $GLOBALS['TYPO3_CONF_VARS']['SYS']['setDBinit'] . '". Please make sure that this command does not overwrite the configured charset. ' .
+                               'Please note that for the TYPO3 database everything other than utf8 is unsupported since version 4.7.',
+                               1389697515
+                       );
+               }
+       }
+
+       /**
         * Disconnect from database if connected
         *
         * @return void
@@ -1613,7 +1752,7 @@ class DatabaseConnection {
                        'called without arguments. Use the setters instead.'
                );
                if ($host) {
-                       if (strpos(':', $host) > 0) {
+                       if (strpos($host, ':') > 0) {
                                list($databaseHost, $databasePort) = explode(':', $host);
                                $this->setDatabaseHost($databaseHost);
                                $this->setDatabasePort($databasePort);
@@ -1647,7 +1786,7 @@ class DatabaseConnection {
         */
        public function debug($func, $query = '') {
                $error = $this->sql_error();
-               if ($error || (int) $this->debugOutput === 2) {
+               if ($error || (int)$this->debugOutput === 2) {
                        \TYPO3\CMS\Core\Utility\DebugUtility::debug(
                                array(
                                        'caller' => 'TYPO3\\CMS\\Core\\Database\\DatabaseConnection::' . $func,
@@ -1666,7 +1805,7 @@ class DatabaseConnection {
        /**
         * Checks if record set is valid and writes debugging information into devLog if not.
         *
-        * @param resource|boolean $res MySQLi result object
+        * @param boolean|\mysqli_result|object MySQLi result object / DBAL object
         * @return boolean TRUE if the  record set is valid, FALSE otherwise
         * @todo Define visibility
         */
@@ -1674,7 +1813,7 @@ class DatabaseConnection {
                if ($res !== FALSE) {
                        return TRUE;
                }
-               $msg = 'Invalid database result resource detected';
+               $msg = 'Invalid database result detected';
                $trace = debug_backtrace();
                array_shift($trace);
                $cnt = count($trace);
@@ -1727,7 +1866,7 @@ class DatabaseConnection {
                ) {
                        // Raw HTML output
                        $explainMode = 1;
-               } elseif ((int) $this->explainOutput == 3 && is_object($GLOBALS['TT'])) {
+               } elseif ((int)$this->explainOutput == 3 && is_object($GLOBALS['TT'])) {
                        // Embed the output into the TS admin panel
                        $explainMode = 2;
                } else {
@@ -1738,7 +1877,7 @@ class DatabaseConnection {
                $explain_tables = array();
                $explain_output = array();
                $res = $this->sql_query('EXPLAIN ' . $query, $this->link);
-               if (is_resource($res)) {
+               if (is_a($res, '\\mysqli_result')) {
                        while ($tempRow = $this->sql_fetch_assoc($res)) {
                                $explain_output[] = $tempRow;
                                $explain_tables[] = $tempRow['table'];
@@ -1758,7 +1897,7 @@ class DatabaseConnection {
                                $isTable = $this->sql_num_rows($tableRes);
                                if ($isTable) {
                                        $res = $this->sql_query('SHOW INDEX FROM ' . $table, $this->link);
-                                       if (is_resource($res)) {
+                                       if (is_a($res, '\\mysqli_result')) {
                                                while ($tempRow = $this->sql_fetch_assoc($res)) {
                                                        $indices_output[] = $tempRow;
                                                }
@@ -1808,6 +1947,7 @@ class DatabaseConnection {
                        'explainOutput',
                        'databaseHost',
                        'databasePort',
+                       'databaseSocket',
                        'databaseName',
                        'databaseUsername',
                        'databaseUserPassword',
@@ -1818,4 +1958,3 @@ class DatabaseConnection {
                );
        }
 }
-?>