Commit 9fbe7440 authored by catch's avatar catch
Browse files

Issue #1608842 by chx, alexpott, sun, effulgentsia, beejeebus, tim.plunkett et...

Issue #1608842 by chx, alexpott, sun, effulgentsia, beejeebus, tim.plunkett et al: Replace list of bootstrap modules, enabled modules, enabled themes, and default theme with config.
parent 16eac3be
......@@ -890,28 +890,27 @@ function drupal_get_filename($type, $name, $filename = NULL) {
elseif (isset($files[$type][$name])) {
// nothing
}
// Verify that we have an active database connection, before querying
// the database. This is required because this function is called both
// before we have a database connection (i.e. during installation) and
// when a database connection fails.
else {
try {
if (function_exists('db_query')) {
$file = db_query("SELECT filename FROM {system} WHERE name = :name AND type = :type", array(':name' => $name, ':type' => $type))->fetchField();
if ($file && file_exists(DRUPAL_ROOT . '/' . $file)) {
$files[$type][$name] = $file;
// Verify that we have an keyvalue service before using it. This is required
// because this function is called during installation.
// @todo Inject database connection into KeyValueStore\DatabaseStorage.
if (drupal_container()->hasDefinition('keyvalue') && function_exists('db_query')) {
try {
$file_list = state()->get('system.' . $type . '.files');
if ($file_list && isset($file_list[$name]) && file_exists(DRUPAL_ROOT . '/' . $file_list[$name])) {
$files[$type][$name] = $file_list[$name];
}
}
}
catch (Exception $e) {
// The database table may not exist because Drupal is not yet installed,
// or the database might be down. We have a fallback for this case so we
// hide the error completely.
catch (Exception $e) {
// The keyvalue service raised an exception because the backend might
// be down. We have a fallback for this case so we hide the error
// completely.
}
}
// Fallback to searching the filesystem if the database could not find the
// file or the file returned by the database is not found.
if (!isset($files[$type][$name])) {
// We have a consistent directory naming: modules, themes...
// We have consistent directory naming: modules, themes...
$dir = $type . 's';
if ($type == 'theme_engine') {
$dir = 'themes/engines';
......@@ -2466,9 +2465,10 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
$container
->register('config.storage.staging', 'Drupal\Core\Config\FileStorage')
->addArgument(config_get_config_directory(CONFIG_STAGING_DIRECTORY));
// Register the KeyValueStore factory.
$container
->register('state.storage', 'Drupal\Core\KeyValueStore\DatabaseStorage')
->addArgument('state');
->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory');
}
return $container;
}
......@@ -2485,7 +2485,7 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
* @return Drupal\Core\KeyValueStore\KeyValueStoreInterface
*/
function state() {
return drupal_container()->get('state.storage');
return drupal_container()->get('keyvalue')->get('state');
}
/**
......@@ -2504,16 +2504,22 @@ function typed_data() {
/**
* Returns the test prefix if this is an internal request from SimpleTest.
*
* @param string $new_prefix
* Internal use only. A new prefix to be stored. Passed in by tests that use
* the test runner from within a test.
*
* @return
* Either the simpletest prefix (the string "simpletest" followed by any
* number of digits) or FALSE if the user agent does not contain a valid
* HMAC and timestamp.
*/
function drupal_valid_test_ua() {
function drupal_valid_test_ua($new_prefix = NULL) {
global $drupal_hash_salt;
// No reason to reset this.
static $test_prefix;
if (isset($new_prefix)) {
$test_prefix = $new_prefix;
}
if (isset($test_prefix)) {
return $test_prefix;
}
......
......@@ -322,9 +322,6 @@ function install_begin_request(&$install_state) {
$container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
->addArgument(new Reference('config.storage'))
->addArgument(new Reference('dispatcher'));
$container
->register('state.storage', 'Drupal\Core\KeyValueStore\DatabaseStorage')
->addArgument('state');
drupal_container($container);
}
......@@ -1661,15 +1658,12 @@ function install_import_translations_remaining(&$install_state) {
* A message informing the user that the installation is complete.
*/
function install_finished(&$install_state) {
$profile = drupal_get_profile();
// Remember the profile which was used.
variable_set('install_profile', drupal_get_profile());
variable_set('install_profile', $profile);
// Installation profiles are always loaded last.
db_update('system')
->fields(array('weight' => 1000))
->condition('type', 'module')
->condition('name', drupal_get_profile())
->execute();
module_set_weight($profile, 1000);
// Flush all caches to ensure that any full bootstraps during the installer
// do not leave stale cached data, and that any content types or other items
......
......@@ -8,16 +8,6 @@
use Drupal\Core\Database\Database;
use Drupal\locale\Gettext;
/**
* Indicates that a module has not been installed yet.
*/
const SCHEMA_UNINSTALLED = -1;
/**
* Indicates that a module has been installed.
*/
const SCHEMA_INSTALLED = 0;
/**
* Requirement severity -- Informational message only.
*/
......@@ -420,28 +410,24 @@ function drupal_install_system() {
require_once DRUPAL_ROOT . '/' . $system_path . '/system.install';
$system_versions = drupal_get_schema_versions('system');
$system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED;
db_insert('system')
->fields(array('filename', 'name', 'type', 'owner', 'status', 'schema_version', 'bootstrap'))
->values(array(
'filename' => $system_path . '/system.module',
'name' => 'system',
'type' => 'module',
'owner' => '',
'status' => 1,
'schema_version' => $system_version,
'bootstrap' => 0,
))
->execute();
// Clear out module list and hook implementation statics before calling
// system_rebuild_theme_data().
drupal_container()
->get('keyvalue')
->get('system.schema')
->set('system', $system_version);
// System module needs to be enabled and the system/module lists need to be
// reset first in order to allow config_install_default_config() to invoke
// config import callbacks.
// @todo Installation profiles may override the system.module config object.
config('system.module')
->set('enabled.system', 0)
->save();
// Clear out module list and hook implementation statics.
system_list_reset();
module_list_reset();
module_implements_reset();
system_rebuild_module_data();
system_rebuild_theme_data();
config_install_default_config('module', 'system');
module_invoke('system', 'install');
......
......@@ -8,7 +8,7 @@
use Drupal\Component\Graph\Graph;
/**
* Loads all the modules that have been enabled in the system table.
* Loads all enabled modules.
*
* @param bool $bootstrap
* Whether to load only the reduced set of modules loaded in "bootstrap mode"
......@@ -132,6 +132,11 @@ function module_list_reset() {
*
* @see module_list()
* @see list_themes()
*
* @todo There are too many layers/levels of caching involved for system_list()
* data. Consider to add a config($name, $cache = TRUE) argument to allow
* callers like system_list() to force-disable a possible configuration
* storage controller cache or some other way to circumvent it/take it over.
*/
function system_list($type) {
$lists = &drupal_static(__FUNCTION__);
......@@ -147,15 +152,15 @@ function system_list($type) {
$bootstrap_list = $cached->data;
}
else {
$bootstrap_list = db_query("SELECT name, filename FROM {system} WHERE status = 1 AND bootstrap = 1 AND type = 'module' ORDER BY weight ASC, name ASC")->fetchAllAssoc('name');
$bootstrap_list = state()->get('system.module.bootstrap') ?: array();
cache('bootstrap')->set('bootstrap_modules', $bootstrap_list);
}
// To avoid a separate database lookup for the filepath, prime the
// drupal_get_filename() static cache for bootstrap modules only.
// The rest is stored separately to keep the bootstrap module cache small.
foreach ($bootstrap_list as $module) {
drupal_classloader_register($module->name, dirname($module->filename));
drupal_get_filename('module', $module->name, $module->filename);
foreach ($bootstrap_list as $name => $filename) {
drupal_classloader_register($name, dirname($filename));
drupal_get_filename('module', $name, $filename);
}
// We only return the module names here since module_list() doesn't need
// the filename itself.
......@@ -177,22 +182,48 @@ function system_list($type) {
// Drupal installations, which might have modules installed in different
// locations in the file system. The ordering here must also be
// consistent with the one used in module_implements().
$result = db_query("SELECT * FROM {system} WHERE type = 'theme' OR (type = 'module' AND status = 1) ORDER BY weight ASC, name ASC");
foreach ($result as $record) {
$enabled_modules = config('system.module')->get('enabled');
$module_files = state()->get('system.module.files');
foreach ($enabled_modules as $name => $weight) {
// Build a list of all enabled modules.
if ($record->type == 'module') {
$lists['module_enabled'][$record->name] = $record->name;
}
// Build a list of themes.
if ($record->type == 'theme') {
$record->info = unserialize($record->info);
$lists['theme'][$record->name] = $record;
}
$lists['module_enabled'][$name] = $name;
// Build a list of filenames so drupal_get_filename can use it.
$lists['filepaths'][] = array(
'type' => 'module',
'name' => $name,
'filepath' => $module_files[$name],
);
}
// Build a list of themes.
$enabled_themes = config('system.theme')->get('enabled');
// @todo Themes include all themes, including disabled/uninstalled. This
// system.theme.data state will go away entirely as soon as themes have
// a proper installation status.
// @see http://drupal.org/node/1067408
$theme_data = state()->get('system.theme.data');
if (empty($theme_data)) {
// @todo: system_list() may be called from _drupal_bootstrap_code() and
// module_load_all(), in which case system.module is not loaded yet.
// Prevent a filesystem scan in drupal_load() and include it directly.
// @see http://drupal.org/node/1067408
require_once DRUPAL_ROOT . '/core/modules/system/system.module';
$theme_data = system_rebuild_theme_data();
}
foreach ($theme_data as $name => $theme) {
$theme->status = (int) isset($enabled_themes[$name]);
$lists['theme'][$name] = $theme;
// Build a list of filenames so drupal_get_filename can use it.
if ($record->status) {
$lists['filepaths'][] = array('type' => $record->type, 'name' => $record->name, 'filepath' => $record->filename);
if (isset($enabled_themes[$name])) {
$lists['filepaths'][] = array(
'type' => 'theme',
'name' => $name,
'filepath' => $theme->filename,
);
}
}
// @todo Move into list_themes(). Read info for a particular requested
// theme from state instead.
foreach ($lists['theme'] as $key => $theme) {
if (!empty($theme->info['base theme'])) {
// Make a list of the theme's base themes.
......@@ -240,6 +271,14 @@ function system_list_reset() {
drupal_static_reset('list_themes');
cache('bootstrap')->deleteMultiple(array('bootstrap_modules', 'system_list'));
cache()->delete('system_info');
// Remove last known theme data state.
// This causes system_list() to call system_rebuild_theme_data() on its next
// invocation. When enabling a module that implements hook_system_info_alter()
// to inject a new (testing) theme or manipulate an existing theme, then that
// will cause system_list_reset() to be called, but theme data is not
// necessarily rebuilt afterwards.
// @todo Obsolete with proper installation status for themes.
state()->delete('system.theme.data');
}
/**
......@@ -348,7 +387,7 @@ function module_load_include($type, $module, $name = NULL) {
}
/**
* Loads an include file for each module enabled in the {system} table.
* Loads an include file for each enabled module.
*/
function module_load_all_includes($type, $name = NULL) {
$modules = module_list();
......@@ -436,26 +475,28 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
$modules_installed = array();
$modules_enabled = array();
$schema_store = drupal_container()->get('keyvalue')->get('system.schema');
$module_config = config('system.module');
$disabled_config = config('system.module.disabled');
foreach ($module_list as $module) {
// Only process modules that are not already enabled.
$existing = db_query("SELECT status FROM {system} WHERE type = :type AND name = :name", array(
':type' => 'module',
':name' => $module))
->fetchObject();
if ($existing->status == 0) {
$enabled = $module_config->get("enabled.$module") !== NULL;
if (!$enabled) {
$weight = $disabled_config->get($module);
if ($weight === NULL) {
$weight = 0;
}
$module_config
->set("enabled.$module", $weight)
->set('enabled', module_config_sort($module_config->get('enabled')))
->save();
$disabled_config
->clear($module)
->save();
// Load the module's code.
drupal_load('module', $module);
module_load_install($module);
// Update the database and module list to reflect the new module. This
// needs to be done first so that the module's hook implementations,
// hook_schema() in particular, can be called while it is being
// installed.
db_update('system')
->fields(array('status' => 1))
->condition('type', 'module')
->condition('name', $module)
->execute();
// Refresh the module list to include it.
system_list_reset();
module_implements_reset();
......@@ -565,15 +606,18 @@ function module_disable($module_list, $disable_dependents = TRUE) {
$invoke_modules = array();
$module_config = config('system.module');
$disabled_config = config('system.module.disabled');
foreach ($module_list as $module) {
if (module_exists($module)) {
module_load_install($module);
module_invoke($module, 'disable');
db_update('system')
->fields(array('status' => 0))
->condition('type', 'module')
->condition('name', $module)
->execute();
$disabled_config
->set($module, $module_config->get($module))
->save();
$module_config
->clear("enabled.$module")
->save();
$invoke_modules[] = $module;
watchdog('system', '%module module disabled.', array('%module' => $module), WATCHDOG_INFO);
}
......@@ -642,6 +686,8 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE)
}
$storage = drupal_container()->get('config.storage');
$schema_store = drupal_container()->get('keyvalue')->get('system.schema');
$disabled_config = config('system.module.disabled');
foreach ($module_list as $module) {
// Uninstall the module.
module_load_install($module);
......@@ -655,8 +701,11 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE)
}
watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
$schema_store->delete($module);
$disabled_config->clear($module);
}
$disabled_config->save();
drupal_get_installed_schema_version(NULL, TRUE);
if (!empty($module_list)) {
// Call hook_module_uninstall to let other modules act
......@@ -1124,3 +1173,65 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
$function($data, $context1, $context2);
}
}
/**
* Sets weight of a particular module.
*
* The weight of uninstalled modules cannot be changed.
*
* @param string $module
* The name of the module (without the .module extension).
* @param int $weight
* An integer representing the weight of the module.
*/
function module_set_weight($module, $weight) {
// Update the module weight in the config file that contains it.
$module_config = config('system.module');
if ($module_config->get("enabled.$module") !== NULL) {
$module_config
->set("enabled.$module", $weight)
->set('enabled', module_config_sort($module_config->get('enabled')))
->save();
return;
}
$disabled_config = config('system.module.disabled');
if ($disabled_config->get($module) !== NULL) {
$disabled_config
->set($module, $weight)
->save();
return;
}
}
/**
* Sorts the configured list of enabled modules.
*
* The list of enabled modules is expected to be ordered by weight and name.
* The list is always sorted on write to avoid the overhead on read.
*
* @param array $data
* An array of module configuration data.
*
* @return array
* An array of module configuration data sorted by weight and name.
*/
function module_config_sort($data) {
// PHP array sorting functions such as uasort() do not work with both keys and
// values at the same time, so we achieve weight and name sorting by computing
// strings with both information concatenated (weight first, name second) and
// use that as a regular string sort reference list via array_multisort(),
// compound of "[sign-as-integer][padded-integer-weight][name]"; e.g., given
// two modules and weights (spaces added for clarity):
// - Block with weight -5: 0 0000000000000000005 block
// - Node with weight 0: 1 0000000000000000000 node
$sort = array();
foreach ($data as $name => $weight) {
// Prefix negative weights with 0, positive weights with 1.
// +/- signs cannot be used, since + (ASCII 43) is before - (ASCII 45).
$prefix = (int) ($weight >= 0);
// The maximum weight is PHP_INT_MAX, so pad all weights to 19 digits.
$sort[] = $prefix . sprintf('%019d', abs($weight)) . $name;
}
array_multisort($sort, SORT_STRING, $data);
return $data;
}
......@@ -14,6 +14,16 @@
* @{
*/
/**
* Indicates that a module has not been installed yet.
*/
const SCHEMA_UNINSTALLED = -1;
/**
* Indicates that a module has been installed.
*/
const SCHEMA_INSTALLED = 0;
/**
* Gets the schema definition of a table, or the whole database schema.
*
......@@ -156,7 +166,7 @@ function drupal_get_schema_versions($module) {
* @param string $module
* A module name.
* @param bool $reset
* Set to TRUE after modifying the system table.
* Set to TRUE after installing or uninstalling an extension.
* @param bool $array
* Set to TRUE if you want to get information about all modules in the
* system.
......@@ -173,10 +183,8 @@ function drupal_get_installed_schema_version($module, $reset = FALSE, $array = F
}
if (!$versions) {
$versions = array();
$result = db_query("SELECT name, schema_version FROM {system} WHERE type = :type", array(':type' => 'module'));
foreach ($result as $row) {
$versions[$row->name] = $row->schema_version;
if (!$versions = drupal_container()->get('keyvalue')->get('system.schema')->getAll()) {
$versions = array();
}
}
......@@ -197,11 +205,7 @@ function drupal_get_installed_schema_version($module, $reset = FALSE, $array = F
* The new schema version.
*/
function drupal_set_installed_schema_version($module, $version) {
db_update('system')
->fields(array('schema_version' => $version))
->condition('name', $module)
->execute();
drupal_container()->get('keyvalue')->get('system.schema')->set($module, $version);
// Reset the static cache of module schema versions.
drupal_get_installed_schema_version(NULL, TRUE);
}
......
......@@ -1412,17 +1412,17 @@ function theme_render_template($template_file, $variables) {
*/
function theme_enable($theme_list) {
drupal_clear_css_cache();
$theme_config = config('system.theme');
$disabled_themes = config('system.theme.disabled');
foreach ($theme_list as $key) {
db_update('system')
->fields(array('status' => 1))
->condition('type', 'theme')
->condition('name', $key)
->execute();
// The value is not used; the weight is ignored for themes currently.
$theme_config->set("enabled.$key", 0);
$disabled_themes->clear($key);
// Install default configuration of the theme.
config_install_default_config('theme', $key);
}
$theme_config->save();
$disabled_themes->save();
list_themes(TRUE);
menu_router_rebuild();
......@@ -1449,13 +1449,15 @@ function theme_disable($theme_list) {
drupal_clear_css_cache();
$theme_config = config('system.theme');
$disabled_themes = config('system.theme.disabled');
foreach ($theme_list as $key) {
db_update('system')
->fields(array('status' => 0))
->condition('type', 'theme')
->condition('name', $key)
->execute();
// The value is not used; the weight is ignored for themes currently.
$theme_config->clear("enabled.$key");
$disabled_themes->set($key, 0);
}
$theme_config->save();
$disabled_themes->save();
list_themes(TRUE);
menu_router_rebuild();
......
......@@ -19,26 +19,29 @@
* Upgrades from Drupal 7 to Drupal 8 require that Drupal 7 be running
* the most recent version, or the upgrade could fail. We can't easily
* check the Drupal 7 version once the update process has begun, so instead
* we check the schema version of system.module in the system table.
* we check the schema version of system.module.
*/
const REQUIRED_D7_SCHEMA_VERSION = '7069';
/**
* Disable any items in the {system} table that are not core compatible.
* Disables any extensions that are incompatible with the current core version.
*/
function update_fix_compatibility() {
$incompatible = array();
$result = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')");
foreach ($result as $row) {
if (update_check_incompatibility($row->name, $row->type)) {
$incompatible[] = $row->name;
foreach (array('module', 'theme') as $type) {
$config = config("system.$type");
$save = FALSE;
foreach ($config->get('enabled') as $name => $weight) {
if (update_check_incompatibility($name, $type)) {
$config->clear("enabled.$name");
$save = TRUE;
}
}
if ($save) {
if ($type == 'module') {
$config->set('enabled', module_config_sort($config->get('enabled')));
}
$config->save();
}
}
if (!empty($incompatible)) {
db_update('system')
->fields(array('status' => 0))
->condition('name', $incompatible, 'IN')
->execute();
}
}
......@@ -117,7 +120,12 @@ function update_prepare_d8_bootstrap() {
// running an up-to-date version of Drupal 7 before proceeding. Note this has
// to happen AFTER the database bootstraps because of
// drupal_get_installed_schema_version().
$system_schema = drupal_get_installed_schema_version('system');
try {
$system_schema = drupal_get_installed_schema_version('system');
}
catch (\Exception $e) {
$system_schema = db_query('SELECT schema_version FROM {system} WHERE name = :system', array(':system' => 'system'))->fetchField();
}
if ($system_schema < 8000) {
$has_required_schema = $system_schema >= REQUIRED_D7_SCHEMA_VERSION;
$requirements = array(
......@@ -130,6 +138,10 @@ function update_prepare_d8_bootstrap() {
);
update_extra_requirements($requirements);
// @todo update.php stages seem to be completely screwed up; the initial
// requirements check is not supposed to change the system. All of the
// following code seems to have been mistakenly/unknowingly added here and
// does not belong into update_prepare_d8_bootstrap().
if ($has_required_schema) {
if (!db_table_exists('key_value')) {
$specs = array(
......@@ -164,18 +176,91 @@ function update_prepare_d8_bootstrap() {