TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
DatabaseSelect.php
Go to the documentation of this file.
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 Doctrine\DBAL\DriverManager;
24 
30 {
34  protected $databaseConnection = null;
35 
41  public function execute()
42  {
43  $postValues = $this->postValues['values'];
44  if ($postValues['type'] === 'new') {
45  $status = $this->createNewDatabase($postValues['new']);
46  if ($status instanceof ErrorStatus) {
47  return [ $status ];
48  }
49  } elseif ($postValues['type'] === 'existing' && !empty($postValues['existing'])) {
50  $status = $this->checkExistingDatabase($postValues['existing']);
51  if ($status instanceof ErrorStatus) {
52  return [ $status ];
53  }
54  } else {
55  $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class);
56  $errorStatus->setTitle('No Database selected');
57  $errorStatus->setMessage('You must select a database.');
58  return [ $errorStatus ];
59  }
60  return [];
61  }
62 
69  public function needsExecution()
70  {
71  $result = true;
72  if ((string)$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname'] !== '') {
73  try {
74  $pingResult = GeneralUtility::makeInstance(ConnectionPool::class)
75  ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME)
76  ->ping();
77  if ($pingResult === true) {
78  $result = false;
79  }
80  } catch (DBALException $e) {
81  }
82  }
83  return $result;
84  }
85 
91  protected function executeAction()
92  {
94  $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
95  $isInitialInstallationInProgress = $configurationManager
96  ->getConfigurationValueByPath('SYS/isInitialInstallationInProgress');
97  $this->view->assign('databaseList', $this->getDatabaseList($isInitialInstallationInProgress));
98  $this->view->assign('isInitialInstallationInProgress', $isInitialInstallationInProgress);
99  $this->assignSteps();
100  return $this->view->render();
101  }
102 
109  protected function getDatabaseList($initialInstallation)
110  {
111  $connectionParams = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME];
112  unset($connectionParams['dbname']);
113 
114  // Establishing the connection using the Doctrine DriverManager directly
115  // as we need a connection without selecting a database right away. Otherwise
116  // an invalid database name would lead to exceptions which would prevent
117  // changing the currently configured database.
118  $connection = DriverManager::getConnection($connectionParams);
119  $databaseArray = $connection->getSchemaManager()->listDatabases();
120  $connection->close();
121 
122  // Remove organizational tables from database list
123  $reservedDatabaseNames = ['mysql', 'information_schema', 'performance_schema'];
124  $allPossibleDatabases = array_diff($databaseArray, $reservedDatabaseNames);
125 
126  // If we are upgrading we show *all* databases the user has access to
127  if ($initialInstallation === false) {
128  return $allPossibleDatabases;
129  }
130 
131  // In first installation we show all databases but disable not empty ones (with tables)
132  $databases = [];
133  foreach ($allPossibleDatabases as $databaseName) {
134  // Reestablising the connection for each database since there is no
135  // portable way to switch databases on the same Doctrine connection.
136  // Directly using the Doctrine DriverManager here to avoid messing with
137  // the $GLOBALS database configuration array.
138  $connectionParams['dbname'] = $databaseName;
139  $connection = DriverManager::getConnection($connectionParams);
140 
141  $databases[] = [
142  'name' => $databaseName,
143  'tables' => count($connection->getSchemaManager()->listTableNames()),
144  ];
145  $connection->close();
146  }
147 
148  return $databases;
149  }
150 
157  protected function isValidDatabaseName($databaseName)
158  {
159  return strlen($databaseName) <= 50 && preg_match('/^[a-zA-Z0-9\$_]*$/', $databaseName);
160  }
161 
172  protected function getDefaultDatabaseCharset(string $dbName): string
173  {
174  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
175  ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
176  $queryBuilder = $connection->createQueryBuilder();
177  $defaultDatabaseCharset = $queryBuilder->select('DEFAULT_CHARACTER_SET_NAME')
178  ->from('information_schema.SCHEMATA')
179  ->where(
180  $queryBuilder->expr()->eq(
181  'SCHEMA_NAME',
182  $queryBuilder->createNamedParameter($dbName, \PDO::PARAM_STR)
183  )
184  )
185  ->setMaxResults(1)
186  ->execute()
187  ->fetchColumn();
188 
189  return (string)$defaultDatabaseCharset;
190  }
191 
199  protected function createNewDatabase($dbName)
200  {
201  if (!$this->isValidDatabaseName($dbName)) {
202  $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class);
203  $errorStatus->setTitle('Database name not valid');
204  $errorStatus->setMessage(
205  'Given database name must be shorter than fifty characters' .
206  ' and consist solely of basic latin letters (a-z), digits (0-9), dollar signs ($)' .
207  ' and underscores (_).'
208  );
209 
210  return $errorStatus;
211  }
212 
213  try {
214  GeneralUtility::makeInstance(ConnectionPool::class)
215  ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME)
216  ->getSchemaManager()
217  ->createDatabase($dbName);
218  GeneralUtility::makeInstance(ConfigurationManager::class)
219  ->setLocalConfigurationValueByPath('DB/Connections/Default/dbname', $dbName);
220  } catch (DBALException $e) {
221  $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class);
222  $errorStatus->setTitle('Unable to create database');
223  $errorStatus->setMessage(
224  'Database with name "' . $dbName . '" could not be created.' .
225  ' Either your database name contains a reserved keyword or your database' .
226  ' user does not have sufficient permissions to create it or the database already exists.' .
227  ' Please choose an existing (empty) database, choose another name or contact administration.'
228  );
229  return $errorStatus;
230  }
231 
232  return GeneralUtility::makeInstance(OkStatus::class);
233  }
234 
243  protected function checkExistingDatabase($dbName)
244  {
245  $result = GeneralUtility::makeInstance(OkStatus::class);
246  $localConfigurationPathValuePairs = [];
247  $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
248  $isInitialInstallation = $configurationManager
249  ->getConfigurationValueByPath('SYS/isInitialInstallationInProgress');
250 
251  $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME]['dbname'] = $dbName;
252  try {
253  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
254  ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
255 
256  if ($isInitialInstallation && !empty($connection->getSchemaManager()->listTableNames())) {
257  $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class);
258  $errorStatus->setTitle('Selected database is not empty!');
259  $errorStatus->setMessage(
260  sprintf('Cannot use database "%s"', $dbName)
261  . ', because it already contains tables. '
262  . 'Please select a different database or choose to create one!'
263  );
264  $result = $errorStatus;
265  }
266  } catch (\Exception $e) {
267  $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class);
268  $errorStatus->setTitle('Could not connect to selected database!');
269  $errorStatus->setMessage(
270  sprintf('Could not connect to database "%s"', $dbName)
271  . '! Make sure it really exists and your database user has the permissions to select it!'
272  );
273  $result = $errorStatus;
274  }
275 
276  if ($result instanceof OkStatus) {
277  $localConfigurationPathValuePairs['DB/Connections/Default/dbname'] = $dbName;
278  }
279 
280  // check if database charset is utf-8 - also allow utf8mb4
281  $defaultDatabaseCharset = $this->getDefaultDatabaseCharset($dbName);
282  if (substr($defaultDatabaseCharset, 0, 4) !== 'utf8') {
283  $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class);
284  $errorStatus->setTitle('Invalid Charset');
285  $errorStatus->setMessage(
286  'Your database uses character set "' . $defaultDatabaseCharset . '", ' .
287  'but only "utf8" is supported with TYPO3. You probably want to change this before proceeding.'
288  );
289  $result = $errorStatus;
290  }
291 
292  if ($result instanceof OkStatus && !empty($localConfigurationPathValuePairs)) {
293  $configurationManager->setLocalConfigurationValuesByPathValuePairs($localConfigurationPathValuePairs);
294  }
295 
296  return $result;
297  }
298 }
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)