Scripted D6 to D7 Drupal Upgrades

AKA, How to upgrade a complex Drupal site to D7

So Drupal 6 reaches end of life on Februrary 24. If you're like many of my clients, only now are you getting around to making that upgrade happen. Accordingly, I've done a bunch of D6 to D7 upgrades recently, and wanted to share the recipe I now follow to make things go as smoothly as possible.

This process has worked out extremely well even for moderately complex sites, for instance e-commerce sites using Ubercart.

1. Site audit

Auditing the site means taking stock of any installed contrib modules to determine, first of all, if they have a D7 version, and second, if they are necessary, what they do, and how they can be replaced if necessary.

This results in a few lists: modules that have a D7 version and thus can hopefully be upgraded; modules without a D7 version that need to be disabled / replaced; modules that need to be upgraded to D7 (often custom modules).

And of course a D7 theme needs to be chosen or created. But I suggest leaving theme work until later for now.

2. Prepare upgrade scripts

You'll want to automate as much as possible, partly because you'll be running this process a few times, and because scripts are a great form of record-keeping.

Here are samples of the scripts I use for my upgrades. This is the master upgrade script that takes a site through the whole proces. Note that I use drush for everything, and so put in some generic aliases below. Adjust for your own situation of course.

#!/bin/bash

echo Update files
cp -ruv /var/www/drupal_6/sites/D6SITE/files/* /var/www/drupal_7/sites/D7SITE/files/
echo "---"

echo Dump live D6 database
drush @d6.alias sql-dump > D6.sql
echo "----"

echo Drop D7 Tables
drush @d7.alias sql-drop
echo "---"

echo Load D6 database into D7
drush @d7.alias sql-cli < D6.sql
echo "---"

echo Clear caches, make pre-upgrade changes
drush @d7.alias sql-cli < d6_cleanup.sql
echo "---"

echo Perform D7 update
drush @d7.alias updb -y
echo "---"

echo Fix image styles
drush @d7.alias scr imagecache_migrate
echo "---"

echo Enable D7 Modules
drush @d7.alias en dblog -y
drush @d7.alias en devel -y
drush @d7.alias en contextual -y
drush @d7.alias en references -y
drush @d7.alias en user_reference -y
drush @d7.alias en node_reference -y
drush @d7.alias en content_migrate -y
drush @d7.alias en sharethis -y
drush @d7.alias en superfish -y
drush @d7.alias en features -y
drush @d7.alias en uuid -y
drush @d7.alias en uuid_features -y
drush @d7.alias en fe_block -y
echo "---"

echo Migrate fields
drush @d7.alias content-migrate-fields -y

echo Misc cleanup
drush @d7.alias vset admin_theme seven
echo "---"

echo Enable Feature
drush @d7.alias en some_feature -y
drush @d7.alias fr some_feature -y --force
echo "---"

echo DONE
exit 0

If you use imagecache in D6, you'll need to convert the caching settings into D7 image styles. This script (called imagecache_migrate.php and invoked with drush) does that. (I found it on D.O: https://www.drupal.org/node/1316472#comment-5430882)

<?php

function imagecache_preset_actions($preset, $reset = FALSE) {
  $actions_cache = array();
    $result = db_query('SELECT * FROM {imagecache_action} where presetid = '.$preset['presetid'].' order by weight' );
    foreach ($result as $row ) {
    $row=(array)$row;
      $row['data'] = unserialize($row['data']);
      $actions_cache[$preset['presetid']][] = $row;
    }

  return isset($actions_cache[$preset['presetid']]) ? $actions_cache[$preset['presetid']] : array();
}

function imagecache_presets() {
  $presets = array();

    $normal_presets = array();

    $result = db_query('SELECT * FROM {imagecache_preset} ORDER BY presetname');
    
    foreach ($result as $preset) {
      $preset=(array)$preset;
      $presets[$preset['presetid']] = $preset;
      $presets[$preset['presetid']]['actions'] = imagecache_preset_actions($preset);
      $presets[$preset['presetid']]['storage'] = 0;

      // Collect normal preset names so we can skip defaults and mark overrides accordingly
      $normal_presets[$preset['presetname']] = $preset['presetid'];
    }

  return $presets;
}

$styles_cnt=0;
$effects_new_cnt=0;
$effects_ext_cnt=0;

$presets=imagecache_presets();
foreach($presets as $preset)
    {
    $styles_cnt++;// inc styles
    
    $style=image_style_load($preset['presetname']);
    
    $style['name']=$preset['presetname'];
    $style=image_style_save($style);
    if(!isset($style['effects']))
        {
        $style['effects']=array();
        }
    foreach($preset['actions'] as $action)
        {
        $action['action']=str_replace('imagecache','image',$action['action']);
        $action['module']=str_replace('imagecache','image',$action['module']);
        
        $effect_ieid=FALSE;// effect not exists
        foreach($style['effects'] as $effect)
            {
            if($effect['name'] == $action['action'] && 
                $effect['module'] == $action['module'] && 
                $effect['weight'] == $action['weight'] && 
                $effect['data'] == $action['data'] )
                {
                $effect_ieid=$effect['ieid'];// effect exists
                }
            }
        $effect=array();    
        if($effect_ieid)
            {
            $effects_ext_cnt++;// inc exists 
            $effect=image_effect_load($effect_ieid,$style['name']);
            }
        else
            {
            $effects_new_cnt++;// inc new
            $effect=image_effect_definition_load($action['action']);
            }
        
        $effect['isid'] = $style['isid'];    
            
        $effect['name'] = $action['action'];
        $effect['module'] == $action['module'];
        $effect['weight'] = $action['weight'];
        $effect['data'] = $action['data'];
        $effect = image_effect_save($effect);
        $style['effects'][$effect['ieid']] = $effect;
        }
    $style=image_style_save($style);
    }

print "Styles: $styles_cnt,    Effects new: $effects_new_cnt, Effects exists: $effects_ext_cnt \n";

?>

And I use a file full of SQL statements (in the file d6_cleanup.sql) to make a bunch of changes to the D6 database before running the upgrade. Here's a representative example:

UPDATE system SET status = 0 WHERE name = 'old_d6_default_theme';
UPDATE system SET status = 1 WHERE name = 'garland';
UPDATE variable SET value = 's:7:"garland";' WHERE name = 'theme_default';
TRUNCATE TABLE cache;
TRUNCATE TABLE cache_block;
TRUNCATE TABLE cache_content;
TRUNCATE TABLE cache_filter;
TRUNCATE TABLE cache_form;
TRUNCATE TABLE cache_menu;
TRUNCATE TABLE cache_page;
TRUNCATE TABLE cache_update;
TRUNCATE TABLE cache_views;
TRUNCATE TABLE cache_views_data;
TRUNCATE TABLE cache_mollom;
TRUNCATE TABLE cache_tax_image;
UPDATE system SET status = 0 WHERE name = 'addtoany';
UPDATE system SET status = 0 WHERE name = 'backup_migrate';
UPDATE system SET status = 0 WHERE name = 'stylizer';
UPDATE system SET status = 0 WHERE name = 'views_content';
UPDATE system SET status = 0 WHERE name = 'date_php4';
UPDATE system SET status = 0 WHERE name = 'fb_social';
UPDATE system SET status = 0 WHERE name = 'jquery_ui';
UPDATE system SET status = 0 WHERE name = 'lightbox2';
UPDATE system SET status = 0 WHERE name = 'poormanscron';
UPDATE system SET status = 0 WHERE name = 'skinr';
UPDATE system SET status = 0 WHERE name = 'taxonomy_defaults';
UPDATE system SET status = 0 WHERE name = 'taxonomy_image_attach';
UPDATE system SET status = 0 WHERE name = 'taxonomy_image_blocks';
UPDATE system SET status = 0 WHERE name = 'taxonomy_image_link_alter';
UPDATE system SET status = 0 WHERE name = 'taxonomy_image_node_display';
UPDATE system SET status = 0 WHERE name = 'taxonomy_multi_edit';
UPDATE system SET status = 0 WHERE name = 'taxonomy_redirect';
UPDATE system SET status = 0 WHERE name = 'thickbox';
UPDATE system SET status = 0 WHERE name = 'tokenSTARTER';
UPDATE system SET status = 0 WHERE name = 'ca';
UPDATE system SET status = 0 WHERE name = 'uc_product_triggers';
UPDATE system SET status = 0 WHERE name = 'uc_stock_update';
UPDATE system SET status = 0 WHERE name = 'uc_affiliate2';

One key to smooth migration is determining if D6 modules should be disabled in this early phase. There's a certain amount of trial and error involved. Which is partly why running an upgrade from scripts is smart. The whole process can be tweaked and repeated in minutes.

But the basic plan is that if modules have no upgrade path to D7, or simply don't exist in D7, disable them and figure out a workaround. My approach is to get a listing of the enabled modules on a site (say using 'drush @alias pml | grep Module | grep Enabled') and to work through the list either downloading it to the new D7 install or adding it to d6_cleanup.sql and making a note to figure out how to replace it.

3. D7 work

Once you are able to run an upgrade at least mostly successfully, you should have a working D7 install. Once you get to that point, and NOT BEFORE, you should start working on your theme, content types, views, and so on. If you get the basic migration working first it offers the huge advantage that you can build on top of real structures and data, and then export the results to a feature. Enabling and reverting that feature to apply your changes becomes the final step in the automated upgrade.

If you are lucky and careful, this process gives you a push-button upgrade path that you can run very quickly and repeatedly, which is the only way to be sure it will really work when it comes time for deadline day.

Add new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
10 + 1 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
By submitting this form, you accept the Mollom privacy policy.