update.inc 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. <?php
  2. /**
  3. * @file
  4. * Drupal database update API.
  5. *
  6. * This file contains functions to perform database updates for a Drupal
  7. * installation. It is included and used extensively by update.php.
  8. */
  9. use Drupal\Component\Graph\Graph;
  10. use Drupal\Core\Utility\Error;
  11. /**
  12. * Disables any extensions that are incompatible with the current core version.
  13. */
  14. function update_fix_compatibility() {
  15. $extension_config = \Drupal::configFactory()->getEditable('core.extension');
  16. $save = FALSE;
  17. foreach (['module', 'theme'] as $type) {
  18. foreach ($extension_config->get($type) as $name => $weight) {
  19. if (update_check_incompatibility($name, $type)) {
  20. $extension_config->clear("$type.$name");
  21. $save = TRUE;
  22. }
  23. }
  24. }
  25. if ($save) {
  26. $extension_config->set('module', module_config_sort($extension_config->get('module')));
  27. $extension_config->save();
  28. }
  29. }
  30. /**
  31. * Tests the compatibility of a module or theme.
  32. */
  33. function update_check_incompatibility($name, $type = 'module') {
  34. static $themes, $modules;
  35. // Store values of expensive functions for future use.
  36. if (empty($themes) || empty($modules)) {
  37. // We need to do a full rebuild here to make sure the database reflects any
  38. // code changes that were made in the filesystem before the update script
  39. // was initiated.
  40. $themes = \Drupal::service('theme_handler')->rebuildThemeData();
  41. $modules = system_rebuild_module_data();
  42. }
  43. if ($type == 'module' && isset($modules[$name])) {
  44. $file = $modules[$name];
  45. }
  46. elseif ($type == 'theme' && isset($themes[$name])) {
  47. $file = $themes[$name];
  48. }
  49. if (!isset($file)
  50. || !isset($file->info['core'])
  51. || $file->info['core'] != \Drupal::CORE_COMPATIBILITY
  52. || version_compare(phpversion(), $file->info['php']) < 0) {
  53. return TRUE;
  54. }
  55. return FALSE;
  56. }
  57. /**
  58. * Returns whether the minimum schema requirement has been satisfied.
  59. *
  60. * @return array
  61. * A requirements info array.
  62. */
  63. function update_system_schema_requirements() {
  64. $requirements = [];
  65. $system_schema = drupal_get_installed_schema_version('system');
  66. $requirements['minimum schema']['title'] = 'Minimum schema version';
  67. if ($system_schema >= \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
  68. $requirements['minimum schema'] += [
  69. 'value' => 'The installed schema version meets the minimum.',
  70. 'description' => 'Schema version: ' . $system_schema,
  71. ];
  72. }
  73. else {
  74. $requirements['minimum schema'] += [
  75. 'value' => 'The installed schema version does not meet the minimum.',
  76. 'severity' => REQUIREMENT_ERROR,
  77. 'description' => 'Your system schema version is ' . $system_schema . '. Updating directly from a schema version prior to 8000 is not supported. You must upgrade your site to Drupal 8 first, see https://www.drupal.org/docs/8/upgrade.',
  78. ];
  79. }
  80. return $requirements;
  81. }
  82. /**
  83. * Checks update requirements and reports errors and (optionally) warnings.
  84. */
  85. function update_check_requirements() {
  86. // Check requirements of all loaded modules.
  87. $requirements = \Drupal::moduleHandler()->invokeAll('requirements', ['update']);
  88. $requirements += update_system_schema_requirements();
  89. return $requirements;
  90. }
  91. /**
  92. * Forces a module to a given schema version.
  93. *
  94. * This function is rarely necessary.
  95. *
  96. * @param string $module
  97. * Name of the module.
  98. * @param string $schema_version
  99. * The schema version the module should be set to.
  100. */
  101. function update_set_schema($module, $schema_version) {
  102. \Drupal::keyValue('system.schema')->set($module, $schema_version);
  103. // system_list_reset() is in module.inc but that would only be available
  104. // once the variable bootstrap is done.
  105. require_once __DIR__ . '/module.inc';
  106. system_list_reset();
  107. }
  108. /**
  109. * Implements callback_batch_operation().
  110. *
  111. * Performs one update and stores the results for display on the results page.
  112. *
  113. * If an update function completes successfully, it should return a message
  114. * as a string indicating success, for example:
  115. * @code
  116. * return t('New index added successfully.');
  117. * @endcode
  118. *
  119. * Alternatively, it may return nothing. In that case, no message
  120. * will be displayed at all.
  121. *
  122. * If it fails for whatever reason, it should throw an instance of
  123. * Drupal\Core\Utility\UpdateException with an appropriate error message, for
  124. * example:
  125. * @code
  126. * use Drupal\Core\Utility\UpdateException;
  127. * throw new UpdateException(t('Description of what went wrong'));
  128. * @endcode
  129. *
  130. * If an exception is thrown, the current update and all updates that depend on
  131. * it will be aborted. The schema version will not be updated in this case, and
  132. * all the aborted updates will continue to appear on update.php as updates
  133. * that have not yet been run.
  134. *
  135. * If an update function needs to be re-run as part of a batch process, it
  136. * should accept the $sandbox array by reference as its first parameter
  137. * and set the #finished property to the percentage completed that it is, as a
  138. * fraction of 1.
  139. *
  140. * @param $module
  141. * The module whose update will be run.
  142. * @param $number
  143. * The update number to run.
  144. * @param $dependency_map
  145. * An array whose keys are the names of all update functions that will be
  146. * performed during this batch process, and whose values are arrays of other
  147. * update functions that each one depends on.
  148. * @param $context
  149. * The batch context array.
  150. *
  151. * @see update_resolve_dependencies()
  152. */
  153. function update_do_one($module, $number, $dependency_map, &$context) {
  154. $function = $module . '_update_' . $number;
  155. // If this update was aborted in a previous step, or has a dependency that
  156. // was aborted in a previous step, go no further.
  157. if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, [$function]))) {
  158. return;
  159. }
  160. $ret = [];
  161. if (function_exists($function)) {
  162. try {
  163. $ret['results']['query'] = $function($context['sandbox']);
  164. $ret['results']['success'] = TRUE;
  165. }
  166. // @TODO We may want to do different error handling for different
  167. // exception types, but for now we'll just log the exception and
  168. // return the message for printing.
  169. // @see https://www.drupal.org/node/2564311
  170. catch (Exception $e) {
  171. watchdog_exception('update', $e);
  172. $variables = Error::decodeException($e);
  173. unset($variables['backtrace']);
  174. $ret['#abort'] = ['success' => FALSE, 'query' => t('%type: @message in %function (line %line of %file).', $variables)];
  175. }
  176. }
  177. if (isset($context['sandbox']['#finished'])) {
  178. $context['finished'] = $context['sandbox']['#finished'];
  179. unset($context['sandbox']['#finished']);
  180. }
  181. if (!isset($context['results'][$module])) {
  182. $context['results'][$module] = [];
  183. }
  184. if (!isset($context['results'][$module][$number])) {
  185. $context['results'][$module][$number] = [];
  186. }
  187. $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);
  188. if (!empty($ret['#abort'])) {
  189. // Record this function in the list of updates that were aborted.
  190. $context['results']['#abort'][] = $function;
  191. }
  192. // Record the schema update if it was completed successfully.
  193. if ($context['finished'] == 1 && empty($ret['#abort'])) {
  194. drupal_set_installed_schema_version($module, $number);
  195. }
  196. $context['message'] = t('Updating @module', ['@module' => $module]);
  197. }
  198. /**
  199. * Executes a single hook_post_update_NAME().
  200. *
  201. * @param string $function
  202. * The function name, that should be executed.
  203. * @param array $context
  204. * The batch context array.
  205. */
  206. function update_invoke_post_update($function, &$context) {
  207. $ret = [];
  208. // If this update was aborted in a previous step, or has a dependency that was
  209. // aborted in a previous step, go no further.
  210. if (!empty($context['results']['#abort'])) {
  211. return;
  212. }
  213. list($module, $name) = explode('_post_update_', $function, 2);
  214. module_load_include('php', $module, $module . '.post_update');
  215. if (function_exists($function)) {
  216. try {
  217. $ret['results']['query'] = $function($context['sandbox']);
  218. $ret['results']['success'] = TRUE;
  219. if (!isset($context['sandbox']['#finished']) || (isset($context['sandbox']['#finished']) && $context['sandbox']['#finished'] >= 1)) {
  220. \Drupal::service('update.post_update_registry')->registerInvokedUpdates([$function]);
  221. }
  222. }
  223. // @TODO We may want to do different error handling for different exception
  224. // types, but for now we'll just log the exception and return the message
  225. // for printing.
  226. // @see https://www.drupal.org/node/2564311
  227. catch (Exception $e) {
  228. watchdog_exception('update', $e);
  229. $variables = Error::decodeException($e);
  230. unset($variables['backtrace']);
  231. $ret['#abort'] = [
  232. 'success' => FALSE,
  233. 'query' => t('%type: @message in %function (line %line of %file).', $variables),
  234. ];
  235. }
  236. }
  237. if (isset($context['sandbox']['#finished'])) {
  238. $context['finished'] = $context['sandbox']['#finished'];
  239. unset($context['sandbox']['#finished']);
  240. }
  241. if (!isset($context['results'][$module][$name])) {
  242. $context['results'][$module][$name] = [];
  243. }
  244. $context['results'][$module][$name] = array_merge($context['results'][$module][$name], $ret);
  245. if (!empty($ret['#abort'])) {
  246. // Record this function in the list of updates that were aborted.
  247. $context['results']['#abort'][] = $function;
  248. }
  249. $context['message'] = t('Post updating @module', ['@module' => $module]);
  250. }
  251. /**
  252. * Returns a list of all the pending database updates.
  253. *
  254. * @return
  255. * An associative array keyed by module name which contains all information
  256. * about database updates that need to be run, and any updates that are not
  257. * going to proceed due to missing requirements. The system module will
  258. * always be listed first.
  259. *
  260. * The subarray for each module can contain the following keys:
  261. * - start: The starting update that is to be processed. If this does not
  262. * exist then do not process any updates for this module as there are
  263. * other requirements that need to be resolved.
  264. * - warning: Any warnings about why this module can not be updated.
  265. * - pending: An array of all the pending updates for the module including
  266. * the update number and the description from source code comment for
  267. * each update function. This array is keyed by the update number.
  268. */
  269. function update_get_update_list() {
  270. // Make sure that the system module is first in the list of updates.
  271. $ret = ['system' => []];
  272. $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
  273. foreach ($modules as $module => $schema_version) {
  274. // Skip uninstalled and incompatible modules.
  275. if ($schema_version == SCHEMA_UNINSTALLED || update_check_incompatibility($module)) {
  276. continue;
  277. }
  278. // Display a requirements error if the user somehow has a schema version
  279. // from the previous Drupal major version.
  280. if ($schema_version < \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
  281. $ret[$module]['warning'] = '<em>' . $module . '</em> module cannot be updated. Its schema version is ' . $schema_version . ', which is from an earlier major release of Drupal. You will need to <a href="https://www.drupal.org/node/2127611">migrate the data for this module</a> instead.';
  282. continue;
  283. }
  284. // Otherwise, get the list of updates defined by this module.
  285. $updates = drupal_get_schema_versions($module);
  286. if ($updates !== FALSE) {
  287. // \Drupal::moduleHandler()->invoke() returns NULL for non-existing hooks,
  288. // so if no updates are removed, it will == 0.
  289. $last_removed = \Drupal::moduleHandler()->invoke($module, 'update_last_removed');
  290. if ($schema_version < $last_removed) {
  291. $ret[$module]['warning'] = '<em>' . $module . '</em> module cannot be updated. Its schema version is ' . $schema_version . '. Updates up to and including ' . $last_removed . ' have been removed in this release. In order to update <em>' . $module . '</em> module, you will first <a href="https://www.drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.';
  292. continue;
  293. }
  294. foreach ($updates as $update) {
  295. if ($update == \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
  296. $ret[$module]['warning'] = '<em>' . $module . '</em> module cannot be updated. It contains an update numbered as ' . \Drupal::CORE_MINIMUM_SCHEMA_VERSION . ' which is reserved for the earliest installation of a module in Drupal ' . \Drupal::CORE_COMPATIBILITY . ', before any updates. In order to update <em>' . $module . '</em> module, you will need to install a version of the module with valid updates.';
  297. continue 2;
  298. }
  299. if ($update > $schema_version) {
  300. // The description for an update comes from its Doxygen.
  301. $func = new ReflectionFunction($module . '_update_' . $update);
  302. $description = str_replace(["\n", '*', '/'], '', $func->getDocComment());
  303. $ret[$module]['pending'][$update] = "$update - $description";
  304. if (!isset($ret[$module]['start'])) {
  305. $ret[$module]['start'] = $update;
  306. }
  307. }
  308. }
  309. if (!isset($ret[$module]['start']) && isset($ret[$module]['pending'])) {
  310. $ret[$module]['start'] = $schema_version;
  311. }
  312. }
  313. }
  314. if (empty($ret['system'])) {
  315. unset($ret['system']);
  316. }
  317. return $ret;
  318. }
  319. /**
  320. * Resolves dependencies in a set of module updates, and orders them correctly.
  321. *
  322. * This function receives a list of requested module updates and determines an
  323. * appropriate order to run them in such that all update dependencies are met.
  324. * Any updates whose dependencies cannot be met are included in the returned
  325. * array but have the key 'allowed' set to FALSE; the calling function should
  326. * take responsibility for ensuring that these updates are ultimately not
  327. * performed.
  328. *
  329. * In addition, the returned array also includes detailed information about the
  330. * dependency chain for each update, as provided by the depth-first search
  331. * algorithm in Drupal\Component\Graph\Graph::searchAndSort().
  332. *
  333. * @param $starting_updates
  334. * An array whose keys contain the names of modules with updates to be run
  335. * and whose values contain the number of the first requested update for that
  336. * module.
  337. *
  338. * @return
  339. * An array whose keys are the names of all update functions within the
  340. * provided modules that would need to be run in order to fulfill the
  341. * request, arranged in the order in which the update functions should be
  342. * run. (This includes the provided starting update for each module and all
  343. * subsequent updates that are available.) The values are themselves arrays
  344. * containing all the keys provided by the
  345. * Drupal\Component\Graph\Graph::searchAndSort() algorithm, which encode
  346. * detailed information about the dependency chain for this update function
  347. * (for example: 'paths', 'reverse_paths', 'weight', and 'component'), as
  348. * well as the following additional keys:
  349. * - 'allowed': A boolean which is TRUE when the update function's
  350. * dependencies are met, and FALSE otherwise. Calling functions should
  351. * inspect this value before running the update.
  352. * - 'missing_dependencies': An array containing the names of any other
  353. * update functions that are required by this one but that are unavailable
  354. * to be run. This array will be empty when 'allowed' is TRUE.
  355. * - 'module': The name of the module that this update function belongs to.
  356. * - 'number': The number of this update function within that module.
  357. *
  358. * @see \Drupal\Component\Graph\Graph::searchAndSort()
  359. */
  360. function update_resolve_dependencies($starting_updates) {
  361. // Obtain a dependency graph for the requested update functions.
  362. $update_functions = update_get_update_function_list($starting_updates);
  363. $graph = update_build_dependency_graph($update_functions);
  364. // Perform the depth-first search and sort on the results.
  365. $graph_object = new Graph($graph);
  366. $graph = $graph_object->searchAndSort();
  367. uasort($graph, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
  368. foreach ($graph as $function => &$data) {
  369. $module = $data['module'];
  370. $number = $data['number'];
  371. // If the update function is missing and has not yet been performed, mark
  372. // it and everything that ultimately depends on it as disallowed.
  373. if (update_is_missing($module, $number, $update_functions) && !update_already_performed($module, $number)) {
  374. $data['allowed'] = FALSE;
  375. foreach (array_keys($data['paths']) as $dependent) {
  376. $graph[$dependent]['allowed'] = FALSE;
  377. $graph[$dependent]['missing_dependencies'][] = $function;
  378. }
  379. }
  380. elseif (!isset($data['allowed'])) {
  381. $data['allowed'] = TRUE;
  382. $data['missing_dependencies'] = [];
  383. }
  384. // Now that we have finished processing this function, remove it from the
  385. // graph if it was not part of the original list. This ensures that we
  386. // never try to run any updates that were not specifically requested.
  387. if (!isset($update_functions[$module][$number])) {
  388. unset($graph[$function]);
  389. }
  390. }
  391. return $graph;
  392. }
  393. /**
  394. * Returns an organized list of update functions for a set of modules.
  395. *
  396. * @param $starting_updates
  397. * An array whose keys contain the names of modules and whose values contain
  398. * the number of the first requested update for that module.
  399. *
  400. * @return
  401. * An array containing all the update functions that should be run for each
  402. * module, including the provided starting update and all subsequent updates
  403. * that are available. The keys of the array contain the module names, and
  404. * each value is an ordered array of update functions, keyed by the update
  405. * number.
  406. *
  407. * @see update_resolve_dependencies()
  408. */
  409. function update_get_update_function_list($starting_updates) {
  410. // Go through each module and find all updates that we need (including the
  411. // first update that was requested and any updates that run after it).
  412. $update_functions = [];
  413. foreach ($starting_updates as $module => $version) {
  414. $update_functions[$module] = [];
  415. $updates = drupal_get_schema_versions($module);
  416. if ($updates !== FALSE) {
  417. $max_version = max($updates);
  418. if ($version <= $max_version) {
  419. foreach ($updates as $update) {
  420. if ($update >= $version) {
  421. $update_functions[$module][$update] = $module . '_update_' . $update;
  422. }
  423. }
  424. }
  425. }
  426. }
  427. return $update_functions;
  428. }
  429. /**
  430. * Constructs a graph which encodes the dependencies between module updates.
  431. *
  432. * This function returns an associative array which contains a "directed graph"
  433. * representation of the dependencies between a provided list of update
  434. * functions, as well as any outside update functions that they directly depend
  435. * on but that were not in the provided list. The vertices of the graph
  436. * represent the update functions themselves, and each edge represents a
  437. * requirement that the first update function needs to run before the second.
  438. * For example, consider this graph:
  439. *
  440. * system_update_8001 ---> system_update_8002 ---> system_update_8003
  441. *
  442. * Visually, this indicates that system_update_8001() must run before
  443. * system_update_8002(), which in turn must run before system_update_8003().
  444. *
  445. * The function takes into account standard dependencies within each module, as
  446. * shown above (i.e., the fact that each module's updates must run in numerical
  447. * order), but also finds any cross-module dependencies that are defined by
  448. * modules which implement hook_update_dependencies(), and builds them into the
  449. * graph as well.
  450. *
  451. * @param $update_functions
  452. * An organized array of update functions, in the format returned by
  453. * update_get_update_function_list().
  454. *
  455. * @return
  456. * A multidimensional array representing the dependency graph, suitable for
  457. * passing in to Drupal\Component\Graph\Graph::searchAndSort(), but with extra
  458. * information about each update function also included. Each array key
  459. * contains the name of an update function, including all update functions
  460. * from the provided list as well as any outside update functions which they
  461. * directly depend on. Each value is an associative array containing the
  462. * following keys:
  463. * - 'edges': A representation of any other update functions that immediately
  464. * depend on this one. See Drupal\Component\Graph\Graph::searchAndSort() for
  465. * more details on the format.
  466. * - 'module': The name of the module that this update function belongs to.
  467. * - 'number': The number of this update function within that module.
  468. *
  469. * @see \Drupal\Component\Graph\Graph::searchAndSort()
  470. * @see update_resolve_dependencies()
  471. */
  472. function update_build_dependency_graph($update_functions) {
  473. // Initialize an array that will define a directed graph representing the
  474. // dependencies between update functions.
  475. $graph = [];
  476. // Go through each update function and build an initial list of dependencies.
  477. foreach ($update_functions as $module => $functions) {
  478. $previous_function = NULL;
  479. foreach ($functions as $number => $function) {
  480. // Add an edge to the directed graph representing the fact that each
  481. // update function in a given module must run after the update that
  482. // numerically precedes it.
  483. if ($previous_function) {
  484. $graph[$previous_function]['edges'][$function] = TRUE;
  485. }
  486. $previous_function = $function;
  487. // Define the module and update number associated with this function.
  488. $graph[$function]['module'] = $module;
  489. $graph[$function]['number'] = $number;
  490. }
  491. }
  492. // Now add any explicit update dependencies declared by modules.
  493. $update_dependencies = update_retrieve_dependencies();
  494. foreach ($graph as $function => $data) {
  495. if (!empty($update_dependencies[$data['module']][$data['number']])) {
  496. foreach ($update_dependencies[$data['module']][$data['number']] as $module => $number) {
  497. $dependency = $module . '_update_' . $number;
  498. $graph[$dependency]['edges'][$function] = TRUE;
  499. $graph[$dependency]['module'] = $module;
  500. $graph[$dependency]['number'] = $number;
  501. }
  502. }
  503. }
  504. return $graph;
  505. }
  506. /**
  507. * Determines if a module update is missing or unavailable.
  508. *
  509. * @param $module
  510. * The name of the module.
  511. * @param $number
  512. * The number of the update within that module.
  513. * @param $update_functions
  514. * An organized array of update functions, in the format returned by
  515. * update_get_update_function_list(). This should represent all module
  516. * updates that are requested to run at the time this function is called.
  517. *
  518. * @return
  519. * TRUE if the provided module update is not installed or is not in the
  520. * provided list of updates to run; FALSE otherwise.
  521. */
  522. function update_is_missing($module, $number, $update_functions) {
  523. return !isset($update_functions[$module][$number]) || !function_exists($update_functions[$module][$number]);
  524. }
  525. /**
  526. * Determines if a module update has already been performed.
  527. *
  528. * @param $module
  529. * The name of the module.
  530. * @param $number
  531. * The number of the update within that module.
  532. *
  533. * @return
  534. * TRUE if the database schema indicates that the update has already been
  535. * performed; FALSE otherwise.
  536. */
  537. function update_already_performed($module, $number) {
  538. return $number <= drupal_get_installed_schema_version($module);
  539. }
  540. /**
  541. * Invokes hook_update_dependencies() in all installed modules.
  542. *
  543. * This function is similar to \Drupal::moduleHandler()->invokeAll(), with the
  544. * main difference that it does not require that a module be enabled to invoke
  545. * its hook, only that it be installed. This allows the update system to
  546. * properly perform updates even on modules that are currently disabled.
  547. *
  548. * @return
  549. * An array of return values obtained by merging the results of the
  550. * hook_update_dependencies() implementations in all installed modules.
  551. *
  552. * @see \Drupal\Core\Extension\ModuleHandlerInterface::invokeAll()
  553. * @see hook_update_dependencies()
  554. */
  555. function update_retrieve_dependencies() {
  556. $return = [];
  557. // Get a list of installed modules, arranged so that we invoke their hooks in
  558. // the same order that \Drupal::moduleHandler()->invokeAll() does.
  559. foreach (\Drupal::keyValue('system.schema')->getAll() as $module => $schema) {
  560. if ($schema == SCHEMA_UNINSTALLED) {
  561. // Nothing to upgrade.
  562. continue;
  563. }
  564. $function = $module . '_update_dependencies';
  565. // Ensure install file is loaded.
  566. module_load_install($module);
  567. if (function_exists($function)) {
  568. $updated_dependencies = $function();
  569. // Each implementation of hook_update_dependencies() returns a
  570. // multidimensional, associative array containing some keys that
  571. // represent module names (which are strings) and other keys that
  572. // represent update function numbers (which are integers). We cannot use
  573. // array_merge_recursive() to properly merge these results, since it
  574. // treats strings and integers differently. Therefore, we have to
  575. // explicitly loop through the expected array structure here and perform
  576. // the merge manually.
  577. if (isset($updated_dependencies) && is_array($updated_dependencies)) {
  578. foreach ($updated_dependencies as $module_name => $module_data) {
  579. foreach ($module_data as $update_version => $update_data) {
  580. foreach ($update_data as $module_dependency => $update_dependency) {
  581. // If there are redundant dependencies declared for the same
  582. // update function (so that it is declared to depend on more than
  583. // one update from a particular module), record the dependency on
  584. // the highest numbered update here, since that automatically
  585. // implies the previous ones. For example, if one module's
  586. // implementation of hook_update_dependencies() required this
  587. // ordering:
  588. //
  589. // system_update_8002 ---> user_update_8001
  590. //
  591. // but another module's implementation of the hook required this
  592. // one:
  593. //
  594. // system_update_8003 ---> user_update_8001
  595. //
  596. // we record the second one, since system_update_8002() is always
  597. // guaranteed to run before system_update_8003() anyway (within
  598. // an individual module, updates are always run in numerical
  599. // order).
  600. if (!isset($return[$module_name][$update_version][$module_dependency]) || $update_dependency > $return[$module_name][$update_version][$module_dependency]) {
  601. $return[$module_name][$update_version][$module_dependency] = $update_dependency;
  602. }
  603. }
  604. }
  605. }
  606. }
  607. }
  608. }
  609. return $return;
  610. }
  611. /**
  612. * Replace permissions during update.
  613. *
  614. * This function can replace one permission to several or even delete an old
  615. * one.
  616. *
  617. * @param array $replace
  618. * An associative array. The keys are the old permissions the values are lists
  619. * of new permissions. If the list is an empty array, the old permission is
  620. * removed.
  621. */
  622. function update_replace_permissions($replace) {
  623. $prefix = 'user.role.';
  624. $cut = strlen($prefix);
  625. $role_names = \Drupal::service('config.storage')->listAll($prefix);
  626. foreach ($role_names as $role_name) {
  627. $rid = substr($role_name, $cut);
  628. $config = \Drupal::config("user.role.$rid");
  629. $permissions = $config->get('permissions') ?: [];
  630. foreach ($replace as $old_permission => $new_permissions) {
  631. if (($index = array_search($old_permission, $permissions)) !== FALSE) {
  632. unset($permissions[$index]);
  633. $permissions = array_unique(array_merge($permissions, $new_permissions));
  634. }
  635. }
  636. $config
  637. ->set('permissions', $permissions)
  638. ->save();
  639. }
  640. }