File "migrations.php"
Full Path: /home/fresvfqn/waterdamagerestorationandrepairsmithtown.com/wp-content/plugins/surerank/inc/api/migrations.php
File size: 39.34 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Migration API Class
*
* Handles migration-related REST API endpoints for the plugin.
*
* @package SureRank\Inc\API
* @since 1.1.0
*/
namespace SureRank\Inc\API;
use SureRank\Inc\Functions\Get;
use SureRank\Inc\Functions\Send_Json;
use SureRank\Inc\Functions\Settings;
use SureRank\Inc\Importers\Importer;
use SureRank\Inc\Importers\ImporterUtils;
use SureRank\Inc\Importers\Rankmath\RankMath;
use SureRank\Inc\Importers\Seopress\Seopress;
use SureRank\Inc\Importers\Yoast\Yoast;
use SureRank\Inc\Traits\Get_Instance;
use WP_REST_Request;
use WP_REST_Server;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Migrations
*
* Handles the REST API endpoints for migrations from other SEO plugins
* and for retrieving all post IDs.
*/
class Migrations extends Api_Base {
use Get_Instance;
/**
* Route Migrated Data
*/
protected const MIGRATED_DATA = '/migration/migrated-data';
/**
* API endpoint for migrating posts.
*/
private const ENDPOINT_POSTS = '/migration/posts';
/**
* API endpoint for migrating terms.
*/
private const ENDPOINT_TERMS = '/migration/terms';
/**
* API endpoint for migrating global settings.
*/
private const ENDPOINT_GLOBAL = '/migration/global-settings';
/**
* API endpoint for deactivating a plugin.
*
* @since 1.1.0
*/
private const ENDPOINT_DEACTIVATE = '/plugins/deactivate';
/**
* Batch size for processing large datasets.
*/
private const BATCH_SIZE = 100;
/**
* Map of slug ⇒ importer class.
*
* @var array<string, class-string>
*/
private array $importers = [
'rankmath' => RankMath::class,
'seopress' => Seopress::class,
'yoast' => Yoast::class,
];
/**
* Register the /migration/posts, /migration/terms, /migration/global-settings routes.
*/
public function register_routes(): void {
if ( ! Settings::get( 'enable_migration' ) ) {
return;
}
$namespace = $this->get_api_namespace();
// -------- Migrate posts -------- .
register_rest_route(
$namespace,
self::ENDPOINT_POSTS,
[
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'migrate_posts' ],
'permission_callback' => [ $this, 'validate_permission' ],
'args' => [
'plugin_slug' => [
'type' => 'string',
'required' => true,
/* translators: %s: list of plugin slugs */
'description' => sprintf( __( 'Plugin slug to migrate from (e.g. %s).', 'surerank' ), implode( ', ', array_keys( $this->importers ) ) ),
'enum' => array_keys( $this->importers ),
],
'post_ids' => [
'type' => [ 'array', 'integer' ],
'required' => true,
'description' => __( 'Post IDs to migrate.', 'surerank' ),
'validate_callback' => fn( $param) => $this->validate_ids( $param ),
],
'cleanup' => [
'type' => 'boolean',
'required' => false,
'default' => false,
'description' => __( 'Whether to clean up source data after import.', 'surerank' ),
],
],
],
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_all_posts' ],
'permission_callback' => [ $this, 'validate_permission' ],
'args' => [
'page' => [
'type' => 'integer',
'required' => false,
'default' => 1,
'description' => __( 'Page number for pagination.', 'surerank' ),
],
'plugin_slug' => [
'type' => 'string',
'required' => true,
/* translators: %s: list of plugin slugs */
'description' => sprintf( __( 'Plugin slug to filter posts by (e.g. %s).', 'surerank' ), implode( ', ', array_keys( $this->importers ) ) ),
'enum' => array_keys( $this->importers ),
'default' => 'rankmath',
],
],
],
]
);
// -------- Migrate terms --------.
register_rest_route(
$namespace,
self::ENDPOINT_TERMS,
[
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'migrate_terms' ],
'permission_callback' => [ $this, 'validate_permission' ],
'args' => [
'plugin_slug' => [
'type' => 'string',
'required' => true,
/* translators: %s: list of plugin slugs */
'description' => sprintf( __( 'Plugin slug to migrate from (e.g. %s).', 'surerank' ), implode( ', ', array_keys( $this->importers ) ) ),
'enum' => array_keys( $this->importers ),
],
'term_ids' => [
'type' => [ 'array', 'integer' ],
'required' => true,
'description' => __( 'Term IDs to migrate.', 'surerank' ),
'validate_callback' => fn( $param) => $this->validate_ids( $param ),
],
'cleanup' => [
'type' => 'boolean',
'required' => false,
'default' => false,
'description' => __( 'Whether to clean up source data after import.', 'surerank' ),
],
],
],
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_all_terms' ],
'permission_callback' => [ $this, 'validate_permission' ],
'args' => [
'page' => [
'type' => 'integer',
'required' => false,
'default' => 1,
'description' => __( 'Page number for pagination.', 'surerank' ),
],
'plugin_slug' => [
'type' => 'string',
'required' => true,
'default' => 'rankmath',
/* translators: %s: list of plugin slugs */
'description' => sprintf( __( 'Plugin slug to filter terms by (e.g. %s).', 'surerank' ), implode( ', ', array_keys( $this->importers ) ) ),
'enum' => array_keys( $this->importers ),
],
],
],
]
);
// -------- Migrate global settings --------.
register_rest_route(
$namespace,
self::ENDPOINT_GLOBAL,
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'migrate_global_settings' ],
'permission_callback' => [ $this, 'validate_permission' ],
'args' => [
'plugin_slug' => [
'type' => 'string',
'required' => true,
/* translators: %s: list of plugin slugs */
'description' => sprintf( __( 'Plugin slug to migrate global settings from (e.g. %s).', 'surerank' ), implode( ', ', array_keys( $this->importers ) ) ),
'enum' => array_keys( $this->importers ),
],
'cleanup' => [
'type' => 'boolean',
'required' => false,
'default' => false,
'description' => __( 'Whether to clean up source global data after import.', 'surerank' ),
],
],
]
);
// -------- Deactivate plugin --------.
register_rest_route(
$namespace,
self::ENDPOINT_DEACTIVATE,
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'deactivate_plugin' ],
'permission_callback' => [ $this, 'validate_permission' ],
'args' => [
'plugin_slug' => [
'type' => 'string',
'required' => true,
'description' => __( 'Plugin slug to deactivate.', 'surerank' ),
'enum' => array_keys( $this->importers ),
],
],
]
);
// -------- Mark migration completed --------.
register_rest_route(
$namespace,
'/migration/completed',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'mark_migration_completed_endpoint' ],
'permission_callback' => [ $this, 'validate_permission' ],
'args' => [
'plugin_slug' => [
'type' => 'string',
'required' => true,
'description' => __( 'Plugin slug that was migrated from.', 'surerank' ),
'enum' => array_keys( $this->importers ),
],
],
]
);
// -------- Get migrated data --------.
register_rest_route(
$this->get_api_namespace(),
self::MIGRATED_DATA,
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_migrated_data' ],
'permission_callback' => [ $this, 'validate_permission' ],
]
);
}
/**
* Get Migrated Data
*
* @since 1.2.0
* @param WP_REST_Request<array<string, mixed>> $request Request object.
* @return array<string, mixed>
*/
public function get_migrated_data( $request ) {
$surerank_onboarding = Get::option( 'surerank_settings_onboarding', [] );
$surerank_settings = Get::option( SURERANK_SETTINGS, [] );
return [
'social_profiles' => $surerank_settings['social_profiles'] ?? [],
'website_details' => $surerank_onboarding,
];
}
/**
* Handle the migration request for posts.
*
* @param WP_REST_Request<array<string, mixed>> $request The REST request.
* @return void
*/
public function migrate_posts( $request ) {
$ids_raw = $request->get_param( 'post_ids' );
$cleanup = (bool) $request->get_param( 'cleanup' );
$ids = is_array( $ids_raw ) ? $ids_raw : [ $ids_raw ];
$importer = $this->validate_and_get_importer( $request );
if ( ! $this->validate_importer_methods( $importer, 'post' ) ) {
Send_Json::error(
[ 'message' => __( 'Invalid importer methods.', 'surerank' ) ]
);
}
$results = $this->process_migration( $ids, $importer, 'post' );
$results = array_merge( $results, $this->handle_cleanup( $importer, $cleanup, $results['success'] ) );
$results = $this->format_response( $results, $importer, $ids, 'posts' );
Send_Json::success( $results );
}
/**
* Handle the migration request for terms.
*
* @param WP_REST_Request<array<string, mixed>> $request The REST request.
* @return void
*/
public function migrate_terms( $request ) {
$ids_raw = $request->get_param( 'term_ids' );
$cleanup = (bool) $request->get_param( 'cleanup' );
$ids = is_array( $ids_raw ) ? $ids_raw : [ $ids_raw ];
$importer = $this->validate_and_get_importer( $request );
if ( ! $this->validate_importer_methods( $importer, 'term' ) ) {
Send_Json::error(
[ 'message' => __( 'Invalid importer methods.', 'surerank' ) ]
);
}
$results = $this->process_migration( $ids, $importer, 'term' );
$results = array_merge( $results, $this->handle_cleanup( $importer, $cleanup, $results['success'] ) );
$results = $this->format_response( $results, $importer, $ids, 'terms' );
Send_Json::success( $results );
}
/**
* Handle the migration request for global settings.
*
* @param WP_REST_Request<array<string, mixed>> $request The REST request.
*/
public function migrate_global_settings( $request ): void {
$plugin_slug = $request->get_param( 'plugin_slug' );
$cleanup = (bool) $request->get_param( 'cleanup' );
$importer_class = $this->importers[ $plugin_slug ];
/**
* The importer class must implement Importer.
*
* @var Importer $importer
* */
$importer = new $importer_class();
// Validate that the importer implements the required interface.
if ( ! $importer instanceof Importer ) {
Send_Json::error(
[
'message' => sprintf(
/* translators: %s: importer class name */
__( 'Invalid importer class: %s does not implement Importer.', 'surerank' ),
$importer_class
),
]
);
}
$results = $importer->import_global_settings();
// Trigger action after all free migration is done.
do_action( 'surerank_migration_after', $plugin_slug );
// Ensure results is an array.
if ( ! is_array( $results ) ) {
$results = [
'success' => false,
'message' => __( 'Invalid response from importer.', 'surerank' ),
];
}
// Only run cleanup if migration was successful.
if ( $cleanup && $results['success'] && method_exists( $importer, 'cleanup' ) ) {
$cleanup_resp = $importer->cleanup();
// Ensure cleanup response is valid.
if ( is_array( $cleanup_resp ) ) {
$results['cleanup'] = $cleanup_resp['success'];
$results['cleanup_message'] = $cleanup_resp['message'];
}
}
$plugin_name = method_exists( $importer, 'get_plugin_name' ) ? $importer->get_plugin_name() : 'Unknown';
$results['message'] = sprintf(
/* translators: 1: import status, 2: plugin name */
__( 'Global settings %1$s from %2$s.', 'surerank' ),
$results['success'] ? __( 'imported successfully', 'surerank' ) : __( 'failed to import', 'surerank' ),
$plugin_name
);
if ( $results['success'] ) {
Send_Json::success( $results );
} else {
Send_Json::error( $results );
}
}
/**
* Retrieve all post IDs grouped by post type, excluding those already migrated.
*
* @param WP_REST_Request<array<string, mixed>> $request The REST request.
*/
public function get_all_posts( $request ): void {
$page = (int) $request->get_param( 'page' );
$plugin_slug = $request->get_param( 'plugin_slug' );
$batch_size = self::BATCH_SIZE;
$importer_class = $this->importers[ $plugin_slug ];
/**
* The importer class must implement Importer.
*
* @var Importer $importer
* */
$importer = new $importer_class();
$post_types = get_post_types(
[
'public' => true,
'show_ui' => true,
],
'names'
);
unset( $post_types['attachment'] );
$post_types = array_values( $post_types );
// Calculate offset for pagination.
$offset = ( $page - 1 ) * $batch_size;
$post_id_and_total_items = $importer->get_count_and_posts( $post_types, $batch_size, $offset );
$total_items = $post_id_and_total_items['total_items'];
$post_ids = $post_id_and_total_items['post_ids'];
$grouped = $this->group_items( $post_ids );
Send_Json::success(
[
'data' => $grouped,
'pagination' => [
'current_page' => $page,
'total_pages' => (int) ceil( $total_items / $batch_size ),
'total_items' => $total_items,
'per_page' => self::BATCH_SIZE,
],
]
);
}
/**
* Retrieve all term IDs grouped by taxonomy, excluding those already migrated.
*
* @param WP_REST_Request<array<string, mixed>> $request The REST request.
*/
public function get_all_terms( $request ): void {
$page = (int) $request->get_param( 'page' );
$plugin_slug = $request->get_param( 'plugin_slug' );
$batch_size = self::BATCH_SIZE;
// Initialize response data.
$grouped = [];
$total_terms = 0;
$term_ids = [];
$taxonomies_objects = $this->get_public_taxonomies();
if ( empty( $taxonomies_objects ) ) {
Send_Json::error(
[
'message' => __( 'No public taxonomies found.', 'surerank' ),
]
);
return;
}
$importer_class = $this->importers[ $plugin_slug ];
/**
* The importer class must implement Importer.
*
* @var Importer $importer
*/
$importer = new $importer_class();
$taxonomies = array_map(
static function ( $taxonomy ) {
return $taxonomy->name ?? '';
},
$taxonomies_objects
);
$taxonomies = array_filter( $taxonomies );
// Calculate offset for pagination.
$offset = ( $page - 1 ) * $batch_size;
// Fetch term IDs with batching.
$term_ids_and_total_items = $importer->get_count_and_terms(
$taxonomies,
$taxonomies_objects,
$batch_size,
$offset
);
$total_terms = $term_ids_and_total_items['total_items'] ?? 0;
$term_ids = $term_ids_and_total_items['term_ids'] ?? [];
$grouped = $this->group_items( $term_ids, true, $taxonomies_objects );
Send_Json::success(
[
'data' => $grouped,
'pagination' => [
'current_page' => $page,
'total_pages' => 'yoast' === $plugin_slug ? 1 : ( $total_terms > 0 ? (int) ceil( $total_terms / $batch_size ) : 1 ),
'total_items' => $total_terms,
'per_page' => self::BATCH_SIZE,
],
]
);
}
/**
* Get a list of SEO plugins available for migration.
*
* Returns a list of installed plugins that are available for migration.
* The results are cached for the duration of the request.
*
* @return array<string, array{name: string, active: bool}> Array of plugin slugs => plugin names that are available for migration.
* @since 1.1.0
*/
public function get_available_plugins(): array {
// Early return if no importers are configured.
if ( empty( $this->importers ) ) {
return [];
}
// Use static cache to avoid repeated processing.
static $available_plugins = null;
if ( null !== $available_plugins ) {
return $available_plugins;
}
// Map of supported plugin slugs to their details.
$supported_plugins = $this->get_supported_plugins();
$installed_plugins = get_plugins();
$available_plugins = [];
// Single pass through the data to build the final array.
foreach ( $supported_plugins as $key => $plugin ) {
if ( isset( $this->importers[ $key ] ) && isset( $installed_plugins[ $plugin['slug'] ] ) ) {
$available_plugins[ $key ] = [
'name' => $plugin['name'],
'active' => $this->is_plugin_active( $plugin['slug'] ),
];
}
}
return $available_plugins;
}
/**
* Check if the plugin is active or not.
*
* @param string $plugin Slug of the plugin to check.
* @return bool True if the plugin is active, false otherwise.
*/
public function is_plugin_active( $plugin ) {
foreach ( $this->importers as $slug => $importer_class ) {
if ( ! class_exists( $importer_class ) ) {
continue;
}
$importer = new $importer_class();
if ( ! method_exists( $importer, 'get_plugin_file' ) || ! method_exists( $importer, 'is_plugin_active' ) ) {
continue;
}
if ( $importer->get_plugin_file() === $plugin ) {
return (bool) $importer->is_plugin_active();
}
}
return false;
}
/**
* Deactivate a plugin after migration.
*
* @param WP_REST_Request<array<string, mixed>> $request The REST request.
*
* @return void
*/
public function deactivate_plugin( $request ): void {
$plugin_slug = $request->get_param( 'plugin_slug' );
if ( ! current_user_can( 'activate_plugins' ) ) {
Send_Json::error( [ 'message' => __( 'You do not have permission to deactivate plugins.', 'surerank' ) ] );
}
$plugin_path = $this->get_plugin_path_from_slug( (string) $plugin_slug );
if ( ! $plugin_path ) {
Send_Json::error( [ 'message' => __( 'Plugin not found.', 'surerank' ) ] );
}
if ( ! $this->is_plugin_active( (string) $plugin_path ) ) {
Send_Json::success( [ 'message' => __( 'Plugin is already inactive.', 'surerank' ) ] );
return;
}
$to_be_deactivated_plugins = $this->get_associated_plugins( (string) $plugin_path );
deactivate_plugins( (array) $to_be_deactivated_plugins );
Send_Json::success( [ 'message' => __( 'Plugin deactivated successfully.', 'surerank' ) ] );
}
/**
* Mark migration as completed endpoint handler.
*
* @param WP_REST_Request<array<string, mixed>> $request The REST request.
* @return void
* @since 1.1.0
*/
public function mark_migration_completed_endpoint( $request ): void {
$plugin_slug = $request->get_param( 'plugin_slug' );
if ( empty( $plugin_slug ) || ! is_string( $plugin_slug ) ) {
Send_Json::error( [ 'message' => __( 'Invalid plugin slug.', 'surerank' ) ] );
}
// Mark the migration as completed.
self::mark_migration_completed( $plugin_slug );
Send_Json::success(
[
'message' => sprintf(
// translators: %s: plugin slug.
__( 'Migration from %s marked as completed successfully.', 'surerank' ),
$plugin_slug
),
'migration_completed' => true,
]
);
}
/**
* Mark migration as completed globally.
*
* @param string $plugin_slug The plugin slug that was migrated from.
* @return void
* @since 1.1.0
*/
public static function mark_migration_completed( string $plugin_slug ): void {
$completed_migrations = get_option( 'surerank_completed_migrations', [] );
if ( ! is_array( $completed_migrations ) ) {
$completed_migrations = [];
}
$completed_migrations[ $plugin_slug ] = [
'timestamp' => time(),
];
update_option( 'surerank_completed_migrations', $completed_migrations );
// Set global flag that any migration has been completed.
update_option( 'surerank_migration_ever_completed', true );
}
/**
* Check if migration has ever been completed.
*
* @return bool True if any migration has ever been completed.
* @since 1.1.0
*/
public static function has_migration_ever_completed(): bool {
return (bool) get_option( 'surerank_migration_ever_completed', false );
}
/**
* Get completed migrations information.
*
* @return array<string, array<string, mixed>> Array of completed migrations with details.
* @since 1.1.0
*/
public static function get_completed_migrations(): array {
$completed_migrations = get_option( 'surerank_completed_migrations', [] );
if ( ! is_array( $completed_migrations ) ) {
return [];
}
return $completed_migrations;
}
/**
* Check if migration from specific plugin has been completed.
*
* @param string $plugin_slug The plugin slug to check.
* @return bool True if migration from this plugin was completed.
* @since 1.1.0
*/
public static function is_plugin_migration_completed( string $plugin_slug ): bool {
$completed_migrations = self::get_completed_migrations();
return isset( $completed_migrations[ $plugin_slug ] );
}
/**
* Check if any cache plugin is currently active on the website.
*
* This method efficiently detects the most popular WordPress cache plugins
* by checking for defined constants, function existence, and class existence
* rather than using is_plugin_active() which requires loading plugin data.
*
* @since 1.2.0
* @return array{
* 'has_cache_plugin': bool,
* 'active_plugins': array<string, array{
* 'name': string,
* 'slug': string,
* 'detection_method': string
* }>
* } Array containing status and details of active cache plugins.
*/
public static function has_active_cache_plugin(): array {
// Use static cache to avoid repeated processing during the same request.
static $cache_result = null;
if ( null !== $cache_result ) {
return $cache_result;
}
$active_cache_plugins = [];
// Define cache plugins with their detection methods.
$cache_plugins = [
// Premium Cache Plugins.
'wp-rocket' => [
'name' => __( 'WP Rocket', 'surerank' ),
'slug' => 'wp-rocket/wp-rocket.php',
'detection_method' => 'constant',
'detection_value' => 'WP_ROCKET_VERSION',
],
'flyingpress' => [
'name' => __( 'FlyingPress', 'surerank' ),
'slug' => 'flyingpress/flyingpress.php',
'detection_method' => 'constant',
'detection_value' => 'FLYINGPRESS_VERSION',
],
'nitropack' => [
'name' => __( 'NitroPack', 'surerank' ),
'slug' => 'nitropack/main.php',
'detection_method' => 'class',
'detection_value' => 'NitroPack\\WordPress\\NitroPack',
],
'wp-optimize-premium' => [
'name' => __( 'WP-Optimize Premium', 'surerank' ),
'slug' => 'wp-optimize-premium/wp-optimize.php',
'detection_method' => 'constant',
'detection_value' => 'WPO_PREMIUM',
],
'swift-performance' => [
'name' => __( 'Swift Performance', 'surerank' ),
'slug' => 'swift-performance/performance.php',
'detection_method' => 'class',
'detection_value' => 'Swift_Performance',
],
// Popular Free Cache Plugins.
'litespeed-cache' => [
'name' => __( 'LiteSpeed Cache', 'surerank' ),
'slug' => 'litespeed-cache/litespeed-cache.php',
'detection_method' => 'constant',
'detection_value' => 'LSCWP_V',
],
'w3-total-cache' => [
'name' => __( 'W3 Total Cache', 'surerank' ),
'slug' => 'w3-total-cache/w3-total-cache.php',
'detection_method' => 'constant',
'detection_value' => 'W3TC_VERSION',
],
'wp-super-cache' => [
'name' => __( 'WP Super Cache', 'surerank' ),
'slug' => 'wp-super-cache/wp-super-cache.php',
'detection_method' => 'constant',
'detection_value' => 'WPCACHEHOME',
],
'wp-fastest-cache' => [
'name' => __( 'WP Fastest Cache', 'surerank' ),
'slug' => 'wp-fastest-cache/wpFastestCache.php',
'detection_method' => 'class',
'detection_value' => 'WpFastestCache',
],
'wp-optimize' => [
'name' => __( 'WP-Optimize', 'surerank' ),
'slug' => 'wp-optimize/wp-optimize.php',
'detection_method' => 'class',
'detection_value' => 'WP_Optimize',
],
'breeze' => [
'name' => __( 'Breeze', 'surerank' ),
'slug' => 'breeze/breeze.php',
'detection_method' => 'constant',
'detection_value' => 'BREEZE_VERSION',
],
'cache-enabler' => [
'name' => __( 'Cache Enabler', 'surerank' ),
'slug' => 'cache-enabler/cache-enabler.php',
'detection_method' => 'class',
'detection_value' => 'Cache_Enabler',
],
'comet-cache' => [
'name' => __( 'Comet Cache', 'surerank' ),
'slug' => 'comet-cache/comet-cache.php',
'detection_method' => 'constant',
'detection_value' => 'COMET_CACHE_VERSION',
],
'swift-performance-lite' => [
'name' => __( 'Swift Performance Lite', 'surerank' ),
'slug' => 'swift-performance-lite/performance.php',
'detection_method' => 'class',
'detection_value' => 'Swift_Performance_Lite',
],
// Object Cache Plugins.
'redis-cache' => [
'name' => __( 'Redis Object Cache', 'surerank' ),
'slug' => 'redis-cache/redis-cache.php',
'detection_method' => 'constant',
'detection_value' => 'WP_REDIS_VERSION',
],
// Performance & Cache Plugins.
'hummingbird-performance' => [
'name' => __( 'Hummingbird Performance', 'surerank' ),
'slug' => 'hummingbird-performance/wp-hummingbird.php',
'detection_method' => 'constant',
'detection_value' => 'WPHB_VERSION',
],
'autoptimize' => [
'name' => __( 'Autoptimize', 'surerank' ),
'slug' => 'autoptimize/autoptimize.php',
'detection_method' => 'class',
'detection_value' => 'autoptimizeMain',
],
'sg-cachepress' => [
'name' => __( 'SiteGround Speed Optimizer', 'surerank' ),
'slug' => 'sg-cachepress/sg-cachepress.php',
'detection_method' => 'class',
'detection_value' => 'SiteGround_Optimizer\\Supercacher\\Supercacher',
],
'cloudflare' => [
'name' => __( 'Cloudflare', 'surerank' ),
'slug' => 'cloudflare/cloudflare.php',
'detection_method' => 'class',
'detection_value' => 'CF\\WordPress\\Hooks',
],
'hyper-cache' => [
'name' => __( 'Hyper Cache', 'surerank' ),
'slug' => 'hyper-cache/plugin.php',
'detection_method' => 'class',
'detection_value' => 'HyperCache',
],
'perfmatters' => [
'name' => __( 'Perfmatters', 'surerank' ),
'slug' => 'perfmatters/perfmatters.php',
'detection_method' => 'class',
'detection_value' => 'Perfmatters\\Config',
],
// Specialized Cache Plugins.
'speedycache' => [
'name' => __( 'SpeedyCache', 'surerank' ),
'slug' => 'speedycache/speedycache.php',
'detection_method' => 'class',
'detection_value' => 'SpeedyCache',
],
'docket-cache' => [
'name' => __( 'Docket Cache', 'surerank' ),
'slug' => 'docket-cache/docket-cache.php',
'detection_method' => 'constant',
'detection_value' => 'DOCKET_CACHE_VERSION',
],
'tenweb-speed-optimizer' => [
'name' => __( '10Web Booster', 'surerank' ),
'slug' => 'tenweb-speed-optimizer/tenweb_speed_optimizer.php',
'detection_method' => 'class',
'detection_value' => 'TenWebOptimizer',
],
'wp-cloudflare-page-cache' => [
'name' => __( 'Super Page Cache', 'surerank' ),
'slug' => 'wp-cloudflare-page-cache/wp-cloudflare-page-cache.php',
'detection_method' => 'class',
'detection_value' => 'CF_Page_Cache',
],
'wp-rest-cache' => [
'name' => __( 'WP REST Cache', 'surerank' ),
'slug' => 'wp-rest-cache/wp-rest-cache.php',
'detection_method' => 'class',
'detection_value' => 'WP_REST_Cache_Plugin',
],
'cache-control' => [
'name' => __( 'Cache Control', 'surerank' ),
'slug' => 'cache-control/cache-control.php',
'detection_method' => 'class',
'detection_value' => 'Cache_Control',
],
// Additional Cache Plugins.
'aruba-hispeed-cache' => [
'name' => __( 'Aruba HiSpeed Cache', 'surerank' ),
'slug' => 'aruba-hispeed-cache/aruba-hispeed-cache.php',
'detection_method' => 'class',
'detection_value' => 'ArubaHiSpeedCacheWp',
],
'atec-cache-apcu' => [
'name' => __( 'atec Cache APCu', 'surerank' ),
'slug' => 'atec-cache-apcu/atec-cache-apcu.php',
'detection_method' => 'class',
'detection_value' => 'ATEC_cache_apcu',
],
];
// Check each cache plugin for active status.
foreach ( $cache_plugins as $key => $plugin ) {
$is_active = false;
switch ( $plugin['detection_method'] ) {
case 'constant':
$is_active = defined( $plugin['detection_value'] );
break;
case 'function':
$is_active = function_exists( $plugin['detection_value'] );
break;
case 'class':
$is_active = class_exists( $plugin['detection_value'] );
break;
default:
$is_active = false;
break;
}
// If plugin is active, add it to the results.
if ( $is_active ) {
$active_cache_plugins[ $key ] = [
'name' => $plugin['name'],
'slug' => $plugin['slug'],
'detection_method' => sprintf(
/* translators: 1: detection method, 2: detection value */
__( 'Detected via %1$s: %2$s', 'surerank' ),
$plugin['detection_method'],
$plugin['detection_value']
),
];
}
}
$cache_result = [
'has_cache_plugin' => ! empty( $active_cache_plugins ),
'active_plugins' => $active_cache_plugins,
];
return $cache_result;
}
/**
* Simple helper method to check if any cache plugin is active.
*
* This is a lightweight wrapper around has_active_cache_plugin()
* that returns only a boolean value for simpler usage.
*
* @since 1.2.0
* @return bool True if any cache plugin is active, false otherwise.
*/
public static function is_cache_plugin_active(): bool {
$result = self::has_active_cache_plugin();
return $result['has_cache_plugin'];
}
/**
* Get associated plugins that should be deactivated along with the main plugin.
*
* @param string $plugin_path The main plugin path.
* @return array<string> List of associated plugin paths to deactivate.
* @since 1.1.1
*/
private function get_associated_plugins( string $plugin_path ) {
$associated_plugins = [];
switch ( $plugin_path ) {
case 'seo-by-rank-math/rank-math.php':
$associated_plugins = [ 'seo-by-rank-math/rank-math.php', 'seo-by-rank-math-pro/rank-math-pro.php' ];
break;
case 'wordpress-seo/wp-seo.php':
$associated_plugins = [ 'wordpress-seo/wp-seo.php', 'wordpress-seo-premium/wp-seo-premium.php' ];
break;
case 'wp-seopress/seopress.php':
$associated_plugins = [ 'wp-seopress/seopress.php', 'wp-seopress-pro/seopress-pro.php' ];
break;
}
return $associated_plugins;
}
/**
* Validates the plugin slug and returns the importer instance.
*
* @param WP_REST_Request<array<string, mixed>> $request The REST request.
* @return Importer The validated importer instance.
*/
private function validate_and_get_importer( $request ) {
$plugin_slug = $request->get_param( 'plugin_slug' );
if ( ! is_string( $plugin_slug ) ) {
Send_Json::error(
[ 'message' => __( 'Invalid plugin slug specified.', 'surerank' ) ]
);
}
$importer_class = $this->importers[ $plugin_slug ];
/**
* The importer class must implement Importer.
*
* @var Importer $importer
*/
$importer = new $importer_class();
if ( ! $importer instanceof Importer ) {
Send_Json::error(
[
'message' => sprintf(
// translators: %s: importer class name.
__( 'Invalid importer class: %s does not implement Importer.', 'surerank' ),
$importer_class
),
]
);
}
return $importer;
}
/**
* Get supported plugins for migration.
*
* @return array<string, array<string, string>>
*/
private function get_supported_plugins(): array {
$supported_plugins = [];
foreach ( $this->importers as $slug => $importer_class ) {
if ( ! class_exists( $importer_class ) ) {
continue;
}
$importer = new $importer_class();
if ( ! method_exists( $importer, 'get_plugin_name' ) || ! method_exists( $importer, 'get_plugin_file' ) ) {
continue;
}
$supported_plugins[ $slug ] = [
'name' => $importer->get_plugin_name(),
'slug' => $importer->get_plugin_file(),
];
}
return $supported_plugins;
}
/**
* Get plugin path from slug.
*
* @param string $slug The plugin slug.
* @return string|null The plugin path or null if not found.
*/
private function get_plugin_path_from_slug( string $slug ): ?string {
$supported_plugins = $this->get_supported_plugins();
return $supported_plugins[ $slug ]['slug'] ?? null;
}
/**
* Validates the importer methods for the given type (post or term).
*
* @param Importer $importer The importer instance.
* @param string $type The type of item ('post' or 'term').
* @return bool True if methods exist, false otherwise.
*/
private function validate_importer_methods( Importer $importer, string $type ): bool {
$detect_method = 'detect_' . $type;
$import_method = 'import_' . $type;
return method_exists( $importer, $detect_method ) && method_exists( $importer, $import_method );
}
/**
* Processes items (posts or terms) for migration.
*
* @param array<int> $ids The array of item IDs.
* @param Importer $importer The importer instance.
* @param string $type The type of item ('post' or 'term').
* @return array<int|string, mixed> The results array with success count, failed items, and import data.
*/
private function process_migration( array $ids, Importer $importer, string $type ): array {
$results = [
'success' => 0,
'failed_items' => [],
];
$send_import_data = [];
$detect_method = 'detect_' . $type;
$import_method = 'import_' . $type;
foreach ( $ids as $id ) {
try {
$detect = $importer->$detect_method( (int) $id );
if ( is_array( $detect ) && isset( $detect['no_data_found'] ) && $detect['no_data_found'] ) {
$send_import_data[ $id ] = $detect['message'] ?? __( 'No data found for this item.', 'surerank' );
$results['success']++;
continue;
}
if ( ! $detect['success'] ) {
$results['failed_items'][ $id ] = $detect['message'] ?? __( 'Detection failed.', 'surerank' );
continue;
}
$import = $importer->$import_method( (int) $id );
if ( ! is_array( $import ) || ! isset( $import['success'] ) ) {
$results['failed_items'][ $id ] = __( 'Invalid import response.', 'surerank' );
continue;
}
$send_import_data[ $id ] = isset( $import['data'] ) && is_array( $import['data'] ) ? $import['data'] : [];
if ( $import['success'] ) {
$results['success']++;
} else {
$results['failed_items'][ $id ] = $import['message'] ?? __( 'Import failed.', 'surerank' );
}
} catch ( \Exception $e ) {
$results['failed_items'][ $id ] = sprintf(
// translators: %s: error message.
__( 'Error: %s', 'surerank' ),
$e->getMessage()
);
}
}
$results['passed_items'] = $send_import_data;
return $results;
}
/**
* Handles cleanup after successful imports.
*
* @param Importer $importer The importer instance.
* @param bool $cleanup Whether cleanup is requested.
* @param int $success_count The number of successful imports.
* @return array<string, mixed> The cleanup results.
*/
private function handle_cleanup( Importer $importer, bool $cleanup, int $success_count ): array {
$results = [];
if ( $cleanup && $success_count > 0 && method_exists( $importer, 'cleanup' ) ) {
$cleanup_resp = $importer->cleanup();
if ( is_array( $cleanup_resp ) ) {
$results['cleanup'] = $cleanup_resp['success'];
$results['cleanup_message'] = $cleanup_resp['message'];
}
}
return $results;
}
/**
* Formats the final migration response.
*
* @param array<int|string, mixed> $results The migration results.
* @param Importer $importer The importer instance.
* @param array<int> $ids The array of item IDs.
* @param string $item_type The type of item ('posts' or 'terms').
* @return array<string, mixed> The formatted results array.
*/
private function format_response( array $results, Importer $importer, array $ids, string $item_type ): array {
$plugin_name = method_exists( $importer, 'get_plugin_name' ) ? $importer->get_plugin_name() : 'Unknown';
$results['message'] = sprintf(
// translators: 1: imported count, 2: total count, 3: item type, 4: plugin name.
__( 'Imported %1$d of %2$d %3$s from %4$s.', 'surerank' ),
$results['success'],
count( $ids ),
$item_type,
$plugin_name
);
return $results;
}
/**
* Helper to validate ID arrays/values.
*
* @param mixed $param Incoming param.
* @return bool
*/
private function validate_ids( $param ): bool {
// Accept both array and single integer.
if ( is_numeric( $param ) && (int) $param > 0 ) {
return true;
}
if ( ! is_array( $param ) || empty( $param ) ) {
return false;
}
// Validate all array elements are positive integers.
return array_reduce(
$param,
static fn( $valid, $id) => $valid && is_numeric( $id ) && (int) $id > 0,
true
);
}
/**
* Fetch all public taxonomies, excluding unsupported ones.
*
* @return array<string, object> Array of taxonomy objects.
*/
private function get_public_taxonomies(): array {
return ImporterUtils::get_excluded_taxonomies();
}
/**
* Group IDs by taxonomy or post type.
*
* @param array<int|string> $ids Array of term IDs or post IDs.
* @param bool $is_taxonomy True to group terms, False to group posts.
* @param array<string, object> $taxonomies_objects Array of taxonomy objects (required if $is_taxonomy is true).
*
* @return array<string, array<mixed>> Grouped data.
*/
private function group_items( array $ids, bool $is_taxonomy = false, array $taxonomies_objects = [] ): array {
$grouped = [];
foreach ( $ids as $id ) {
if ( $is_taxonomy ) {
$term = get_term( (int) $id );
if ( is_wp_error( $term ) || ! $term ) {
continue;
}
$type = $term->taxonomy;
$label = $taxonomies_objects[ $type ]->label ?? $type;
$key = 'term_ids';
} else {
$type = get_post_type( (int) $id );
if ( false === $type ) {
continue;
}
$object = get_post_type_object( $type );
$label = isset( $object->labels ) ? $object->labels->name : $type;
$key = 'post_ids';
}
if ( ! isset( $grouped[ $type ] ) ) {
$grouped[ $type ] = [
'count' => 0,
'title' => $label,
$key => [],
];
}
$grouped[ $type ][ $key ][] = (int) $id;
$grouped[ $type ]['count']++;
}
return $grouped;
}
}