2009-07-29 05:59:59 +00:00
< ? php
// $Id$
/**
* @ 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 .
*/
/**
2010-01-25 10:38:35 +00:00
* Disable any items in the { system } table that are not core compatible .
2009-07-29 05:59:59 +00:00
*/
function update_fix_compatibility () {
$incompatible = array ();
2009-09-18 00:04:24 +00:00
$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 ;
2009-07-29 05:59:59 +00:00
}
}
if ( ! empty ( $incompatible )) {
2009-11-09 01:51:24 +00:00
db_update ( 'system' )
-> fields ( array ( 'status' => 0 ))
-> condition ( 'name' , $incompatible , 'IN' )
-> execute ();
2009-07-29 05:59:59 +00:00
}
}
/**
* Helper function to test compatibility of a module or theme .
*/
function update_check_incompatibility ( $name , $type = 'module' ) {
static $themes , $modules ;
// Store values of expensive functions for future use.
if ( empty ( $themes ) || empty ( $modules )) {
2009-10-13 05:26:57 +00:00
$themes = _system_rebuild_theme_data ();
$modules = system_rebuild_module_data ();
2009-07-29 05:59:59 +00:00
}
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
|| version_compare ( phpversion (), $file -> info [ 'php' ]) < 0
|| ( $type == 'module' && empty ( $file -> info [ 'files' ]))) {
return TRUE ;
}
return FALSE ;
}
/**
2009-12-31 13:43:36 +00:00
* Performs extra steps required to bootstrap when using a Drupal 6 database .
*
2009-07-29 05:59:59 +00:00
* Users who still have a Drupal 6 database ( and are in the process of
* updating to Drupal 7 ) need extra help before a full bootstrap can be
* 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
* changes to the database should be made here .
*/
function update_prepare_d7_bootstrap () {
// Allow the bootstrap to proceed even if a Drupal 6 settings.php file is
// still being used.
include_once DRUPAL_ROOT . '/includes/install.inc' ;
drupal_bootstrap ( DRUPAL_BOOTSTRAP_CONFIGURATION );
global $databases , $db_url , $update_rewrite_settings ;
if ( empty ( $databases ) && ! empty ( $db_url )) {
$databases = update_parse_db_url ( $db_url );
// Record the fact that the settings.php file will need to be rewritten.
$update_rewrite_settings = TRUE ;
$settings_file = conf_path () . '/settings.php' ;
$writable = drupal_verify_install_file ( $settings_file , FILE_EXIST | FILE_READABLE | FILE_WRITABLE );
$requirements = array (
'settings file' => array (
'title' => 'Settings file' ,
'value' => $writable ? 'The settings file is writable.' : 'The settings file is not writable.' ,
'severity' => $writable ? REQUIREMENT_OK : REQUIREMENT_ERROR ,
2010-01-09 23:03:22 +00:00
'description' => $writable ? '' : '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>.' ,
2009-07-29 05:59:59 +00:00
),
);
update_extra_requirements ( $requirements );
}
2009-09-21 08:07:07 +00:00
// The new {blocked_ips} table is used in Drupal 7 to store a list of
// banned IP addresses. If this table doesn't exist then we are still
// running on a Drupal 6 database, so we suppress the unavoidable errors
// that occur by creating a static list.
$GLOBALS [ 'conf' ][ 'blocked_ips' ] = array ();
2009-07-29 05:59:59 +00:00
// Allow the database system to work even if the registry has not been
// created yet.
drupal_bootstrap ( DRUPAL_BOOTSTRAP_DATABASE );
drupal_install_initialize_database ();
spl_autoload_unregister ( 'drupal_autoload_class' );
spl_autoload_unregister ( 'drupal_autoload_interface' );
2009-12-08 07:07:15 +00:00
// The new cache_bootstrap bin is required to bootstrap to
// DRUPAL_BOOTSTRAP_SESSION, so create it here rather than in
// update_fix_d7_requirements().
$cache_bootstrap = array (
'description' => 'Cache table for data required to bootstrap Drupal, may be routed to a shared memory cache.' ,
'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 ,
),
'headers' => array (
'description' => 'Any custom HTTP headers to be added to cached data.' ,
'type' => 'text' ,
'not null' => FALSE ,
),
'serialized' => array (
'description' => 'A flag to indicate whether content is serialized (1) or not (0).' ,
'type' => 'int' ,
'size' => 'small' ,
'not null' => TRUE ,
'default' => 0 ,
),
),
'indexes' => array (
'expire' => array ( 'expire' ),
),
'primary key' => array ( 'cid' ),
);
db_create_table ( 'cache_bootstrap' , $cache_bootstrap );
2009-07-29 05:59:59 +00:00
}
2010-01-13 06:15:39 +00:00
/**
* A helper function that modules can use to assist with the transformation
* from numeric block deltas to string block deltas during the 6. x -> 7. x
* upgrade .
*
* @ todo This function should be removed in 8. x .
*
* @ param $sandbox
* An array holding data for the batch process .
* @ param $renamed_deltas
* An associative array . Keys are module names , values an associative array
* mapping the old block deltas to the new block deltas for the module .
* Example :
* $renamed_deltas = array (
* 'mymodule' =>
* array (
* 0 => 'mymodule-block-1' ,
* 1 => 'mymodule-block-2' ,
2010-02-07 17:33:41 +00:00
* ),
2010-01-13 06:15:39 +00:00
* );
*/
function update_fix_d7_block_deltas ( & $sandbox , $renamed_deltas ) {
// Loop through each block and make changes to the block tables.
// Only run this the first time through the batch update.
if ( ! isset ( $sandbox [ 'progress' ])) {
2010-02-07 05:09:59 +00:00
// Determine whether to use the old or new block table names.
$block_tables = db_table_exists ( 'blocks' ) ? array ( 'blocks' , 'blocks_roles' ) : array ( 'block' , 'block_role' );
2010-01-13 06:15:39 +00:00
foreach ( $block_tables as $table ) {
foreach ( $renamed_deltas as $module => $deltas ) {
foreach ( $deltas as $old_delta => $new_delta ) {
// Only do the update if the old block actually exists.
$block_exists = db_query ( " SELECT COUNT(*) FROM { " . $table . " } WHERE module = :module AND delta = :delta " , array (
':module' => $module ,
':delta' => $old_delta ,
))
-> fetchField ();
if ( $block_exists ) {
db_update ( $table )
-> fields ( array ( 'delta' => $new_delta ))
-> condition ( 'module' , $module )
-> condition ( 'delta' , $old_delta )
-> execute ();
}
}
}
}
// Initialize batch update information.
$sandbox [ 'progress' ] = 0 ;
$sandbox [ 'last_user_processed' ] = - 1 ;
$sandbox [ 'max' ] = db_query ( " SELECT COUNT(*) FROM { users} WHERE data IS NOT NULL " ) -> fetchField ();
}
// Now do the batch update of the user-specific block visibility settings.
$limit = 100 ;
$result = db_select ( 'users' , 'u' )
-> fields ( 'u' , array ( 'uid' , 'data' ))
-> condition ( 'uid' , $sandbox [ 'last_user_processed' ], '>' )
-> where ( 'data IS NOT NULL' )
-> range ( 0 , $limit )
-> execute ();
foreach ( $result as $row ) {
$data = unserialize ( $row -> data );
$user_needs_update = FALSE ;
foreach ( $renamed_deltas as $module => $deltas ) {
foreach ( $deltas as $old_delta => $new_delta ) {
if ( isset ( $data [ 'block' ][ $module ][ $old_delta ])) {
// Transfer the old block visibility settings to the newly-renamed
// block, and mark this user for a database update.
$data [ 'block' ][ $module ][ $new_delta ] = $data [ 'block' ][ $module ][ $old_delta ];
unset ( $data [ 'block' ][ $module ][ $old_delta ]);
$user_needs_update = TRUE ;
}
}
}
// Update the current user.
if ( $user_needs_update ) {
db_update ( 'users' )
-> fields ( array ( 'data' => serialize ( $data )))
-> condition ( 'uid' , $row -> uid )
-> execute ();
}
// Update our progress information for the batch update.
$sandbox [ 'progress' ] ++ ;
$sandbox [ 'last_user_processed' ] = $row -> uid ;
}
// Indicate our current progress to the batch update system.
if ( $sandbox [ 'progress' ] < $sandbox [ 'max' ]) {
$sandbox [ '#finished' ] = $sandbox [ 'progress' ] / $sandbox [ 'max' ];
}
}
2009-07-29 05:59:59 +00:00
/**
* Perform Drupal 6. x to 7. x updates that are required for update . php
* to function properly .
*
* This function runs when update . php is run the first time for 7. x ,
* 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 .
*/
function update_fix_d7_requirements () {
2009-08-22 18:24:14 +00:00
global $conf ;
2009-07-29 05:59:59 +00:00
2009-12-31 13:43:36 +00:00
// Rewrite the settings.php file if necessary, see
// update_prepare_d7_bootstrap().
2009-07-29 05:59:59 +00:00
global $update_rewrite_settings , $db_url ;
if ( ! empty ( $update_rewrite_settings )) {
$databases = update_parse_db_url ( $db_url );
2010-01-14 18:45:17 +00:00
$salt = sha1 ( drupal_random_bytes ( 64 ));
file_put_contents ( conf_path () . '/settings.php' , " \n " . '$databases = ' . var_export ( $databases , TRUE ) . " ; \n \$ drupal_hash_salt = ' $salt '; " , FILE_APPEND );
2009-07-29 05:59:59 +00:00
}
if ( drupal_get_installed_schema_version ( 'system' ) < 7000 && ! variable_get ( 'update_d7_requirements' , FALSE )) {
2010-02-04 05:10:00 +00:00
// Change 6.x system table field values to 7.x equivalent.
// Change field values.
db_change_field ( 'system' , 'schema_version' , 'schema_version' , array (
'type' => 'int' ,
'size' => 'small' ,
'not null' => TRUE ,
'default' => - 1 )
);
db_change_field ( 'system' , 'status' , 'status' , array (
2010-02-04 17:03:19 +00:00
'type' => 'int' , 'not null' => TRUE , 'default' => 0 ));
2010-02-04 05:10:00 +00:00
db_change_field ( 'system' , 'weight' , 'weight' , array (
2010-02-04 17:03:19 +00:00
'type' => 'int' , 'not null' => TRUE , 'default' => 0 ));
2010-02-04 05:10:00 +00:00
db_change_field ( 'system' , 'bootstrap' , 'bootstrap' , array (
2010-02-04 17:03:19 +00:00
'type' => 'int' , 'not null' => TRUE , 'default' => 0 ));
2010-02-04 05:10:00 +00:00
// Drop and recreate 6.x indexes.
db_drop_index ( 'system' , 'bootstrap' );
db_add_index ( 'system' , 'bootstrap' , array (
'status' , 'bootstrap' , array ( 'type' , 12 ), 'weight' , 'name' ));
db_drop_index ( 'system' , 'modules' );
db_add_index ( 'system' , 'modules' , array ( array (
'type' , 12 ), 'status' , 'weight' , 'name' ));
db_drop_index ( 'system' , 'type_name' );
db_add_index ( 'system' , 'type_name' , array ( array ( 'type' , 12 ), 'name' ));
// Add 7.x indexes.
db_add_index ( 'system' , 'system_list' , array ( 'weight' , 'name' ));
2009-07-29 05:59:59 +00:00
// Add the cache_path table.
$schema [ 'cache_path' ] = drupal_get_schema_unprocessed ( 'system' , 'cache' );
$schema [ 'cache_path' ][ 'description' ] = 'Cache table used for path alias lookups.' ;
2010-01-09 22:07:57 +00:00
db_create_table ( 'cache_path' , $schema [ 'cache_path' ]);
2009-10-24 01:22:28 +00:00
// system_update_7042() renames columns, but these are needed to bootstrap.
2009-10-24 01:46:13 +00:00
// Add empty columns for now.
2009-10-24 01:22:28 +00:00
db_add_field ( 'url_alias' , 'source' , array ( 'type' => 'varchar' , 'length' => 255 , 'not null' => TRUE , 'default' => '' ));
db_add_field ( 'url_alias' , 'alias' , array ( 'type' => 'varchar' , 'length' => 255 , 'not null' => TRUE , 'default' => '' ));
2009-07-29 05:59:59 +00:00
2009-10-24 03:20:43 +00:00
// Add new columns to {menu_router}.
2009-10-24 02:22:53 +00:00
db_add_field ( 'menu_router' , 'delivery_callback' , array ( 'type' => 'varchar' , 'length' => 255 , 'not null' => TRUE , 'default' => '' ));
2009-10-24 03:20:43 +00:00
db_add_field ( 'menu_router' , 'context' , array (
'description' => 'Only for local tasks (tabs) - the context of a local task to control its placement.' ,
'type' => 'int' ,
'not null' => TRUE ,
'default' => 0 ,
));
2009-11-07 13:35:21 +00:00
db_drop_index ( 'menu_router' , 'tab_parent' );
db_add_index ( 'menu_router' , 'tab_parent' , array ( array ( 'tab_parent' , 64 ), 'weight' , 'title' ));
2009-10-24 03:20:43 +00:00
db_add_field ( 'menu_router' , 'theme_callback' , array ( 'type' => 'varchar' , 'length' => 255 , 'not null' => TRUE , 'default' => '' ));
db_add_field ( 'menu_router' , 'theme_arguments' , array ( 'type' => 'varchar' , 'length' => 255 , 'not null' => TRUE , 'default' => '' ));
2009-10-24 02:22:53 +00:00
2010-01-25 10:38:35 +00:00
// Add the role_permission table.
2009-10-24 01:46:13 +00:00
$schema [ 'role_permission' ] = array (
'fields' => array (
'rid' => array (
'type' => 'int' ,
'unsigned' => TRUE ,
'not null' => TRUE ,
),
'permission' => array (
'type' => 'varchar' ,
'length' => 64 ,
'not null' => TRUE ,
'default' => '' ,
),
),
'primary key' => array ( 'rid' , 'permission' ),
'indexes' => array (
'permission' => array ( 'permission' ),
),
);
db_create_table ( 'role_permission' , $schema [ 'role_permission' ]);
2009-10-24 03:20:43 +00:00
// Add the {semaphore} table in case menu_rebuild() gets called during
// an update.
$schema [ 'semaphore' ] = array (
'description' => 'Table for holding semaphores, locks, flags, etc. that cannot be stored as Drupal variables since they must not be cached.' ,
'fields' => array (
'name' => array (
'description' => 'Primary Key: Unique name.' ,
'type' => 'varchar' ,
'length' => 255 ,
'not null' => TRUE ,
'default' => ''
),
'value' => array (
'description' => 'A value for the semaphore.' ,
'type' => 'varchar' ,
'length' => 255 ,
'not null' => TRUE ,
'default' => ''
),
'expire' => array (
'description' => 'A Unix timestamp with microseconds indicating when the semaphore should expire.' ,
'type' => 'float' ,
'size' => 'big' ,
'not null' => TRUE
),
),
'indexes' => array ( 'value' => array ( 'value' )),
'primary key' => array ( 'name' ),
);
db_create_table ( 'semaphore' , $schema [ 'semaphore' ]);
2010-01-09 22:07:57 +00:00
// Add registry tables since these are required during an update.
$schema [ 'registry' ] = array (
'fields' => array (
'name' => array ( 'type' => 'varchar' , 'length' => 255 , 'not null' => TRUE , 'default' => '' ),
'type' => array ( 'type' => 'varchar' , 'length' => 9 , 'not null' => TRUE , 'default' => '' ),
'filename' => array ( 'type' => 'varchar' , 'length' => 255 , 'not null' => TRUE , 'default' => '' ),
'module' => array ( 'type' => 'varchar' , 'length' => 255 , 'not null' => TRUE , 'default' => '' ),
'weight' => array ( 'type' => 'int' , 'not null' => TRUE , 'default' => 0 ),
),
'primary key' => array ( 'name' , 'type' ),
'indexes' => array (
'hook' => array ( 'type' , 'weight' , 'module' ),
),
);
$schema [ 'registry_file' ] = array (
'fields' => array (
'filename' => array ( 'type' => 'varchar' , 'length' => 255 , 'not null' => TRUE ),
'filectime' => array ( 'type' => 'int' , 'not null' => TRUE , 'default' => 0 ),
'filemtime' => array ( 'type' => 'int' , 'not null' => TRUE , 'default' => 0 ),
),
'primary key' => array ( 'filename' ),
);
db_create_table ( 'registry' , $schema [ 'registry' ]);
db_create_table ( 'registry_file' , $schema [ 'registry_file' ]);
2009-10-24 23:19:21 +00:00
$schema [ 'date_format_type' ] = array (
'description' => 'Stores configured date format types.' ,
'fields' => array (
'type' => array (
'description' => 'The date format type, e.g. medium.' ,
'type' => 'varchar' ,
'length' => 64 ,
'not null' => TRUE ,
),
'title' => array (
'description' => 'The human readable name of the format type.' ,
'type' => 'varchar' ,
'length' => 255 ,
'not null' => TRUE ,
),
'locked' => array (
'description' => 'Whether or not this is a system provided format.' ,
'type' => 'int' ,
'size' => 'tiny' ,
'default' => 0 ,
'not null' => TRUE ,
),
),
'primary key' => array ( 'type' ),
);
$schema [ 'date_formats' ] = array (
'description' => 'Stores configured date formats.' ,
'fields' => array (
'dfid' => array (
'description' => 'The date format identifier.' ,
'type' => 'serial' ,
'not null' => TRUE ,
'unsigned' => TRUE ,
),
'format' => array (
'description' => 'The date format string.' ,
'type' => 'varchar' ,
'length' => 100 ,
'not null' => TRUE ,
),
'type' => array (
'description' => 'The date format type, e.g. medium.' ,
'type' => 'varchar' ,
'length' => 64 ,
'not null' => TRUE ,
),
'locked' => array (
'description' => 'Whether or not this format can be modified.' ,
'type' => 'int' ,
'size' => 'tiny' ,
'default' => 0 ,
'not null' => TRUE ,
),
),
'primary key' => array ( 'dfid' ),
'unique keys' => array ( 'formats' => array ( 'format' , 'type' )),
);
$schema [ 'date_format_locale' ] = array (
'description' => 'Stores configured date formats for each locale.' ,
'fields' => array (
'format' => array (
'description' => 'The date format string.' ,
'type' => 'varchar' ,
'length' => 100 ,
'not null' => TRUE ,
),
'type' => array (
'description' => 'The date format type, e.g. medium.' ,
'type' => 'varchar' ,
'length' => 64 ,
'not null' => TRUE ,
),
'language' => array (
'description' => 'A {languages}.language for this format to be used with.' ,
'type' => 'varchar' ,
'length' => 12 ,
'not null' => TRUE ,
),
),
'primary key' => array ( 'type' , 'language' ),
);
db_create_table ( 'date_format_type' , $schema [ 'date_format_type' ]);
db_create_table ( 'date_formats' , $schema [ 'date_formats' ]);
db_create_table ( 'date_format_locale' , $schema [ 'date_format_locale' ]);
2010-01-08 06:36:34 +00:00
// Add the queue table.
$schema [ 'queue' ] = array (
'description' => 'Stores items in queues.' ,
'fields' => array (
'item_id' => array (
'type' => 'serial' ,
'unsigned' => TRUE ,
'not null' => TRUE ,
'description' => 'Primary Key: Unique item ID.' ,
),
'name' => array (
'type' => 'varchar' ,
'length' => 255 ,
'not null' => TRUE ,
'default' => '' ,
'description' => 'The queue name.' ,
),
'data' => array (
'type' => 'text' ,
'not null' => FALSE ,
'size' => 'big' ,
'serialize' => TRUE ,
'description' => 'The arbitrary data for the item.' ,
),
'expire' => array (
'type' => 'int' ,
'not null' => TRUE ,
'default' => 0 ,
'description' => 'Timestamp when the claim lease expires on the item.' ,
),
'created' => array (
'type' => 'int' ,
'not null' => TRUE ,
'default' => 0 ,
'description' => 'Timestamp when the item was created.' ,
),
),
'primary key' => array ( 'item_id' ),
'indexes' => array (
'name_created' => array ( 'name' , 'created' ),
'expire' => array ( 'expire' ),
),
);
2010-02-25 09:42:39 +00:00
// Check for queue table that may remain from D5 or D6, if found
//drop it.
if ( db_table_exists ( 'queue' )) {
db_drop_table ( 'queue' );
}
2010-01-08 06:36:34 +00:00
db_create_table ( 'queue' , $schema [ 'queue' ]);
2010-01-09 02:51:09 +00:00
// Create the sequences table.
$schema [ 'sequences' ] = array (
'description' => 'Stores IDs.' ,
'fields' => array (
'value' => array (
'description' => 'The value of the sequence.' ,
'type' => 'serial' ,
'unsigned' => TRUE ,
'not null' => TRUE ,
),
),
'primary key' => array ( 'value' ),
);
2010-02-25 09:42:39 +00:00
// Check for sequences table that may remain from D5 or D6, if found
//drop it.
if ( db_table_exists ( 'sequences' )) {
db_drop_table ( 'sequences' );
}
2010-01-09 02:51:09 +00:00
db_create_table ( 'sequences' , $schema [ 'sequences' ]);
// Initialize the table with the maximum current increment of the tables
// that will rely on it for their ids.
$max_aid = db_query ( 'SELECT MAX(aid) FROM {actions_aid}' ) -> fetchField ();
$max_uid = db_query ( 'SELECT MAX(uid) FROM {users}' ) -> fetchField ();
$max_batch_id = db_query ( 'SELECT MAX(bid) FROM {batch}' ) -> fetchField ();
db_insert ( 'sequences' ) -> fields ( array ( 'value' => max ( $max_aid , $max_uid , $max_batch_id ))) -> execute ();
2009-07-29 05:59:59 +00:00
// Add column for locale context.
if ( db_table_exists ( 'locales_source' )) {
2009-09-29 15:13:57 +00:00
db_add_field ( 'locales_source' , 'context' , array ( 'type' => 'varchar' , 'length' => 255 , 'not null' => TRUE , 'default' => '' , 'description' => 'The context this string applies to.' ));
2009-07-29 05:59:59 +00:00
}
2009-08-22 18:24:14 +00:00
// Rename 'site_offline_message' variable to 'maintenance_mode_message'.
2009-12-31 13:43:36 +00:00
// Old variable is removed in update for system.module, see
// system_update_7036().
2009-08-22 18:24:14 +00:00
if ( $message = variable_get ( 'site_offline_message' , NULL )) {
variable_set ( 'maintenance_mode_message' , $message );
}
variable_set ( 'update_d7_requirements' , TRUE );
2009-07-29 05:59:59 +00:00
}
2009-08-21 07:50:08 +00:00
update_fix_d7_install_profile ();
2009-07-29 05:59:59 +00:00
}
2009-08-21 07:50:08 +00:00
/**
* Register the currently installed profile in the system table .
*
2009-12-31 13:43:36 +00:00
* Install profiles are now treated as modules by Drupal , and have an upgrade
* path based on their schema version in the system table .
2009-09-07 15:43:55 +00:00
*
2009-08-21 07:50:08 +00:00
* The install profile will be set to schema_version 0 , as it has already been
* installed . Any other hook_update_N functions provided by the install profile
* will be run by update . php .
*/
function update_fix_d7_install_profile () {
$profile = drupal_get_profile ();
$results = db_select ( 'system' , 's' )
-> fields ( 's' , array ( 'name' , 'schema_version' ))
-> condition ( 'name' , $profile )
-> condition ( 'type' , 'module' )
-> execute ()
-> fetchAll ();
if ( empty ( $results )) {
$filename = 'profiles/' . $profile . '/' . $profile . '.profile' ;
// Read profile info file
$info = drupal_parse_info_file ( dirname ( $filename ) . '/' . $profile . '.info' );
// Merge in defaults.
$info = $info + array (
'dependencies' => array (),
'dependents' => array (),
'description' => '' ,
'package' => 'Other' ,
'version' => NULL ,
'php' => DRUPAL_MINIMUM_PHP ,
'files' => array (),
);
// The install profile is always required.
$file -> info [ 'required' ] = TRUE ;
$values = array (
'filename' => $filename ,
'name' => $profile ,
'info' => serialize ( $info ),
'schema_version' => 0 ,
'type' => 'module' ,
'status' => 1 ,
'owner' => '' ,
);
// Install profile hooks are always executed last by the module system
$values [ 'weight' ] = 1000 ;
// Initializing the system table entry for the install profile
db_insert ( 'system' )
-> fields ( array_keys ( $values ))
-> values ( $values )
-> execute ();
// Reset the cached schema version.
drupal_get_installed_schema_version ( $profile , TRUE );
// Load the updates again to make sure the install profile updates are loaded
drupal_load_updates ();
}
}
2009-07-29 05:59:59 +00:00
/**
2010-01-25 10:38:35 +00:00
* Parse pre - Drupal 7 database connection URLs and return D7 compatible array .
2009-12-31 13:43:36 +00:00
*
* @ return
* Drupal 7 DBTNG compatible array of database connection information .
2009-07-29 05:59:59 +00:00
*/
function update_parse_db_url ( $db_url ) {
$databases = array ();
if ( ! is_array ( $db_url )) {
$db_url = array ( 'default' => $db_url );
}
foreach ( $db_url as $database => $url ) {
$url = parse_url ( $url );
$databases [ $database ][ 'default' ] = array (
// MySQLi uses the mysql driver.
'driver' => $url [ 'scheme' ] == 'mysqli' ? 'mysql' : $url [ 'scheme' ],
// Remove the leading slash to get the database name.
'database' => substr ( urldecode ( $url [ 'path' ]), 1 ),
'username' => urldecode ( $url [ 'user' ]),
'password' => isset ( $url [ 'pass' ]) ? urldecode ( $url [ 'pass' ]) : '' ,
'host' => urldecode ( $url [ 'host' ]),
'port' => isset ( $url [ 'port' ]) ? urldecode ( $url [ 'port' ]) : '' ,
);
}
return $databases ;
}
/**
2009-12-31 13:43:36 +00:00
* Perform one update and store the results for display on finished page .
2009-07-29 05:59:59 +00:00
*
2009-09-07 15:43:55 +00:00
* 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
* DrupalUpdateException with an appropriate error message , for example :
* @ code
* throw new DrupalUpdateException ( t ( 'Description of what went wrong' ));
* @ endcode
*
2010-02-03 18:16:23 +00:00
* 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 .
2009-07-29 05:59:59 +00:00
*
2009-09-07 15:43:55 +00:00
* 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.
*
2009-07-29 05:59:59 +00:00
* @ param $module
* The module whose update will be run .
* @ param $number
* The update number to run .
2010-02-03 18:16:23 +00:00
* @ 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 .
2009-07-29 05:59:59 +00:00
* @ param $context
2010-02-03 18:16:23 +00:00
* The batch context array .
*
* @ see update_resolve_dependencies ()
2009-07-29 05:59:59 +00:00
*/
2010-02-03 18:16:23 +00:00
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.
2010-02-17 20:45:36 +00:00
if ( ! empty ( $context [ 'results' ][ '#abort' ]) && array_intersect ( $context [ 'results' ][ '#abort' ], array_merge ( $dependency_map , array ( $function )))) {
2009-07-29 05:59:59 +00:00
return ;
}
2009-09-07 15:43:55 +00:00
if ( ! isset ( $context [ 'log' ])) {
$context [ 'log' ] = variable_get ( 'update_log_queries' , 0 );
}
$ret = array ();
2009-07-29 05:59:59 +00:00
if ( function_exists ( $function )) {
2009-09-07 15:43:55 +00:00
try {
if ( $context [ 'log' ]) {
Database :: startLog ( $function );
}
$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 print the message.
catch ( Exception $e ) {
$ret [ '#abort' ] = array ( 'success' => FALSE , 'query' => $e -> getMessage ());
}
if ( $context [ 'log' ]) {
$ret [ 'queries' ] = Database :: getLog ( $function );
}
2009-07-29 05:59:59 +00:00
}
2009-09-07 15:43:55 +00:00
if ( isset ( $context [ 'sandbox' ][ '#finished' ])) {
$context [ 'finished' ] = $context [ 'sandbox' ][ '#finished' ];
unset ( $context [ 'sandbox' ][ '#finished' ]);
}
2009-07-29 05:59:59 +00:00
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' ])) {
2010-02-03 18:16:23 +00:00
// Record this function in the list of updates that were aborted.
$context [ 'results' ][ '#abort' ][] = $function ;
2009-07-29 05:59:59 +00:00
}
2009-09-07 15:43:55 +00:00
2009-07-29 05:59:59 +00:00
// Record the schema update if it was completed successfully.
2010-02-03 18:16:23 +00:00
if ( $context [ 'finished' ] == 1 && empty ( $ret [ '#abort' ])) {
2009-07-29 05:59:59 +00:00
drupal_set_installed_schema_version ( $module , $number );
}
$context [ 'message' ] = 'Updating ' . check_plain ( $module ) . ' module' ;
}
2009-09-07 15:43:55 +00:00
/**
* @ class Exception class used to throw error if a module update fails .
*/
class DrupalUpdateException extends Exception { }
2009-08-03 19:37:38 +00:00
/**
* Start the database update batch process .
*
* @ param $start
2010-02-03 18:16:23 +00:00
* 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 .
2009-08-03 19:37:38 +00:00
* @ 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 .
2009-10-03 20:17:46 +00:00
* @ param $redirect_callback
* ( optional ) Specify a function to be called to redirect to the progressive
* processing page .
2010-02-03 18:16:23 +00:00
*
* @ see update_resolve_dependencies ()
2009-08-03 19:37:38 +00:00
*/
2009-10-03 20:17:46 +00:00
function update_batch ( $start , $redirect = NULL , $url = NULL , $batch = array (), $redirect_callback = 'drupal_goto' ) {
2009-08-03 19:37:38 +00:00
// During the update, bring the site offline so that schema changes do not
// affect visiting users.
2009-08-22 18:24:14 +00:00
$_SESSION [ 'maintenance_mode' ] = variable_get ( 'maintenance_mode' , FALSE );
if ( $_SESSION [ 'maintenance_mode' ] == FALSE ) {
variable_set ( 'maintenance_mode' , TRUE );
2009-08-03 19:37:38 +00:00
}
2010-02-03 18:16:23 +00:00
// 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 ();
}
2009-08-03 19:37:38 +00:00
$operations = array ();
2010-02-03 18:16:23 +00:00
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' ]]);
2009-08-03 19:37:38 +00:00
}
2010-02-03 18:16:23 +00:00
// Add this update function to the batch.
2010-02-17 20:45:36 +00:00
$function = $update [ 'module' ] . '_update_' . $update [ 'number' ];
$operations [] = array ( 'update_do_one' , array ( $update [ 'module' ], $update [ 'number' ], $dependency_map [ $function ]));
2009-08-03 19:37:38 +00:00
}
}
$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' ,
'file' => 'includes/update.inc' ,
);
batch_set ( $batch );
2009-10-03 20:17:46 +00:00
batch_process ( $redirect , $url , $redirect_callback );
2009-08-03 19:37:38 +00:00
}
/**
* Finish the update process and store results for eventual display .
*
* 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.
drupal_flush_all_caches ();
$_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
2009-08-22 18:24:14 +00:00
// previously in maintenance mode.
if ( isset ( $_SESSION [ 'maintenance_mode' ]) && $_SESSION [ 'maintenance_mode' ] == FALSE ) {
variable_set ( 'maintenance_mode' , FALSE );
unset ( $_SESSION [ 'maintenance_mode' ]);
2009-08-03 19:37:38 +00:00
}
}
2009-08-21 06:40:05 +00:00
/**
* Return a list of all the pending database updates .
*
* @ 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 ());
2009-09-07 15:43:55 +00:00
2009-08-21 06:40:05 +00:00
$modules = drupal_get_installed_schema_version ( NULL , FALSE , TRUE );
foreach ( $modules as $module => $schema_version ) {
$pending = array ();
$updates = drupal_get_schema_versions ( $module );
// Skip incompatible module updates, otherwise test schema versions.
if ( ! update_check_incompatibility ( $module ) && $updates !== FALSE && $schema_version >= 0 ) {
// 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 ;
}
2009-09-07 15:43:55 +00:00
2009-08-21 06:40:05 +00:00
$updates = drupal_map_assoc ( $updates );
foreach ( array_keys ( $updates ) as $update ) {
if ( $update > $schema_version ) {
// The description for an update comes from its Doxygen.
$func = new ReflectionFunction ( $module . '_update_' . $update );
$description = str_replace ( array ( " \n " , '*' , '/' ), '' , $func -> getDocComment ());
$ret [ $module ][ 'pending' ][ $update ] = " $update - $description " ;
if ( ! isset ( $ret [ $module ][ 'start' ])) {
$ret [ $module ][ 'start' ] = $update ;
}
}
}
if ( ! isset ( $ret [ $module ][ 'start' ]) && isset ( $ret [ $module ][ 'pending' ])) {
$ret [ $module ][ 'start' ] = $schema_version ;
}
}
}
2009-09-07 15:43:55 +00:00
2009-08-21 06:40:05 +00:00
if ( empty ( $ret [ 'system' ])) {
unset ( $ret [ 'system' ]);
}
return $ret ;
}
2010-02-03 18:16:23 +00:00
/**
* Resolves dependencies in a set of module updates , and orders them correctly .
*
* This function receives a list of requested module updates and determines an
* appropriate order to run them in such that all update dependencies are met .
* Any updates whose dependencies cannot be met are included in the returned
* array but have the key 'allowed' set to FALSE ; the calling function should
* take responsibility for ensuring that these updates are ultimately not
* performed .
*
* In addition , the returned array also includes detailed information about the
* dependency chain for each update , as provided by the depth - first search
* algorithm in drupal_depth_first_search () .
*
* @ param $starting_updates
* An array whose keys contain the names of modules with updates to be run
* and whose values contain the number of the first requested update for that
* module .
*
* @ return
* An array whose keys are the names of all update functions within the
* provided modules that would need to be run in order to fulfill the
* request , arranged in the order in which the update functions should be
* run . ( This includes the provided starting update for each module and all
* subsequent updates that are available . ) The values are themselves arrays
* containing all the keys provided by the drupal_depth_first_search ()
* algorithm , which encode detailed information about the dependency chain
* for this update function ( for example : 'paths' , 'reverse_paths' , 'weight' ,
* and 'component' ), as well as the following additional keys :
* - 'allowed' : A boolean which is TRUE when the update function ' s
* dependencies are met , and FALSE otherwise . Calling functions should
* inspect this value before running the update .
* - 'missing_dependencies' : An array containing the names of any other
* update functions that are required by this one but that are unavailable
* to be run . This array will be empty when 'allowed' is TRUE .
* - 'module' : The name of the module that this update function belongs to .
* - 'number' : The number of this update function within that module .
*
* @ see drupal_depth_first_search ()
*/
function update_resolve_dependencies ( $starting_updates ) {
// Obtain a dependency graph for the requested update functions.
$update_functions = update_get_update_function_list ( $starting_updates );
$graph = update_build_dependency_graph ( $update_functions );
// Perform the depth-first search and sort the results.
require_once DRUPAL_ROOT . '/includes/graph.inc' ;
drupal_depth_first_search ( $graph );
uasort ( $graph , 'drupal_sort_weight' );
foreach ( $graph as $function => & $data ) {
$module = $data [ 'module' ];
$number = $data [ 'number' ];
// If the update function is missing and has not yet been performed, mark
// it and everything that ultimately depends on it as disallowed.
if ( update_is_missing ( $module , $number , $update_functions ) && ! update_already_performed ( $module , $number )) {
$data [ 'allowed' ] = FALSE ;
foreach ( array_keys ( $data [ 'paths' ]) as $dependent ) {
$graph [ $dependent ][ 'allowed' ] = FALSE ;
$graph [ $dependent ][ 'missing_dependencies' ][] = $function ;
}
}
elseif ( ! isset ( $data [ 'allowed' ])) {
$data [ 'allowed' ] = TRUE ;
$data [ 'missing_dependencies' ] = array ();
}
// Now that we have finished processing this function, remove it from the
// graph if it was not part of the original list. This ensures that we
// never try to run any updates that were not specifically requested.
if ( ! isset ( $update_functions [ $module ][ $number ])) {
unset ( $graph [ $function ]);
}
}
return $graph ;
}
/**
* Returns an organized list of update functions for a set of modules .
*
* @ param $starting_updates
* An array whose keys contain the names of modules and whose values contain
* the number of the first requested update for that module .
*
* @ return
* An array containing all the update functions that should be run for each
* module , including the provided starting update and all subsequent updates
* that are available . The keys of the array contain the module names , and
* each value is an ordered array of update functions , keyed by the update
* number .
*
* @ see update_resolve_dependencies ()
*/
function update_get_update_function_list ( $starting_updates ) {
// Go through each module and find all updates that we need (including the
// first update that was requested and any updates that run after it).
$update_functions = array ();
foreach ( $starting_updates as $module => $version ) {
$update_functions [ $module ] = array ();
$updates = drupal_get_schema_versions ( $module );
$max_version = max ( $updates );
if ( $version <= $max_version ) {
foreach ( $updates as $update ) {
if ( $update >= $version ) {
$update_functions [ $module ][ $update ] = $module . '_update_' . $update ;
}
}
}
}
return $update_functions ;
}
/**
* Constructs a graph which encodes the dependencies between module updates .
*
* This function returns an associative array which contains a " directed graph "
* representation of the dependencies between a provided list of update
* functions , as well as any outside update functions that they directly depend
* on but that were not in the provided list . The vertices of the graph
* represent the update functions themselves , and each edge represents a
* requirement that the first update function needs to run before the second .
* For example , consider this graph :
*
* system_update_7000 ---> system_update_7001 ---> system_update_7002
*
* Visually , this indicates that system_update_7000 () must run before
* system_update_7001 (), which in turn must run before system_update_7002 () .
*
* The function takes into account standard dependencies within each module , as
* shown above ( i . e . , the fact that each module ' s updates must run in numerical
* order ), but also finds any cross - module dependencies that are defined by
* modules which implement hook_update_dependencies (), and builds them into the
* graph as well .
*
* @ param $update_functions
* An organized array of update functions , in the format returned by
* update_get_update_function_list () .
*
* @ return
* A multidimensional array representing the dependency graph , suitable for
* passing in to drupal_depth_first_search (), but with extra information
* about each update function also included . Each array key contains the name
* of an update function , including all update functions from the provided
* list as well as any outside update functions which they directly depend
* on . Each value is an associative array containing the following keys :
* - 'edges' : A representation of any other update functions that immediately
* depend on this one . See drupal_depth_first_search () for more details on
* the format .
* - 'module' : The name of the module that this update function belongs to .
* - 'number' : The number of this update function within that module .
*
* @ see drupal_depth_first_search ()
* @ see update_resolve_dependencies ()
*/
function update_build_dependency_graph ( $update_functions ) {
// Initialize an array that will define a directed graph representing the
// dependencies between update functions.
$graph = array ();
// Go through each update function and build an initial list of dependencies.
foreach ( $update_functions as $module => $functions ) {
$previous_function = NULL ;
foreach ( $functions as $number => $function ) {
// Add an edge to the directed graph representing the fact that each
// update function in a given module must run after the update that
// numerically precedes it.
if ( $previous_function ) {
$graph [ $previous_function ][ 'edges' ][ $function ] = TRUE ;
}
$previous_function = $function ;
// Define the module and update number associated with this function.
$graph [ $function ][ 'module' ] = $module ;
$graph [ $function ][ 'number' ] = $number ;
}
}
// Now add any explicit update dependencies declared by modules.
$update_dependencies = update_invoke_all ( 'update_dependencies' );
foreach ( $graph as $function => $data ) {
if ( ! empty ( $update_dependencies [ $data [ 'module' ]][ $data [ 'number' ]])) {
foreach ( $update_dependencies [ $data [ 'module' ]][ $data [ 'number' ]] as $module => $number ) {
// If we have an explicit dependency on more than one update from a
// particular module, choose the highest one, since that contains the
// actual direct dependency.
if ( is_array ( $number )) {
$number = max ( $number );
}
$dependency = $module . '_update_' . $number ;
$graph [ $dependency ][ 'edges' ][ $function ] = TRUE ;
$graph [ $dependency ][ 'module' ] = $module ;
$graph [ $dependency ][ 'number' ] = $number ;
}
}
}
return $graph ;
}
/**
* Determines if a module update is missing or unavailable .
*
* @ param $module
* The name of the module .
* @ param $number
* The number of the update within that module .
* @ param $update_functions
* An organized array of update functions , in the format returned by
* update_get_update_function_list () . This should represent all module
* updates that are requested to run at the time this function is called .
*
* @ return
* TRUE if the provided module update is not installed or is not in the
* provided list of updates to run ; FALSE otherwise .
*/
function update_is_missing ( $module , $number , $update_functions ) {
return ! isset ( $update_functions [ $module ][ $number ]) || ! function_exists ( $update_functions [ $module ][ $number ]);
}
/**
* Determines if a module update has already been performed .
*
* @ param $module
* The name of the module .
* @ param $number
* The number of the update within that module .
*
* @ return
* TRUE if the database schema indicates that the update has already been
* performed ; FALSE otherwise .
*/
function update_already_performed ( $module , $number ) {
return $number <= drupal_get_installed_schema_version ( $module );
}
/**
* Invoke an update system hook in all installed modules .
*
* This function is similar to module_invoke_all (), except it does not require
* that a module be enabled to invoke its hook , only that it be installed . This
* allows the update system to properly perform updates even on modules that
* are currently disabled .
*
* @ param $hook
* The name of the hook to invoke .
* @ param ...
* Arguments to pass to the hook .
*
* @ return
* An array of return values of the hook implementations . If modules return
* arrays from their implementations , those are merged into one array .
*
* @ see module_invoke_all ()
*/
function update_invoke_all () {
$args = func_get_args ();
$hook = $args [ 0 ];
unset ( $args [ 0 ]);
$return = array ();
$modules = db_query ( " SELECT name FROM { system} WHERE type = 'module' AND schema_version != :schema " , array ( ':schema' => SCHEMA_UNINSTALLED )) -> fetchCol ();
foreach ( $modules as $module ) {
$function = $module . '_' . $hook ;
if ( function_exists ( $function )) {
$result = call_user_func_array ( $function , $args );
if ( isset ( $result ) && is_array ( $result )) {
$return = array_merge_recursive ( $return , $result );
}
elseif ( isset ( $result )) {
$return [] = $result ;
}
}
}
return $return ;
}