prefix; nothing user-supplied reaches a query. /** * Legacy — takes over the data of ClusterIQ, the plugin's earlier name (1.3.0). * * ClusterIQ stored everything under cci_ / clusteriq names. On the first * boot after the switch, Clusterlane renames its tables, copies its options, * renames post meta and moves the role capabilities, so a site keeps its * clusters, history, settings (including the encrypted AI key) and access * rules. Runs once per site; on multisite each site migrates the first time * Clusterlane boots there. * * While ClusterIQ itself is still active, nothing is moved and Clusterlane * does not start: a notice asks the user to deactivate ClusterIQ (a button * to WordPress's own Deactivate action). The move happens on the first * request after that. Clusterlane never deactivates a plugin by itself. */ final class Legacy { /** Set when the move is done (or there was nothing to move). */ const DONE = 'clusterlane_migrated'; /** Guards against two requests moving the data at the same time. */ const LOCK = 'clusterlane_migrating'; /** One-time notice for the admin after the move. */ const NOTICE = 'clusterlane_migrated_notice'; /** Plugin files of ClusterIQ (free and Pro, any folder) and its predecessor. */ const OLD_FILE = '#(^|/)(clusteriq|content-cluster-intelligence)\.php$#'; const TABLES = [ 'links', 'clusters', 'post_metrics', 'ai_cache', 'custom_members', 'tms_skipped', 'cluster_post_class', 'snapshots', 'activity' ]; const OPTIONS = [ 'settings', 'db_version', 'caps_version', 'ai_usage', 'chosen_pillars', 'cluster_sections', 'cluster_groups', 'data_version', 'last_snapshot', 'render_log', 'page_queue', 'hub_relations' ]; const POST_META = [ 'gap_cluster', 'elementor_backup', 'seo_check', 'seo_check_min', 'seo_original', 'page_stats' ]; const CAPS = [ 'manage_clusteriq' => 'manage_clusterlane', 'use_clusteriq' => 'use_clusterlane', 'use_clusteriq_ai' => 'use_clusterlane_ai', 'clusteriq_add_links' => 'clusterlane_add_links', 'clusteriq_write_seo' => 'clusterlane_write_seo', ]; /* ------------------------------------------------------------------ Entry points ------------------------------------------------------------------ */ /** * Called first thing in Plugin::boot(). Cheap once the site is done. * * @return bool false while ClusterIQ itself is still active: Clusterlane * then waits (it only shows a notice) until the user * deactivates ClusterIQ — it never switches a plugin off by itself. */ public static function maybe_migrate(): bool { add_action( 'admin_notices', [ self::class, 'notice' ] ); add_action( 'network_admin_notices', [ self::class, 'notice' ] ); if ( get_option( self::DONE ) ) return true; if ( self::old_active() ) return false; // never move tables out from under a running ClusterIQ if ( ! self::has_old_data() ) { update_option( self::DONE, [ 'at' => time(), 'moved' => false ], true ); return true; } if ( ! add_option( self::LOCK, time(), '', false ) ) { if ( time() - (int) get_option( self::LOCK, 0 ) < 5 * MINUTE_IN_SECONDS ) return true; update_option( self::LOCK, time(), false ); // a run that died: take over } self::migrate_site(); if ( is_multisite() ) self::migrate_network(); delete_option( self::LOCK ); update_option( self::DONE, [ 'at' => time(), 'moved' => true ], true ); set_transient( self::NOTICE, 1, DAY_IN_SECONDS ); wp_cache_flush(); // post meta and roles were changed directly return true; } /** * While ClusterIQ is active: what to do, with a button for each active * copy that leads to WordPress's own Deactivate action (the user decides). * After the move: one confirmation. */ public static function notice(): void { if ( ! current_user_can( 'activate_plugins' ) ) return; $old = array_values( array_filter( self::active_plugins(), static fn( $p ) => (bool) preg_match( self::OLD_FILE, $p ) ) ); if ( $old && ! get_option( self::DONE ) ) { echo '
Clusterlane: ' . esc_html__( 'ClusterIQ is now called Clusterlane. Deactivate ClusterIQ to move your clusters, history, settings and access rules over — Clusterlane waits until then, so the two never run at the same time.', 'clusterlane' ) . '
'; foreach ( $old as $plugin ) { $network = is_multisite() && array_key_exists( $plugin, (array) get_site_option( 'active_sitewide_plugins', [] ) ); $base = $network ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' ); $url = wp_nonce_url( add_query_arg( [ 'action' => 'deactivate', 'plugin' => rawurlencode( $plugin ) ], $base ), 'deactivate-plugin_' . $plugin ); echo '' . esc_html( /* translators: %s: plugin folder and file, e.g. clusteriq/clusteriq.php */ sprintf( __( 'Deactivate ClusterIQ (%s)', 'clusterlane' ), $plugin ) ) . ' '; } echo '
Clusterlane: ' . esc_html__( 'Your ClusterIQ data has moved over: clusters, history, settings and access rules. The old ClusterIQ plugin can be deleted — deleting it no longer touches this data.', 'clusterlane' ) . '