update.inc 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  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. $updates = array_combine($updates, $updates);
  295. foreach (array_keys($updates) as $update) {
  296. if ($update == \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
  297. $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.';
  298. continue 2;
  299. }
  300. if ($update > $schema_version) {
  301. // The description for an update comes from its Doxygen.
  302. $func = new ReflectionFunction($module . '_update_' . $update);
  303. $description = str_replace(["\n", '*', '/'], '', $func->getDocComment());
  304. $ret[$module]['pending'][$update] = "$update - $description";
  305. if (!isset($ret[$module]['start'])) {
  306. $ret[$module]['start'] = $update;
  307. }
  308. }
  309. }
  310. if (!isset($ret[$module]['start']) && isset($ret[$module]['pending'])) {
  311. $ret[$module]['start'] = $schema_version;
  312. }
  313. }
  314. }
  315. if (empty($ret['system'])) {
  316. unset($ret['system']);
  317. }
  318. return $ret;
  319. }
  320. /**
  321. * Resolves dependencies in a set of module updates, and orders them correctly.
  322. *
  323. * This function receives a list of requested module updates and determines an
  324. * appropriate order to run them in such that all update dependencies are met.
  325. * Any updates whose dependencies cannot be met are included in the returned
  326. * array but have the key 'allowed' set to FALSE; the calling function should
  327. * take responsibility for ensuring that these updates are ultimately not
  328. * performed.
  329. *
  330. * In addition, the returned array also includes detailed information about the
  331. * dependency chain for each update, as provided by the depth-first search
  332. * algorithm in Drupal\Component\Graph\Graph::searchAndSort().
  333. *
  334. * @param $starting_updates
  335. * An array whose keys contain the names of modules with updates to be run
  336. * and whose values contain the number of the first requested update for that
  337. * module.
  338. *
  339. * @return
  340. * An array whose keys are the names of all update functions within the
  341. * provided modules that would need to be run in order to fulfill the
  342. * request, arranged in the order in which the update functions should be
  343. * run. (This includes the provided starting update for each module and all
  344. * subsequent updates that are available.) The values are themselves arrays
  345. * containing all the keys provided by the
  346. * Drupal\Component\Graph\Graph::searchAndSort() algorithm, which encode
  347. * detailed information about the dependency chain for this update function
  348. * (for example: 'paths', 'reverse_paths', 'weight', and 'component'), as
  349. * well as the following additional keys:
  350. * - 'allowed': A boolean which is TRUE when the update function's
  351. * dependencies are met, and FALSE otherwise. Calling functions should
  352. * inspect this value before running the update.
  353. * - 'missing_dependencies': An array containing the names of any other
  354. * update functions that are required by this one but that are unavailable
  355. * to be run. This array will be empty when 'allowed' is TRUE.
  356. * - 'module': The name of the module that this update function belongs to.
  357. * - 'number': The number of this update function within that module.
  358. *
  359. * @see \Drupal\Component\Graph\Graph::searchAndSort()
  360. */
  361. function update_resolve_dependencies($starting_updates) {
  362. // Obtain a dependency graph for the requested update functions.
  363. $update_functions = update_get_update_function_list($starting_updates);
  364. $graph = update_build_dependency_graph($update_functions);
  365. // Perform the depth-first search and sort on the results.
  366. $graph_object = new Graph($graph);
  367. $graph = $graph_object->searchAndSort();
  368. uasort($graph, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
  369. foreach ($graph as $function => &$data) {
  370. $module = $data['module'];
  371. $number = $data['number'];
  372. // If the update function is missing and has not yet been performed, mark
  373. // it and everything that ultimately depends on it as disallowed.
  374. if (update_is_missing($module, $number, $update_functions) && !update_already_performed($module, $number)) {
  375. $data['allowed'] = FALSE;
  376. foreach (array_keys($data['paths']) as $dependent) {
  377. $graph[$dependent]['allowed'] = FALSE;
  378. $graph[$dependent]['missing_dependencies'][] = $function;
  379. }
  380. }
  381. elseif (!isset($data['allowed'])) {
  382. $data['allowed'] = TRUE;
  383. $data['missing_dependencies'] = [];
  384. }
  385. // Now that we have finished processing this function, remove it from the
  386. // graph if it was not part of the original list. This ensures that we
  387. // never try to run any updates that were not specifically requested.
  388. if (!isset($update_functions[$module][$number])) {
  389. unset($graph[$function]);
  390. }
  391. }
  392. return $graph;
  393. }
  394. /**
  395. * Returns an organized list of update functions for a set of modules.
  396. *
  397. * @param $starting_updates
  398. * An array whose keys contain the names of modules and whose values contain
  399. * the number of the first requested update for that module.
  400. *
  401. * @return
  402. * An array containing all the update functions that should be run for each
  403. * module, including the provided starting update and all subsequent updates
  404. * that are available. The keys of the array contain the module names, and
  405. * each value is an ordered array of update functions, keyed by the update
  406. * number.
  407. *
  408. * @see update_resolve_dependencies()
  409. */
  410. function update_get_update_function_list($starting_updates) {
  411. // Go through each module and find all updates that we need (including the
  412. // first update that was requested and any updates that run after it).
  413. $update_functions = [];
  414. foreach ($starting_updates as $module => $version) {
  415. $update_functions[$module] = [];
  416. $updates = drupal_get_schema_versions($module);
  417. if ($updates !== FALSE) {
  418. $max_version = max($updates);
  419. if ($version <= $max_version) {
  420. foreach ($updates as $update) {
  421. if ($update >= $version) {
  422. $update_functions[$module][$update] = $module . '_update_' . $update;
  423. }
  424. }
  425. }
  426. }
  427. }
  428. return $update_functions;
  429. }
  430. /**
  431. * Constructs a graph which encodes the dependencies between module updates.
  432. *
  433. * This function returns an associative array which contains a "directed graph"
  434. * representation of the dependencies between a provided list of update
  435. * functions, as well as any outside update functions that they directly depend
  436. * on but that were not in the provided list. The vertices of the graph
  437. * represent the update functions themselves, and each edge represents a
  438. * requirement that the first update function needs to run before the second.
  439. * For example, consider this graph:
  440. *
  441. * system_update_8001 ---> system_update_8002 ---> system_update_8003
  442. *
  443. * Visually, this indicates that system_update_8001() must run before
  444. * system_update_8002(), which in turn must run before system_update_8003().
  445. *
  446. * The function takes into account standard dependencies within each module, as
  447. * shown above (i.e., the fact that each module's updates must run in numerical
  448. * order), but also finds any cross-module dependencies that are defined by
  449. * modules which implement hook_update_dependencies(), and builds them into the
  450. * graph as well.
  451. *
  452. * @param $update_functions
  453. * An organized array of update functions, in the format returned by
  454. * update_get_update_function_list().
  455. *
  456. * @return
  457. * A multidimensional array representing the dependency graph, suitable for
  458. * passing in to Drupal\Component\Graph\Graph::searchAndSort(), but with extra
  459. * information about each update function also included. Each array key
  460. * contains the name of an update function, including all update functions
  461. * from the provided list as well as any outside update functions which they
  462. * directly depend on. Each value is an associative array containing the
  463. * following keys:
  464. * - 'edges': A representation of any other update functions that immediately
  465. * depend on this one. See Drupal\Component\Graph\Graph::searchAndSort() for
  466. * more details on the format.
  467. * - 'module': The name of the module that this update function belongs to.
  468. * - 'number': The number of this update function within that module.
  469. *
  470. * @see \Drupal\Component\Graph\Graph::searchAndSort()
  471. * @see update_resolve_dependencies()
  472. */
  473. function update_build_dependency_graph($update_functions) {
  474. // Initialize an array that will define a directed graph representing the
  475. // dependencies between update functions.
  476. $graph = [];
  477. // Go through each update function and build an initial list of dependencies.
  478. foreach ($update_functions as $module => $functions) {
  479. $previous_function = NULL;
  480. foreach ($functions as $number => $function) {
  481. // Add an edge to the directed graph representing the fact that each
  482. // update function in a given module must run after the update that
  483. // numerically precedes it.
  484. if ($previous_function) {
  485. $graph[$previous_function]['edges'][$function] = TRUE;
  486. }
  487. $previous_function = $function;
  488. // Define the module and update number associated with this function.
  489. $graph[$function]['module'] = $module;
  490. $graph[$function]['number'] = $number;
  491. }
  492. }
  493. // Now add any explicit update dependencies declared by modules.
  494. $update_dependencies = update_retrieve_dependencies();
  495. foreach ($graph as $function => $data) {
  496. if (!empty($update_dependencies[$data['module']][$data['number']])) {
  497. foreach ($update_dependencies[$data['module']][$data['number']] as $module => $number) {
  498. $dependency = $module . '_update_' . $number;
  499. $graph[$dependency]['edges'][$function] = TRUE;
  500. $graph[$dependency]['module'] = $module;
  501. $graph[$dependency]['number'] = $number;
  502. }
  503. }
  504. }
  505. return $graph;
  506. }
  507. /**
  508. * Determines if a module update is missing or unavailable.
  509. *
  510. * @param $module
  511. * The name of the module.
  512. * @param $number
  513. * The number of the update within that module.
  514. * @param $update_functions
  515. * An organized array of update functions, in the format returned by
  516. * update_get_update_function_list(). This should represent all module
  517. * updates that are requested to run at the time this function is called.
  518. *
  519. * @return
  520. * TRUE if the provided module update is not installed or is not in the
  521. * provided list of updates to run; FALSE otherwise.
  522. */
  523. function update_is_missing($module, $number, $update_functions) {
  524. return !isset($update_functions[$module][$number]) || !function_exists($update_functions[$module][$number]);
  525. }
  526. /**
  527. * Determines if a module update has already been performed.
  528. *
  529. * @param $module
  530. * The name of the module.
  531. * @param $number
  532. * The number of the update within that module.
  533. *
  534. * @return
  535. * TRUE if the database schema indicates that the update has already been
  536. * performed; FALSE otherwise.
  537. */
  538. function update_already_performed($module, $number) {
  539. return $number <= drupal_get_installed_schema_version($module);
  540. }
  541. /**
  542. * Invokes hook_update_dependencies() in all installed modules.
  543. *
  544. * This function is similar to \Drupal::moduleHandler()->invokeAll(), with the
  545. * main difference that it does not require that a module be enabled to invoke
  546. * its hook, only that it be installed. This allows the update system to
  547. * properly perform updates even on modules that are currently disabled.
  548. *
  549. * @return
  550. * An array of return values obtained by merging the results of the
  551. * hook_update_dependencies() implementations in all installed modules.
  552. *
  553. * @see \Drupal\Core\Extension\ModuleHandlerInterface::invokeAll()
  554. * @see hook_update_dependencies()
  555. */
  556. function update_retrieve_dependencies() {
  557. $return = [];
  558. // Get a list of installed modules, arranged so that we invoke their hooks in
  559. // the same order that \Drupal::moduleHandler()->invokeAll() does.
  560. foreach (\Drupal::keyValue('system.schema')->getAll() as $module => $schema) {
  561. if ($schema == SCHEMA_UNINSTALLED) {
  562. // Nothing to upgrade.
  563. continue;
  564. }
  565. $function = $module . '_update_dependencies';
  566. // Ensure install file is loaded.
  567. module_load_install($module);
  568. if (function_exists($function)) {
  569. $updated_dependencies = $function();
  570. // Each implementation of hook_update_dependencies() returns a
  571. // multidimensional, associative array containing some keys that
  572. // represent module names (which are strings) and other keys that
  573. // represent update function numbers (which are integers). We cannot use
  574. // array_merge_recursive() to properly merge these results, since it
  575. // treats strings and integers differently. Therefore, we have to
  576. // explicitly loop through the expected array structure here and perform
  577. // the merge manually.
  578. if (isset($updated_dependencies) && is_array($updated_dependencies)) {
  579. foreach ($updated_dependencies as $module_name => $module_data) {
  580. foreach ($module_data as $update_version => $update_data) {
  581. foreach ($update_data as $module_dependency => $update_dependency) {
  582. // If there are redundant dependencies declared for the same
  583. // update function (so that it is declared to depend on more than
  584. // one update from a particular module), record the dependency on
  585. // the highest numbered update here, since that automatically
  586. // implies the previous ones. For example, if one module's
  587. // implementation of hook_update_dependencies() required this
  588. // ordering:
  589. //
  590. // system_update_8002 ---> user_update_8001
  591. //
  592. // but another module's implementation of the hook required this
  593. // one:
  594. //
  595. // system_update_8003 ---> user_update_8001
  596. //
  597. // we record the second one, since system_update_8002() is always
  598. // guaranteed to run before system_update_8003() anyway (within
  599. // an individual module, updates are always run in numerical
  600. // order).
  601. if (!isset($return[$module_name][$update_version][$module_dependency]) || $update_dependency > $return[$module_name][$update_version][$module_dependency]) {
  602. $return[$module_name][$update_version][$module_dependency] = $update_dependency;
  603. }
  604. }
  605. }
  606. }
  607. }
  608. }
  609. }
  610. return $return;
  611. }
  612. /**
  613. * Replace permissions during update.
  614. *
  615. * This function can replace one permission to several or even delete an old
  616. * one.
  617. *
  618. * @param array $replace
  619. * An associative array. The keys are the old permissions the values are lists
  620. * of new permissions. If the list is an empty array, the old permission is
  621. * removed.
  622. */
  623. function update_replace_permissions($replace) {
  624. $prefix = 'user.role.';
  625. $cut = strlen($prefix);
  626. $role_names = \Drupal::service('config.storage')->listAll($prefix);
  627. foreach ($role_names as $role_name) {
  628. $rid = substr($role_name, $cut);
  629. $config = \Drupal::config("user.role.$rid");
  630. $permissions = $config->get('permissions') ?: [];
  631. foreach ($replace as $old_permission => $new_permissions) {
  632. if (($index = array_search($old_permission, $permissions)) !== FALSE) {
  633. unset($permissions[$index]);
  634. $permissions = array_unique(array_merge($permissions, $new_permissions));
  635. }
  636. }
  637. $config
  638. ->set('permissions', $permissions)
  639. ->save();
  640. }
  641. }