Forked from kimcoleman/pmpro_migrate_basic_user_avatars.php
Last active
March 26, 2026 12:20
-
-
Save dwanjuki/1e45d9e718440c6c57e59110b456fbc4 to your computer and use it in GitHub Desktop.
Admin queryable/Non WP-CLI Migration script: Basic User Avatars → PMPro Avatars
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Migration script: Basic User Avatars → PMPro Avatars | |
| * | |
| * To trigger, visit one of: | |
| * - /wp-admin/?pmpro_avatar_migrate=1&dry_run=1 (dry run) | |
| * - /wp-admin/?pmpro_avatar_migrate=1&dry_run=0 (real migration) | |
| * | |
| * Check PHP error log for progress output. | |
| * | |
| * You can add this recipe to your site by creating a custom plugin | |
| * or using the Code Snippets plugin available for free in the WordPress repository. | |
| * Read this companion article for step-by-step directions on either method. | |
| * https://www.paidmembershipspro.com/create-a-plugin-for-pmpro-customizations/ | |
| */ | |
| function pmpro_avatar_migration_admin_trigger() { | |
| if ( ! isset( $_GET['pmpro_avatar_migrate'] ) ) { | |
| return; | |
| } | |
| // Safety: admins only. | |
| if ( ! current_user_can( 'manage_options' ) ) { | |
| wp_die( 'Unauthorized.' ); | |
| } | |
| $dry_run = true; | |
| if ( isset( $_GET['dry_run'] ) ) { | |
| $dry_run = $_GET['dry_run'] !== '0'; | |
| } | |
| pmpro_migrate_basic_user_avatars( $dry_run ); | |
| wp_die( | |
| $dry_run | |
| ? 'PMPro avatar migration dry run complete. Check PHP error log.' | |
| : 'PMPro avatar migration complete. Check PHP error log.' | |
| ); | |
| } | |
| add_action( 'admin_init', 'pmpro_avatar_migration_admin_trigger' ); | |
| /** | |
| * 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 ) { | |
| log_msg( '--- 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 error_log. | |
| * | |
| * @param string $message | |
| * @param string $type 'log' or 'error' | |
| */ | |
| function log_msg( $message, $type = 'log' ) { | |
| error_log( '[PMPro Avatar Migration] ' . $message ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment