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";
}
}
}
}
}