diff --git a/core/includes/update.inc b/core/includes/update.inc index 70edeaf..0869f1f 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -219,6 +219,48 @@ function update_do_one($module, $number, $dependency_map, &$context) { } /** + * Executes a single hook_update_after_$name(). + */ +function update_after_do_one($function, &$context) { + $ret = []; + list($module, ) = explode('_update_after_', $function); + module_load_include('inc', $module, $module . '.update_after'); + if (function_exists($function)) { + try { + $ret['results']['query'] = $function($context['sandbox']); + $ret['results']['success'] = TRUE; + } + // @TODO We may want to do different error handling for different + // exception types, but for now we'll just log the exception and + // return the message for printing. + catch (Exception $e) { + watchdog_exception('update', $e); + + $variables = Error::decodeException($e); + unset($variables['backtrace']); + $ret['#abort'] = [ + 'success' => FALSE, + 'query' => t('%type: @message in %function (line %line of %file).', $variables) + ]; + } + } + + if (isset($context['sandbox']['#finished'])) { + $context['finished'] = $context['sandbox']['#finished']; + unset($context['sandbox']['#finished']); + } + + $context['results'][$function] = $ret; + + if (!empty($ret['#abort'])) { + // Record this function in the list of updates that were aborted. + $context['results']['#abort'][] = $function; + } + + $context['message'] = 'Updating content after: ' . Html::escape($function); +} + +/** * Returns a list of all the pending database updates. * * @return diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php index 98067f6..5ce0f7b 100644 --- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php +++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php @@ -95,6 +95,12 @@ class ExtensionDiscovery { * * @param string $root * The app root. + * @param bool $use_file_cache + * Whether file cache should be used. + * @param string[] $profile_directories + * The available profile directories + * @param string $site_path + * The path to the site. */ public function __construct($root, $use_file_cache = TRUE, $profile_directories = NULL, $site_path = NULL) { $this->root = $root; diff --git a/core/lib/Drupal/Core/Update/UpdateRegistry.php b/core/lib/Drupal/Core/Update/UpdateRegistry.php index 0a7e90d..cf4bcc9 100644 --- a/core/lib/Drupal/Core/Update/UpdateRegistry.php +++ b/core/lib/Drupal/Core/Update/UpdateRegistry.php @@ -16,7 +16,7 @@ */ class UpdateRegistry { - protected $updateName = 'update_data'; + protected $updateName = 'update_after'; /** * The app root. @@ -44,11 +44,19 @@ class UpdateRegistry { */ protected $keyValue; - public function __construct($root, $site_path, array $enabled_modules, KeyValueStoreInterface $key_value) { + /** + * Should we respect update functions in tests. + * + * @var bool|null + */ + protected $includeTests = NULL; + + public function __construct($root, $site_path, array $enabled_modules, KeyValueStoreInterface $key_value, $include_tests = NULL) { $this->root = $root; $this->sitePath = $site_path; $this->enabledModules = $enabled_modules; $this->keyValue = $key_value; + $this->includeTests = $include_tests; } /** @@ -85,7 +93,7 @@ public function getMissingUpdateFunctions() { // Scan the module list. // We don't support install profiles at that point? $extension_discovery = new ExtensionDiscovery($this->root, FALSE, [], $this->sitePath); - $module_extensions = $extension_discovery->scan('module', FALSE); + $module_extensions = $extension_discovery->scan('module'); $this->loadUpdateFiles($module_extensions); @@ -102,7 +110,7 @@ protected function loadUpdateFiles(array $module_extensions) { // Load all the {$this->updateName}.inc files. foreach ($this->enabledModules as $module) { if (isset($module_extensions[$module])) { - $this->loadUpdateEarlyFile($module_extensions[$module]); + $this->loadUpdateFile($module_extensions[$module]); } } } @@ -112,7 +120,7 @@ protected function loadUpdateFiles(array $module_extensions) { * * @param \Drupal\Core\Extension\Extension $module */ - protected function loadUpdateEarlyFile(Extension $module) { + protected function loadUpdateFile(Extension $module) { $filename = $this->root . '/' . $module->getPath() . '/' . $module->getName() . ".{$this->updateName}.inc"; if (file_exists($filename)) { include_once $filename; diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php index fa8104d..fce7819 100644 --- a/core/modules/system/src/Controller/DbUpdateController.php +++ b/core/modules/system/src/Controller/DbUpdateController.php @@ -15,6 +15,7 @@ use Drupal\Core\Session\AccountInterface; use Drupal\Core\Site\Settings; use Drupal\Core\State\StateInterface; +use Drupal\Core\Update\UpdateRegistry; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Response; @@ -575,6 +576,17 @@ protected function triggerBatch(Request $request) { } } + // Now we rebuild all caches and after that execute the hook_update_after() + // functions. + $operations[] = ['drupal_flush_all_caches', []]; + + $after_update_registry = $this->getUpdateAfterRegistry(); + $after_updates = $after_update_registry->getMissingUpdateFunctions(); + + foreach ($after_updates as $function) { + $operations[] = ['update_after_do_one', [$function]]; + } + $batch['operations'] = $operations; $batch += array( 'title' => $this->t('Updating'), @@ -588,6 +600,10 @@ protected function triggerBatch(Request $request) { return batch_process(Url::fromUri('base://results'), Url::fromUri('base://start')); } + protected function getUpdateAfterRegistry() { + return new UpdateRegistry($this->root, \Drupal::service('site.path'), array_keys($this->moduleHandler()->getModuleList()), \Drupal::keyValue('update_after')); + } + /** * Finishes the update process and stores the results for eventual display. * diff --git a/core/modules/system/src/Tests/Update/UpdateAfterTest.php b/core/modules/system/src/Tests/Update/UpdateAfterTest.php index 1cab15d..0b85fcb 100644 --- a/core/modules/system/src/Tests/Update/UpdateAfterTest.php +++ b/core/modules/system/src/Tests/Update/UpdateAfterTest.php @@ -24,4 +24,15 @@ protected function setDatabaseDumpFiles() { ]; } + public function testUpdateAfter() { + $this->runUpdates(); + + $this->assertIdentical([ + 'update_test_after_update_after_first', + 'update_test_after_update_after_second', + 'update_test_after_update_after_test1', + 'update_test_after_update_after_test0', + ], \Drupal::state()->get('update_test_after_execution', [])); + } + } diff --git a/core/modules/system/tests/fixtures/update/drupal-8.update-test-after-enabled.php b/core/modules/system/tests/fixtures/update/drupal-8.update-test-after-enabled.php index a4abe2d..681a5fb 100644 --- a/core/modules/system/tests/fixtures/update/drupal-8.update-test-after-enabled.php +++ b/core/modules/system/tests/fixtures/update/drupal-8.update-test-after-enabled.php @@ -1,2 +1,39 @@ merge('key_value') + ->condition('collection', 'system.schema') + ->condition('name', 'update_test_schema') + ->fields([ + 'collection' => 'system.schema', + 'name' => 'update_test_after', + 'value' => 'i:8000;', + ]) + ->execute(); + +// Update core.extension. +$extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); +$extensions = unserialize($extensions); +$extensions['module']['update_test_after'] = 8000; +$connection->update('config') + ->fields([ + 'data' => serialize($extensions), + ]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); + diff --git a/core/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php b/core/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php index 0fb1ae9..b4cd0b5 100644 --- a/core/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php +++ b/core/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php @@ -37,17 +37,17 @@ protected function setupBasicModules($directories) { $module_a = <<<'EOS' [ 'modules' => [ 'module_a' => [ - 'module_a.update_data.inc' => $module_a, + 'module_a.update_after.inc' => $module_a, 'module_a.info.yml' => $info_a ], 'module_b' => [ - 'module_b.update_data.inc' => $module_b, + 'module_b.update_after.inc' => $module_b, 'module_b.info.yml' => $info_b ], ] @@ -77,18 +77,18 @@ public function testGetMissingUpdateFunctionsWithNoExistingUpdates() { $this->setupBasicModules([]); $key_value = $this->prophesize(KeyValueStoreInterface::class); - $key_value->get('core.update__update_data', [])->wilLReturn([]); + $key_value->get('core.update__update_after', [])->wilLReturn([]); $key_value = $key_value->reveal(); $update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [ 'module_a', 'module_b' - ], $key_value); + ], $key_value, FALSE); $this->assertEquals([ - 'module_a_update_data_a', - 'module_a_update_data_b', - 'module_b_update_data_a' + 'module_a_update_after_a', + 'module_a_update_after_b', + 'module_b_update_after_a' ], $update_registry->getMissingUpdateFunctions()); } @@ -99,17 +99,17 @@ public function testGetMissingUpdateFunctionsWithExistingUpdates() { $this->setupBasicModules([]); $key_value = $this->prophesize(KeyValueStoreInterface::class); - $key_value->get('core.update__update_data', [])->wilLReturn(['module_a_update_data_a']); + $key_value->get('core.update__update_after', [])->wilLReturn(['module_a_update_after_a']); $key_value = $key_value->reveal(); $update_registry = new UpdateRegistry('vfs://drupal', 'sites/default', [ 'module_a', 'module_b' - ], $key_value); + ], $key_value, FALSE); $this->assertEquals(array_values([ - 'module_a_update_data_b', - 'module_b_update_data_a' + 'module_a_update_after_b', + 'module_b_update_after_a' ]), array_values($update_registry->getMissingUpdateFunctions())); }