Skip to content

Instantly share code, notes, and snippets.

@kimcoleman
Created March 6, 2026 16:56
Show Gist options
  • Select an option

  • Save kimcoleman/f031b36f4a685eff315390ccc19889b1 to your computer and use it in GitHub Desktop.

Select an option

Save kimcoleman/f031b36f4a685eff315390ccc19889b1 to your computer and use it in GitHub Desktop.
Migration script: Basic User Avatars → PMPro Avatars
<?php
/**
* Migration script: Basic User Avatars → PMPro Avatars
*
* Run this as a WP-CLI command.
* Save this code to a file migrate-avatars.php.
* Put the file in your WordPress root directory (the same folder where wp-config.php lives).
*
* WP-CLI usage: wp eval-file migrate-avatars.php
* Update the $dry_run value to "false" to run the actual migration.
*
* Place this file in your WordPress root and run via WP-CLI,
* or call pmpro_migrate_basic_user_avatars() from a temporary admin hook.
*/
// WP-CLI dry run?
$dry_run = true;
pmpro_migrate_basic_user_avatars( $dry_run );
/**
* Migrate all users from Basic User Avatars to PMPro avatar storage.
*
* @param bool $dry_run If true, log actions without making changes.
*/
function pmpro_migrate_basic_user_avatars( $dry_run = false ) {
if ( $dry_run ) {
WP_CLI::log( '--- DRY RUN MODE — no changes will be made ---' );
}
// Get all users who have a Basic User Avatar set.
$users = get_users( array(
'meta_key' => 'basic_user_avatar',
'meta_compare' => 'EXISTS',
'fields' => 'ids',
'number' => -1,
) );
if ( empty( $users ) ) {
log_msg( 'No users found with basic_user_avatar meta. Nothing to migrate.' );
return;
}
log_msg( sprintf( 'Found %d user(s) to migrate.', count( $users ) ) );
$success = 0;
$skipped = 0;
$failed = 0;
foreach ( $users as $user_id ) {
$result = pmpro_migrate_single_user_avatar( $user_id, $dry_run );
if ( true === $result ) {
$success++;
} elseif ( 'skipped' === $result ) {
$skipped++;
} else {
$failed++;
log_msg( sprintf( 'FAILED user %d: %s', $user_id, $result ), 'error' );
}
}
log_msg( sprintf(
'Migration complete. Success: %d | Skipped: %d | Failed: %d',
$success, $skipped, $failed
) );
}
/**
* Migrate a single user's avatar from Basic User Avatars to PMPro.
*
* @param int $user_id The user ID.
* @param bool $dry_run If true, log actions without making changes.
* @return true|string True on success, 'skipped' if nothing to do, error string on failure.
*/
function pmpro_migrate_single_user_avatar( $user_id, $dry_run = false ) {
// Get Basic User Avatars meta.
$basic_avatar = get_user_meta( $user_id, 'basic_user_avatar', true );
if ( empty( $basic_avatar ) || ! is_array( $basic_avatar ) || empty( $basic_avatar['full'] ) ) {
log_msg( sprintf( 'User %d: no valid basic_user_avatar meta found, skipping.', $user_id ) );
return 'skipped';
}
// Skip if the user already has a PMPro avatar.
$existing_pmpro = get_user_meta( $user_id, 'pmpro_avatar', true );
if ( ! empty( $existing_pmpro ) ) {
log_msg( sprintf( 'User %d: already has pmpro_avatar, skipping.', $user_id ) );
return 'skipped';
}
$source_url = $basic_avatar['full'];
log_msg( sprintf( 'User %d: migrating from %s', $user_id, $source_url ) );
if ( $dry_run ) {
return true;
}
// Resolve source URL to a local filesystem path if possible,
// otherwise download it to a temp file.
$source_path = pmpro_avatar_url_to_local_path( $source_url );
$is_temp = false;
if ( ! $source_path || ! file_exists( $source_path ) ) {
// Fall back to downloading the file.
$source_path = pmpro_avatar_download_to_temp( $source_url );
if ( is_wp_error( $source_path ) ) {
return $source_path->get_error_message();
}
$is_temp = true;
}
// Determine extension from the source file.
$filetype = wp_check_filetype( $source_path );
$ext = ! empty( $filetype['ext'] ) ? $filetype['ext'] : 'jpg';
$save_ext = pmpro_avatar_get_save_extension( $ext );
// Set up the PMPro user avatar directory.
pmpro_avatar_setup_directory();
$user_dir = pmpro_avatar_get_upload_dir( $user_id );
if ( ! file_exists( $user_dir ) ) {
wp_mkdir_p( $user_dir );
}
// Process the image — crop to square, resize to max dimension.
$max_dimension = pmpro_avatar_get_max_dimension();
$image = wp_get_image_editor( $source_path );
if ( is_wp_error( $image ) ) {
if ( $is_temp ) {
@unlink( $source_path );
}
return 'Could not open image editor: ' . $image->get_error_message();
}
$size = $image->get_size();
$orig_width = $size['width'];
$orig_height = $size['height'];
$min_dim = min( $orig_width, $orig_height );
$crop_x = ( $orig_width - $min_dim ) / 2;
$crop_y = ( $orig_height - $min_dim ) / 2;
$image->crop( $crop_x, $crop_y, $min_dim, $min_dim );
if ( $min_dim > $max_dimension ) {
$image->resize( $max_dimension, $max_dimension, true );
}
$image->set_quality( 90 );
$base_filename = 'avatar.' . $save_ext;
$base_path = $user_dir . $base_filename;
$saved = $image->save( $base_path );
if ( $is_temp ) {
@unlink( $source_path );
}
if ( is_wp_error( $saved ) ) {
return 'Failed to save processed image: ' . $saved->get_error_message();
}
// Pre-generate bucket sizes.
$bucket_sizes = pmpro_avatar_get_bucket_sizes();
$base_size = $image->get_size();
$base_dim = $base_size['width']; // Square, so width = height.
foreach ( $bucket_sizes as $bucket ) {
if ( $bucket < $base_dim ) {
$bucket_image = wp_get_image_editor( $base_path );
if ( ! is_wp_error( $bucket_image ) ) {
$bucket_image->resize( $bucket, $bucket, true );
$bucket_image->set_quality( 90 );
$bucket_filename = sprintf( 'avatar-%dx%d.%s', $bucket, $bucket, $save_ext );
$bucket_image->save( $user_dir . $bucket_filename );
}
}
}
// Save PMPro avatar meta.
$avatar_data = array(
'extension' => $save_ext,
'uploaded' => time(),
);
update_user_meta( $user_id, 'pmpro_avatar', $avatar_data );
log_msg( sprintf( 'User %d: migrated successfully.', $user_id ) );
return true;
}
/**
* Attempt to convert an upload URL to a local filesystem path.
*
* Returns false if the URL doesn't map to the local uploads directory.
*
* @param string $url
* @return string|false
*/
function pmpro_avatar_url_to_local_path( $url ) {
$upload_dir = wp_upload_dir();
// Strip query strings.
$url = strtok( $url, '?' );
// Handle SSL/non-SSL mismatches.
$base_url = str_replace( 'https://', 'http://', $upload_dir['baseurl'] );
$clean_url = str_replace( 'https://', 'http://', $url );
if ( strpos( $clean_url, $base_url ) !== 0 ) {
return false;
}
$relative_path = substr( $clean_url, strlen( $base_url ) );
return $upload_dir['basedir'] . $relative_path;
}
/**
* Download a remote file to a temp location.
*
* @param string $url
* @return string|WP_Error Local temp path or WP_Error.
*/
function pmpro_avatar_download_to_temp( $url ) {
if ( ! function_exists( 'download_url' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
$tmp = download_url( $url, 30 );
if ( is_wp_error( $tmp ) ) {
return $tmp;
}
return $tmp;
}
/**
* Simple logger — uses WP-CLI if available, otherwise error_log.
*
* @param string $message
* @param string $type 'log' or 'error'
*/
function log_msg( $message, $type = 'log' ) {
if ( defined( 'WP_CLI' ) && WP_CLI ) {
if ( $type === 'error' ) {
WP_CLI::warning( $message );
} else {
WP_CLI::log( $message );
}
} else {
error_log( '[PMPro Avatar Migration] ' . $message );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment