Issue #2001310 by chx, effulgentsia, Berdir, YesCT: Disallow firing hooks during update.

8.0.x
Alex Pott 2013-06-06 09:08:39 +01:00
parent 6801679a0f
commit 9d73599020
13 changed files with 262 additions and 114 deletions

View File

@ -15,6 +15,7 @@ use Drupal\Core\Config\ConfigException;
use Drupal\Core\DrupalKernel;
use Drupal\Component\Uuid\Uuid;
use Drupal\Component\Utility\NestedArray;
use Symfony\Component\HttpFoundation\Request;
/**
* Minimum schema version of Drupal 7 required for upgrade to Drupal 8.
@ -102,8 +103,10 @@ function update_prepare_d8_bootstrap() {
$settings['cache']['default'] = 'cache.backend.memory';
new Settings($settings);
// Enable UpdateBundle service overrides.
$GLOBALS['conf']['container_bundles'][] = 'Drupal\Core\DependencyInjection\UpdateBundle';
// Enable UpdateBundle service overrides. While the container_bundles array
// does not need a key, let's use so it can be removed once the upgrade are
// finished. @see update_flush_all_caches()
$GLOBALS['conf']['container_bundles']['UpdateBundle'] = 'Drupal\Core\DependencyInjection\UpdateBundle';
// Check whether settings.php needs to be rewritten.
$settings_exist = !empty($GLOBALS['config_directories']);
@ -351,10 +354,6 @@ function update_prepare_d8_bootstrap() {
':schema_uninstalled' => SCHEMA_UNINSTALLED,
));
// Populate a fixed module list (again, why did it get lost?) to avoid
// errors due to the drupal_alter() in _system_rebuild_module_data().
$module_list['system'] = 'core/modules/system/system.module';
Drupal::moduleHandler()->setModuleList($module_list);
$module_data = _system_rebuild_module_data();
// Migrate each extension into configuration, varying by the extension's
@ -452,6 +451,9 @@ function update_prepare_stored_includes() {
*/
function update_prepare_d8_language() {
if (db_table_exists('languages')) {
module_enable(array('language'));
$languages = db_select('languages', 'l')
->fields('l')
->execute();
@ -490,12 +492,6 @@ function update_prepare_d8_language() {
// Rename the languages table to language.
db_rename_table('languages', 'language');
// Install/enable the language module. We need to use the update specific
// version of this function to ensure schema conflicts don't happen due to
// our updated data.
$modules = array('language');
update_module_enable($modules);
// Rename language column to langcode and set it again as the primary key.
if (db_field_exists('language', 'language')) {
db_drop_primary_key('language');
@ -631,7 +627,7 @@ function update_fix_d8_requirements() {
// Make sure that file.module is enabled as it is required for the user
// picture upgrade path.
update_module_enable(array('file'));
module_enable(array('file'));
$schema = array(
'description' => 'Generic key/value storage table with an expiration.',
@ -671,73 +667,33 @@ function update_fix_d8_requirements() {
);
db_create_table('key_value_expire', $schema);
// Views module is required to convert all previously existing listings into
// views configurations.
// Like any other module APIs and services, Views' services are not available
// in update.php. Existing listings are migrated into configuration, using
// the limited standard tools of raw database queries and config().
module_enable(array('views'));
update_variable_set('update_d8_requirements', TRUE);
}
}
/**
* Installs a new module in Drupal 8 via hook_update_N().
* Forces a module to a given schema version.
*
* @param array $modules
* List of modules to enable. Dependencies are not checked and must be
* ensured by the caller.
* @param bool $update_schema_version
* (optional) The schema version the module should be set to. Defaults to 0
* which works well for completely new modules.
* This function is rarely necessary.
*
* @return array
* List of the old schema versions keyed by the module names,
* SCHEMA_UNINSTALLED if it was not installed before. Additional manual
* installation steps like installing default configuration might be necessary
* if the module was not installed before.
* @param string $module
* Name of the module.
* @param string $schema_version
* The schema version the module should be set to.
*/
function update_module_enable(array $modules, $schema_version = 0) {
$schema_store = Drupal::keyValue('system.schema');
$old_schema = array();
foreach ($modules as $module) {
// Check for initial schema and install it. The schema version of a newly
// installed module is always 0. Using 8000 here would be inconsistent
// since $module_update_8000() may involve a schema change, and we want
// to install the schema as it was before any updates were added.
module_load_install($module);
$function = $module . '_schema_0';
if (function_exists($function)) {
$schema = $function();
foreach ($schema as $table => $spec) {
db_create_table($table, $spec);
}
}
// Enable the module with a weight of 0.
$module_config = config('system.module');
$module_config
->set("enabled.$module", 0)
->set('enabled', module_config_sort($module_config->get('enabled')))
->save();
// Ensure the module is not contained in disabled modules.
config('system.module.disabled')
->clear($module)
->save();
$current_schema = $schema_store->get($module);
// Set the schema version if the module was not just disabled before.
if ($current_schema === NULL || $current_schema === SCHEMA_UNINSTALLED) {
// Change the schema version to the given value (defaults to 0), so any
// module updates since the module's inception are executed in a core
// upgrade.
$schema_store->set($module, $schema_version);
$old_schema[$module] = SCHEMA_UNINSTALLED;
}
else {
$old_schema[$module] = $current_schema;
}
// system_list_reset() is in module.inc but that would only be available
// once the variable bootstrap is done.
require_once __DIR__ . '/module.inc';
system_list_reset();
// @todo: figure out what to do about hook_install() and hook_enable().
}
return $old_schema;
function update_set_schema($module, $schema_version) {
Drupal::keyValue('system.schema')->set($module, $schema_version);
// system_list_reset() is in module.inc but that would only be available
// once the variable bootstrap is done.
require_once __DIR__ . '/module.inc';
system_list_reset();
}
/**
@ -935,7 +891,7 @@ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $
*/
function update_finished($success, $results, $operations) {
// Clear the caches in case the data has been updated.
drupal_flush_all_caches();
update_flush_all_caches();
$_SESSION['update_results'] = $results;
$_SESSION['update_success'] = $success;
@ -1271,7 +1227,7 @@ function update_retrieve_dependencies() {
// Nothing to upgrade.
continue;
}
$function = $module . '_update_dependencies';
$function = $module . '_update_dependencies';
// Ensure install file is loaded.
module_load_install($module);
if (function_exists($function)) {

View File

@ -218,7 +218,7 @@ class DatabaseBackend implements CacheBackendInterface {
* Implements Drupal\Core\Cache\CacheBackendInterface::deleteTags().
*/
public function deleteTags(array $tags) {
$tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache');
$tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array());
foreach ($this->flattenTags($tags) as $tag) {
unset($tag_cache[$tag]);
try {
@ -277,7 +277,7 @@ class DatabaseBackend implements CacheBackendInterface {
*/
public function invalidateTags(array $tags) {
try {
$tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache');
$tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array());
foreach ($this->flattenTags($tags) as $tag) {
unset($tag_cache[$tag]);
$this->connection->merge('cache_tags')

View File

@ -32,6 +32,12 @@ class UpdateBundle extends Bundle {
$container
->register('config.storage', 'Drupal\Core\Config\FileStorage')
->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
$container->register('module_handler', 'Drupal\Core\Extension\UpdateModuleHandler')
->addArgument('%container.modules%');
$container
->register("cache_factory", 'Drupal\Core\Cache\MemoryBackendFactory');
$container
->register('router.builder', 'Drupal\Core\Routing\RouteBuilderStatic');
}
}

View File

@ -0,0 +1,144 @@
<?php
/**
* @file
* Contains \Drupal\Core\Extension\UpdateModuleHandler.
*/
namespace Drupal\Core\Extension;
use Drupal\Core\Config\ConfigException;
use Drupal\Core\Config\FileStorage;
/**
* Deals with module enables and throws exception if hooks fired during updates.
*
* This is necessary for a reliable and testable update environment.
*/
class UpdateModuleHandler extends ModuleHandler {
/**
* {@inheritdoc}
*/
public function getImplementations($hook) {
if (substr($hook, -6) === '_alter') {
return array();
}
switch ($hook) {
// hook_requirements is necessary for updates to work.
case 'requirements':
// Allow logging.
case 'watchdog':
return parent::getImplementations($hook);
// Forms and pages do not render without the basic elements defined in
// system_element_info().
case 'element_info':
// Forms do not render without the basic elements in
// drupal_common_theme() called from system_theme().
case 'theme':
// user_update_8011() uses public:// to create the image style directory.
case 'stream_wrappers':
return array('system');
// This is called during rebuild to find testing themes.
case 'system_theme_info':
return array();
// t() in system_stream_wrappers() needs this. Other schema calls aren't
// supported.
case 'schema':
return array('locale');
default:
throw new \LogicException("Invoking hooks $hook is not supported during updates");
}
}
/**
* {@inheritdoc}
*/
public function enable($module_list, $enable_dependencies = TRUE) {
$schema_store = \Drupal::keyValue('system.schema');
$old_schema = array();
foreach ($module_list as $module) {
// Check for initial schema and install it. The schema version of a newly
// installed module is always 0. Using 8000 here would be inconsistent
// since $module_update_8000() may involve a schema change, and we want
// to install the schema as it was before any updates were added.
module_load_install($module);
$function = $module . '_schema_0';
if (function_exists($function)) {
$schema = $function();
foreach ($schema as $table => $spec) {
db_create_table($table, $spec);
}
}
// Enable the module with a weight of 0.
$module_config = config('system.module');
$module_config
->set("enabled.$module", 0)
->set('enabled', module_config_sort($module_config->get('enabled')))
->save();
// Ensure the module is not contained in disabled modules.
config('system.module.disabled')
->clear($module)
->save();
$current_schema = $schema_store->get($module);
// Set the schema version if the module was not just disabled before.
if ($current_schema === NULL || $current_schema === SCHEMA_UNINSTALLED) {
// Change the schema version to the given value (defaults to 0), so any
// module updates since the module's inception are executed in a core
// upgrade.
$schema_store->set($module, 0);
$old_schema[$module] = SCHEMA_UNINSTALLED;
}
else {
$old_schema[$module] = $current_schema;
}
// Copy the default configuration of the module into the active storage.
// The default configuration is not altered in any way, and since the module
// is just being installed, none of its configuration can exist already, so
// this is a plain copy operation from one storage to another.
$module_config_path = drupal_get_path('module', $module) . '/config';
if (is_dir($module_config_path)) {
$module_filestorage = new FileStorage($module_config_path);
$config_storage = drupal_container()->get('config.storage');
foreach ($module_filestorage->listAll() as $config_name) {
// If this file already exists, something in the upgrade path went
// completely wrong and we want to know.
if ($config_storage->exists($config_name)) {
throw new ConfigException(format_string('Default configuration file @name of @module module unexpectedly exists already before the module was installed.', array(
'@module' => $module,
'@name' => $config_name,
)));
}
$config_storage->write($config_name, $module_filestorage->read($config_name));
}
}
// system_list_reset() is in module.inc but that would only be available
// once the variable bootstrap is done.
require_once DRUPAL_ROOT . '/core/includes/module.inc';
system_list_reset();
$this->moduleList[$module] = drupal_get_filename('module', $module);
$this->load($module);
drupal_classloader_register($module, dirname($this->moduleList[$module]));
// @todo Figure out what to do about hook_install() and hook_enable().
}
return $old_schema;
}
/**
* {@inheritdoc}
*/
public function disable($module_list, $disable_dependents = TRUE) {
throw new \LogicException('Disabling modules is not supported during updates');
}
/**
* {@inheritdoc}
*/
public function uninstall($module_list = array(), $uninstall_dependents = TRUE) {
throw new \LogicException('Uninstalling modules is not supported during updates');
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* @file
* Contains \Drupal\Core\Routing\RouteBuilderStatic.
*/
namespace Drupal\Core\Routing;
/**
* This builds a static version of the router.
*/
class RouteBuilderStatic {
/**
* Rebuilds router.
*/
public function rebuild() {
// @todo Add the route for the batch pages when that conversion happens,
// http://drupal.org/node/1987816.
}
}

View File

@ -180,7 +180,7 @@ function block_update_8005() {
* Enable the Custom Block module.
*/
function block_update_8006() {
update_module_enable(array('custom_block'));
module_enable(array('custom_block'));
}
/**

View File

@ -117,3 +117,13 @@ function language_disable() {
// will be FALSE, because the language module is disabled.
variable_set('language_count', 1);
}
/**
* Implements hook_requirements().
*/
function language_requirements($phase) {
if ($phase == 'update') {
// Load the include files to make constants available for updates.
language_negotiation_include();
}
}

View File

@ -789,7 +789,7 @@ function node_update_8011() {
*/
function node_update_8012() {
// Enable the history module without re-installing the schema.
update_module_enable(array('history'));
module_enable(array('history'));
}
/**
@ -812,7 +812,7 @@ function node_update_8013() {
*/
function node_update_8014() {
// Enable the datetime module.
update_module_enable(array('datetime'));
module_enable(array('datetime'));
}
/**

View File

@ -1,13 +1,13 @@
<?php
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Database\Database;
use Drupal\Core\Language\Language;
/**
* @file
* Install, update and uninstall functions for the system module.
*/
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Database\Database;
use Drupal\Core\Language\Language;
/**
* Test and report Drupal installation requirements.
@ -1539,7 +1539,7 @@ function system_update_8020() {
->condition('aid', 'system_block_ip_action')
->execute();
// Enable the new Ban module.
update_module_enable(array('ban'));
module_enable(array('ban'));
}
else {
// Drop old table.
@ -1552,7 +1552,7 @@ function system_update_8020() {
*/
function system_update_8021() {
// Enable the module without re-installing the schema.
update_module_enable(array('action'));
module_enable(array('action'));
// Rename former System module actions.
$map = array(
'system_message_action' => 'action_message_action',
@ -1857,7 +1857,7 @@ function system_update_8041() {
* Enable the new Entity module.
*/
function system_update_8042() {
update_module_enable(array('entity'));
module_enable(array('entity'));
}
/**
@ -1922,18 +1922,21 @@ function system_update_8045() {
function system_update_8046() {
$front_page = config('system.site')->get('page.front');
if (!isset($front_page) || $front_page == 'node') {
update_module_enable(array('views'));
// Register views to the container, so views can use it's services.
$module_list = drupal_container()->getParameter('container.modules');
drupal_load('module', 'views');
drupal_container()->get('kernel')->updateModules($module_list, array('views' => 'core/modules/views/views.module'));
// This does not fire a hook just calls views.
config_install_default_config('module', 'views');
// This imports the node frontpage view.
config_install_default_config('module', 'node');
$module = 'node';
$config_name = 'views.view.frontpage';
$module_config_path = drupal_get_path('module', $module) . '/config';
$module_filestorage = new FileStorage($module_config_path);
$config_storage = drupal_container()->get('config.storage');
// If this file already exists, something in the upgrade path went
// completely wrong and we want to know.
if ($config_storage->exists($config_name)) {
throw new ConfigException(format_string('Default configuration file @name of @module module unexpectedly exists already before the module was installed.', array(
'@module' => $module,
'@name' => $config_name,
)));
}
$config_storage->write($config_name, $module_filestorage->read($config_name));
}
}
@ -1960,7 +1963,7 @@ function system_update_8047() {
*/
function system_update_8048() {
// Enable the module without re-installing the schema.
update_module_enable(array('menu_link'));
module_enable(array('menu_link'));
// Add the langcode column if it doesn't exist.
if (!db_field_exists('menu_inks', 'langcode')) {

View File

@ -18,7 +18,7 @@
*/
function toolbar_update_8000() {
// Enable the modules without re-installing the schema.
update_module_enable(array('breakpoint'));
module_enable(array('breakpoint'));
}
/**

View File

@ -657,13 +657,11 @@ function user_update_8011() {
// User pictures can only be migrated to the new user picture image field
// if Image module is installed.
if (!module_exists('image')) {
// Install image.module with schema version 8002 as a previous version
// would have to create tables that would be removed again.
$old_schema = update_module_enable(array('image'), 8002);
$old_schema = module_enable(array('image'));
if ($old_schema['image'] == SCHEMA_UNINSTALLED) {
// If image.module was not installed before, install default
// configuration and run the install hook.
config_install_default_config('module', 'image');
// Install image.module with schema version 8002 as a previous version
// would have to create tables that would be removed again.
update_set_schema('image', 8002);
// Inlined version of image_install(), make sure that the styles
// directory exists.
$directory = update_variable_get('file_default_scheme', 'public') . '://styles';

View File

@ -17,7 +17,7 @@ function views_install() {
/**
* Provide an initial schema.
*
* @see update_module_enable().
* @see UpdateModuleHandler::enable().
*/
function views_schema_0() {
module_load_install('system');

View File

@ -14,6 +14,7 @@
* back to its original state!
*/
use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\DependencyInjection\Reference;
@ -134,7 +135,7 @@ function update_script_selection_form($form, &$form_state) {
);
// No updates to run, so caches won't get flushed later. Clear them now.
drupal_flush_all_caches();
update_flush_all_caches();
}
else {
$form['help'] = array(
@ -178,6 +179,20 @@ function update_helpful_links() {
return $links;
}
/**
* Remove update overrides and flush all caches.
*
* This will need to be run once all (if any) updates are run. Do not call this
* while updates are running.
*/
function update_flush_all_caches() {
unset($GLOBALS['conf']['container_bundles']['UpdateBundle']);
Drupal::service('kernel')->updateModules(Drupal::moduleHandler()->getModuleList());
// No updates to run, so caches won't get flushed later. Clear them now.
drupal_flush_all_caches();
}
/**
* Displays results of the update script with any accompanying errors.
*/
@ -435,13 +450,6 @@ if (is_null($op) && update_access_allowed()) {
require_once __DIR__ . '/includes/install.inc';
require_once DRUPAL_ROOT . '/core/modules/system/system.install';
// Load module basics.
include_once __DIR__ . '/includes/module.inc';
$module_list['system'] = 'core/modules/system/system.module';
$module_handler = Drupal::moduleHandler();
$module_handler->setModuleList($module_list);
$module_handler->load('system');
// Set up $language, since the installer components require it.
drupal_language_initialize();