[!!!][TASK] Rewrite install tool
[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 * Copyright notice
6 *
7 * (c) 2013 Christian Kuhn <lolli@schwarzbu.ch>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26
27 use TYPO3\CMS\Install\Controller\Action;
28
29 /**
30 * Database connect step:
31 * - Needs execution if database credentials are not set or fail to connect
32 * - Renders fields for database connection fields
33 * - Sets database credentials in LocalConfiguration
34 * - Loads / unloads ext:dbal and ext:adodb if requested
35 */
36 class DatabaseConnect extends Action\AbstractAction implements StepInterface {
37
38 /**
39 * Execute database step:
40 * - Load / unload dbal & adodb
41 * - Set database connect credentials in LocalConfiguration
42 *
43 * @return array<\TYPO3\CMS\Install\Status\StatusInterface>
44 */
45 public function execute() {
46 $result = array();
47
48 /** @var $configurationManager \TYPO3\CMS\Core\Configuration\ConfigurationManager */
49 $configurationManager = $this->objectManager->get('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager');
50
51 $postValues = $this->postValues['values'];
52 if (isset($postValues['loadDbal'])) {
53 $result[] = $this->executeLoadDbalExtension();
54 } elseif ($postValues['unloadDbal']) {
55 $result[] = $this->executeUnloadDbalExtension();
56 } elseif ($postValues['setDbalDriver']) {
57 $driver = $postValues['setDbalDriver'];
58 switch ($driver) {
59 case 'mssql':
60 case 'odbc_mssql':
61 $driverConfig = array(
62 'useNameQuote' => TRUE,
63 'quoteClob' => FALSE,
64 );
65 break;
66 case 'oci8':
67 $driverConfig = array(
68 'driverOptions' => array(
69 'connectSID' => '',
70 ),
71 );
72 break;
73 }
74 $config = array(
75 '_DEFAULT' => array(
76 'type' => 'adodb',
77 'config' => array(
78 'driver' => $driver,
79 )
80 )
81 );
82 if (isset($driverConfig)) {
83 $config['_DEFAULT']['config'] = array_merge($config['_DEFAULT']['config'], $driverConfig);
84 }
85 $configurationManager->setLocalConfigurationValueByPath('EXTCONF/dbal/handlerCfg', $config);
86 } else {
87 $localConfigurationPathValuePairs = array();
88
89 if ($this->isDbalEnabled()) {
90 $config = $configurationManager->getConfigurationValueByPath('EXTCONF/dbal/handlerCfg');
91 $driver = $config['_DEFAULT']['config']['driver'];
92 if ($driver === 'oci8') {
93 $configurationManager['_DEFAULT']['config']['driverOptions']['connectSID']
94 = $postValues['type'] === 'sid' ? TRUE : FALSE;
95 }
96 }
97
98 if (isset($postValues['username'])) {
99 $value = $postValues['username'];
100 if (strlen($value) <= 50) {
101 $localConfigurationPathValuePairs['DB/username'] = $value;
102 } else {
103 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
104 $errorStatus = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
105 $errorStatus->setTitle('Database username not valid');
106 $errorStatus->setMessage('Given username must be shorter than fifty characters.');
107 $result[] = $errorStatus;
108 }
109 }
110
111 if (isset($postValues['password'])) {
112 $value = $postValues['password'];
113 if (strlen($value) <= 50) {
114 $localConfigurationPathValuePairs['DB/password'] = $value;
115 } else {
116 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
117 $errorStatus = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
118 $errorStatus->setTitle('Database password not valid');
119 $errorStatus->setMessage('Given password must be shorter than fifty characters.');
120 $result[] = $errorStatus;
121 }
122 }
123
124 if (isset($postValues['host'])) {
125 $value = $postValues['host'];
126 if (preg_match('/^[a-zA-Z0-9_\\.-]+(:.+)?$/', $value) && strlen($value) <= 50) {
127 $localConfigurationPathValuePairs['DB/host'] = $value;
128 } else {
129 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
130 $errorStatus = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
131 $errorStatus->setTitle('Database host not valid');
132 $errorStatus->setMessage('Given host is not alphanumeric (a-z, A-Z, 0-9 or _-.:) or longer than fifty characters.');
133 $result[] = $errorStatus;
134 }
135 }
136
137 if (isset($postValues['port'])) {
138 $value = $postValues['port'];
139 if (preg_match('/^[0-9]+(:.+)?$/', $value) && $value > 0 && $value <= 65535) {
140 $localConfigurationPathValuePairs['DB/port'] = (int)$value;
141 } else {
142 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
143 $errorStatus = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
144 $errorStatus->setTitle('Database port not valid');
145 $errorStatus->setMessage('Given port is not numeric or within range 1 to 65535');
146 $result[] = $errorStatus;
147 }
148 }
149
150 if (isset($postValues['database'])) {
151 $value = $postValues['database'];
152 if (strlen($value) <= 50) {
153 $localConfigurationPathValuePairs['DB/database'] = $value;
154 } else {
155 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
156 $errorStatus = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
157 $errorStatus->setTitle('Database name not valid');
158 $errorStatus->setMessage('Given database name must be shorter than fifty characters.');
159 $result[] = $errorStatus;
160 }
161 }
162
163 if (!empty($localConfigurationPathValuePairs)) {
164 $configurationManager->setLocalConfigurationValuesByPathValuePairs($localConfigurationPathValuePairs);
165
166 // After setting new credentials, test again and create an error message if connect is not successful
167 // @TODO: This could be simplified, if isConnectSuccessful could be released from TYPO3_CONF_VARS
168 // and feeded with connect values directly in order to obsolete the bootstrap reload.
169 \TYPO3\CMS\Core\Core\Bootstrap::getInstance()
170 ->populateLocalConfiguration()
171 ->setCoreCacheToNullBackend();
172 if ($this->isDbalEnabled()) {
173 require(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('dbal') . 'ext_localconf.php');
174 $GLOBALS['typo3CacheManager']->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
175 }
176 if (!$this->isConnectSuccessful()) {
177 /** @var $errorStatus \TYPO3\CMS\Install\Status\ErrorStatus */
178 $errorStatus = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\ErrorStatus');
179 $errorStatus->setTitle('Database connect not successful');
180 $errorStatus->setMessage('Connecting the database with given settings failed. Please check.');
181 $result[] = $errorStatus;
182 }
183 }
184 }
185
186 return $result;
187 }
188
189 /**
190 * Step needs to be executed if database connection is not successful.
191 *
192 * @return boolean
193 */
194 public function needsExecution() {
195 if (!$this->isConnectSuccessful()) {
196 return TRUE;
197 }
198 if (!isset($GLOBALS['TYPO3_CONF_VARS']['DB']['host'])
199 || !isset($GLOBALS['TYPO3_CONF_VARS']['DB']['port'])
200 ) {
201 return TRUE;
202 }
203 return FALSE;
204 }
205
206 /**
207 * Render this step
208 *
209 * @return string
210 */
211 public function handle() {
212 $this->initialize();
213
214 $isDbalEnabled = $this->isDbalEnabled();
215 $this->view
216 ->assign('isDbalEnabled', $isDbalEnabled)
217 ->assign('username', $GLOBALS['TYPO3_CONF_VARS']['DB']['username'] ?: '')
218 ->assign('password', $GLOBALS['TYPO3_CONF_VARS']['DB']['password'] ?: '')
219 ->assign('host', $this->getConfiguredHost() ?: '127.0.0.1')
220 ->assign('port', $this->getConfiguredOrDefaultPort())
221 ->assign('database', $GLOBALS['TYPO3_CONF_VARS']['DB']['database'] ?: '');
222
223 if ($isDbalEnabled) {
224 $this->view->assign('selectedDbalDriver', $this->getSelectedDbalDriver());
225 $this->view->assign('dbalDrivers', $this->getAvailableDbalDrivers());
226 $this->setDbalInputFieldsToRender();
227 } else {
228 $this->view
229 ->assign('renderConnectDetailsUsername', TRUE)
230 ->assign('renderConnectDetailsPassword', TRUE)
231 ->assign('renderConnectDetailsHost', TRUE)
232 ->assign('renderConnectDetailsPort', TRUE);
233 }
234
235 return $this->view->render();
236 }
237
238 /**
239 * Render fields required for successful connect based on dbal driver selection.
240 * Hint: There is a code duplication in handle() and this method. This
241 * is done by intention to keep this code area easy to maintain and understand.
242 *
243 * @return void
244 */
245 protected function setDbalInputFieldsToRender() {
246 $driver = $this->getSelectedDbalDriver();
247 switch($driver) {
248 case 'mssql':
249 case 'odbc_mssql':
250 case 'postgres':
251 $this->view
252 ->assign('renderConnectDetailsUsername', TRUE)
253 ->assign('renderConnectDetailsPassword', TRUE)
254 ->assign('renderConnectDetailsHost', TRUE)
255 ->assign('renderConnectDetailsPort', TRUE)
256 ->assign('renderConnectDetailsDatabase', TRUE);
257 break;
258 case 'oci8':
259 $this->view
260 ->assign('renderConnectDetailsUsername', TRUE)
261 ->assign('renderConnectDetailsPassword', TRUE)
262 ->assign('renderConnectDetailsHost', TRUE)
263 ->assign('renderConnectDetailsPort', TRUE)
264 ->assign('renderConnectDetailsDatabase', TRUE)
265 ->assign('renderConnectDetailsOracleSidConnect', TRUE);
266 $type = isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['dbal']['handlerCfg']['_DEFAULT']['config']['driverOptions']['connectSID'])
267 ? $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['dbal']['handlerCfg']['_DEFAULT']['config']['driverOptions']['connectSID']
268 : '';
269 if ($type === TRUE) {
270 $this->view->assign('oracleSidSelected', TRUE);
271 }
272 break;
273 }
274 }
275
276 /**
277 * Render connect port and label
278 *
279 * @return integer Configured or default port
280 */
281 protected function getConfiguredOrDefaultPort() {
282 $configuredPort = (int)$this->getConfiguredPort();
283 if (!$configuredPort) {
284 if ($this->isDbalEnabled()) {
285 $driver = $this->getSelectedDbalDriver();
286 switch ($driver) {
287 case 'postgres':
288 $port = 5432;
289 break;
290 case 'mssql':
291 case 'odbc_mssql':
292 $port = 1433;
293 break;
294 case 'oci8':
295 $port = 1521;
296 break;
297 default:
298 $port = 3306;
299 }
300 } else {
301 $port = 3306;
302 }
303 } else {
304 $port = $configuredPort;
305 }
306 return $port;
307 }
308
309 /**
310 * Test connection with given credentials
311 *
312 * @return boolean TRUE if connect was successful
313 */
314 protected function isConnectSuccessful() {
315 /** @var $databaseConnection \TYPO3\CMS\Core\Database\DatabaseConnection */
316 $databaseConnection = $this->objectManager->get('TYPO3\\CMS\\Core\\Database\\DatabaseConnection');
317
318 if ($this->isDbalEnabled()) {
319 // Set additional connect information based on dbal driver. postgres for example needs
320 // database name already for connect.
321 if (isset($GLOBALS['TYPO3_CONF_VARS']['DB']['database'])) {
322 $databaseConnection->setDatabaseName($GLOBALS['TYPO3_CONF_VARS']['DB']['database']);
323 }
324 }
325
326 $username = isset($GLOBALS['TYPO3_CONF_VARS']['DB']['username']) ? $GLOBALS['TYPO3_CONF_VARS']['DB']['username'] : '';
327 $databaseConnection->setDatabaseUsername($username);
328 $password = isset($GLOBALS['TYPO3_CONF_VARS']['DB']['password']) ? $GLOBALS['TYPO3_CONF_VARS']['DB']['password'] : '';
329 $databaseConnection->setDatabasePassword($password);
330 $databaseConnection->setDatabaseHost($this->getConfiguredHost());
331 $databaseConnection->setDatabasePort($this->getConfiguredPort());
332
333 $result = FALSE;
334 if (@$databaseConnection->sql_pconnect()) {
335 $result = TRUE;
336 }
337 return $result;
338 }
339
340 /**
341 * Returns a list of database drivers that are available on current server.
342 *
343 * @return array
344 */
345 protected function getAvailableDbalDrivers() {
346 $supportedDrivers = $this->getSupportedDbalDrivers();
347 $availableDrivers = array();
348 $selectedDbalDriver = $this->getSelectedDbalDriver();
349 foreach ($supportedDrivers as $abstractionLayer => $drivers) {
350 foreach ($drivers as $driver => $info) {
351 if (isset($info['combine']) && $info['combine'] === 'OR') {
352 $isAvailable = FALSE;
353 } else {
354 $isAvailable = TRUE;
355 }
356 // Loop through each PHP module dependency to ensure it is loaded
357 foreach ($info['extensions'] as $extension) {
358 if (isset($info['combine']) && $info['combine'] === 'OR') {
359 $isAvailable |= extension_loaded($extension);
360 } else {
361 $isAvailable &= extension_loaded($extension);
362 }
363 }
364 if ($isAvailable) {
365 if (!isset($availableDrivers[$abstractionLayer])) {
366 $availableDrivers[$abstractionLayer] = array();
367 }
368 $availableDrivers[$abstractionLayer][$driver] = array();
369 $availableDrivers[$abstractionLayer][$driver]['driver'] = $driver;
370 $availableDrivers[$abstractionLayer][$driver]['label'] = $info['label'];
371 $availableDrivers[$abstractionLayer][$driver]['selected'] = FALSE;
372 if ($selectedDbalDriver === $driver) {
373 $availableDrivers[$abstractionLayer][$driver]['selected'] = TRUE;
374 }
375 }
376 }
377 }
378 return $availableDrivers;
379 }
380
381 /**
382 * Returns a list of DBAL supported database drivers, with a
383 * user-friendly name and any PHP module dependency.
384 *
385 * @return array
386 */
387 protected function getSupportedDbalDrivers() {
388 $supportedDrivers = array(
389 'Native' => array(
390 'mssql' => array(
391 'label' => 'Microsoft SQL Server',
392 'extensions' => array('mssql')
393 ),
394 'oci8' => array(
395 'label' => 'Oracle OCI8',
396 'extensions' => array('oci8')
397 ),
398 'postgres' => array(
399 'label' => 'PostgreSQL',
400 'extensions' => array('pgsql')
401 )
402 ),
403 'ODBC' => array(
404 'odbc_mssql' => array(
405 'label' => 'Microsoft SQL Server',
406 'extensions' => array('odbc', 'mssql')
407 )
408 )
409 );
410 return $supportedDrivers;
411 }
412
413 /**
414 * Get selected dbal driver if any
415 *
416 * @return string Dbal driver or empty string if not yet selected
417 */
418 protected function getSelectedDbalDriver() {
419 if (isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['dbal']['handlerCfg']['_DEFAULT']['config']['driver'])) {
420 return $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['dbal']['handlerCfg']['_DEFAULT']['config']['driver'];
421 }
422 return '';
423 }
424
425 /**
426 * Adds dbal and adodb to list of loaded extensions
427 *
428 * @return \TYPO3\CMS\Install\Status\StatusInterface
429 */
430 protected function executeLoadDbalExtension() {
431 if (!\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('adodb')) {
432 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::loadExtension('adodb');
433 }
434 if (!\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('dbal')) {
435 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::loadExtension('dbal');
436 }
437 /** @var $errorStatus \TYPO3\CMS\Install\Status\WarningStatus */
438 $warningStatus = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\WarningStatus');
439 $warningStatus->setTitle('Loaded database abstraction layer');
440 return $warningStatus;
441 }
442
443 /**
444 * Remove dbal and adodb from list of loaded extensions
445 *
446 * @return \TYPO3\CMS\Install\Status\StatusInterface
447 */
448 protected function executeUnloadDbalExtension() {
449 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('adodb')) {
450 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::unloadExtension('adodb');
451 }
452 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('dbal')) {
453 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::unloadExtension('dbal');
454 }
455 // @TODO: Remove configuration from TYPO3_CONF_VARS['EXTCONF']['dbal']
456 /** @var $errorStatus \TYPO3\CMS\Install\Status\WarningStatus */
457 $warningStatus = $this->objectManager->get('TYPO3\\CMS\\Install\\Status\\WarningStatus');
458 $warningStatus->setTitle('Removed database abstraction layer');
459 return $warningStatus;
460 }
461
462 /**
463 * Returns configured host with port split off if given
464 *
465 * @return string
466 */
467 protected function getConfiguredHost() {
468 $host = isset($GLOBALS['TYPO3_CONF_VARS']['DB']['host']) ? $GLOBALS['TYPO3_CONF_VARS']['DB']['host'] : '';
469 $port = isset($GLOBALS['TYPO3_CONF_VARS']['DB']['port']) ? $GLOBALS['TYPO3_CONF_VARS']['DB']['port'] : '';
470 if (strlen($port) < 1 && strpos($host, ':') > 0) {
471 list($host) = explode(':', $host);
472 }
473 return $host;
474 }
475
476 /**
477 * Returns configured port. Gets port from host value if port is not yet set.
478 *
479 * @return integer
480 */
481 protected function getConfiguredPort() {
482 $host = isset($GLOBALS['TYPO3_CONF_VARS']['DB']['host']) ? $GLOBALS['TYPO3_CONF_VARS']['DB']['host'] : '';
483 $port = isset($GLOBALS['TYPO3_CONF_VARS']['DB']['port']) ? $GLOBALS['TYPO3_CONF_VARS']['DB']['port'] : '';
484 if (!strlen($port) > 0 && strpos($host, ':') > 0) {
485 $hostPortArray = explode(':', $host);
486 $port = $hostPortArray[1];
487 }
488 return (int)$port;
489 }
490 }
491 ?>