TYPO3 CMS  TYPO3_8-7
DatabaseSelect.php
Go to the documentation of this file.
1 <?php
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 
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  {
93  $errors = [];
95  $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
96  $isInitialInstallationInProgress = $configurationManager
97  ->getConfigurationValueByPath('SYS/isInitialInstallationInProgress');
98  try {
99  $this->view->assign('databaseList', $this->getDatabaseList($isInitialInstallationInProgress));
100  } catch (\Exception $exception) {
101  $errors[] = $exception->getMessage();
102  }
103  $this->view->assign('errors', $errors);
104  $this->view->assign('isInitialInstallationInProgress', $isInitialInstallationInProgress);
105  $this->assignSteps();
106  return $this->view->render();
107  }
108 
115  protected function getDatabaseList($initialInstallation)
116  {
117  $connectionParams = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME];
118  unset($connectionParams['dbname']);
119 
120  // Establishing the connection using the Doctrine DriverManager directly
121  // as we need a connection without selecting a database right away. Otherwise
122  // an invalid database name would lead to exceptions which would prevent
123  // changing the currently configured database.
124  $connection = DriverManager::getConnection($connectionParams);
125  $databaseArray = $connection->getSchemaManager()->listDatabases();
126  $connection->close();
127 
128  // Remove organizational tables from database list
129  $reservedDatabaseNames = ['mysql', 'information_schema', 'performance_schema'];
130  $allPossibleDatabases = array_diff($databaseArray, $reservedDatabaseNames);
131 
132  // If we are upgrading we show *all* databases the user has access to
133  if ($initialInstallation === false) {
134  return $allPossibleDatabases;
135  }
136 
137  // In first installation we show all databases but disable not empty ones (with tables)
138  $databases = [];
139  foreach ($allPossibleDatabases as $databaseName) {
140  // Reestablising the connection for each database since there is no
141  // portable way to switch databases on the same Doctrine connection.
142  // Directly using the Doctrine DriverManager here to avoid messing with
143  // the $GLOBALS database configuration array.
144  $connectionParams['dbname'] = $databaseName;
145  $connection = DriverManager::getConnection($connectionParams);
146 
147  $databases[] = [
148  'name' => $databaseName,
149  'tables' => count($connection->getSchemaManager()->listTableNames()),
150  ];
151  $connection->close();
152  }
153 
154  return $databases;
155  }
156 
163  protected function isValidDatabaseName($databaseName)
164  {
165  return strlen($databaseName) <= 50 && preg_match('/^[a-zA-Z0-9\$_]*$/', $databaseName);
166  }
167 
178  protected function getDefaultDatabaseCharset(string $dbName): string
179  {
180  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
181  ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
182  $queryBuilder = $connection->createQueryBuilder();
183  $defaultDatabaseCharset = $queryBuilder->select('DEFAULT_CHARACTER_SET_NAME')
184  ->from('information_schema.SCHEMATA')
185  ->where(
186  $queryBuilder->expr()->eq(
187  'SCHEMA_NAME',
188  $queryBuilder->createNamedParameter($dbName, \PDO::PARAM_STR)
189  )
190  )
191  ->setMaxResults(1)
192  ->execute()
193  ->fetchColumn();
194 
195  return (string)$defaultDatabaseCharset;
196  }
197 
205  protected function createNewDatabase($dbName)
206  {
207  if (!$this->isValidDatabaseName($dbName)) {
208  $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class);
209  $errorStatus->setTitle('Database name not valid');
210  $errorStatus->setMessage(
211  'Given database name must be shorter than fifty characters' .
212  ' and consist solely of basic latin letters (a-z), digits (0-9), dollar signs ($)' .
213  ' and underscores (_).'
214  );
215 
216  return $errorStatus;
217  }
218 
219  try {
220  GeneralUtility::makeInstance(ConnectionPool::class)
221  ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME)
222  ->getSchemaManager()
223  ->createDatabase($dbName);
224  GeneralUtility::makeInstance(ConfigurationManager::class)
225  ->setLocalConfigurationValueByPath('DB/Connections/Default/dbname', $dbName);
226  } catch (DBALException $e) {
227  $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class);
228  $errorStatus->setTitle('Unable to create database');
229  $errorStatus->setMessage(
230  'Database with name "' . $dbName . '" could not be created.' .
231  ' Either your database name contains a reserved keyword or your database' .
232  ' user does not have sufficient permissions to create it or the database already exists.' .
233  ' Please choose an existing (empty) database, choose another name or contact administration.'
234  );
235  return $errorStatus;
236  }
237 
238  return GeneralUtility::makeInstance(OkStatus::class);
239  }
240 
249  protected function checkExistingDatabase($dbName)
250  {
251  $result = GeneralUtility::makeInstance(OkStatus::class);
252  $localConfigurationPathValuePairs = [];
253  $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
254  $isInitialInstallation = $configurationManager
255  ->getConfigurationValueByPath('SYS/isInitialInstallationInProgress');
256 
257  $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME]['dbname'] = $dbName;
258  try {
259  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
260  ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
261 
262  if ($isInitialInstallation && !empty($connection->getSchemaManager()->listTableNames())) {
263  $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class);
264  $errorStatus->setTitle('Selected database is not empty!');
265  $errorStatus->setMessage(
266  sprintf('Cannot use database "%s"', $dbName)
267  . ', because it already contains tables. '
268  . 'Please select a different database or choose to create one!'
269  );
270  $result = $errorStatus;
271  }
272  } catch (\Exception $e) {
273  $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class);
274  $errorStatus->setTitle('Could not connect to selected database!');
275  $errorStatus->setMessage(
276  sprintf('Could not connect to database "%s"', $dbName)
277  . '! Make sure it really exists and your database user has the permissions to select it!'
278  );
279  $result = $errorStatus;
280  }
281 
282  if ($result instanceof OkStatus) {
283  $localConfigurationPathValuePairs['DB/Connections/Default/dbname'] = $dbName;
284  }
285 
286  // check if database charset is utf-8 - also allow utf8mb4
287  $defaultDatabaseCharset = $this->getDefaultDatabaseCharset($dbName);
288  if (substr($defaultDatabaseCharset, 0, 4) !== 'utf8') {
289  $errorStatus = GeneralUtility::makeInstance(ErrorStatus::class);
290  $errorStatus->setTitle('Invalid Charset');
291  $errorStatus->setMessage(
292  'Your database uses character set "' . $defaultDatabaseCharset . '", ' .
293  'but only "utf8" is supported with TYPO3. You probably want to change this before proceeding.'
294  );
295  $result = $errorStatus;
296  }
297 
298  if ($result instanceof OkStatus && !empty($localConfigurationPathValuePairs)) {
299  $configurationManager->setLocalConfigurationValuesByPathValuePairs($localConfigurationPathValuePairs);
300  }
301 
302  return $result;
303  }
304 }
static makeInstance($className,... $constructorArguments)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']