install.inc 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256
  1. <?php
  2. /**
  3. * @file
  4. * API functions for installing modules and themes.
  5. */
  6. use Drupal\Component\Utility\Crypt;
  7. use Drupal\Component\Utility\OpCodeCache;
  8. use Drupal\Component\Utility\Unicode;
  9. use Drupal\Component\Utility\UrlHelper;
  10. use Drupal\Core\Extension\Dependency;
  11. use Drupal\Core\Extension\ExtensionDiscovery;
  12. use Drupal\Core\File\FileSystemInterface;
  13. use Drupal\Core\Installer\InstallerKernel;
  14. use Drupal\Core\Site\Settings;
  15. use Symfony\Component\HttpFoundation\RedirectResponse;
  16. /**
  17. * Requirement severity -- Informational message only.
  18. */
  19. const REQUIREMENT_INFO = -1;
  20. /**
  21. * Requirement severity -- Requirement successfully met.
  22. */
  23. const REQUIREMENT_OK = 0;
  24. /**
  25. * Requirement severity -- Warning condition; proceed but flag warning.
  26. */
  27. const REQUIREMENT_WARNING = 1;
  28. /**
  29. * Requirement severity -- Error condition; abort installation.
  30. */
  31. const REQUIREMENT_ERROR = 2;
  32. /**
  33. * File permission check -- File exists.
  34. */
  35. const FILE_EXIST = 1;
  36. /**
  37. * File permission check -- File is readable.
  38. */
  39. const FILE_READABLE = 2;
  40. /**
  41. * File permission check -- File is writable.
  42. */
  43. const FILE_WRITABLE = 4;
  44. /**
  45. * File permission check -- File is executable.
  46. */
  47. const FILE_EXECUTABLE = 8;
  48. /**
  49. * File permission check -- File does not exist.
  50. */
  51. const FILE_NOT_EXIST = 16;
  52. /**
  53. * File permission check -- File is not readable.
  54. */
  55. const FILE_NOT_READABLE = 32;
  56. /**
  57. * File permission check -- File is not writable.
  58. */
  59. const FILE_NOT_WRITABLE = 64;
  60. /**
  61. * File permission check -- File is not executable.
  62. */
  63. const FILE_NOT_EXECUTABLE = 128;
  64. /**
  65. * Loads .install files for installed modules to initialize the update system.
  66. */
  67. function drupal_load_updates() {
  68. /** @var \Drupal\Core\Extension\ModuleExtensionList $extension_list_module */
  69. $extension_list_module = \Drupal::service('extension.list.module');
  70. foreach (drupal_get_installed_schema_version(NULL, FALSE, TRUE) as $module => $schema_version) {
  71. if ($extension_list_module->exists($module) && !$extension_list_module->checkIncompatibility($module)) {
  72. if ($schema_version > -1) {
  73. module_load_install($module);
  74. }
  75. }
  76. }
  77. }
  78. /**
  79. * Loads the installation profile, extracting its defined distribution name.
  80. *
  81. * @return
  82. * The distribution name defined in the profile's .info.yml file. Defaults to
  83. * "Drupal" if none is explicitly provided by the installation profile.
  84. *
  85. * @see install_profile_info()
  86. */
  87. function drupal_install_profile_distribution_name() {
  88. // During installation, the profile information is stored in the global
  89. // installation state (it might not be saved anywhere yet).
  90. $info = [];
  91. if (InstallerKernel::installationAttempted()) {
  92. global $install_state;
  93. if (isset($install_state['profile_info'])) {
  94. $info = $install_state['profile_info'];
  95. }
  96. }
  97. // At all other times, we load the profile via standard methods.
  98. else {
  99. $profile = \Drupal::installProfile();
  100. $info = \Drupal::service('extension.list.profile')->getExtensionInfo($profile);
  101. }
  102. return isset($info['distribution']['name']) ? $info['distribution']['name'] : 'Drupal';
  103. }
  104. /**
  105. * Loads the installation profile, extracting its defined version.
  106. *
  107. * @return string
  108. * Distribution version defined in the profile's .info.yml file.
  109. * Defaults to \Drupal::VERSION if no version is explicitly provided by the
  110. * installation profile.
  111. *
  112. * @see install_profile_info()
  113. */
  114. function drupal_install_profile_distribution_version() {
  115. // During installation, the profile information is stored in the global
  116. // installation state (it might not be saved anywhere yet).
  117. if (InstallerKernel::installationAttempted()) {
  118. global $install_state;
  119. return isset($install_state['profile_info']['version']) ? $install_state['profile_info']['version'] : \Drupal::VERSION;
  120. }
  121. // At all other times, we load the profile via standard methods.
  122. else {
  123. $profile = \Drupal::installProfile();
  124. $info = \Drupal::service('extension.list.profile')->getExtensionInfo($profile);
  125. return $info['version'];
  126. }
  127. }
  128. /**
  129. * Detects all supported databases that are compiled into PHP.
  130. *
  131. * @return
  132. * An array of database types compiled into PHP.
  133. */
  134. function drupal_detect_database_types() {
  135. $databases = drupal_get_database_types();
  136. foreach ($databases as $driver => $installer) {
  137. $databases[$driver] = $installer->name();
  138. }
  139. return $databases;
  140. }
  141. /**
  142. * Returns all supported database driver installer objects.
  143. *
  144. * @return \Drupal\Core\Database\Install\Tasks[]
  145. * An array of available database driver installer objects.
  146. */
  147. function drupal_get_database_types() {
  148. $databases = [];
  149. $drivers = [];
  150. // The internal database driver name is any valid PHP identifier.
  151. $mask = ExtensionDiscovery::PHP_FUNCTION_PATTERN;
  152. // Find drivers in the Drupal\Core and Drupal\Driver namespaces.
  153. /** @var \Drupal\Core\File\FileSystemInterface $file_system */
  154. $file_system = \Drupal::service('file_system');
  155. $files = $file_system->scanDirectory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, ['recurse' => FALSE]);
  156. if (is_dir(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database')) {
  157. $files += $file_system->scanDirectory(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database/', $mask, ['recurse' => FALSE]);
  158. }
  159. foreach ($files as $file) {
  160. if (file_exists($file->uri . '/Install/Tasks.php')) {
  161. // The namespace doesn't need to be added here, because
  162. // db_installer_object() will find it.
  163. $drivers[$file->filename] = NULL;
  164. }
  165. }
  166. // Find drivers in Drupal module namespaces.
  167. /** @var \Composer\Autoload\ClassLoader $class_loader */
  168. $class_loader = \Drupal::service('class_loader');
  169. // We cannot use the file cache because it does not always exist.
  170. $extension_discovery = new ExtensionDiscovery(DRUPAL_ROOT, FALSE, []);
  171. $modules = $extension_discovery->scan('module');
  172. foreach ($modules as $module) {
  173. $module_driver_path = DRUPAL_ROOT . '/' . $module->getPath() . '/src/Driver/Database';
  174. if (is_dir($module_driver_path)) {
  175. $driver_files = $file_system->scanDirectory($module_driver_path, $mask, ['recurse' => FALSE]);
  176. foreach ($driver_files as $driver_file) {
  177. $tasks_file = $module_driver_path . '/' . $driver_file->filename . '/Install/Tasks.php';
  178. if (file_exists($tasks_file)) {
  179. $namespace = 'Drupal\\' . $module->getName() . '\\Driver\\Database\\' . $driver_file->filename;
  180. // The namespace needs to be added for db_installer_object() to find
  181. // it.
  182. $drivers[$driver_file->filename] = $namespace;
  183. // The directory needs to be added to the autoloader, because this is
  184. // early in the installation process: the module hasn't been enabled
  185. // yet and the database connection info array (including its 'autoload'
  186. // key) hasn't been created yet.
  187. $class_loader->addPsr4($namespace . '\\', $module->getPath() . '/src/Driver/Database/' . $driver_file->filename);
  188. }
  189. }
  190. }
  191. }
  192. foreach ($drivers as $driver => $namespace) {
  193. $installer = db_installer_object($driver, $namespace);
  194. if ($installer->installable()) {
  195. $databases[$driver] = $installer;
  196. }
  197. }
  198. // Usability: unconditionally put the MySQL driver on top.
  199. if (isset($databases['mysql'])) {
  200. $mysql_database = $databases['mysql'];
  201. unset($databases['mysql']);
  202. $databases = ['mysql' => $mysql_database] + $databases;
  203. }
  204. return $databases;
  205. }
  206. /**
  207. * Replaces values in settings.php with values in the submitted array.
  208. *
  209. * This function replaces values in place if possible, even for
  210. * multidimensional arrays. This way the old settings do not linger,
  211. * overridden and also the doxygen on a value remains where it should be.
  212. *
  213. * @param $settings
  214. * An array of settings that need to be updated. Multidimensional arrays
  215. * are dumped up to a stdClass object. The object can have value, required
  216. * and comment properties.
  217. * @code
  218. * $settings['settings']['config_sync_directory'] = (object) array(
  219. * 'value' => 'config_hash/sync',
  220. * 'required' => TRUE,
  221. * );
  222. * @endcode
  223. * gets dumped as:
  224. * @code
  225. * $settings['config_sync_directory'] = 'config_hash/sync'
  226. * @endcode
  227. */
  228. function drupal_rewrite_settings($settings = [], $settings_file = NULL) {
  229. if (!isset($settings_file)) {
  230. $settings_file = \Drupal::service('site.path') . '/settings.php';
  231. }
  232. // Build list of setting names and insert the values into the global namespace.
  233. $variable_names = [];
  234. $settings_settings = [];
  235. foreach ($settings as $setting => $data) {
  236. if ($setting != 'settings') {
  237. _drupal_rewrite_settings_global($GLOBALS[$setting], $data);
  238. }
  239. else {
  240. _drupal_rewrite_settings_global($settings_settings, $data);
  241. }
  242. $variable_names['$' . $setting] = $setting;
  243. }
  244. $contents = file_get_contents($settings_file);
  245. if ($contents !== FALSE) {
  246. // Initialize the contents for the settings.php file if it is empty.
  247. if (trim($contents) === '') {
  248. $contents = "<?php\n";
  249. }
  250. // Step through each token in settings.php and replace any variables that
  251. // are in the passed-in array.
  252. $buffer = '';
  253. $state = 'default';
  254. foreach (token_get_all($contents) as $token) {
  255. if (is_array($token)) {
  256. list($type, $value) = $token;
  257. }
  258. else {
  259. $type = -1;
  260. $value = $token;
  261. }
  262. // Do not operate on whitespace.
  263. if (!in_array($type, [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
  264. switch ($state) {
  265. case 'default':
  266. if ($type === T_VARIABLE && isset($variable_names[$value])) {
  267. // This will be necessary to unset the dumped variable.
  268. $parent = &$settings;
  269. // This is the current index in parent.
  270. $index = $variable_names[$value];
  271. // This will be necessary for descending into the array.
  272. $current = &$parent[$index];
  273. $state = 'candidate_left';
  274. }
  275. break;
  276. case 'candidate_left':
  277. if ($value == '[') {
  278. $state = 'array_index';
  279. }
  280. if ($value == '=') {
  281. $state = 'candidate_right';
  282. }
  283. break;
  284. case 'array_index':
  285. if (_drupal_rewrite_settings_is_array_index($type, $value)) {
  286. $index = trim($value, '\'"');
  287. $state = 'right_bracket';
  288. }
  289. else {
  290. // $a[foo()] or $a[$bar] or something like that.
  291. throw new Exception('invalid array index');
  292. }
  293. break;
  294. case 'right_bracket':
  295. if ($value == ']') {
  296. if (isset($current[$index])) {
  297. // If the new settings has this index, descend into it.
  298. $parent = &$current;
  299. $current = &$parent[$index];
  300. $state = 'candidate_left';
  301. }
  302. else {
  303. // Otherwise, jump back to the default state.
  304. $state = 'wait_for_semicolon';
  305. }
  306. }
  307. else {
  308. // $a[1 + 2].
  309. throw new Exception('] expected');
  310. }
  311. break;
  312. case 'candidate_right':
  313. if (_drupal_rewrite_settings_is_simple($type, $value)) {
  314. $value = _drupal_rewrite_settings_dump_one($current);
  315. // Unsetting $current would not affect $settings at all.
  316. unset($parent[$index]);
  317. // Skip the semicolon because _drupal_rewrite_settings_dump_one() added one.
  318. $state = 'semicolon_skip';
  319. }
  320. else {
  321. $state = 'wait_for_semicolon';
  322. }
  323. break;
  324. case 'wait_for_semicolon':
  325. if ($value == ';') {
  326. $state = 'default';
  327. }
  328. break;
  329. case 'semicolon_skip':
  330. if ($value == ';') {
  331. $value = '';
  332. $state = 'default';
  333. }
  334. else {
  335. // If the expression was $a = 1 + 2; then we replaced 1 and
  336. // the + is unexpected.
  337. throw new Exception('Unexpected token after replacing value.');
  338. }
  339. break;
  340. }
  341. }
  342. $buffer .= $value;
  343. }
  344. foreach ($settings as $name => $setting) {
  345. $buffer .= _drupal_rewrite_settings_dump($setting, '$' . $name);
  346. }
  347. // Write the new settings file.
  348. if (file_put_contents($settings_file, $buffer) === FALSE) {
  349. throw new Exception("Failed to modify '$settings_file'. Verify the file permissions.");
  350. }
  351. else {
  352. // In case any $settings variables were written, import them into the
  353. // Settings singleton.
  354. if (!empty($settings_settings)) {
  355. $old_settings = Settings::getAll();
  356. new Settings($settings_settings + $old_settings);
  357. }
  358. // The existing settings.php file might have been included already. In
  359. // case an opcode cache is enabled, the rewritten contents of the file
  360. // will not be reflected in this process. Ensure to invalidate the file
  361. // in case an opcode cache is enabled.
  362. OpCodeCache::invalidate(DRUPAL_ROOT . '/' . $settings_file);
  363. }
  364. }
  365. else {
  366. throw new Exception("Failed to open '$settings_file'. Verify the file permissions.");
  367. }
  368. }
  369. /**
  370. * Helper for drupal_rewrite_settings().
  371. *
  372. * Checks whether this token represents a scalar or NULL.
  373. *
  374. * @param int $type
  375. * The token type.
  376. * @param string $value
  377. * The value of the token.
  378. *
  379. * @return bool
  380. * TRUE if this token represents a scalar or NULL.
  381. *
  382. * @see token_name()
  383. */
  384. function _drupal_rewrite_settings_is_simple($type, $value) {
  385. $is_integer = $type == T_LNUMBER;
  386. $is_float = $type == T_DNUMBER;
  387. $is_string = $type == T_CONSTANT_ENCAPSED_STRING;
  388. $is_boolean_or_null = $type == T_STRING && in_array(strtoupper($value), ['TRUE', 'FALSE', 'NULL']);
  389. return $is_integer || $is_float || $is_string || $is_boolean_or_null;
  390. }
  391. /**
  392. * Helper for drupal_rewrite_settings().
  393. *
  394. * Checks whether this token represents a valid array index: a number or a
  395. * string.
  396. *
  397. * @param int $type
  398. * The token type.
  399. *
  400. * @return bool
  401. * TRUE if this token represents a number or a string.
  402. *
  403. * @see token_name()
  404. */
  405. function _drupal_rewrite_settings_is_array_index($type) {
  406. $is_integer = $type == T_LNUMBER;
  407. $is_float = $type == T_DNUMBER;
  408. $is_string = $type == T_CONSTANT_ENCAPSED_STRING;
  409. return $is_integer || $is_float || $is_string;
  410. }
  411. /**
  412. * Helper for drupal_rewrite_settings().
  413. *
  414. * Makes the new settings global.
  415. *
  416. * @param array|null $ref
  417. * A reference to a nested index in $GLOBALS.
  418. * @param array|object $variable
  419. * The nested value of the setting being copied.
  420. */
  421. function _drupal_rewrite_settings_global(&$ref, $variable) {
  422. if (is_object($variable)) {
  423. $ref = $variable->value;
  424. }
  425. else {
  426. foreach ($variable as $k => $v) {
  427. _drupal_rewrite_settings_global($ref[$k], $v);
  428. }
  429. }
  430. }
  431. /**
  432. * Helper for drupal_rewrite_settings().
  433. *
  434. * Dump the relevant value properties.
  435. *
  436. * @param array|object $variable
  437. * The container for variable values.
  438. * @param string $variable_name
  439. * Name of variable.
  440. *
  441. * @return string
  442. * A string containing valid PHP code of the variable suitable for placing
  443. * into settings.php.
  444. */
  445. function _drupal_rewrite_settings_dump($variable, $variable_name) {
  446. $return = '';
  447. if (is_object($variable)) {
  448. if (!empty($variable->required)) {
  449. $return .= _drupal_rewrite_settings_dump_one($variable, "$variable_name = ", "\n");
  450. }
  451. }
  452. else {
  453. foreach ($variable as $k => $v) {
  454. $return .= _drupal_rewrite_settings_dump($v, $variable_name . "['" . $k . "']");
  455. }
  456. }
  457. return $return;
  458. }
  459. /**
  460. * Helper for drupal_rewrite_settings().
  461. *
  462. * Dump the value of a value property and adds the comment if it exists.
  463. *
  464. * @param object $variable
  465. * A stdClass object with at least a value property.
  466. * @param string $prefix
  467. * A string to prepend to the variable's value.
  468. * @param string $suffix
  469. * A string to append to the variable's value.
  470. *
  471. * @return string
  472. * A string containing valid PHP code of the variable suitable for placing
  473. * into settings.php.
  474. */
  475. function _drupal_rewrite_settings_dump_one(\stdClass $variable, $prefix = '', $suffix = '') {
  476. $return = $prefix . var_export($variable->value, TRUE) . ';';
  477. if (!empty($variable->comment)) {
  478. $return .= ' // ' . $variable->comment;
  479. }
  480. $return .= $suffix;
  481. return $return;
  482. }
  483. /**
  484. * Creates the config directory and ensures it is operational.
  485. *
  486. * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. There is no
  487. * replacement.
  488. *
  489. * @see https://www.drupal.org/node/3018145
  490. */
  491. function drupal_install_config_directories() {
  492. @trigger_error('drupal_install_config_directories() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. There is no replacement. See https://www.drupal.org/node/3018145.', E_USER_DEPRECATED);
  493. global $config_directories, $install_state;
  494. // If settings.php does not contain a config sync directory name we need to
  495. // configure one.
  496. if (empty($config_directories[CONFIG_SYNC_DIRECTORY])) {
  497. if (empty($install_state['config_install_path'])) {
  498. // Add a randomized config directory name to settings.php
  499. $config_directories[CONFIG_SYNC_DIRECTORY] = \Drupal::service('site.path') . '/files/config_' . Crypt::randomBytesBase64(55) . '/sync';
  500. }
  501. else {
  502. // Install profiles can contain a config sync directory. If they do,
  503. // 'config_install_path' is a path to the directory.
  504. $config_directories[CONFIG_SYNC_DIRECTORY] = $install_state['config_install_path'];
  505. }
  506. $settings['config_directories'][CONFIG_SYNC_DIRECTORY] = (object) [
  507. 'value' => $config_directories[CONFIG_SYNC_DIRECTORY],
  508. 'required' => TRUE,
  509. ];
  510. // Rewrite settings.php, which also sets the value as global variable.
  511. drupal_rewrite_settings($settings);
  512. }
  513. // This should never fail, since if the config directory was specified in
  514. // settings.php it will have already been created and verified earlier, and
  515. // if it wasn't specified in settings.php, it is created here inside the
  516. // public files directory, which has already been verified to be writable
  517. // itself. But if it somehow fails anyway, the installation cannot proceed.
  518. // Bail out using a similar error message as in system_requirements().
  519. if (!\Drupal::service('file_system')->prepareDirectory($config_directories[CONFIG_SYNC_DIRECTORY], FileSystemInterface::CREATE_DIRECTORY)
  520. && !file_exists($config_directories[CONFIG_SYNC_DIRECTORY])) {
  521. throw new Exception("The directory '" . config_get_config_directory(CONFIG_SYNC_DIRECTORY) . "' could not be created. To proceed with the installation, either create the directory or ensure that the installer has the permissions to create it automatically. For more information, see the <a href='https://www.drupal.org/server-permissions'>online handbook</a>.");
  522. }
  523. elseif (is_writable($config_directories[CONFIG_SYNC_DIRECTORY])) {
  524. // Put a README.txt into the sync config directory. This is required so that
  525. // they can later be added to git. Since this directory is auto-created, we
  526. // have to write out the README rather than just adding it to the drupal core
  527. // repo.
  528. $text = 'This directory contains configuration to be imported into your Drupal site. To make this configuration active, visit admin/config/development/configuration/sync.' . ' For information about deploying configuration between servers, see https://www.drupal.org/documentation/administer/config';
  529. file_put_contents(config_get_config_directory(CONFIG_SYNC_DIRECTORY) . '/README.txt', "$text\n");
  530. }
  531. }
  532. /**
  533. * Ensures that the config directory exists and is writable, or can be made so.
  534. *
  535. * @param string $type
  536. * Type of config directory to return. Drupal core provides 'sync'.
  537. *
  538. * @return bool
  539. * TRUE if the config directory exists and is writable.
  540. *
  541. * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Use
  542. * config_get_config_directory() and
  543. * \Drupal\Core\File\FileSystemInterface::prepareDirectory() instead.
  544. *
  545. * @see https://www.drupal.org/node/2501187
  546. */
  547. function install_ensure_config_directory($type) {
  548. @trigger_error('install_ensure_config_directory() is deprecated in Drupal 8.1.0 and will be removed before Drupal 9.0.0. Use config_get_config_directory() and \Drupal\Core\File\FileSystemInterface::prepareDirectory() instead. See https://www.drupal.org/node/2501187.', E_USER_DEPRECATED);
  549. // The config directory must be defined in settings.php.
  550. global $config_directories;
  551. if (!isset($config_directories[$type])) {
  552. return FALSE;
  553. }
  554. // The logic here is similar to that used by system_requirements() for other
  555. // directories that the installer creates.
  556. else {
  557. $config_directory = config_get_config_directory($type);
  558. return \Drupal::service('file_system')->prepareDirectory($config_directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
  559. }
  560. }
  561. /**
  562. * Verifies that all dependencies are met for a given installation profile.
  563. *
  564. * @param $install_state
  565. * An array of information about the current installation state.
  566. *
  567. * @return
  568. * The list of modules to install.
  569. */
  570. function drupal_verify_profile($install_state) {
  571. $profile = $install_state['parameters']['profile'];
  572. $info = $install_state['profile_info'];
  573. // Get the list of available modules for the selected installation profile.
  574. $listing = new ExtensionDiscovery(\Drupal::root());
  575. $present_modules = [];
  576. foreach ($listing->scan('module') as $present_module) {
  577. $present_modules[] = $present_module->getName();
  578. }
  579. // The installation profile is also a module, which needs to be installed
  580. // after all the other dependencies have been installed.
  581. $present_modules[] = $profile;
  582. // Verify that all of the profile's required modules are present.
  583. $missing_modules = array_diff($info['install'], $present_modules);
  584. $requirements = [];
  585. if ($missing_modules) {
  586. $build = [
  587. '#theme' => 'item_list',
  588. '#context' => ['list_style' => 'comma-list'],
  589. ];
  590. foreach ($missing_modules as $module) {
  591. $build['#items'][] = ['#markup' => '<span class="admin-missing">' . Unicode::ucfirst($module) . '</span>'];
  592. }
  593. $modules_list = \Drupal::service('renderer')->renderPlain($build);
  594. $requirements['required_modules'] = [
  595. 'title' => t('Required modules'),
  596. 'value' => t('Required modules not found.'),
  597. 'severity' => REQUIREMENT_ERROR,
  598. 'description' => t('The following modules are required but were not found. Move them into the appropriate modules subdirectory, such as <em>/modules</em>. Missing modules: @modules', ['@modules' => $modules_list]),
  599. ];
  600. }
  601. return $requirements;
  602. }
  603. /**
  604. * Installs the system module.
  605. *
  606. * Separated from the installation of other modules so core system
  607. * functions can be made available while other modules are installed.
  608. *
  609. * @param array $install_state
  610. * An array of information about the current installation state. This is used
  611. * to set the default language.
  612. */
  613. function drupal_install_system($install_state) {
  614. // Remove the service provider of the early installer.
  615. unset($GLOBALS['conf']['container_service_providers']['InstallerServiceProvider']);
  616. // Add the normal installer service provider.
  617. $GLOBALS['conf']['container_service_providers']['InstallerServiceProvider'] = 'Drupal\Core\Installer\NormalInstallerServiceProvider';
  618. // Get the existing request.
  619. $request = \Drupal::request();
  620. // Reboot into a full production environment to continue the installation.
  621. /** @var \Drupal\Core\Installer\InstallerKernel $kernel */
  622. $kernel = \Drupal::service('kernel');
  623. $kernel->shutdown();
  624. // Have installer rebuild from the disk, rather then building from scratch.
  625. $kernel->rebuildContainer(FALSE);
  626. // Reboot the kernel with new container.
  627. $kernel->boot();
  628. $kernel->preHandle($request);
  629. // Ensure our request includes the session if appropriate.
  630. if (PHP_SAPI !== 'cli') {
  631. $request->setSession($kernel->getContainer()->get('session'));
  632. }
  633. // Before having installed the system module and being able to do a module
  634. // rebuild, prime the \Drupal\Core\Extension\ModuleExtensionList static cache
  635. // with the module's location.
  636. // @todo Try to install system as any other module, see
  637. // https://www.drupal.org/node/2719315.
  638. \Drupal::service('extension.list.module')->setPathname('system', 'core/modules/system/system.info.yml');
  639. // Install base system configuration.
  640. \Drupal::service('config.installer')->installDefaultConfig('core', 'core');
  641. // Store the installation profile in configuration to populate the
  642. // 'install_profile' container parameter.
  643. \Drupal::configFactory()->getEditable('core.extension')
  644. ->set('profile', $install_state['parameters']['profile'])
  645. ->save();
  646. // Install System module and rebuild the newly available routes.
  647. $kernel->getContainer()->get('module_installer')->install(['system'], FALSE);
  648. \Drupal::service('router.builder')->rebuild();
  649. // Ensure default language is saved.
  650. if (isset($install_state['parameters']['langcode'])) {
  651. \Drupal::configFactory()->getEditable('system.site')
  652. ->set('langcode', (string) $install_state['parameters']['langcode'])
  653. ->set('default_langcode', (string) $install_state['parameters']['langcode'])
  654. ->save(TRUE);
  655. }
  656. }
  657. /**
  658. * Verifies the state of the specified file.
  659. *
  660. * @param $file
  661. * The file to check for.
  662. * @param $mask
  663. * An optional bitmask created from various FILE_* constants.
  664. * @param $type
  665. * The type of file. Can be file (default), dir, or link.
  666. * @param bool $autofix
  667. * (optional) Determines whether to attempt fixing the permissions according
  668. * to the provided $mask. Defaults to TRUE.
  669. *
  670. * @return
  671. * TRUE on success or FALSE on failure. A message is set for the latter.
  672. */
  673. function drupal_verify_install_file($file, $mask = NULL, $type = 'file', $autofix = TRUE) {
  674. $return = TRUE;
  675. // Check for files that shouldn't be there.
  676. if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) {
  677. return FALSE;
  678. }
  679. // Verify that the file is the type of file it is supposed to be.
  680. if (isset($type) && file_exists($file)) {
  681. $check = 'is_' . $type;
  682. if (!function_exists($check) || !$check($file)) {
  683. $return = FALSE;
  684. }
  685. }
  686. // Verify file permissions.
  687. if (isset($mask)) {
  688. $masks = [FILE_EXIST, FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE];
  689. foreach ($masks as $current_mask) {
  690. if ($mask & $current_mask) {
  691. switch ($current_mask) {
  692. case FILE_EXIST:
  693. if (!file_exists($file)) {
  694. if ($type == 'dir' && $autofix) {
  695. drupal_install_mkdir($file, $mask);
  696. }
  697. if (!file_exists($file)) {
  698. $return = FALSE;
  699. }
  700. }
  701. break;
  702. case FILE_READABLE:
  703. if (!is_readable($file)) {
  704. $return = FALSE;
  705. }
  706. break;
  707. case FILE_WRITABLE:
  708. if (!is_writable($file)) {
  709. $return = FALSE;
  710. }
  711. break;
  712. case FILE_EXECUTABLE:
  713. if (!is_executable($file)) {
  714. $return = FALSE;
  715. }
  716. break;
  717. case FILE_NOT_READABLE:
  718. if (is_readable($file)) {
  719. $return = FALSE;
  720. }
  721. break;
  722. case FILE_NOT_WRITABLE:
  723. if (is_writable($file)) {
  724. $return = FALSE;
  725. }
  726. break;
  727. case FILE_NOT_EXECUTABLE:
  728. if (is_executable($file)) {
  729. $return = FALSE;
  730. }
  731. break;
  732. }
  733. }
  734. }
  735. }
  736. if (!$return && $autofix) {
  737. return drupal_install_fix_file($file, $mask);
  738. }
  739. return $return;
  740. }
  741. /**
  742. * Creates a directory with the specified permissions.
  743. *
  744. * @param $file
  745. * The name of the directory to create;
  746. * @param $mask
  747. * The permissions of the directory to create.
  748. * @param $message
  749. * (optional) Whether to output messages. Defaults to TRUE.
  750. *
  751. * @return
  752. * TRUE/FALSE whether or not the directory was successfully created.
  753. */
  754. function drupal_install_mkdir($file, $mask, $message = TRUE) {
  755. $mod = 0;
  756. $masks = [FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE];
  757. foreach ($masks as $m) {
  758. if ($mask & $m) {
  759. switch ($m) {
  760. case FILE_READABLE:
  761. $mod |= 0444;
  762. break;
  763. case FILE_WRITABLE:
  764. $mod |= 0222;
  765. break;
  766. case FILE_EXECUTABLE:
  767. $mod |= 0111;
  768. break;
  769. }
  770. }
  771. }
  772. if (@\Drupal::service('file_system')->mkdir($file, $mod)) {
  773. return TRUE;
  774. }
  775. else {
  776. return FALSE;
  777. }
  778. }
  779. /**
  780. * Attempts to fix file permissions.
  781. *
  782. * The general approach here is that, because we do not know the security
  783. * setup of the webserver, we apply our permission changes to all three
  784. * digits of the file permission (i.e. user, group and all).
  785. *
  786. * To ensure that the values behave as expected (and numbers don't carry
  787. * from one digit to the next) we do the calculation on the octal value
  788. * using bitwise operations. This lets us remove, for example, 0222 from
  789. * 0700 and get the correct value of 0500.
  790. *
  791. * @param $file
  792. * The name of the file with permissions to fix.
  793. * @param $mask
  794. * The desired permissions for the file.
  795. * @param $message
  796. * (optional) Whether to output messages. Defaults to TRUE.
  797. *
  798. * @return
  799. * TRUE/FALSE whether or not we were able to fix the file's permissions.
  800. */
  801. function drupal_install_fix_file($file, $mask, $message = TRUE) {
  802. // If $file does not exist, fileperms() issues a PHP warning.
  803. if (!file_exists($file)) {
  804. return FALSE;
  805. }
  806. $mod = fileperms($file) & 0777;
  807. $masks = [FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE];
  808. // FILE_READABLE, FILE_WRITABLE, and FILE_EXECUTABLE permission strings
  809. // can theoretically be 0400, 0200, and 0100 respectively, but to be safe
  810. // we set all three access types in case the administrator intends to
  811. // change the owner of settings.php after installation.
  812. foreach ($masks as $m) {
  813. if ($mask & $m) {
  814. switch ($m) {
  815. case FILE_READABLE:
  816. if (!is_readable($file)) {
  817. $mod |= 0444;
  818. }
  819. break;
  820. case FILE_WRITABLE:
  821. if (!is_writable($file)) {
  822. $mod |= 0222;
  823. }
  824. break;
  825. case FILE_EXECUTABLE:
  826. if (!is_executable($file)) {
  827. $mod |= 0111;
  828. }
  829. break;
  830. case FILE_NOT_READABLE:
  831. if (is_readable($file)) {
  832. $mod &= ~0444;
  833. }
  834. break;
  835. case FILE_NOT_WRITABLE:
  836. if (is_writable($file)) {
  837. $mod &= ~0222;
  838. }
  839. break;
  840. case FILE_NOT_EXECUTABLE:
  841. if (is_executable($file)) {
  842. $mod &= ~0111;
  843. }
  844. break;
  845. }
  846. }
  847. }
  848. // chmod() will work if the web server is running as owner of the file.
  849. if (@chmod($file, $mod)) {
  850. return TRUE;
  851. }
  852. else {
  853. return FALSE;
  854. }
  855. }
  856. /**
  857. * Sends the user to a different installer page.
  858. *
  859. * This issues an on-site HTTP redirect. Messages (and errors) are erased.
  860. *
  861. * @param $path
  862. * An installer path.
  863. */
  864. function install_goto($path) {
  865. global $base_url;
  866. $headers = [
  867. // Not a permanent redirect.
  868. 'Cache-Control' => 'no-cache',
  869. ];
  870. $response = new RedirectResponse($base_url . '/' . $path, 302, $headers);
  871. $response->send();
  872. }
  873. /**
  874. * Returns the URL of the current script, with modified query parameters.
  875. *
  876. * This function can be called by low-level scripts (such as install.php and
  877. * update.php) and returns the URL of the current script. Existing query
  878. * parameters are preserved by default, but new ones can optionally be merged
  879. * in.
  880. *
  881. * This function is used when the script must maintain certain query parameters
  882. * over multiple page requests in order to work correctly. In such cases (for
  883. * example, update.php, which requires the 'continue=1' parameter to remain in
  884. * the URL throughout the update process if there are any requirement warnings
  885. * that need to be bypassed), using this function to generate the URL for links
  886. * to the next steps of the script ensures that the links will work correctly.
  887. *
  888. * @param $query
  889. * (optional) An array of query parameters to merge in to the existing ones.
  890. *
  891. * @return
  892. * The URL of the current script, with query parameters modified by the
  893. * passed-in $query. The URL is not sanitized, so it still needs to be run
  894. * through \Drupal\Component\Utility\UrlHelper::filterBadProtocol() if it will be
  895. * used as an HTML attribute value.
  896. *
  897. * @see drupal_requirements_url()
  898. * @see Drupal\Component\Utility\UrlHelper::filterBadProtocol()
  899. */
  900. function drupal_current_script_url($query = []) {
  901. $uri = $_SERVER['SCRIPT_NAME'];
  902. $query = array_merge(UrlHelper::filterQueryParameters(\Drupal::request()->query->all()), $query);
  903. if (!empty($query)) {
  904. $uri .= '?' . UrlHelper::buildQuery($query);
  905. }
  906. return $uri;
  907. }
  908. /**
  909. * Returns a URL for proceeding to the next page after a requirements problem.
  910. *
  911. * This function can be called by low-level scripts (such as install.php and
  912. * update.php) and returns a URL that can be used to attempt to proceed to the
  913. * next step of the script.
  914. *
  915. * @param $severity
  916. * The severity of the requirements problem, as returned by
  917. * drupal_requirements_severity().
  918. *
  919. * @return
  920. * A URL for attempting to proceed to the next step of the script. The URL is
  921. * not sanitized, so it still needs to be run through
  922. * \Drupal\Component\Utility\UrlHelper::filterBadProtocol() if it will be used
  923. * as an HTML attribute value.
  924. *
  925. * @see drupal_current_script_url()
  926. * @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol()
  927. */
  928. function drupal_requirements_url($severity) {
  929. $query = [];
  930. // If there are no errors, only warnings, append 'continue=1' to the URL so
  931. // the user can bypass this screen on the next page load.
  932. if ($severity == REQUIREMENT_WARNING) {
  933. $query['continue'] = 1;
  934. }
  935. return drupal_current_script_url($query);
  936. }
  937. /**
  938. * Checks an installation profile's requirements.
  939. *
  940. * @param string $profile
  941. * Name of installation profile to check.
  942. *
  943. * @return array
  944. * Array of the installation profile's requirements.
  945. */
  946. function drupal_check_profile($profile) {
  947. $info = install_profile_info($profile);
  948. // Collect requirement testing results.
  949. $requirements = [];
  950. // Performs an ExtensionDiscovery scan as the system module is unavailable and
  951. // we don't yet know where all the modules are located.
  952. // @todo Remove as part of https://www.drupal.org/node/2186491
  953. $drupal_root = \Drupal::root();
  954. $module_list = (new ExtensionDiscovery($drupal_root))->scan('module');
  955. foreach ($info['install'] as $module) {
  956. // If the module is in the module list we know it exists and we can continue
  957. // including and registering it.
  958. // @see \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory()
  959. if (isset($module_list[$module])) {
  960. $function = $module . '_requirements';
  961. $module_path = $module_list[$module]->getPath();
  962. $install_file = "$drupal_root/$module_path/$module.install";
  963. if (is_file($install_file)) {
  964. require_once $install_file;
  965. }
  966. \Drupal::service('class_loader')->addPsr4('Drupal\\' . $module . '\\', \Drupal::root() . "/$module_path/src");
  967. if (function_exists($function)) {
  968. $requirements = array_merge($requirements, $function('install'));
  969. }
  970. }
  971. }
  972. // Add the profile requirements.
  973. $function = $profile . '_requirements';
  974. if (function_exists($function)) {
  975. $requirements = array_merge($requirements, $function('install'));
  976. }
  977. return $requirements;
  978. }
  979. /**
  980. * Extracts the highest severity from the requirements array.
  981. *
  982. * @param $requirements
  983. * An array of requirements, in the same format as is returned by
  984. * hook_requirements().
  985. *
  986. * @return
  987. * The highest severity in the array.
  988. */
  989. function drupal_requirements_severity(&$requirements) {
  990. $severity = REQUIREMENT_OK;
  991. foreach ($requirements as $requirement) {
  992. if (isset($requirement['severity'])) {
  993. $severity = max($severity, $requirement['severity']);
  994. }
  995. }
  996. return $severity;
  997. }
  998. /**
  999. * Checks a module's requirements.
  1000. *
  1001. * @param $module
  1002. * Machine name of module to check.
  1003. *
  1004. * @return
  1005. * TRUE or FALSE, depending on whether the requirements are met.
  1006. */
  1007. function drupal_check_module($module) {
  1008. module_load_install($module);
  1009. // Check requirements
  1010. $requirements = \Drupal::moduleHandler()->invoke($module, 'requirements', ['install']);
  1011. if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
  1012. // Print any error messages
  1013. foreach ($requirements as $requirement) {
  1014. if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
  1015. $message = $requirement['description'];
  1016. if (isset($requirement['value']) && $requirement['value']) {
  1017. $message = t('@requirements_message (Currently using @item version @version)', ['@requirements_message' => $requirement['description'], '@item' => $requirement['title'], '@version' => $requirement['value']]);
  1018. }
  1019. \Drupal::messenger()->addError($message);
  1020. }
  1021. }
  1022. return FALSE;
  1023. }
  1024. return TRUE;
  1025. }
  1026. /**
  1027. * Retrieves information about an installation profile from its .info.yml file.
  1028. *
  1029. * The information stored in a profile .info.yml file is similar to that stored
  1030. * in a normal Drupal module .info.yml file. For example:
  1031. * - name: The real name of the installation profile for display purposes.
  1032. * - description: A brief description of the profile.
  1033. * - dependencies: An array of shortnames of other modules that this install
  1034. * profile requires.
  1035. * - install: An array of shortname of other modules to install that are not
  1036. * required by this install profile.
  1037. *
  1038. * Additional, less commonly-used information that can appear in a
  1039. * profile.info.yml file but not in a normal Drupal module .info.yml file
  1040. * includes:
  1041. *
  1042. * - distribution: Existence of this key denotes that the installation profile
  1043. * is intended to be the only eligible choice in a distribution and will be
  1044. * auto-selected during installation, whereas the installation profile
  1045. * selection screen will be skipped. If more than one distribution profile is
  1046. * found then the first one discovered will be selected.
  1047. * The following subproperties may be set:
  1048. * - name: The name of the distribution that is being installed, to be shown
  1049. * throughout the installation process. If omitted,
  1050. * drupal_install_profile_distribution_name() defaults to 'Drupal'.
  1051. * - install: Optional parameters to override the installer:
  1052. * - theme: The machine name of a theme to use in the installer instead of
  1053. * Drupal's default installer theme.
  1054. * - finish_url: A destination to visit after the installation of the
  1055. * distribution is finished
  1056. *
  1057. * Note that this function does an expensive file system scan to get info file
  1058. * information for dependencies. If you only need information from the info
  1059. * file itself, use
  1060. * \Drupal::service('extension.list.profile')->getExtensionInfo().
  1061. *
  1062. * Example of .info.yml file:
  1063. * @code
  1064. * name: Minimal
  1065. * description: Start fresh, with only a few modules enabled.
  1066. * install:
  1067. * - block
  1068. * - dblog
  1069. * @endcode
  1070. *
  1071. * @param $profile
  1072. * Name of profile.
  1073. * @param $langcode
  1074. * Language code (if any).
  1075. *
  1076. * @return
  1077. * The info array.
  1078. */
  1079. function install_profile_info($profile, $langcode = 'en') {
  1080. static $cache = [];
  1081. if (!isset($cache[$profile][$langcode])) {
  1082. // Set defaults for module info.
  1083. $defaults = [
  1084. 'dependencies' => [],
  1085. 'install' => [],
  1086. 'themes' => ['stark'],
  1087. 'description' => '',
  1088. 'version' => NULL,
  1089. 'hidden' => FALSE,
  1090. 'php' => DRUPAL_MINIMUM_PHP,
  1091. 'config_install_path' => NULL,
  1092. ];
  1093. $profile_path = drupal_get_path('profile', $profile);
  1094. $info = \Drupal::service('info_parser')->parse("$profile_path/$profile.info.yml");
  1095. $info += $defaults;
  1096. $dependency_name_function = function ($dependency) {
  1097. return Dependency::createFromString($dependency)->getName();
  1098. };
  1099. // Convert dependencies in [project:module] format.
  1100. $info['dependencies'] = array_map($dependency_name_function, $info['dependencies']);
  1101. // Convert install key in [project:module] format.
  1102. $info['install'] = array_map($dependency_name_function, $info['install']);
  1103. // drupal_required_modules() includes the current profile as a dependency.
  1104. // Remove that dependency, since a module cannot depend on itself.
  1105. $required = array_diff(drupal_required_modules(), [$profile]);
  1106. $locale = !empty($langcode) && $langcode != 'en' ? ['locale'] : [];
  1107. // Merge dependencies, required modules and locale into install list and
  1108. // remove any duplicates.
  1109. $info['install'] = array_unique(array_merge($info['install'], $required, $info['dependencies'], $locale));
  1110. // If the profile has a config/sync directory use that to install drupal.
  1111. if (is_dir($profile_path . '/config/sync')) {
  1112. $info['config_install_path'] = $profile_path . '/config/sync';
  1113. }
  1114. $cache[$profile][$langcode] = $info;
  1115. }
  1116. return $cache[$profile][$langcode];
  1117. }
  1118. /**
  1119. * Returns a database installer object.
  1120. *
  1121. * Before calling this function it is important the database installer object
  1122. * is autoloadable. Database drivers provided by contributed modules are added
  1123. * to the autoloader in drupal_get_database_types() and Settings::initialize().
  1124. *
  1125. * @param $driver
  1126. * The name of the driver.
  1127. * @param string $namespace
  1128. * (optional) The database driver namespace.
  1129. *
  1130. * @return \Drupal\Core\Database\Install\Tasks
  1131. * A class defining the requirements and tasks for installing the database.
  1132. *
  1133. * @see drupal_get_database_types()
  1134. * @see \Drupal\Core\Site\Settings::initialize()
  1135. */
  1136. function db_installer_object($driver, $namespace = NULL) {
  1137. // We cannot use Database::getConnection->getDriverClass() here, because
  1138. // the connection object is not yet functional.
  1139. if ($namespace) {
  1140. $task_class = $namespace . "\\Install\\Tasks";
  1141. return new $task_class();
  1142. }
  1143. // Old Drupal 8 style contrib namespace.
  1144. $task_class = "Drupal\\Driver\\Database\\{$driver}\\Install\\Tasks";
  1145. if (class_exists($task_class)) {
  1146. return new $task_class();
  1147. }
  1148. else {
  1149. // Core provided driver.
  1150. $task_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Install\\Tasks";
  1151. return new $task_class();
  1152. }
  1153. }