Newer
Older
Dries Buytaert
committed
<?php
/**
* @file
* Drupal database update API.
*
* This file contains functions to perform database updates for a Drupal
* installation. It is included and used extensively by update.php.
*/
Dries Buytaert
committed
use Drupal\Component\Graph\Graph;
use Drupal\Component\Utility\Settings;
Angie Byron
committed
use Drupal\Component\Utility\String;
Dries Buytaert
committed
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\ConfigException;
use Drupal\Core\DrupalKernel;
catch
committed
use Drupal\Core\Utility\Error;
use Drupal\Component\Uuid\Uuid;
use Drupal\Component\Utility\NestedArray;
Alex Pott
committed
use Symfony\Component\HttpFoundation\Request;
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Minimum schema version of Drupal 7 required for upgrade to Drupal 8.
*
Dries Buytaert
committed
* 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
Dries Buytaert
committed
* check the Drupal 7 version once the update process has begun, so instead
* we check the schema version of system.module.
*/
const REQUIRED_D7_SCHEMA_VERSION = '7069';
Dries Buytaert
committed
/**
* Disables any extensions that are incompatible with the current core version.
Dries Buytaert
committed
*/
function update_fix_compatibility() {
foreach (array('module', 'theme') as $type) {
$config = \Drupal::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();
Dries Buytaert
committed
}
}
}
/**
Jennifer Hodgdon
committed
* Tests the compatibility of a module or theme.
Dries Buytaert
committed
*/
function update_check_incompatibility($name, $type = 'module') {
static $themes, $modules;
// Store values of expensive functions for future use.
if (empty($themes) || empty($modules)) {
Dries Buytaert
committed
// We need to do a full rebuild here to make sure the database reflects any
// code changes that were made in the filesystem before the update script
// was initiated.
$themes = system_rebuild_theme_data();
Angie Byron
committed
$modules = system_rebuild_module_data();
Dries Buytaert
committed
}
if ($type == 'module' && isset($modules[$name])) {
$file = $modules[$name];
}
elseif ($type == 'theme' && isset($themes[$name])) {
$file = $themes[$name];
}
if (!isset($file)
|| !isset($file->info['core'])
|| $file->info['core'] != \Drupal::CORE_COMPATIBILITY
Dries Buytaert
committed
|| version_compare(phpversion(), $file->info['php']) < 0) {
Dries Buytaert
committed
return TRUE;
}
return FALSE;
}
/**
Dries Buytaert
committed
* Performs extra steps required to bootstrap when using a Drupal 7 database.
Dries Buytaert
committed
* Users who still have a Drupal 7 database (and are in the process of
* updating to Drupal 8) need extra help before a full bootstrap can be
Dries Buytaert
committed
* achieved. This function does the necessary preliminary work that allows
* the bootstrap to be successful.
*
* No access check has been performed when this function is called, so no
Dries Buytaert
committed
* irreversible changes to the database are made here.
Dries Buytaert
committed
*/
Dries Buytaert
committed
function update_prepare_d8_bootstrap() {
include_once __DIR__ . '/install.inc';
include_once __DIR__ . '/schema.inc';
catch
committed
// Bootstrap to configuration.
drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
// During the bootstrap to DRUPAL_BOOTSTRAP_PAGE_CACHE, code will try to read
// the cache but the cache tables are not compatible yet. Use the Null backend
// by default to avoid exceptions.
$settings = settings()->getAll();
$settings['cache']['default'] = 'cache.backend.memory';
new Settings($settings);
// Enable UpdateServiceProvider service overrides.
// @see update_flush_all_caches()
$GLOBALS['conf']['container_service_providers']['UpdateServiceProvider'] = 'Drupal\Core\DependencyInjection\UpdateServiceProvider';
// Check whether settings.php needs to be rewritten.
$settings_exist = !empty($GLOBALS['config_directories']);
if (!$settings_exist || !is_dir(config_get_config_directory('active')) || !is_dir(config_get_config_directory('staging'))) {
drupal_install_config_directories();
}
// Bootstrap the kernel.
// Do not attempt to dump and write it.
$kernel = new DrupalKernel('update', drupal_classloader(), FALSE);
$kernel->boot();
// If any of the required settings needs to be written, then settings.php
// needs to be writable.
if (!$settings_exist) {
$settings_file = conf_path() . '/settings.php';
$writable = drupal_verify_install_file($settings_file, FILE_EXIST | FILE_READABLE | FILE_WRITABLE);
$requirements['settings file']['title'] = 'Settings file';
if ($writable) {
$requirements['settings file'] += array(
'value' => 'settings.php is writable.',
);
}
else {
$requirements['settings file'] += array(
'value' => 'settings.php is not writable.',
'severity' => REQUIREMENT_ERROR,
'description' => 'Drupal requires write permissions to <em>' . $settings_file . '</em> during the update process. If you are unsure how to grant file permissions, consult the <a href="http://drupal.org/server-permissions">online handbook</a>.',
);
}
update_extra_requirements($requirements);
}
// Bootstrap the database.
require_once __DIR__ . '/database.inc';
catch
committed
// module.inc is not yet loaded but there are calls to module_config_sort()
// below.
require_once __DIR__ . '/module.inc';
Dries Buytaert
committed
// If the site has not updated to Drupal 8 yet, check to make sure that it is
// 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().
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();
}
Dries Buytaert
committed
if ($system_schema < 8000) {
Dries Buytaert
committed
$has_required_schema = $system_schema >= REQUIRED_D7_SCHEMA_VERSION;
$requirements = array(
Dries Buytaert
committed
'drupal 7 version' => array(
'title' => 'Drupal 7 version',
'value' => $has_required_schema ? 'You are running a current version of Drupal 7.' : 'You are not running a current version of Drupal 7',
'severity' => $has_required_schema ? NULL : REQUIREMENT_ERROR,
Dries Buytaert
committed
'description' => $has_required_schema ? '' : 'Please update your Drupal 7 installation to the most recent version before attempting to upgrade to Drupal 8',
Angie Byron
committed
),
);
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(
'description' => 'Generic key-value storage table. See the state system for an example.',
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
'fields' => array(
'collection' => array(
'description' => 'A named collection of key and value pairs.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'name' => array(
'description' => 'The key of the key-value pair. As KEY is a SQL reserved keyword, name was chosen instead.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'value' => array(
'description' => 'The value.',
'type' => 'blob',
'not null' => TRUE,
'size' => 'big',
'translatable' => TRUE,
),
),
'primary key' => array('collection', 'name'),
);
db_create_table('key_value', $specs);
}
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
if (!db_table_exists('cache_tags')) {
$table = array(
'description' => 'Cache table for tracking cache tags related to the cache bin.',
'fields' => array(
'tag' => array(
'description' => 'Namespace-prefixed tag string.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'invalidations' => array(
'description' => 'Number incremented when the tag is invalidated.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'deletions' => array(
'description' => 'Number incremented when the tag is deleted.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('tag'),
);
db_create_table('cache_tags', $table);
}
if (!db_table_exists('cache_config')) {
$spec = array(
'description' => 'Cache table for configuration data.',
'fields' => array(
'cid' => array(
'description' => 'Primary Key: Unique cache ID.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'data' => array(
'description' => 'A collection of data to cache.',
'type' => 'blob',
'not null' => FALSE,
'size' => 'big',
),
'expire' => array(
'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'created' => array(
'description' => 'A Unix timestamp indicating when the cache entry was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'serialized' => array(
'description' => 'A flag to indicate whether content is serialized (1) or not (0).',
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
),
'tags' => array(
'description' => 'Space-separated list of cache tags for this entry.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
),
'checksum_invalidations' => array(
'description' => 'The tag invalidation sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'checksum_deletions' => array(
'description' => 'The tag deletion sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'indexes' => array(
'expire' => array('expire'),
),
'primary key' => array('cid'),
);
db_create_table('cache_config', $spec);
}
require_once DRUPAL_ROOT . '/core/modules/system/system.install';
$tables = array(
'cache',
'cache_bootstrap',
'cache_block',
'cache_field',
'cache_filter',
'cache_form',
'cache_image',
'cache_menu',
'cache_page',
'cache_path',
'cache_update',
);
foreach ($tables as $table) {
update_add_cache_columns($table);
}
// Bootstrap variables so we can update theme while preparing the update
// process.
drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
Dries Buytaert
committed
// Update the 'language_default' system variable, if configured.
// Required to run before drupal_install_config_directories(), since that
// triggers a call into system_stream_wrappers(), which calls t(), which
// calls into language_default().
Angie Byron
committed
$language_default = update_variable_get('language_default');
if (!empty($language_default) && (isset($language_default->id) || isset($language_default->language))) {
if (!isset($language_default->id)) {
$language_default->id = $language_default->language;
}
unset($language_default->language);
// In D8, the 'language_default' is not anymore an object, but an array,
// so make sure that the new value that is saved into this variable is an
// array.
Angie Byron
committed
update_variable_set('language_default', (array) $language_default);
}
$module_config = \Drupal::config('system.module');
$theme_config = \Drupal::config('system.theme');
$disabled_themes = \Drupal::config('system.theme.disabled');
$schema_store = \Drupal::keyValue('system.schema');
// Load system.module, because update_prepare_d8_bootstrap() is called in
// the initial minimal update.php bootstrap that performs the core
// requirements check.
require_once DRUPAL_ROOT . '/core/modules/system/system.module';
catch
committed
// Make sure that the bootstrap cache is cleared as that might contain
// incompatible data structures.
cache('bootstrap')->deleteAll();
catch
committed
// Retrieve all installed extensions from the {system} table.
// Uninstalled extensions are ignored and not converted.
$result = db_query('SELECT name, status, weight, schema_version, type FROM {system} WHERE type = :theme OR (type = :module AND schema_version <> :schema_uninstalled)', array(
':theme' => 'theme',
':module' => 'module',
':schema_uninstalled' => SCHEMA_UNINSTALLED,
));
catch
committed
$module_data = _system_rebuild_module_data();
// Migrate each extension into configuration, varying by the extension's
// status, and record its schema version.
foreach ($result as $record) {
// Before migrating any extension into configuration, make sure the
// extensions name length is not higher than the limit.
if (drupal_strlen($record->name) > 50) {
$requirements['module name too long ' . $record->name] = array(
'title' => 'Module name too long',
'value' => format_string('@name is @count characters long.', array('@name' => $record->name, '@count' => drupal_strlen($record->name))),
'description' => 'Module names longer than 50 characters are <a href="https://drupal.org/node/2014073">no longer supported</a>.',
'severity' => REQUIREMENT_ERROR,
);
update_extra_requirements($requirements);
}
if ($record->type == 'module') {
if ($record->status && isset($module_data[$record->name])) {
$module_config->set('enabled.' . $record->name, $record->weight);
}
}
elseif ($record->type == 'theme') {
if ($record->status) {
$theme_config->set('enabled.' . $record->name, 0);
}
else {
$disabled_themes->set($record->name, 0);
}
}
$schema_store->set($record->name, $record->schema_version);
}
$sorted_modules = module_config_sort($module_config->get('enabled'));
$module_config->set('enabled', $sorted_modules)->save();
$sorted_with_filenames = array();
foreach (array_keys($sorted_modules) as $m) {
$sorted_with_filenames[$m] = drupal_get_filename('module', $m);
}
\Drupal::moduleHandler()->setModuleList($sorted_with_filenames);
$theme_config->save();
$disabled_themes->save();
// Migrate the private key to state. This is used to create the token for
// the upgrade batch so needs to be be done before the upgrade has begun.
update_variables_to_state(array(
'drupal_private_key' => 'system.private_key',
));
// Update the dynamic include paths that might be used before running the
// proper update functions.
update_prepare_stored_includes();
// Update the environment for the language bootstrap if needed.
update_prepare_d8_language();
// Rebuild kernel after new language fields are added in the database
// because the translation service depends on them being there.
\Drupal::service('kernel')->updateModules($sorted_with_filenames, $sorted_with_filenames);
Dries Buytaert
committed
// Change language column to langcode in url_alias.
if (db_table_exists('url_alias') && db_field_exists('url_alias', 'language')) {
db_drop_index('url_alias', 'alias_language_pid');
db_drop_index('url_alias', 'source_language_pid');
$langcode_spec = array(
'description' => "The language code this alias is for; if 'und', the alias will be used for unknown languages. Each Drupal path can have an alias for each supported language.",
'type' => 'varchar',
'length' => 12,
'not null' => TRUE,
Dries Buytaert
committed
'default' => '',
Dries Buytaert
committed
);
$langcode_indexes = array('indexes' =>
array(
'alias_langcode_pid' => array('alias', 'langcode', 'pid'),
'source_langcode_pid' => array('source', 'langcode', 'pid'),
),
);
db_change_field('url_alias', 'language', 'langcode', $langcode_spec, $langcode_indexes);
}
}
}
Dries Buytaert
committed
// Moves install_profile from variable to settings. You can't do that in
// system.install because _system_rebuild_module_data() needs the profile
// directly. Check that it has not been set already. This is the case for
// Simpletest upgrade path tests.
if (!settings()->get('install_profile')) {
$old_variable = unserialize(\Drupal::database()->query('SELECT value FROM {variable} WHERE name = :name', array(':name' => 'install_profile'))->fetchField());
Dries Buytaert
committed
$settings = array(
'settings' => array(
'install_profile' => (object) array(
'value' => $old_variable,
'required' => TRUE,
),
)
);
drupal_rewrite_settings($settings);
}
// Now remove the cache override.
$settings = settings()->getAll();
unset($settings['cache']['default']);
new Settings($settings);
$kernel = new DrupalKernel('update', drupal_classloader(), FALSE);
$kernel->boot();
// Clear the D7 caches, to ensure that for example the theme_registry does not
// take part in the upgrade process.
Drupal::cache('cache')->deleteAll();
}
/**
Jennifer Hodgdon
committed
* Fixes stored include paths to match the "/core" migration.
*/
function update_prepare_stored_includes() {
// Retrieve the currently stored language types. Default to the hardcoded D7
// values.
$default_language_types = array('language' => TRUE, 'language_content' => FALSE, 'language_url' => FALSE);
$language_types = array_keys(update_variable_get('language_types', $default_language_types));
// Update language negotiation settings.
foreach ($language_types as $language_type) {
$negotiation = update_variable_get("language_negotiation_$language_type", array());
Alex Pott
committed
foreach ($negotiation as &$method) {
Dries Buytaert
committed
if (isset($method['file']) && $method['file'] == 'includes/locale.inc') {
Dries Buytaert
committed
$method['file'] = 'core/modules/language/language.negotiation.inc';
}
}
update_variable_set("language_negotiation_$language_type", $negotiation);
}
}
Jennifer Hodgdon
committed
* Prepares Drupal 8 language changes for the bootstrap if needed.
*/
function update_prepare_d8_language() {
if (db_table_exists('languages')) {
Alex Pott
committed
\Drupal::moduleHandler()->install(array('language'));
Alex Pott
committed
$languages = db_select('languages', 'l')
->fields('l')
->execute();
$plurals = array();
$javascript = array();
$prefixes = array();
$domains = array();
foreach ($languages as $language) {
$plurals[$language->language] = array(
'plurals' => $language->plurals,
'formula' => $language->formula,
);
$javascript[$language->language] = $language->javascript;
$prefixes[$language->language] = $language->prefix;
$domains[$language->language] = $language->domain;
}
\Drupal::state()->set('locale.translation.plurals', $plurals);
\Drupal::state()->set('locale.translation.javascript', $javascript);
\Drupal::config('language.negotiation')
Dries Buytaert
committed
->set('url.prefixes', $prefixes)
->set('url.domains', $domains)
->save();
// Drop now unneeded columns.
db_drop_field('languages', 'plurals');
db_drop_field('languages', 'formula');
db_drop_field('languages', 'javascript');
db_drop_field('languages', 'prefix');
db_drop_field('languages', 'domain');
db_drop_field('languages', 'native');
Dries Buytaert
committed
db_drop_field('languages', 'enabled');
// Update language count.
\Drupal::state()->set('language_count', db_query('SELECT COUNT(language) FROM {languages}')->fetchField());
// Rename the languages table to language.
db_rename_table('languages', 'language');
Angie Byron
committed
// Rename language column to langcode and set it again as the primary key.
if (db_field_exists('language', 'language')) {
db_drop_primary_key('language');
$langcode_spec = array(
'type' => 'varchar',
'length' => 12,
'not null' => TRUE,
'default' => '',
'description' => "Language code, e.g. 'de' or 'en-US'.",
);
db_change_field('language', 'language', 'langcode', $langcode_spec, array('primary key' => array('langcode')));
}
// Adds the locked column and saves the special languages.
if (!db_field_exists('language', 'locked')) {
$locked_spec = array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
'description' => 'A boolean indicating whether the administrator can edit or delete the language.',
);
db_add_field('language', 'locked', $locked_spec);
$max_language_weight = db_query('SELECT MAX(weight) FROM {language}')->fetchField();
Dries Buytaert
committed
$languages = language_default_locked_languages($max_language_weight);
Angie Byron
committed
foreach ($languages as $language) {
db_insert('language')
->fields(array(
'langcode' => $language->id,
Angie Byron
committed
'name' => $language->name,
'weight' => $language->weight,
// These languages are locked, default to enabled.
'locked' => 1,
))
->execute();
}
}
Dries Buytaert
committed
// Update the 'language_default' system variable with the langcode change.
Angie Byron
committed
$language_default = update_variable_get('language_default');
Dries Buytaert
committed
if (!empty($language_default)) {
if (isset($language_default->language)) {
$language_default->id = $language_default->language;
Dries Buytaert
committed
unset($language_default->language);
}
unset($language_default->enabled);
Dries Buytaert
committed
// In D8, the 'language_default' is not anymore an object, but an array,
// so make sure that the new value that is saved into this variable is an
// array.
$language_default = (array) $language_default;
$language_default['langcode'] = 'en';
Angie Byron
committed
update_variable_set('language_default', $language_default);
}
// Convert languages to config entities.
$result = db_query('SELECT * FROM {language}');
$uuid = \Drupal::service('uuid');
foreach ($result as $language) {
\Drupal::config('language.entity.' . $language->langcode)
->set('id', $language->langcode)
->set('uuid', $uuid->generate())
->set('label', $language->name)
->set('direction', $language->direction)
->set('weight', $language->weight)
->set('locked', $language->locked)
->set('langcode', 'en')
->save();
Dries Buytaert
committed
}
Angie Byron
committed
// Add column to track customized string status to locales_target.
// When updating in a non-English language, the locale translation system is
// triggered, which attempts to query string translations already.
if (db_table_exists('locales_target') && !db_field_exists('locales_target', 'customized')) {
$spec = array(
'type' => 'int',
'not null' => TRUE,
'default' => 0, // LOCALE_NOT_CUSTOMIZED
'description' => 'Boolean indicating whether the translation is custom to this site.',
);
db_add_field('locales_target', 'customized', $spec);
}
catch
committed
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
// Add locales_location table to track string locations.
// When updating in a non-English language, this table is used for
// refreshing JavaScript translations.
if (db_table_exists('locales_source') && !db_table_exists('locales_location')) {
$table = array(
'description' => 'Location information for source strings.',
'fields' => array(
'lid' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Unique identifier of this location.',
),
'sid' => array(
'type' => 'int',
'not null' => TRUE,
'description' => 'Unique identifier of this string.',
),
'type' => array(
'type' => 'varchar',
'length' => 50,
'not null' => TRUE,
'default' => '',
'description' => 'The location type (file, config, path, etc).',
),
'name' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Type dependent location information (file name, path, etc).',
),
'version' => array(
'type' => 'varchar',
'length' => 20,
'not null' => TRUE,
'default' => 'none',
'description' => 'Version of Drupal where the location was found.',
),
),
'primary key' => array('lid'),
'foreign keys' => array(
'locales_source' => array(
'table' => 'locales_source',
'columns' => array('sid' => 'lid'),
),
),
'indexes' => array(
'string_id' => array('sid'),
'string_type' => array('sid', 'type'),
),
);
db_create_table('locales_location', $table);
}
}
}
Dries Buytaert
committed
/**
Jennifer Hodgdon
committed
* Performs Drupal 7.x to 8.x required update.php updates.
Dries Buytaert
committed
*
Dries Buytaert
committed
* This function runs when update.php is run the first time for 8.x,
Dries Buytaert
committed
* even before updates are selected or performed. It is important
* that if updates are not ultimately performed that no changes are
* made which make it impossible to continue using the prior version.
*/
Dries Buytaert
committed
function update_fix_d8_requirements() {
if (drupal_get_installed_schema_version('system') < 8000 && !update_variable_get('update_d8_requirements', FALSE)) {
// Make sure that file.module is enabled as it is required for the user
// picture upgrade path.
Alex Pott
committed
\Drupal::moduleHandler()->install(array('file'));
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
$schema = array(
'description' => 'Generic key/value storage table with an expiration.',
'fields' => array(
'collection' => array(
'description' => 'A named collection of key and value pairs.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'name' => array(
// KEY is an SQL reserved word, so use 'name' as the key's field name.
'description' => 'The key of the key/value pair.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'value' => array(
'description' => 'The value of the key/value pair.',
'type' => 'blob',
'not null' => TRUE,
'size' => 'big',
),
'expire' => array(
'description' => 'The time since Unix epoch in seconds when this item expires. Defaults to the maximum possible time.',
'type' => 'int',
'not null' => TRUE,
'default' => 2147483647,
),
),
'primary key' => array('collection', 'name'),
'indexes' => array(
'all' => array('name', 'collection', 'expire'),
),
);
db_create_table('key_value_expire', $schema);
Alex Pott
committed
// 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
Alex Pott
committed
// the limited standard tools of raw database queries and \Drupal::config().
Dries Buytaert
committed
\Drupal::moduleHandler()->install(array('views'));
Alex Pott
committed
update_variable_set('update_d8_requirements', TRUE);
Dries Buytaert
committed
}
Angie Byron
committed
}
Dries Buytaert
committed
/**
Alex Pott
committed
* Forces a module to a given schema version.
Alex Pott
committed
* This function is rarely necessary.
Alex Pott
committed
* @param string $module
* Name of the module.
* @param string $schema_version
* The schema version the module should be set to.
Dries Buytaert
committed
*/
Alex Pott
committed
function update_set_schema($module, $schema_version) {
\Drupal::keyValue('system.schema')->set($module, $schema_version);
Alex Pott
committed
// 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();
Dries Buytaert
committed
}
Angie Byron
committed
Dries Buytaert
committed
/**
Jennifer Hodgdon
committed
* Performs one update and stores the results for display on the results page.
Dries Buytaert
committed
*
* If an update function completes successfully, it should return a message
* as a string indicating success, for example:
* @code
* return t('New index added successfully.');
* @endcode
*
* Alternatively, it may return nothing. In that case, no message
* will be displayed at all.
*
* If it fails for whatever reason, it should throw an instance of
* Drupal\Core\Utility\UpdateException with an appropriate error message, for
* example:
* @code
* use Drupal\Core\Utility\UpdateException;
* throw new UpdateException(t('Description of what went wrong'));
* @endcode
*
Angie Byron
committed
* If an exception is thrown, the current update and all updates that depend on
* it will be aborted. The schema version will not be updated in this case, and
* all the aborted updates will continue to appear on update.php as updates
* that have not yet been run.
Dries Buytaert
committed
*
* If an update function needs to be re-run as part of a batch process, it
* should accept the $sandbox array by reference as its first parameter
* and set the #finished property to the percentage completed that it is, as a
* fraction of 1.
*
Dries Buytaert
committed
* @param $module
* The module whose update will be run.
* @param $number
* The update number to run.
Angie Byron
committed
* @param $dependency_map
* An array whose keys are the names of all update functions that will be
* performed during this batch process, and whose values are arrays of other
* update functions that each one depends on.
Dries Buytaert
committed
* @param $context
Angie Byron
committed
* The batch context array.
*
* @see update_resolve_dependencies()
Dries Buytaert
committed
*/
Angie Byron
committed
function update_do_one($module, $number, $dependency_map, &$context) {
$function = $module . '_update_' . $number;
// If this update was aborted in a previous step, or has a dependency that
// was aborted in a previous step, go no further.
Angie Byron
committed
if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) {
Dries Buytaert
committed
return;
}
$ret = array();
Dries Buytaert
committed
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);
catch
committed
$variables = Error::decodeException($e);
Angie Byron
committed
unset($variables['backtrace']);
Angie Byron
committed
// The exception message is run through
// \Drupal\Component\Utility\String::checkPlain() by
catch
committed
// \Drupal\Core\Utility\Error::decodeException().
Dries Buytaert
committed
$ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables));
Dries Buytaert
committed
}
if (isset($context['sandbox']['#finished'])) {
$context['finished'] = $context['sandbox']['#finished'];
unset($context['sandbox']['#finished']);
}
Dries Buytaert
committed
if (!isset($context['results'][$module])) {
$context['results'][$module] = array();
}
if (!isset($context['results'][$module][$number])) {
$context['results'][$module][$number] = array();
}
$context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);
if (!empty($ret['#abort'])) {
Angie Byron
committed
// Record this function in the list of updates that were aborted.
$context['results']['#abort'][] = $function;
Dries Buytaert
committed
}
Dries Buytaert
committed
// Record the schema update if it was completed successfully.
Angie Byron
committed
if ($context['finished'] == 1 && empty($ret['#abort'])) {
Dries Buytaert
committed
drupal_set_installed_schema_version($module, $number);
}
Angie Byron
committed
$context['message'] = 'Updating ' . String::checkPlain($module) . ' module';
Dries Buytaert
committed
}
Angie Byron
committed
/**
Jennifer Hodgdon
committed
* Starts the database update batch process.
Angie Byron
committed
*
* @param $start
Angie Byron
committed
* An array whose keys contain the names of modules to be updated during the
* current batch process, and whose values contain the number of the first
* requested update for that module. The actual updates that are run (and the
* order they are run in) will depend on the results of passing this data
* through the update dependency system.
Angie Byron
committed
* @param $redirect
* Path to redirect to when the batch has finished processing.
* @param $url
* URL of the batch processing page (should only be used for separate
* scripts like update.php).
* @param $batch
* Optional parameters to pass into the batch API.
Dries Buytaert
committed
* @param $redirect_callback
* (optional) Specify a function to be called to redirect to the progressive
* processing page.
Angie Byron
committed
*
* @see update_resolve_dependencies()
Angie Byron
committed
*/
function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $redirect_callback = NULL) {
Angie Byron
committed
// During the update, bring the site offline so that schema changes do not
// affect visiting users.
$maintenance_mode = \Drupal::config('system.maintenance')->get('enabled');
if (isset($maintenance_mode)) {
$_SESSION['maintenance_mode'] = $maintenance_mode;
}
if (empty($_SESSION['maintenance_mode'])) {
if (db_table_exists('state')) {
\Drupal::state()->set('system.maintenance_mode', TRUE);
}
Angie Byron
committed
}
Angie Byron
committed
// Resolve any update dependencies to determine the actual updates that will
// be run and the order they will be run in.
$updates = update_resolve_dependencies($start);
// Store the dependencies for each update function in an array which the
// batch API can pass in to the batch operation each time it is called. (We
// do not store the entire update dependency array here because it is
// potentially very large.)
$dependency_map = array();
foreach ($updates as $function => $update) {
$dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array();
}
Angie Byron
committed
$operations = array();
Angie Byron
committed
foreach ($updates as $update) {
if ($update['allowed']) {
// Set the installed version of each module so updates will start at the
// correct place. (The updates are already sorted, so we can simply base
// this on the first one we come across in the above foreach loop.)
if (isset($start[$update['module']])) {
drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
unset($start[$update['module']]);
Angie Byron
committed
}
Angie Byron
committed
// Add this update function to the batch.
Angie Byron
committed
$function = $update['module'] . '_update_' . $update['number'];
$operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
Angie Byron
committed
}
}
$batch['operations'] = $operations;
$batch += array(
'title' => 'Updating',
'init_message' => 'Starting updates',
'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
'finished' => 'update_finished',
Nate Lampton
committed
'file' => 'core/includes/update.inc',
Angie Byron
committed
);
batch_set($batch);
return batch_process($redirect, $url, $redirect_callback);
Angie Byron
committed
}
/**
Jennifer Hodgdon
committed
* Finishes the update process and stores the results for eventual display.
Angie Byron
committed
*
* After the updates run, all caches are flushed. The update results are
* stored into the session (for example, to be displayed on the update results
* page in update.php). Additionally, if the site was off-line, now that the
* update process is completed, the site is set back online.
*
* @param $success
* Indicate that the batch API tasks were all completed successfully.
* @param $results
* An array of all the results that were updated in update_do_one().
* @param $operations
* A list of all the operations that had not been completed by the batch API.
*
* @see update_batch()
*/
function update_finished($success, $results, $operations) {
// Clear the caches in case the data has been updated.
Alex Pott
committed
update_flush_all_caches();
Angie Byron
committed
$_SESSION['update_results'] = $results;
$_SESSION['update_success'] = $success;
$_SESSION['updates_remaining'] = $operations;
// Now that the update is done, we can put the site back online if it was
// previously in maintenance mode.
if (isset($_SESSION['maintenance_mode'])) {
\Drupal::state()->set('system.maintenance_mode', FALSE);
unset($_SESSION['maintenance_mode']);
Angie Byron
committed
}
}
Angie Byron
committed
/**
Jennifer Hodgdon
committed
* Returns a list of all the pending database updates.
Angie Byron
committed
*
* @return
* An associative array keyed by module name which contains all information
* about database updates that need to be run, and any updates that are not
* going to proceed due to missing requirements. The system module will
* always be listed first.
*
* The subarray for each module can contain the following keys:
* - start: The starting update that is to be processed. If this does not
* exist then do not process any updates for this module as there are
* other requirements that need to be resolved.
* - warning: Any warnings about why this module can not be updated.
* - pending: An array of all the pending updates for the module including
* the update number and the description from source code comment for
* each update function. This array is keyed by the update number.
*/
function update_get_update_list() {
// Make sure that the system module is first in the list of updates.
$ret = array('system' => array());
Angie Byron
committed
$modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
foreach ($modules as $module => $schema_version) {
// Skip uninstalled and incompatible modules.
if ($schema_version == SCHEMA_UNINSTALLED || update_check_incompatibility($module)) {
continue;
}
// Otherwise, get the list of updates defined by this module.
Angie Byron
committed
$updates = drupal_get_schema_versions($module);
if ($updates !== FALSE) {
Angie Byron
committed
// module_invoke returns NULL for nonexisting hooks, so if no updates
// are removed, it will == 0.
$last_removed = module_invoke($module, 'update_last_removed');
if ($schema_version < $last_removed) {
$ret[$module]['warning'] = '<em>' . $module . '</em> module can not 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="http://drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.';
continue;
}