[TASK] Remove ext:dbal from installation steps
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Controller / Action / Step / DatabaseConnect.php
1 <?php
2 namespace TYPO3\CMS\Install\Controller\Action\Step;
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
17 use Doctrine\DBAL\DBALException;
18 use TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21 /**
22 * Database connect step:
23 * - Needs execution if database credentials are not set or fail to connect
24 * - Renders fields for database connection fields
25 * - Sets database credentials in LocalConfiguration
26 */
27 class DatabaseConnect extends AbstractStepAction
28 {
29 /**
30 * Execute database step:
31 * - Set database connect credentials in LocalConfiguration
32 *
33 * @return array<\TYPO3\CMS\Install\Status\StatusInterface>
34 */
35 public function execute()
36 {
37 $result = [];
38
39 /** @var $configurationManager \TYPO3\CMS\Core\Configuration\ConfigurationManager */
40 $configurationManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
41
42 $postValues = $this->postValues['values'];
43
44 $localConfigurationPathValuePairs = [];
45
46 if (isset($postValues['username'])) {
47 $value = $postValues['username'];
48 if (strlen($value) <= 50) {
49 $localConfigurationPathValuePairs['DB/Connections/Default/user'] = $value;
50 } else {
51 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
52 $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
53 $errorStatus->setTitle('Database username not valid');
54 $errorStatus->setMessage('Given username must be shorter than fifty characters.');
55 $result[] = $errorStatus;
56 }
57 }
58
59 if (isset($postValues['password'])) {
60 $value = $postValues['password'];
61 if (strlen($value) <= 50) {
62 $localConfigurationPathValuePairs['DB/Connections/Default/password'] = $value;
63 } else {
64 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
65 $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
66 $errorStatus->setTitle('Database password not valid');
67 $errorStatus->setMessage('Given password must be shorter than fifty characters.');
68 $result[] = $errorStatus;
69 }
70 }
71
72 if (isset($postValues['host'])) {
73 $value = $postValues['host'];
74 if (preg_match('/^[a-zA-Z0-9_\\.-]+(:.+)?$/', $value) && strlen($value) <= 255) {
75 $localConfigurationPathValuePairs['DB/Connections/Default/host'] = $value;
76 } else {
77 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
78 $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
79 $errorStatus->setTitle('Database host not valid');
80 $errorStatus->setMessage('Given host is not alphanumeric (a-z, A-Z, 0-9 or _-.:) or longer than 255 characters.');
81 $result[] = $errorStatus;
82 }
83 }
84
85 if (isset($postValues['port']) && $postValues['host'] !== 'localhost') {
86 $value = $postValues['port'];
87 if (preg_match('/^[0-9]+(:.+)?$/', $value) && $value > 0 && $value <= 65535) {
88 $localConfigurationPathValuePairs['DB/Connections/Default/port'] = (int)$value;
89 } else {
90 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
91 $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
92 $errorStatus->setTitle('Database port not valid');
93 $errorStatus->setMessage('Given port is not numeric or within range 1 to 65535.');
94 $result[] = $errorStatus;
95 }
96 }
97
98 if (isset($postValues['socket']) && $postValues['socket'] !== '') {
99 if (@file_exists($postValues['socket'])) {
100 $localConfigurationPathValuePairs['DB/Connections/Default/unix_socket'] = $postValues['socket'];
101 } else {
102 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
103 $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
104 $errorStatus->setTitle('Socket does not exist');
105 $errorStatus->setMessage('Given socket location does not exist on server.');
106 $result[] = $errorStatus;
107 }
108 }
109
110 if (isset($postValues['database'])) {
111 $value = $postValues['database'];
112 if (strlen($value) <= 50) {
113 $localConfigurationPathValuePairs['DB/Connections/Default/dbname'] = $value;
114 } else {
115 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
116 $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
117 $errorStatus->setTitle('Database name not valid');
118 $errorStatus->setMessage('Given database name must be shorter than fifty characters.');
119 $result[] = $errorStatus;
120 }
121 }
122
123 if (!empty($localConfigurationPathValuePairs)) {
124 $configurationManager->setLocalConfigurationValuesByPathValuePairs($localConfigurationPathValuePairs);
125
126 // After setting new credentials, test again and create an error message if connect is not successful
127 // @TODO: This could be simplified, if isConnectSuccessful could be released from TYPO3_CONF_VARS
128 // and fed with connect values directly in order to obsolete the bootstrap reload.
129 \TYPO3\CMS\Core\Core\Bootstrap::getInstance()
130 ->populateLocalConfiguration()
131 ->disableCoreCache();
132 if (!$this->isConnectSuccessful()) {
133 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
134 $errorStatus = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
135 $errorStatus->setTitle('Database connect not successful');
136 $errorStatus->setMessage('Connecting to the database with given settings failed. Please check.');
137 $result[] = $errorStatus;
138 }
139 }
140
141 return $result;
142 }
143
144 /**
145 * Step needs to be executed if database connection is not successful.
146 *
147 * @throws \TYPO3\CMS\Install\Controller\Exception\RedirectException
148 * @return bool
149 */
150 public function needsExecution()
151 {
152 if ($this->isConnectSuccessful() && $this->isConfigurationComplete()) {
153 return false;
154 }
155 if (!$this->isHostConfigured()) {
156 $this->useDefaultValuesForNotConfiguredOptions();
157 throw new \TYPO3\CMS\Install\Controller\Exception\RedirectException(
158 'Wrote default settings to LocalConfiguration.php, redirect needed',
159 1377611168
160 );
161 }
162 return true;
163 }
164
165 /**
166 * Executes the step
167 *
168 * @return string Rendered content
169 */
170 protected function executeAction()
171 {
172 $this->view
173 ->assign('username', $this->getConfiguredUsername())
174 ->assign('password', $this->getConfiguredPassword())
175 ->assign('host', $this->getConfiguredHost())
176 ->assign('port', $this->getConfiguredOrDefaultPort())
177 ->assign('database', $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname'] ?: '')
178 ->assign('socket', $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['unix_socket'] ?: '')
179 ->assign('renderConnectDetailsUsername', true)
180 ->assign('renderConnectDetailsPassword', true)
181 ->assign('renderConnectDetailsHost', true)
182 ->assign('renderConnectDetailsPort', true)
183 ->assign('renderConnectDetailsSocket', true);
184
185 $this->assignSteps();
186
187 return $this->view->render();
188 }
189
190 /**
191 * Render connect port and label
192 *
193 * @return int Configured or default port
194 */
195 protected function getConfiguredOrDefaultPort()
196 {
197 $configuredPort = (int)$this->getConfiguredPort();
198 if (!$configuredPort) {
199 $port = 3306;
200 } else {
201 $port = $configuredPort;
202 }
203 return $port;
204 }
205
206 /**
207 * Test connection with given credentials
208 *
209 * @return bool TRUE if connect was successful
210 */
211 protected function isConnectSuccessful()
212 {
213 try {
214 GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionByName('Default')->ping();
215 } catch (DBALException $e) {
216 return false;
217 }
218 return true;
219 }
220
221 /**
222 * Check LocalConfiguration.php for required database settings:
223 * - 'host' is mandatory and must not be empty
224 * - 'port' OR 'socket' is mandatory, but may be empty
225 *
226 * @return bool TRUE if host is set
227 */
228 protected function isHostConfigured()
229 {
230 $hostConfigured = true;
231 if (empty($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'])) {
232 $hostConfigured = false;
233 }
234 if (!isset($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['port'])
235 && !isset($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['unix_socket'])
236 ) {
237 $hostConfigured = false;
238 }
239 return $hostConfigured;
240 }
241
242 /**
243 * Check LocalConfiguration.php for required database settings:
244 * - 'host' is mandatory and must not be empty
245 * - 'port' OR 'socket' is mandatory, but may be empty
246 * - 'username' and 'password' are mandatory, but may be empty
247 *
248 * @return bool TRUE if required settings are present
249 */
250 protected function isConfigurationComplete()
251 {
252 $configurationComplete = $this->isHostConfigured();
253 if (!isset($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'])) {
254 $configurationComplete = false;
255 }
256 if (!isset($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'])) {
257 $configurationComplete = false;
258 }
259 return $configurationComplete;
260 }
261
262 /**
263 * Write DB settings to LocalConfiguration.php, using default values.
264 * With the switch from mysql to mysqli in 6.1, some mandatory settings were
265 * added. This method tries to add those settings in case of an upgrade, and
266 * pre-configures settings in case of a "new" install process.
267 *
268 * There are two different connection types:
269 * - Unix domain socket. This may be available if mysql is running on localhost
270 * - TCP/IP connection to some mysql system somewhere.
271 *
272 * Unix domain socket connections are quicker than TCP/IP, so it is
273 * tested if a unix domain socket connection to localhost is successful. If not,
274 * a default configuration for TCP/IP is used.
275 *
276 * @return void
277 */
278 protected function useDefaultValuesForNotConfiguredOptions()
279 {
280 $localConfigurationPathValuePairs = [];
281
282 $localConfigurationPathValuePairs['DB/Connections/Default/host'] = $this->getConfiguredHost();
283
284 // If host is "local" either by upgrading or by first install, we try a socket
285 // connection first and use TCP/IP as fallback
286 if ($localConfigurationPathValuePairs['DB/Connections/Default/host'] === 'localhost'
287 || GeneralUtility::cmpIP($localConfigurationPathValuePairs['DB/Connections/Default/host'], '127.*.*.*')
288 || (string)$localConfigurationPathValuePairs['DB/Connections/Default/host'] === ''
289 ) {
290 if ($this->isConnectionWithUnixDomainSocketPossible()) {
291 $localConfigurationPathValuePairs['DB/Connections/Default/host'] = 'localhost';
292 $localConfigurationPathValuePairs['DB/Connections/Default/unix_socket'] = $this->getConfiguredSocket();
293 } else {
294 if (!GeneralUtility::isFirstPartOfStr($localConfigurationPathValuePairs['DB/Connections/Default/host'], '127.')) {
295 $localConfigurationPathValuePairs['DB/Connections/Default/host'] = '127.0.0.1';
296 }
297 }
298 }
299
300 if (!isset($localConfigurationPathValuePairs['DB/Connections/Default/unix_socket'])) {
301 // Make sure a default port is set if not configured yet
302 // This is independent from any host configuration
303 $port = $this->getConfiguredPort();
304 if ($port > 0) {
305 $localConfigurationPathValuePairs['DB/Connections/Default/port'] = $port;
306 } else {
307 $localConfigurationPathValuePairs['DB/Connections/Default/port'] = $this->getConfiguredOrDefaultPort();
308 }
309 }
310
311 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
312 $configurationManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
313 $configurationManager->setLocalConfigurationValuesByPathValuePairs($localConfigurationPathValuePairs);
314 }
315
316 /**
317 * Test if a unix domain socket can be opened. This does not
318 * authenticate but only tests if a connect is successful.
319 *
320 * @return bool TRUE on success
321 */
322 protected function isConnectionWithUnixDomainSocketPossible()
323 {
324 $result = false;
325 // Use configured socket
326 $socket = (string)$this->getConfiguredSocket();
327 if ($socket === '') {
328 // If no configured socket, use default php socket
329 $defaultSocket = (string)ini_get('mysqli.default_socket');
330 if ($defaultSocket !== '') {
331 $socket = $defaultSocket;
332 }
333 }
334 if ($socket !== '') {
335 $socketOpenResult = @fsockopen('unix://' . $socket);
336 if ($socketOpenResult) {
337 fclose($socketOpenResult);
338 $result = true;
339 }
340 }
341 return $result;
342 }
343
344 /**
345 * Returns configured username, if set
346 *
347 * @return string
348 */
349 protected function getConfiguredUsername()
350 {
351 $username = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'] ?? '';
352 return $username;
353 }
354
355 /**
356 * Returns configured password, if set
357 *
358 * @return string
359 */
360 protected function getConfiguredPassword()
361 {
362 $password = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'] ?? '';
363 return $password;
364 }
365
366 /**
367 * Returns configured host with port split off if given
368 *
369 * @return string
370 */
371 protected function getConfiguredHost()
372 {
373 $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'] ?? '';
374 $port = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['port'] ?? '';
375 if (strlen($port) < 1 && substr_count($host, ':') === 1) {
376 list($host) = explode(':', $host);
377 }
378 return $host;
379 }
380
381 /**
382 * Returns configured port. Gets port from host value if port is not yet set.
383 *
384 * @return int
385 */
386 protected function getConfiguredPort()
387 {
388 $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'] ?? '';
389 $port = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['port'] ?? '';
390 if ($port === '' && substr_count($host, ':') === 1) {
391 $hostPortArray = explode(':', $host);
392 $port = $hostPortArray[1];
393 }
394 return (int)$port;
395 }
396
397 /**
398 * Returns configured socket, if set
399 *
400 * @return string|NULL
401 */
402 protected function getConfiguredSocket()
403 {
404 $socket = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['unix_socket'] ?? '';
405 return $socket;
406 }
407 }