diff --git a/includes/install.inc b/includes/install.inc index 55cda0bba344a2246b509a0a7c0c845e4d3db67d..c3e0536c357d957467e47df2ca27fbc219bef53a 100644 --- a/includes/install.inc +++ b/includes/install.inc @@ -131,7 +131,8 @@ function drupal_get_schema_versions($module) { * Set to TRUE if you want to get information about all modules in the * system. * @return - * The currently installed schema version. + * The currently installed schema version, or SCHEMA_UNINSTALLED if the + * module is not installed. */ function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) { static $versions = array(); @@ -148,7 +149,12 @@ function drupal_get_installed_schema_version($module, $reset = FALSE, $array = F } } - return $array ? $versions : $versions[$module]; + if ($array) { + return $versions; + } + else { + return isset($versions[$module]) ? $versions[$module] : SCHEMA_UNINSTALLED; + } } /** diff --git a/includes/update.inc b/includes/update.inc index ebea5a144ca1f207b733726adb8dbbc648d9c56e..401fd80e3a9eb9b538704ece8aff693db1a278f5 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -651,10 +651,10 @@ function update_parse_db_url($db_url) { * throw new DrupalUpdateException(t('Description of what went wrong')); * @endcode * - * If an exception is thrown, the current and all later updates for this module - * 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. + * 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. * * 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 @@ -665,13 +665,21 @@ function update_parse_db_url($db_url) { * The module whose update will be run. * @param $number * The update number to run. + * @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. * @param $context - * The batch context array + * The batch context array. + * + * @see update_resolve_dependencies() */ -function update_do_one($module, $number, &$context) { - // If updates for this module have been aborted - // in a previous step, go no further. - if (!empty($context['results'][$module]['#abort'])) { +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. + if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map[$function], array($function)))) { return; } @@ -680,7 +688,6 @@ function update_do_one($module, $number, &$context) { } $ret = array(); - $function = $module . '_update_' . $number; if (function_exists($function)) { try { if ($context['log']) { @@ -714,11 +721,12 @@ function update_do_one($module, $number, &$context) { $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); if (!empty($ret['#abort'])) { - $context['results'][$module]['#abort'] = TRUE; + // Record this function in the list of updates that were aborted. + $context['results']['#abort'][] = $function; } // Record the schema update if it was completed successfully. - if ($context['finished'] == 1 && empty($context['results'][$module]['#abort'])) { + if ($context['finished'] == 1 && empty($ret['#abort'])) { drupal_set_installed_schema_version($module, $number); } @@ -734,7 +742,11 @@ class DrupalUpdateException extends Exception { } * Start the database update batch process. * * @param $start - * An array of all the modules and which update to start at. + * 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. * @param $redirect * Path to redirect to when the batch has finished processing. * @param $url @@ -745,6 +757,8 @@ class DrupalUpdateException extends Exception { } * @param $redirect_callback * (optional) Specify a function to be called to redirect to the progressive * processing page. + * + * @see update_resolve_dependencies() */ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $redirect_callback = 'drupal_goto') { // During the update, bring the site offline so that schema changes do not @@ -754,18 +768,31 @@ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $ variable_set('maintenance_mode', TRUE); } + // 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(); + } + $operations = array(); - // Set the installed version so updates start at the correct place. - foreach ($start as $module => $version) { - drupal_set_installed_schema_version($module, $version - 1); - $updates = drupal_get_schema_versions($module); - $max_version = max($updates); - if ($version <= $max_version) { - foreach ($updates as $update) { - if ($update >= $version) { - $operations[] = array('update_do_one', array($module, $update)); - } + 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']]); } + // Add this update function to the batch. + $operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map)); } } $batch['operations'] = $operations; @@ -873,3 +900,279 @@ function update_get_update_list() { return $ret; } +/** + * 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; +} + diff --git a/modules/block/block.install b/modules/block/block.install index 497e48404d66970f4d10288d7cbe3dd98c3b4828..95129143b2a9befce28339c776907fe8fd7a9e8c 100644 --- a/modules/block/block.install +++ b/modules/block/block.install @@ -267,9 +267,19 @@ function block_update_7001() { } /** - * Change the weight column to normal int. + * Rename {blocks} table to {block}, {blocks_roles} to {block_role} and + * {boxes} to {block_custom}. */ function block_update_7002() { + db_rename_table('blocks', 'block'); + db_rename_table('blocks_roles', 'block_role'); + db_rename_table('boxes', 'block_custom'); +} + +/** + * Change the weight column to normal int. + */ +function block_update_7003() { db_drop_index('block', 'list'); db_change_field('block', 'weight', 'weight', array( 'type' => 'int', @@ -282,3 +292,162 @@ function block_update_7002() { ), )); } + +/** + * Add new blocks to new regions, migrate custom variables to blocks. + */ +function block_update_7004() { + // Collect a list of themes with blocks. + $themes_with_blocks = array(); + $result = db_query("SELECT s.name FROM {system} s INNER JOIN {block} b ON s.name = b.theme WHERE s.type = 'theme' GROUP by s.name"); + + $insert = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'pages', 'cache')); + foreach ($result as $theme) { + $themes_with_blocks[] = $theme->name; + // Add new system generated help block. + $insert->values(array( + 'module' => 'system', + 'delta' => 'help', + 'theme' => $theme->name, + 'status' => 1, + 'weight' => 0, + 'region' => 'help', + 'pages' => '', + 'cache' => 1, + )); + // Add new system generated main page content block. + $insert->values(array( + 'module' => 'system', + 'delta' => 'main', + 'theme' => $theme->name, + 'status' => 1, + 'weight' => 0, + 'region' => 'content', + 'pages' => '', + 'cache' => -1, + )); + } + $insert->execute(); + + // Migrate blocks from left/right regions to first/second regions. + db_update('block') + ->fields(array('region' => 'sidebar_first')) + ->condition('region', 'left') + ->execute(); + db_update('block') + ->fields(array('region' => 'sidebar_second')) + ->condition('region', 'right') + ->execute(); + + // Migrate contact form information. + $default_format = variable_get('filter_default_format', 1); + if ($contact_help = variable_get('contact_form_information', '')) { + $bid = db_insert('block_custom') + ->fields(array( + 'body' => $contact_help, + 'info' => 'Contact page help', + 'format' => $default_format, + )) + ->execute(); + + $insert = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'visibility', 'pages', 'cache')); + foreach ($themes_with_blocks as $theme) { + // Add contact help block for themes, which had blocks. + $insert->values(array( + 'module' => 'block', + 'delta' => $bid, + 'theme' => $theme, + 'status' => 1, + 'weight' => 5, + 'region' => 'help', + 'visibility' => 1, + 'pages' => 'contact', + 'cache' => -1, + )); + } + drupal_set_message('The contact form information setting was migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to only show on the site-wide contact page. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right.'); + } + $insert->execute(); + + // Migrate user help setting. + if ($user_help = variable_get('user_registration_help', '')) { + $bid = db_insert('block_custom')->fields(array('body' => $user_help, 'info' => 'User registration guidelines', 'format' => $default_format))->execute(); + + $insert = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'visibility', 'pages', 'cache')); + foreach ($themes_with_blocks as $theme) { + // Add user registration help block for themes, which had blocks. + $insert->values(array( + 'module' => 'block', + 'delta' => $bid, + 'theme' => $theme, + 'status' => 1, + 'weight' => 5, + 'region' => 'help', + 'visibility' => 1, + 'pages' => 'user/register', + 'cache' => -1, + )); + } + drupal_set_message('The user registration guidelines were migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to only show on the user registration page. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right.'); + $insert->execute(); + } + + // Migrate site mission setting. + if ($mission = variable_get('site_mission')) { + $bid = db_insert('block_custom')->fields(array('body' => $mission, 'info' => 'Site mission', 'format' => $default_format))->execute(); + + $insert = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'visibility', 'pages', 'cache')); + foreach ($themes_with_blocks as $theme) { + // Add mission block for themes, which had blocks. + $insert->values(array( + 'module' => 'block', + 'delta' => $bid, + 'theme' => $theme, + 'status' => 1, + 'weight' => 0, + 'region' => 'highlight', + 'visibility' => 1, + 'pages' => '<front>', + 'cache' => -1, + )); + } + drupal_set_message('The site mission was migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to only show on the front page in the highlighted content region. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right. If your theme does not have a highlighted content region, you might need to <a href="' . url('admin/structure/block') . '">relocate the block</a>.'); + $insert->execute(); + // Migrate mission to RSS site description. + variable_set('feed_description', $mission); + } + + // Migrate site footer message to a custom block. + if ($footer_message = variable_get('site_footer', '')) { + $bid = db_insert('block_custom')->fields(array('body' => $footer_message, 'info' => 'Footer message', 'format' => $default_format))->execute(); + + $insert = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'pages', 'cache')); + foreach ($themes_with_blocks as $theme) { + // Add site footer block for themes, which had blocks. + // Set low weight, so the block comes early (it used to be + // before the other blocks). + $insert->values(array( + 'module' => 'block', + 'delta' => $bid, + 'theme' => $theme, + 'status' => 1, + 'weight' => -10, + 'region' => 'footer', + 'pages' => '', + 'cache' => -1, + )); + } + drupal_set_message('The footer message was migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to appear in the footer. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right. If your theme does not have a footer region, you might need to <a href="' . url('admin/structure/block') . '">relocate the block</a>.'); + $insert->execute(); + } + + // Remove the variables (even if they were saved empty on the admin interface), + // to avoid keeping clutter in the variables table. + variable_del('contact_form_information'); + variable_del('user_registration_help'); + variable_del('site_mission'); + variable_del('site_footer'); + + // Rebuild theme data, so the new 'help' region is identified. + system_rebuild_theme_data(); +} diff --git a/modules/comment/comment.install b/modules/comment/comment.install index 6865c6b6b9e0cdd18ad04901424dc0f5a12e1483..9eff9006846d4ac553e17e68ab07f14d4f5d42fc 100644 --- a/modules/comment/comment.install +++ b/modules/comment/comment.install @@ -75,6 +75,29 @@ function comment_enable() { } } +/** + * Implements hook_update_dependencies(). + */ +function comment_update_dependencies() { + // Comment update 7005 creates comment Field API bundles and therefore must + // run after the Field module has been enabled, but before upgrading field + // data. + $dependencies['comment'][7005] = array( + 'system' => 7049, + ); + $dependencies['system'][7050] = array( + 'comment' => 7005, + ); + + // Comment update 7012 creates the comment body field and therefore must run + // after all entities have been updated. + $dependencies['comment'][7012] = array( + 'system' => 7021, + ); + + return $dependencies; +} + /** * @defgroup updates-6.x-to-7.x Comment updates from 6.x to 7.x * @{ @@ -248,9 +271,6 @@ function comment_update_7011() { * Create the comment_body field. */ function comment_update_7012() { - // @todo Remove when http://drupal.org/node/211182 is fixed. - taxonomy_update_7002(); - // Create comment body field. $field = array( 'field_name' => 'comment_body', diff --git a/modules/filter/filter.install b/modules/filter/filter.install index e41176767854c753c9db18928e00e2a1f2bcd8b5..337a066986c51a2b45a7e53961c5d3fe1ca8e856 100644 --- a/modules/filter/filter.install +++ b/modules/filter/filter.install @@ -106,6 +106,18 @@ function filter_schema() { return $schema; } +/** + * Implements hook_update_dependencies(). + */ +function filter_update_dependencies() { + // Filter update 7005 migrates block data and therefore needs to run after + // the {block_custom} table is properly set up. + $dependencies['filter'][7005] = array( + 'system' => 7037, + ); + return $dependencies; +} + /** * @defgroup updates-6.x-to-7.x Filter updates from 6.x to 7.x * @{ diff --git a/modules/node/node.install b/modules/node/node.install index 26296facb0d477d2ce318989e3c211328e39ba79..f099fffb16adb19ba9e4e9a30df62f650d2d1bcf 100644 --- a/modules/node/node.install +++ b/modules/node/node.install @@ -356,6 +356,21 @@ function node_schema() { return $schema; } +/** + * Implements hook_update_dependencies(). + */ +function node_update_dependencies() { + // Node update 7006 migrates node data to fields and therefore must run after + // the Field module has been enabled, but before upgrading field data. + $dependencies['node'][7006] = array( + 'system' => 7049, + ); + $dependencies['system'][7050] = array( + 'node' => 7006, + ); + return $dependencies; +} + /** * @defgroup updates-6.x-to-7.x System updates from 6.x to 7.x * @{ @@ -437,9 +452,6 @@ function node_update_7005() { * Convert body and teaser from node properties to fields, and migrate status/comment/promote and sticky columns to the {node_revision} table. */ function node_update_7006(&$context) { - // @todo Remove when http://drupal.org/node/211182 is fixed. - taxonomy_update_7002(); - $context['#finished'] = 0; // Get node type info for every invocation. diff --git a/modules/profile/profile.install b/modules/profile/profile.install index 35185c6c47bf8587ff6c1c0edb626aef3fb92ab8..d10dc3a065e74d9849f79130051935034c3313ae 100644 --- a/modules/profile/profile.install +++ b/modules/profile/profile.install @@ -158,7 +158,7 @@ function profile_update_7001() { * Change the weight column to normal int. */ function profile_update_7002() { - db_change_field('profile', 'weight', 'weight', array( + db_change_field('profile_field', 'weight', 'weight', array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info index 08ba39b7b440697507f50b222657da7d481db906..a66b8b9cc6d0a49a0f08923ec5348c1253219a6f 100644 --- a/modules/simpletest/simpletest.info +++ b/modules/simpletest/simpletest.info @@ -35,4 +35,5 @@ files[] = tests/schema.test files[] = tests/session.test files[] = tests/theme.test files[] = tests/unicode.test +files[] = tests/update.test files[] = tests/xmlrpc.test diff --git a/modules/simpletest/tests/update.test b/modules/simpletest/tests/update.test new file mode 100644 index 0000000000000000000000000000000000000000..0809690e63c99dc1c07099db786704d9f703a70f --- /dev/null +++ b/modules/simpletest/tests/update.test @@ -0,0 +1,88 @@ +<?php +// $Id$ + +/** + * @file + * Tests for the update system. + */ + +/** + * Tests for the update dependency ordering system. + */ +class UpdateDependencyOrderingTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Update dependency ordering', + 'description' => 'Test that update functions are run in the proper order.', + 'group' => 'Update API', + ); + } + + function setUp() { + parent::setUp('update_test_1', 'update_test_2', 'update_test_3'); + require_once DRUPAL_ROOT . '/includes/update.inc'; + } + + /** + * Test that updates within a single module run in the correct order. + */ + function testUpdateOrderingSingleModule() { + $starting_updates = array( + 'update_test_1' => 7000, + ); + $expected_updates = array( + 'update_test_1_update_7000', + 'update_test_1_update_7001', + 'update_test_1_update_7002', + ); + $actual_updates = array_keys(update_resolve_dependencies($starting_updates)); + $this->assertEqual($expected_updates, $actual_updates, t('Updates within a single module run in the correct order.')); + } + + /** + * Test that dependencies between modules are resolved correctly. + */ + function testUpdateOrderingModuleInterdependency() { + $starting_updates = array( + 'update_test_2' => 7000, + 'update_test_3' => 7000, + ); + $update_order = array_keys(update_resolve_dependencies($starting_updates)); + // Make sure that each dependency is satisfied. + $first_dependency_satisfied = array_search('update_test_2_update_7000', $update_order) < array_search('update_test_3_update_7000', $update_order); + $this->assertTrue($first_dependency_satisfied, t('The dependency of the second module on the first module is respected by the update function order.')); + $second_dependency_satisfied = array_search('update_test_3_update_7000', $update_order) < array_search('update_test_2_update_7001', $update_order); + $this->assertTrue($second_dependency_satisfied, t('The dependency of the first module on the second module is respected by the update function order.')); + } +} + +/** + * Tests for missing update dependencies. + */ +class UpdateDependencyMissingTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Missing update dependencies', + 'description' => 'Test that missing update dependencies are correctly flagged.', + 'group' => 'Update API', + ); + } + + function setUp() { + // Only install update_test_2.module, even though its updates have a + // dependency on update_test_3.module. + parent::setUp('update_test_2'); + require_once DRUPAL_ROOT . '/includes/update.inc'; + } + + function testMissingUpdate() { + $starting_updates = array( + 'update_test_2' => 7000, + ); + $update_graph = update_resolve_dependencies($starting_updates); + $this->assertTrue($update_graph['update_test_2_update_7000']['allowed'], t("The module's first update function is allowed to run, since it does not have any missing dependencies.")); + $this->assertFalse($update_graph['update_test_2_update_7001']['allowed'], t("The module's second update function is not allowed to run, since it has a direct dependency on a missing update.")); + $this->assertFalse($update_graph['update_test_2_update_7002']['allowed'], t("The module's third update function is not allowed to run, since it has an indirect dependency on a missing update.")); + } +} + diff --git a/modules/simpletest/tests/update_test_1.info b/modules/simpletest/tests/update_test_1.info new file mode 100644 index 0000000000000000000000000000000000000000..7f128129517c930f71c4efd8e2ec8c78bbdc798b --- /dev/null +++ b/modules/simpletest/tests/update_test_1.info @@ -0,0 +1,9 @@ +; $Id$ +name = "Update test" +description = "Support module for update testing." +package = Testing +version = VERSION +core = 7.x +files[] = update_test_1.module +files[] = update_test_1.install +hidden = TRUE diff --git a/modules/simpletest/tests/update_test_1.install b/modules/simpletest/tests/update_test_1.install new file mode 100644 index 0000000000000000000000000000000000000000..96e8787b3bd63a8bd55a3fb6ed3225b5925882af --- /dev/null +++ b/modules/simpletest/tests/update_test_1.install @@ -0,0 +1,25 @@ +<?php +// $Id$ + +/** + * @file + * Install, update and uninstall functions for the update_test_1 module. + */ + +/** + * Dummy update_test_1 update 7000. + */ +function update_test_1_update_7000() { +} + +/** + * Dummy update_test_1 update 7001. + */ +function update_test_1_update_7001() { +} + +/** + * Dummy update_test_1 update 7002. + */ +function update_test_1_update_7002() { +} diff --git a/modules/simpletest/tests/update_test_1.module b/modules/simpletest/tests/update_test_1.module new file mode 100644 index 0000000000000000000000000000000000000000..13bb990c402e41fc1232a92361bde3761cd4b861 --- /dev/null +++ b/modules/simpletest/tests/update_test_1.module @@ -0,0 +1,2 @@ +<?php +// $Id$ diff --git a/modules/simpletest/tests/update_test_2.info b/modules/simpletest/tests/update_test_2.info new file mode 100644 index 0000000000000000000000000000000000000000..00db1eb98c42fb6ba24408d665bdf5d07166df3a --- /dev/null +++ b/modules/simpletest/tests/update_test_2.info @@ -0,0 +1,9 @@ +; $Id$ +name = "Update test" +description = "Support module for update testing." +package = Testing +version = VERSION +core = 7.x +files[] = update_test_2.module +files[] = update_test_2.install +hidden = TRUE diff --git a/modules/simpletest/tests/update_test_2.install b/modules/simpletest/tests/update_test_2.install new file mode 100644 index 0000000000000000000000000000000000000000..e2b4fb25fb38974812977d3a3fb22f52436b79fc --- /dev/null +++ b/modules/simpletest/tests/update_test_2.install @@ -0,0 +1,35 @@ +<?php +// $Id$ + +/** + * @file + * Install, update and uninstall functions for the update_test_2 module. + */ + +/** + * Implements hook_update_dependencies(). + */ +function update_test_2_update_dependencies() { + $dependencies['update_test_2'][7001] = array( + 'update_test_3' => 7000, + ); + return $dependencies; +} + +/** + * Dummy update_test_2 update 7000. + */ +function update_test_2_update_7000() { +} + +/** + * Dummy update_test_2 update 7001. + */ +function update_test_2_update_7001() { +} + +/** + * Dummy update_test_2 update 7002. + */ +function update_test_2_update_7002() { +} diff --git a/modules/simpletest/tests/update_test_2.module b/modules/simpletest/tests/update_test_2.module new file mode 100644 index 0000000000000000000000000000000000000000..13bb990c402e41fc1232a92361bde3761cd4b861 --- /dev/null +++ b/modules/simpletest/tests/update_test_2.module @@ -0,0 +1,2 @@ +<?php +// $Id$ diff --git a/modules/simpletest/tests/update_test_3.info b/modules/simpletest/tests/update_test_3.info new file mode 100644 index 0000000000000000000000000000000000000000..c193f42749fd8b2087174855d2bf89cd76443fba --- /dev/null +++ b/modules/simpletest/tests/update_test_3.info @@ -0,0 +1,9 @@ +; $Id$ +name = "Update test" +description = "Support module for update testing." +package = Testing +version = VERSION +core = 7.x +files[] = update_test_3.module +files[] = update_test_3.install +hidden = TRUE diff --git a/modules/simpletest/tests/update_test_3.install b/modules/simpletest/tests/update_test_3.install new file mode 100644 index 0000000000000000000000000000000000000000..952ebf0b67a51dae2a5681e2c1a99eccae6bfe4e --- /dev/null +++ b/modules/simpletest/tests/update_test_3.install @@ -0,0 +1,23 @@ +<?php +// $Id$ + +/** + * @file + * Install, update and uninstall functions for the update_test_3 module. + */ + +/** + * Implements hook_update_dependencies(). + */ +function update_test_3_update_dependencies() { + $dependencies['update_test_3'][7000] = array( + 'update_test_2' => 7000, + ); + return $dependencies; +} + +/** + * Dummy update_test_3 update 7000. + */ +function update_test_3_update_7000() { +} diff --git a/modules/simpletest/tests/update_test_3.module b/modules/simpletest/tests/update_test_3.module new file mode 100644 index 0000000000000000000000000000000000000000..13bb990c402e41fc1232a92361bde3761cd4b861 --- /dev/null +++ b/modules/simpletest/tests/update_test_3.module @@ -0,0 +1,2 @@ +<?php +// $Id$ diff --git a/modules/system/system.api.php b/modules/system/system.api.php index c18058c4dee1ba363c86135fa1f24057c42d9f1d..65ab9725cba0b45efde783a3a9bbd315bcc77eb4 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -2232,6 +2232,51 @@ function hook_update_N(&$sandbox) { throw new DrupalUpdateException('Something went wrong; here is what you should do.'); } +/** + * Return an array of information about module update dependencies. + * + * This can be used to indicate update functions from other modules that your + * module's update functions depend on, or vice versa. It is used by the update + * system to determine the appropriate order in which updates should be run, as + * well as to search for missing dependencies. + * + * Implementations of this hook should be placed in a mymodule.install file in + * the same directory as mymodule.module. + * + * @return + * A multidimensional array containing information about the module update + * dependencies. The first two levels of keys represent the module and update + * number (respectively) for which information is being returned, and the + * value is an array of information about that update's dependencies. Within + * this array, each key represents a module, and each value represents the + * number of an update function within that module. In the event that your + * update function depends on more than one update from a particular module, + * you should always list the highest numbered one here (since updates within + * a given module always run in numerical order). + * + * @see update_resolve_dependencies() + * @see hook_update_N() + */ +function hook_update_dependencies() { + // Indicate that the mymodule_update_7000() function provided by this module + // must run after the another_module_update_7002() function provided by the + // 'another_module' module. + $dependencies['mymodule'][7000] = array( + 'another_module' => 7002, + ); + // Indicate that the mymodule_update_7001() function provided by this module + // must run before the yet_another_module_update_7004() function provided by + // the 'yet_another_module' module. (Note that declaring dependencies in this + // direction should be done only in rare situations, since it can lead to the + // following problem: If a site has already run the yet_another_module + // module's database updates before it updates its codebase to pick up the + // newest mymodule code, then the dependency declared here will be ignored.) + $dependencies['yet_another_module'][7004] = array( + 'mymodule' => 7001, + ); + return $dependencies; +} + /** * Return a number which is no longer available as hook_update_N(). * diff --git a/modules/system/system.install b/modules/system/system.install index 5d1f63bf605d9528e9efe5ea6d6cdb945afc38eb..162c6b90d7b5bb9156303581e14f54c4deae22ac 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -1882,15 +1882,7 @@ function system_update_7011() { $insert->execute(); } -/** - * Rename {blocks} table to {block}, {blocks_roles} to {block_role} and - * {boxes} to {box}. - */ -function system_update_7012() { - db_rename_table('blocks', 'block'); - db_rename_table('blocks_roles', 'block_role'); - db_rename_table('boxes', 'box'); -} +// system_update_7012() moved to block_update_7002(). /** * Convert default time zone offset to default time zone name. @@ -2027,176 +2019,10 @@ function system_update_7020() { module_enable($module_list, FALSE); } -/** - * Add new blocks to new regions, migrate custom variables to blocks. - */ -function system_update_7021() { - // Collect a list of themes with blocks. - $themes_with_blocks = array(); - $result = db_query("SELECT s.name FROM {system} s INNER JOIN {block} b ON s.name = b.theme WHERE s.type = 'theme' GROUP by s.name"); - - $insert = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'pages', 'cache')); - foreach ($result as $theme) { - $themes_with_blocks[] = $theme->name; - // Add new system generated help block. - $insert->values(array( - 'module' => 'system', - 'delta' => 'help', - 'theme' => $theme->name, - 'status' => 1, - 'weight' => 0, - 'region' => 'help', - 'pages' => '', - 'cache' => 1, - )); - // Add new system generated main page content block. - $insert->values(array( - 'module' => 'system', - 'delta' => 'main', - 'theme' => $theme->name, - 'status' => 1, - 'weight' => 0, - 'region' => 'content', - 'pages' => '', - 'cache' => -1, - )); - } - $insert->execute(); - - // Migrate blocks from left/right regions to first/second regions. - db_update('block') - ->fields(array('region' => 'sidebar_first')) - ->condition('region', 'left') - ->execute(); - db_update('block') - ->fields(array('region' => 'sidebar_second')) - ->condition('region', 'right') - ->execute(); - - // Migrate contact form information. - $default_format = variable_get('filter_default_format', 1); - if ($contact_help = variable_get('contact_form_information', '')) { - $bid = db_insert('box') - ->fields(array( - 'body' => $contact_help, - 'info' => 'Contact page help', - 'format' => $default_format, - )) - ->execute(); - - $insert = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'visibility', 'pages', 'cache')); - foreach ($themes_with_blocks as $theme) { - // Add contact help block for themes, which had blocks. - $insert->values(array( - 'module' => 'block', - 'delta' => $bid, - 'theme' => $theme, - 'status' => 1, - 'weight' => 5, - 'region' => 'help', - 'visibility' => 1, - 'pages' => 'contact', - 'cache' => -1, - )); - } - drupal_set_message('The contact form information setting was migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to only show on the site-wide contact page. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right.'); - } - $insert->execute(); - - // Migrate user help setting. - if ($user_help = variable_get('user_registration_help', '')) { - $bid = db_insert('box')->fields(array('body' => $user_help, 'info' => 'User registration guidelines', 'format' => $default_format))->execute(); - - $insert = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'visibility', 'pages', 'cache')); - foreach ($themes_with_blocks as $theme) { - // Add user registration help block for themes, which had blocks. - $insert->values(array( - 'module' => 'block', - 'delta' => $bid, - 'theme' => $theme, - 'status' => 1, - 'weight' => 5, - 'region' => 'help', - 'visibility' => 1, - 'pages' => 'user/register', - 'cache' => -1, - )); - } - drupal_set_message('The user registration guidelines were migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to only show on the user registration page. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right.'); - $insert->execute(); - } - - // Migrate site mission setting. - if ($mission = variable_get('site_mission')) { - $bid = db_insert('box')->fields(array('body' => $mission, 'info' => 'Site mission', 'format' => $default_format))->execute(); - - $insert = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'visibility', 'pages', 'cache')); - foreach ($themes_with_blocks as $theme) { - // Add mission block for themes, which had blocks. - $insert->values(array( - 'module' => 'block', - 'delta' => $bid, - 'theme' => $theme, - 'status' => 1, - 'weight' => 0, - 'region' => 'highlight', - 'visibility' => 1, - 'pages' => '<front>', - 'cache' => -1, - )); - } - drupal_set_message('The site mission was migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to only show on the front page in the highlighted content region. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right. If your theme does not have a highlighted content region, you might need to <a href="' . url('admin/structure/block') . '">relocate the block</a>.'); - $insert->execute(); - // Migrate mission to RSS site description. - variable_set('feed_description', $mission); - } - - // Migrate site footer message to a custom block. - if ($footer_message = variable_get('site_footer', '')) { - $bid = db_insert('box')->fields(array('body' => $footer_message, 'info' => 'Footer message', 'format' => $default_format))->execute(); - - $insert = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'pages', 'cache')); - foreach ($themes_with_blocks as $theme) { - // Add site footer block for themes, which had blocks. - // Set low weight, so the block comes early (it used to be - // before the other blocks). - $insert->values(array( - 'module' => 'block', - 'delta' => $bid, - 'theme' => $theme, - 'status' => 1, - 'weight' => -10, - 'region' => 'footer', - 'pages' => '', - 'cache' => -1, - )); - } - drupal_set_message('The footer message was migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to appear in the footer. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right. If your theme does not have a footer region, you might need to <a href="' . url('admin/structure/block') . '">relocate the block</a>.'); - $insert->execute(); - } - - // Remove the variables (even if they were saved empty on the admin interface), - // to avoid keeping clutter in the variables table. - variable_del('contact_form_information'); - variable_del('user_registration_help'); - variable_del('site_mission'); - variable_del('site_footer'); - - // Rebuild theme data, so the new 'help' region is identified. - system_rebuild_theme_data(); -} - -/** - * Add the queue tables. - */ -function system_update_7022() { - // Moved to update_fix_d7_requirements(). -} - /** * Change the PHP for settings permission. */ -function system_update_7023() { +function system_update_7021() { db_update('role_permission') ->fields(array('permission' => 'use PHP for settings')) ->condition('permission', 'use PHP for block visibility') @@ -2245,18 +2071,7 @@ function system_update_7027() { module_enable($module_list, FALSE); } -/** - * Rename taxonomy tables. - */ -function system_update_7028() { - db_rename_table('term_data', 'taxonomy_term_data'); - db_rename_table('term_hierarchy', 'taxonomy_term_hierarchy'); - db_rename_table('term_node', 'taxonomy_term_node'); - db_rename_table('term_relation', 'taxonomy_term_relation'); - db_rename_table('term_synonym', 'taxonomy_term_synonym'); - db_rename_table('vocabulary', 'taxonomy_vocabulary'); - db_rename_table('vocabulary_node_types', 'taxonomy_vocabulary_node_type'); -} +// system_update_7028() moved to taxonomy_update_7001(). /** * Add new 'view own unpublished content' permission for authenticated users. @@ -2404,7 +2219,7 @@ function system_update_7035() { // The old {files} tables still exists. We migrate core data from upload // module, but any contrib module using it will need to do its own update. - $result = db_query('SELECT fid, uid, filename, filepath AS uri, filemime, filesize, status, timestamp FROM {files} f INNER JOIN {upload} u ON u.fid = f.fid', array(), array('fetch' => PDO::FETCH_ASSOC)); + $result = db_query('SELECT f.fid, uid, filename, filepath AS uri, filemime, filesize, status, timestamp FROM {files} f INNER JOIN {upload} u ON u.fid = f.fid', array(), array('fetch' => PDO::FETCH_ASSOC)); // We will convert filepaths to uri using the default schmeme // and stripping off the existing file directory path. @@ -2445,17 +2260,10 @@ function system_update_7036() { variable_del('site_offline_message'); } -/** - * Rename {box} table to {block_custom}. - */ -function system_update_7037() { - db_rename_table('box', 'block_custom'); -} - /** * Rename action description to label. */ -function system_update_7038() { +function system_update_7037() { db_change_field('actions', 'description', 'label', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '0')); } diff --git a/modules/taxonomy/taxonomy.install b/modules/taxonomy/taxonomy.install index a4408bb9342365c4737dbf7627640b988fa95e70..d84797fcbc9111a18bdf1cffd441e95134d06b1e 100644 --- a/modules/taxonomy/taxonomy.install +++ b/modules/taxonomy/taxonomy.install @@ -205,31 +205,60 @@ function taxonomy_schema() { return $schema; } +/** + * Implements hook_update_dependencies(). + */ +function taxonomy_update_dependencies() { + // Taxonomy update 7002 creates comment Field API bundles and therefore must + // run after the Field module has been enabled, but before upgrading field + // data. + $dependencies['taxonomy'][7002] = array( + 'system' => 7049, + ); + $dependencies['system'][7050] = array( + 'taxonomy' => 7002, + ); + // It also must run before nodes are upgraded to use the Field API. + $dependencies['node'][7006] = array( + 'taxonomy' => 7002, + ); + return $dependencies; +} + +/** + * Rename taxonomy tables. + */ +function taxonomy_update_7001() { + db_rename_table('term_data', 'taxonomy_term_data'); + db_rename_table('term_hierarchy', 'taxonomy_term_hierarchy'); + db_rename_table('term_node', 'taxonomy_term_node'); + db_rename_table('term_relation', 'taxonomy_term_relation'); + db_rename_table('term_synonym', 'taxonomy_term_synonym'); + db_rename_table('vocabulary', 'taxonomy_vocabulary'); + db_rename_table('vocabulary_node_types', 'taxonomy_vocabulary_node_type'); +} + /** * Add {vocabulary}.machine_name column. */ function taxonomy_update_7002() { - if (!variable_get('taxonomy_update_7002_done', FALSE)) { - $field = array( - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The vocabulary machine name.', - ); - - db_add_field('taxonomy_vocabulary', 'machine_name', $field); + $field = array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The vocabulary machine name.', + ); - foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) { - $machine_name = 'vocabulary_' . $vid; - db_update('taxonomy_vocabulary') - ->fields(array('machine_name' => 'vocabulary_' . $vid)) - ->condition('vid', $vid) - ->execute(); - field_attach_create_bundle('taxonomy_term', $machine_name); - } + db_add_field('taxonomy_vocabulary', 'machine_name', $field); - variable_set('taxonomy_update_7002_done', TRUE); + foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) { + $machine_name = 'vocabulary_' . $vid; + db_update('taxonomy_vocabulary') + ->fields(array('machine_name' => 'vocabulary_' . $vid)) + ->condition('vid', $vid) + ->execute(); + field_attach_create_bundle('taxonomy_term', $machine_name); } } diff --git a/update.php b/update.php index 7ad82f5838ec01463efb24966209b7252602e659..5c01ea92e0c9e00e57b40b5ffac8574003c2643b 100644 --- a/update.php +++ b/update.php @@ -39,6 +39,7 @@ function update_selection_page() { function update_script_selection_form($form, &$form_state) { $count = 0; + $incompatible_count = 0; $form['start'] = array( '#tree' => TRUE, '#type' => 'fieldset', @@ -50,6 +51,8 @@ function update_script_selection_form($form, &$form_state) { $form['start']['system'] = array(); $updates = update_get_update_list(); + $starting_updates = array(); + $incompatible_updates_exist = FALSE; foreach ($updates as $module => $update) { if (!isset($update['start'])) { $form['start'][$module] = array( @@ -58,15 +61,19 @@ function update_script_selection_form($form, &$form_state) { '#prefix' => '<div class="warning">', '#suffix' => '</div>', ); + $incompatible_updates_exist = TRUE; continue; } if (!empty($update['pending'])) { + $starting_updates[$module] = $update['start']; $form['start'][$module] = array( '#type' => 'hidden', '#value' => $update['start'], ); $form['start'][$module . '_updates'] = array( - '#markup' => theme('item_list', array('items' => $update['pending'], 'title' => $module . ' module')), + '#theme' => 'item_list', + '#items' => $update['pending'], + '#title' => $module . ' module', ); } if (isset($update['pending'])) { @@ -74,6 +81,26 @@ function update_script_selection_form($form, &$form_state) { } } + // Find and label any incompatible updates. + foreach (update_resolve_dependencies($starting_updates) as $function => $data) { + if (!$data['allowed']) { + $incompatible_updates_exist = TRUE; + $incompatible_count++; + $module_update_key = $data['module'] . '_updates'; + if (isset($form['start'][$module_update_key]['#items'][$data['number']])) { + $text = $data['missing_dependencies'] ? 'This update will been skipped due to the following missing dependencies: <em>' . implode(', ', $data['missing_dependencies']) . '</em>' : "This update will be skipped due to an error in the module's code."; + $form['start'][$module_update_key]['#items'][$data['number']] .= '<div class="warning">' . $text . '</div>'; + } + // Move the module containing this update to the top of the list. + $form['start'] = array($module_update_key => $form['start'][$module_update_key]) + $form['start']; + } + } + + // Warn the user if any updates were incompatible. + if ($incompatible_updates_exist) { + drupal_set_message('Some of the pending updates cannot be applied because their dependencies were not met.', 'warning'); + } + if (empty($count)) { drupal_set_message(t('No pending updates.')); unset($form); @@ -86,7 +113,17 @@ function update_script_selection_form($form, &$form_state) { '#markup' => '<p>The version of Drupal you are updating from has been automatically detected.</p>', '#weight' => -5, ); - $form['start']['#title'] = format_plural($count, '1 pending update', '@count pending updates'); + if ($incompatible_count) { + $form['start']['#title'] = format_plural( + $count, + '1 pending update (@number_applied to be applied, @number_incompatible skipped)', + '@count pending updates (@number_applied to be applied, @number_incompatible skipped)', + array('@number_applied' => $count - $incompatible_count, '@number_incompatible' => $incompatible_count) + ); + } + else { + $form['start']['#title'] = format_plural($count, '1 pending update', '@count pending updates'); + } $form['has_js'] = array( '#type' => 'hidden', '#default_value' => FALSE, @@ -142,9 +179,9 @@ function update_results_page() { $output .= '<div id="update-results">'; $output .= '<h2>The following updates returned messages</h2>'; foreach ($_SESSION['update_results'] as $module => $updates) { - $output .= '<h3>' . $module . ' module</h3>'; - foreach ($updates as $number => $queries) { - if ($number != '#abort') { + if ($module != '#abort') { + $output .= '<h3>' . $module . ' module</h3>'; + foreach ($updates as $number => $queries) { $messages = array(); foreach ($queries as $query) { // If there is no message for this update, don't show anything.