Tasks.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <?php
  2. namespace Drupal\Core\Database\Install;
  3. use Drupal\Core\Database\Database;
  4. /**
  5. * Database installer structure.
  6. *
  7. * Defines basic Drupal requirements for databases.
  8. */
  9. abstract class Tasks {
  10. /**
  11. * The name of the PDO driver this database type requires.
  12. *
  13. * @var string
  14. */
  15. protected $pdoDriver;
  16. /**
  17. * Structure that describes each task to run.
  18. *
  19. * @var array
  20. *
  21. * Each value of the tasks array is an associative array defining the function
  22. * to call (optional) and any arguments to be passed to the function.
  23. */
  24. protected $tasks = [
  25. [
  26. 'function' => 'checkEngineVersion',
  27. 'arguments' => [],
  28. ],
  29. [
  30. 'arguments' => [
  31. 'CREATE TABLE {drupal_install_test} (id int NOT NULL PRIMARY KEY)',
  32. 'Drupal can use CREATE TABLE database commands.',
  33. 'Failed to <strong>CREATE</strong> a test table on your database server with the command %query. The server reports the following message: %error.<p>Are you sure the configured username has the necessary permissions to create tables in the database?</p>',
  34. TRUE,
  35. ],
  36. ],
  37. [
  38. 'arguments' => [
  39. 'INSERT INTO {drupal_install_test} (id) VALUES (1)',
  40. 'Drupal can use INSERT database commands.',
  41. 'Failed to <strong>INSERT</strong> a value into a test table on your database server. We tried inserting a value with the command %query and the server reported the following error: %error.',
  42. ],
  43. ],
  44. [
  45. 'arguments' => [
  46. 'UPDATE {drupal_install_test} SET id = 2',
  47. 'Drupal can use UPDATE database commands.',
  48. 'Failed to <strong>UPDATE</strong> a value in a test table on your database server. We tried updating a value with the command %query and the server reported the following error: %error.',
  49. ],
  50. ],
  51. [
  52. 'arguments' => [
  53. 'DELETE FROM {drupal_install_test}',
  54. 'Drupal can use DELETE database commands.',
  55. 'Failed to <strong>DELETE</strong> a value from a test table on your database server. We tried deleting a value with the command %query and the server reported the following error: %error.',
  56. ],
  57. ],
  58. [
  59. 'arguments' => [
  60. 'DROP TABLE {drupal_install_test}',
  61. 'Drupal can use DROP TABLE database commands.',
  62. 'Failed to <strong>DROP</strong> a test table from your database server. We tried dropping a table with the command %query and the server reported the following error %error.',
  63. ],
  64. ],
  65. ];
  66. /**
  67. * Results from tasks.
  68. *
  69. * @var array
  70. */
  71. protected $results = [
  72. 'fail' => [],
  73. 'pass' => [],
  74. ];
  75. /**
  76. * Ensure the PDO driver is supported by the version of PHP in use.
  77. */
  78. protected function hasPdoDriver() {
  79. return in_array($this->pdoDriver, \PDO::getAvailableDrivers());
  80. }
  81. /**
  82. * Assert test as failed.
  83. */
  84. protected function fail($message) {
  85. $this->results['fail'][] = $message;
  86. }
  87. /**
  88. * Assert test as a pass.
  89. */
  90. protected function pass($message) {
  91. $this->results['pass'][] = $message;
  92. }
  93. /**
  94. * Check whether Drupal is installable on the database.
  95. */
  96. public function installable() {
  97. return $this->hasPdoDriver() && empty($this->error);
  98. }
  99. /**
  100. * Return the human-readable name of the driver.
  101. */
  102. abstract public function name();
  103. /**
  104. * Return the minimum required version of the engine.
  105. *
  106. * @return
  107. * A version string. If not NULL, it will be checked against the version
  108. * reported by the Database engine using version_compare().
  109. */
  110. public function minimumVersion() {
  111. return NULL;
  112. }
  113. /**
  114. * Run database tasks and tests to see if Drupal can run on the database.
  115. *
  116. * @return array
  117. * A list of error messages.
  118. */
  119. public function runTasks() {
  120. // We need to establish a connection before we can run tests.
  121. if ($this->connect()) {
  122. foreach ($this->tasks as $task) {
  123. if (!isset($task['function'])) {
  124. $task['function'] = 'runTestQuery';
  125. }
  126. if (method_exists($this, $task['function'])) {
  127. // Returning false is fatal. No other tasks can run.
  128. if (FALSE === call_user_func_array([$this, $task['function']], $task['arguments'])) {
  129. break;
  130. }
  131. }
  132. else {
  133. $this->fail(t("Failed to run all tasks against the database server. The task %task wasn't found.", ['%task' => $task['function']]));
  134. }
  135. }
  136. }
  137. return $this->results['fail'];
  138. }
  139. /**
  140. * Checks engine version requirements for the status report.
  141. *
  142. * This method is called during runtime and update requirements checks.
  143. *
  144. * @return \Drupal\Core\StringTranslation\TranslatableMarkup[]
  145. * A list of error messages.
  146. */
  147. final public function engineVersionRequirementsCheck() {
  148. $this->checkEngineVersion();
  149. return $this->results['fail'];
  150. }
  151. /**
  152. * Check if we can connect to the database.
  153. */
  154. protected function connect() {
  155. try {
  156. // This doesn't actually test the connection.
  157. Database::setActiveConnection();
  158. // Now actually do a check.
  159. Database::getConnection();
  160. $this->pass('Drupal can CONNECT to the database ok.');
  161. }
  162. catch (\Exception $e) {
  163. $this->fail(t('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist, and have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname and port number?</li></ul>', ['%error' => $e->getMessage()]));
  164. return FALSE;
  165. }
  166. return TRUE;
  167. }
  168. /**
  169. * Run SQL tests to ensure the database can execute commands with the current user.
  170. */
  171. protected function runTestQuery($query, $pass, $fail, $fatal = FALSE) {
  172. try {
  173. Database::getConnection()->query($query);
  174. $this->pass(t($pass));
  175. }
  176. catch (\Exception $e) {
  177. $this->fail(t($fail, ['%query' => $query, '%error' => $e->getMessage(), '%name' => $this->name()]));
  178. return !$fatal;
  179. }
  180. }
  181. /**
  182. * Check the engine version.
  183. */
  184. protected function checkEngineVersion() {
  185. // Ensure that the database server has the right version.
  186. if ($this->minimumVersion() && version_compare(Database::getConnection()->version(), $this->minimumVersion(), '<')) {
  187. $this->fail(t("The database server version %version is less than the minimum required version %minimum_version.", ['%version' => Database::getConnection()->version(), '%minimum_version' => $this->minimumVersion()]));
  188. }
  189. }
  190. /**
  191. * Return driver specific configuration options.
  192. *
  193. * @param $database
  194. * An array of driver specific configuration options.
  195. *
  196. * @return
  197. * The options form array.
  198. */
  199. public function getFormOptions(array $database) {
  200. // Use reflection to determine the driver name.
  201. // @todo https:///www.drupal.org/node/3123240 Provide a better way to get
  202. // the driver name.
  203. $reflection = new \ReflectionClass($this);
  204. $dir_parts = explode(DIRECTORY_SEPARATOR, dirname(dirname($reflection->getFileName())));
  205. $driver = array_pop($dir_parts);
  206. $form['database'] = [
  207. '#type' => 'textfield',
  208. '#title' => t('Database name'),
  209. '#default_value' => empty($database['database']) ? '' : $database['database'],
  210. '#size' => 45,
  211. '#required' => TRUE,
  212. '#states' => [
  213. 'required' => [
  214. ':input[name=driver]' => ['value' => $driver],
  215. ],
  216. ],
  217. ];
  218. $form['username'] = [
  219. '#type' => 'textfield',
  220. '#title' => t('Database username'),
  221. '#default_value' => empty($database['username']) ? '' : $database['username'],
  222. '#size' => 45,
  223. '#required' => TRUE,
  224. '#states' => [
  225. 'required' => [
  226. ':input[name=driver]' => ['value' => $driver],
  227. ],
  228. ],
  229. ];
  230. $form['password'] = [
  231. '#type' => 'password',
  232. '#title' => t('Database password'),
  233. '#default_value' => empty($database['password']) ? '' : $database['password'],
  234. '#required' => FALSE,
  235. '#size' => 45,
  236. ];
  237. $form['advanced_options'] = [
  238. '#type' => 'details',
  239. '#title' => t('Advanced options'),
  240. '#weight' => 10,
  241. ];
  242. global $install_state;
  243. // @todo https://www.drupal.org/project/drupal/issues/3110839 remove PHP 7.4
  244. // work around and add a better message for the migrate UI.
  245. $profile = $install_state['parameters']['profile'] ?? NULL;
  246. $db_prefix = ($profile == 'standard') ? 'drupal_' : $profile . '_';
  247. $form['advanced_options']['prefix'] = [
  248. '#type' => 'textfield',
  249. '#title' => t('Table name prefix'),
  250. '#default_value' => empty($database['prefix']) ? '' : $database['prefix'],
  251. '#size' => 45,
  252. '#description' => t('If more than one application will be sharing this database, a unique table name prefix – such as %prefix – will prevent collisions.', ['%prefix' => $db_prefix]),
  253. '#weight' => 10,
  254. ];
  255. $form['advanced_options']['host'] = [
  256. '#type' => 'textfield',
  257. '#title' => t('Host'),
  258. '#default_value' => empty($database['host']) ? 'localhost' : $database['host'],
  259. '#size' => 45,
  260. // Hostnames can be 255 characters long.
  261. '#maxlength' => 255,
  262. '#required' => TRUE,
  263. ];
  264. $form['advanced_options']['port'] = [
  265. '#type' => 'number',
  266. '#title' => t('Port number'),
  267. '#default_value' => empty($database['port']) ? '' : $database['port'],
  268. '#min' => 0,
  269. '#max' => 65535,
  270. ];
  271. return $form;
  272. }
  273. /**
  274. * Validates driver specific configuration settings.
  275. *
  276. * Checks to ensure correct basic database settings and that a proper
  277. * connection to the database can be established.
  278. *
  279. * @param $database
  280. * An array of driver specific configuration options.
  281. *
  282. * @return
  283. * An array of driver configuration errors, keyed by form element name.
  284. */
  285. public function validateDatabaseSettings($database) {
  286. $errors = [];
  287. // Verify the table prefix.
  288. if (!empty($database['prefix']) && is_string($database['prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['prefix'])) {
  289. $errors[$database['driver'] . '][prefix'] = t('The database table prefix you have entered, %prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', ['%prefix' => $database['prefix']]);
  290. }
  291. return $errors;
  292. }
  293. }