WordPress Password Module

This is a Drupal module that implements PHPass (the third-party crypto library used by WordPress) to allow seamless login after a WordPress-to-Drupal user migration, without the Drupal site ever being aware of the stored password.

It works in conjunction with a command-line migration script that saves the PHPass hash for the unknown password to Drupal's user.data blob, among many other data point mappings.


wordpress_password.module:
<?php

/**
 * Implements hook_form_alter()
 *
 * @see wordpress_password_check_login for new validator.
 */

function wordpress_password_form_user_login_alter(&$form, &$form_state, $form_id) {

    // Make sure we're coming from one of the login screens
    if ($form_id == 'user_login' || $form_id = 'user_login_block') {

        // Bypass weird Drupal validation bug
        if (!is_array($form['#validate'])) {
            $form['#validate'] = array($form['#validate']);
        }

        // Add our validation function to the pile
        $form['#validate'][] = 'wordpress_password_check_login';
    }
}

/**
 * Validation function, loosely based on WordPress's authentication & forgotten_login module.
 */

function wordpress_password_check_login($form, &$form_state) {

    // Get logged-in user if there is one
    global $user;

    // Check that no user is logged in
    if ($user->uid == 0) {

        // Get user id from entered username
        $uid = db_query(
            'select u.uid from {users} u where u.status <> 0 and u.name = :name',
            array(':name' => $form_state['values']['name'])
        )->fetchField();

        // Skip hook if no user was found with that name
        if ($uid == false) {
            return;
        } else {

            // Otherwise get user's data blob
            $user_data = unserialize(
                db_query(
                    'select u.data from {users} u where u.status <> 0 and u.name = :name',
                    [':name' => $form_state['values']['name']]
                )->fetchField()
            );

            // Check that pw wasn't already converted
            if ($user_data['wp_pass'] != 'ACTIVATED') {

                // Load hash library & instantiate hasher
                require 'class-phpass.php';
                $wp_hasher = new PasswordHash(8, true);

                // Check submitted pw against hash in data blob
                if ($wp_hasher->CheckPassword($form_state['values']['pass'], $user_data['wp_pass'])) {

                    // Set conversion flag & generate new hash
                    $user_data['wp_pass'] = 'ACTIVATED';
                    $new_pw_hash = user_hash_password($form_state['values']['pass']);

                    // Save to db
                    $success = db_query(
                        'update users set pass = :new_pw_hash, data = :user_data where name = :name',

                        [
                            ':name' => $form_state['values']['name'],
                            ':new_pw_hash' => $new_pw_hash,
                            ':user_data' => mysql_real_escape_string(serialize($user_data))
                        ]
                    );

                    // Log in the user
                    if ($success) {
                        user_load($uid);
                    }
                }
            } else {
                // Skip hook if pw already activated
                return;
            }
        }
    }
}

user-migration.php:
<?php

// Get db lib & WP db conn info
require 'database.class.php';
require 'wordpress-database.config.php';

// Get a db interface from factory
$db = db::getInstance($config);

// Include the role definitions
require 'role-values.config.php';

// Create role mapping
$role_map = array(
    WP_ACCESS_LEVEL_INACTIVE => array(DRUPAL_ROLE_AUTHENTICATED,),
    WP_ACCESS_LEVEL_STUDENT => array(DRUPAL_ROLE_INACTIVE_STUDENT,),
    WP_ACCESS_LEVEL_TEACHER => array(DRUPAL_ROLE_AUTHENTICATED, DRUPAL_ROLE_TEACHER, DRUPAL_ROLE_SUBSCRIBER)
);

// Define some keys used later for coding ergonomics
define('DATA', 'data');
define('REVISION', 'revision');

// Get WordPress user info
$users = $db->query('select u.*, m1.meta_value as first_name, m2.meta_value as last_name from wp_users u
    left join wp_usermeta m1 on m1.user_id = u.ID
    left join wp_usermeta m2 on m2.user_id = u.ID
    where m1.meta_key = "first_name"
    and m2.meta_key = "last_name";');

// Kill the db instance (used again later)
unset($db);

// Set up counters
$i=0;
$len = count($users);

// Make sure db found some users
if ($len > 0) {

    // Store some frequently-used SQL snippets
    $value_split_sql = '","';
    $record_split_sql = '),(';
    $end_sql = ');';

    // Start main SQL strings
    $users_sql = 'insert into users(uid, name, pass, mail, created, status, init, data) values(';
    $roles_sql = 'insert into users_roles(uid, rid) values(';

    // Set up list of keys for normal WP data that is going into Drupal custom fields
    $custom_field_names = array('expiration', 'first_name', 'last_name', 'mature_content');

    // Start SQL strings for these fields
    foreach ($custom_field_names as $key) {
        $field_sql[DATA][$key] = "insert into field_data_field_$key (entity_type, bundle, deleted, entity_id, revision_id, language, delta, field_$key_value) values(";
        $field_sql[REVISION][$key] = "insert into field_revision_field_$key (entity_type, bundle, deleted, entity_id, revision_id, language, delta, field_$key_value) values(";
    }

    // Loop through users
    while ($i < $len) {

        // Get user data as variables
        extract($users[$i]);

        // Loop all roles
        foreach ($role_map as $wp_access_level => $roles) {

            // Reset counters
            $role_counter = 1;
            $roles_length = count($roles);

            // Check role match and add Drupal roles if necessary
            if ($access_level == $wp_access_level) {
                foreach ($roles as $role) {
                    $roles_sql .= $ID . ',' . $role . $record_split_sql;
                    ++$role_counter;
                }
            }
        }

        // Convert timestamp format
        $user_registered = strtotime($user_registered);

        // Build the data blob in appropriate format
        $user_data = mysql_real_escape_string(serialize(array(
            'wp_pass' => $user_pass,
            'nicename' => $user_nicename,
            'url' => $user_url,
            'display_name' => $display_name,
            'activation_key' => $user_activation_key,
            'status' => $user_status
        )));

        // Add record to user SQL
        $users_sql .=
            $ID . ',"' .
            $user_login . $value_split_sql .
            $user_pass . $value_split_sql .
            $user_email . $value_split_sql .
            $user_registered . '", 1, "' .
            $user_email . $value_split_sql .
            $user_data . '"';

        // Set values for custom fields
        $custom_field_values['first_name'] = $first_name;
        $custom_field_values['last_name'] = $last_name;
        $custom_field_values['expiration'] = strtotime($access_expiration);
        $custom_field_values['mature_content'] = $dentista;

        // Loop through custom fields
        foreach ($custom_field_names as $key) {

            // Check that there's something to add in this field
            if ($custom_field_values[$key] !== null) {

                // Add quotes for non-numeric values
                if (is_numeric($custom_field_values[$key])) {
                    $value_encaps = null;
                } else {
                    $value_encaps = '"';
                }

                // Build SQL for Drupal's custom field tables
                $value = '"user", "user", 0, ' . $ID . ', 1, "und", 0, ' . $value_encaps . $custom_field_values[$key] . $value_encaps;
                $field_sql[DATA][$key] .= $value;
                $field_sql[REVISION][$key] .= $value;
            }
        }

        ++$i;

        // Add split snippets if we're not at the end yet
        if ($i < $len) {
            $users_sql .= $record_split_sql;

            foreach ($custom_field_names as $key) {
                $field_sql[DATA][$key] .= $record_split_sql;
                $field_sql[REVISION][$key] .= $record_split_sql;
            }
        }

        // Kill all temp vars from this iteration
        unset(
            $ID,
            $user_login,
            $user_pass,
            $user_nicename,
            $user_email,
            $user_url,
            $user_registered,
            $access_level,
            $access_expiration,
            $remind_num,
            $remind_date,
            $dentista,
            $user_activation_key,
            $user_status,
            $display_name,
            $first_name,
            $last_name
        );
    }

    // Add end snippets to all SQL strings
    $users_sql .= $end_sql;
    $roles_sql .= substr_replace($roles_sql, ';', strrpos($roles_sql, ',('), 2);

    foreach ($custom_field_names as $key) {
        $field_sql[DATA][$key] .= $end_sql;
        $field_sql[REVISION][$key] .= $end_sql;
    }
}

// Get Drupal db conn info
require 'drupal-database.config.php';

// Get a new conn from the factory
$db = db::getInstance($config);

// Select the schema
if ($db->select_database('khameleo_drupal')) {

    // Create user records & output success msg
    if (is_array($db->query($users_sql))) {
        echo 'Successfully migrated users.', "\n";

        // Create role records & output success msg
        if (is_array($db->query($roles_sql))) {
            echo 'Successfully migrated roles.', "\n";
        }

        // Create custom field records & output success msg for each
        foreach ($field_sql as $table_type) {
            foreach ($table_type as $key => $sql) {
                if (is_array($db->query($sql))) {
                    echo 'Successfully migrated ', $key, ' data.', "\n";
                }
            }
        }
    }
}