Completed
Push — master ( 379288...d5b5b2 )
by
unknown
14:56
created

MySql::checkDatabaseName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 12
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck\Platform;
19
20
use TYPO3\CMS\Core\Database\Connection;
21
use TYPO3\CMS\Core\Database\ConnectionPool;
22
use TYPO3\CMS\Core\Messaging\FlashMessage;
23
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
24
use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26
/**
27
 * Check database configuration status for MySQL server
28
 *
29
 * This class is a hardcoded requirement check for the database server.
30
 *
31
 * The status messages and title *must not* include HTML, use plain
32
 * text only. The return values of this class are not bound to HTML
33
 * and can be used in different scopes (eg. as json array).
34
 *
35
 * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
36
 */
37
class MySql extends AbstractPlatform
38
{
39
    /**
40
     * Minimum supported MySQL version
41
     *
42
     * @var string
43
     */
44
    protected $minimumMySQLVersion = '5.5.0';
45
46
    /**
47
     * List of MySQL modes that are incompatible with TYPO3 CMS
48
     *
49
     * @var array
50
     */
51
    protected $incompatibleSqlModes = [
52
        'NO_BACKSLASH_ESCAPES'
53
    ];
54
55
    /**
56
     * Charset of the database that should be fulfilled
57
     * @var array
58
     */
59
    protected $databaseCharsetToCheck = [
60
        'utf8',
61
        'utf8mb4',
62
    ];
63
64
    /**
65
     * Charset of the database server that should be fulfilled
66
     * @var array
67
     */
68
    protected $databaseServerCharsetToCheck = [
69
        'utf8',
70
        'utf8mb4',
71
    ];
72
73
    /**
74
     * Get all status information as array with status objects
75
     *
76
     * @return FlashMessageQueue
77
     * @throws \InvalidArgumentException
78
     * @throws \Doctrine\DBAL\DBALException
79
     */
80
    public function getStatus(): FlashMessageQueue
81
    {
82
        $defaultConnection = GeneralUtility::makeInstance(ConnectionPool::class)
83
            ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
84
        if (strpos($defaultConnection->getServerVersion(), 'MySQL') !== 0) {
85
            return $this->messageQueue;
86
        }
87
        $this->checkMysqlVersion($defaultConnection);
88
        $this->checkInvalidSqlModes($defaultConnection);
89
        $this->checkDefaultDatabaseCharset($defaultConnection);
90
        $this->checkDefaultDatabaseServerCharset($defaultConnection);
91
        $this->checkDatabaseName($defaultConnection);
92
        return $this->messageQueue;
93
    }
94
95
    /**
96
     * Check if any SQL mode is set which is not compatible with TYPO3
97
     *
98
     * @param Connection $connection to the database to be checked
99
     */
100
    protected function checkInvalidSqlModes(Connection $connection)
101
    {
102
        $detectedIncompatibleSqlModes = $this->getIncompatibleSqlModes($connection);
103
        if (!empty($detectedIncompatibleSqlModes)) {
104
            $this->messageQueue->enqueue(new FlashMessage(
105
                'Incompatible SQL modes have been detected:'
106
                    . ' ' . implode(', ', $detectedIncompatibleSqlModes) . '.'
107
                    . ' The listed modes are not compatible with TYPO3 CMS.'
108
                    . ' You have to change that setting in your MySQL environment'
109
                    . ' or in $GLOBALS[\'TYPO3_CONF_VARS\'][\'DB\'][\'Connections\'][\'Default\'][\'initCommands\']',
110
                'Incompatible SQL modes found!',
111
                FlashMessage::ERROR
112
            ));
113
        } else {
114
            $this->messageQueue->enqueue(new FlashMessage(
115
                '',
116
                'No incompatible SQL modes found.'
117
            ));
118
        }
119
    }
120
121
    /**
122
     * Check minimum MySQL version
123
     *
124
     * @param Connection $connection to the database to be checked
125
     */
126
    protected function checkMysqlVersion(Connection $connection)
127
    {
128
        preg_match('/MySQL ((\d+\.)*(\d+\.)*\d+)/', $connection->getServerVersion(), $match);
129
        $currentMysqlVersion = $match[1];
130
        if (version_compare($currentMysqlVersion, $this->minimumMySQLVersion, '<')) {
131
            $this->messageQueue->enqueue(new FlashMessage(
132
                'Your MySQL version ' . $currentMysqlVersion . ' is too old. TYPO3 CMS does not run'
133
                    . ' with this version. Update to at least MySQL ' . $this->minimumMySQLVersion,
134
                'MySQL version too low',
135
                FlashMessage::ERROR
136
            ));
137
        } else {
138
            $this->messageQueue->enqueue(new FlashMessage(
139
                '',
140
                'MySQL version is fine'
141
            ));
142
        }
143
    }
144
145
    /**
146
     * Checks the character set of the database and reports an error if it is not utf-8.
147
     *
148
     * @param Connection $connection to the database to be checked
149
     */
150
    public function checkDefaultDatabaseCharset(Connection $connection): void
151
    {
152
        $queryBuilder = $connection->createQueryBuilder();
153
        $defaultDatabaseCharset = (string)$queryBuilder->select('DEFAULT_CHARACTER_SET_NAME')
154
            ->from('information_schema.SCHEMATA')
155
            ->where(
156
                $queryBuilder->expr()->eq(
157
                    'SCHEMA_NAME',
158
                    $queryBuilder->createNamedParameter($connection->getDatabase(), \PDO::PARAM_STR)
159
                )
160
            )
161
            ->setMaxResults(1)
162
            ->execute()
163
            ->fetchColumn();
164
165
        if (!in_array($defaultDatabaseCharset, $this->databaseCharsetToCheck, true)) {
166
            $this->messageQueue->enqueue(new FlashMessage(
167
                sprintf(
168
                    'Checking database character set failed, got key "%s" instead of "%s"',
169
                    $defaultDatabaseCharset,
170
                    implode(' or ', $this->databaseCharsetToCheck)
171
                ),
172
                'MySQL database character set check failed',
173
                FlashMessage::ERROR
174
            ));
175
        } else {
176
            $this->messageQueue->enqueue(new FlashMessage(
177
                '',
178
                sprintf('MySQL database uses %s. All good.', implode(' or ', $this->databaseCharsetToCheck))
179
            ));
180
        }
181
    }
182
183
    /**
184
     * Returns an array with the current sql mode settings
185
     *
186
     * @param Connection $connection to the database to be checked
187
     * @return array Contains all configured SQL modes that are incompatible
188
     */
189
    protected function getIncompatibleSqlModes(Connection $connection): array
190
    {
191
        $sqlModes = explode(',', $connection->executeQuery('SELECT @@SESSION.sql_mode;')
192
            ->fetch(0)['@@SESSION.sql_mode']);
193
        return array_intersect($this->incompatibleSqlModes, $sqlModes);
194
    }
195
196
    /**
197
     * Checks the character set of the database server and reports an info if it is not utf-8.
198
     *
199
     * @param Connection $connection to the database to be checked
200
     */
201
    public function checkDefaultDatabaseServerCharset(Connection $connection): void
202
    {
203
        $defaultServerCharset = $connection->executeQuery('SHOW VARIABLES LIKE \'character_set_server\'')->fetch();
204
205
        if (!in_array($defaultServerCharset['Value'], $this->databaseServerCharsetToCheck, true)) {
206
            $this->messageQueue->enqueue(new FlashMessage(
207
                sprintf(
208
                    'Checking server character set failed, got key "%s" instead of "%s"',
209
                    $defaultServerCharset['Value'],
210
                    implode(' or ', $this->databaseServerCharsetToCheck)
211
                ),
212
                'MySQL database server character set check failed',
213
                FlashMessage::INFO
214
            ));
215
        } else {
216
            $this->messageQueue->enqueue(new FlashMessage(
217
                '',
218
                sprintf('MySQL server default uses %s. All good.', implode(' or ', $this->databaseCharsetToCheck))
219
            ));
220
        }
221
    }
222
223
    /**
224
     * Validate the database name
225
     *
226
     * @param string $databaseName
227
     * @return bool
228
     */
229
    public static function isValidDatabaseName(string $databaseName): bool
230
    {
231
        return strlen($databaseName) <= static::SCHEMA_NAME_MAX_LENGTH && preg_match('/^[\x{0001}-\x{FFFF}]*$/u', $databaseName);
232
    }
233
234
    protected function checkDatabaseName(Connection $connection): void
235
    {
236
        if (static::isValidDatabaseName($connection->getDatabase())) {
237
            return;
238
        }
239
240
        $this->messageQueue->enqueue(
241
            new FlashMessage(
242
                'The given database name must not be longer than ' . static::SCHEMA_NAME_MAX_LENGTH . ' characters'
243
                . ' and consist of the Unicode Basic Multilingual Plane (BMP), except U+0000',
244
                'Database name not valid',
245
                FlashMessage::ERROR
246
            )
247
        );
248
    }
249
}
250