get_var( "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} WHERE meta_key IN ('_yoast_wpseo_title','_yoast_wpseo_metadesc') AND meta_value != ''" ); if ( $yoast_active || $yoast_count > 0 ) { $sources['yoast'] = [ 'label' => 'Yoast SEO', 'count' => $yoast_count, 'active' => $yoast_active, ]; } // ── RankMath ─────────────────────────────────────────────────────── $rm_active = defined( 'RANK_MATH_VERSION' ) || is_plugin_active( 'seo-by-rank-math/rank-math.php' ) || is_plugin_active( 'seo-by-rank-math-pro/rank-math-pro.php' ); $rm_count = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} WHERE meta_key IN ('rank_math_title','rank_math_description') AND meta_value != ''" ); if ( $rm_active || $rm_count > 0 ) { $sources['rankmath'] = [ 'label' => 'RankMath SEO', 'count' => $rm_count, 'active' => $rm_active, ]; } // ── AIOSEO ───────────────────────────────────────────────────────── $aio_active = defined( 'AIOSEO_VERSION' ) || is_plugin_active( 'all-in-one-seo-pack/all_in_one_seo_pack.php' ) || is_plugin_active( 'aioseo/aioseo.php' ); // AIOSEO v4 uses a custom table; v3 uses post meta. $aio_table = $wpdb->prefix . 'aioseo_posts'; $has_table = $wpdb->get_var( "SHOW TABLES LIKE '{$aio_table}'" ) === $aio_table; $aio_count = $has_table ? (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$aio_table}" ) : (int) $wpdb->get_var( "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} WHERE meta_key IN ('_aioseo_title','_aioseop_description') AND meta_value != ''" ); if ( $aio_active || $aio_count > 0 ) { $sources['aioseo'] = [ 'label' => 'All in One SEO', 'count' => $aio_count, 'active' => $aio_active, ]; } return $sources; } // ── AJAX: detect ────────────────────────────────────────────────────────── public static function ajax_detect() { check_ajax_referer( 'awb_wizard_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Unauthorized.' ); wp_send_json_success( self::detect_plugins() ); } // ── AJAX: import ────────────────────────────────────────────────────────── public static function ajax_import() { check_ajax_referer( 'awb_wizard_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Unauthorized.' ); $source = sanitize_text_field( $_POST['source'] ?? '' ); $offset = max( 0, (int) ( $_POST['offset'] ?? 0 ) ); $allowed = [ 'yoast', 'rankmath', 'aioseo' ]; if ( ! in_array( $source, $allowed, true ) ) { wp_send_json_error( 'Unknown import source.' ); } $method = 'import_' . $source; wp_send_json_success( self::$method( $offset ) ); } // ── Import: Yoast ───────────────────────────────────────────────────────── private static function import_yoast( $offset ) { global $wpdb; $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT post_id FROM {$wpdb->postmeta} WHERE meta_key IN ('_yoast_wpseo_title','_yoast_wpseo_metadesc','_yoast_wpseo_focuskw') AND meta_value != '' ORDER BY post_id ASC LIMIT %d OFFSET %d", self::BATCH_SIZE, $offset ) ); $map = [ '_yoast_wpseo_title' => 'title', '_yoast_wpseo_metadesc' => 'description', '_yoast_wpseo_focuskw' => 'focus_keyword', '_yoast_wpseo_canonical' => 'canonical', '_yoast_wpseo_opengraph-title' => 'og_title', '_yoast_wpseo_opengraph-description' => 'og_description', '_yoast_wpseo_opengraph-image' => 'og_image', '_yoast_wpseo_twitter-title' => 'twitter_title', '_yoast_wpseo_twitter-description' => 'twitter_desc', '_yoast_wpseo_twitter-image' => 'twitter_image', ]; foreach ( $post_ids as $post_id ) { foreach ( $map as $from => $to ) { $val = get_post_meta( $post_id, $from, true ); if ( $val !== '' && $val !== false ) { update_post_meta( $post_id, '_awb_seo_' . $to, $val ); } } // Robots if ( get_post_meta( $post_id, '_yoast_wpseo_meta-robots-noindex', true ) === '1' ) { update_post_meta( $post_id, '_awb_seo_robots_index', 'noindex' ); } if ( get_post_meta( $post_id, '_yoast_wpseo_meta-robots-nofollow', true ) === '1' ) { update_post_meta( $post_id, '_awb_seo_robots_follow', 'nofollow' ); } } $total = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} WHERE meta_key IN ('_yoast_wpseo_title','_yoast_wpseo_metadesc','_yoast_wpseo_focuskw') AND meta_value != ''" ); return self::progress( count( $post_ids ), $offset, $total ); } // ── Import: RankMath ────────────────────────────────────────────────────── private static function import_rankmath( $offset ) { global $wpdb; $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT post_id FROM {$wpdb->postmeta} WHERE meta_key IN ('rank_math_title','rank_math_description','rank_math_focus_keyword') AND meta_value != '' ORDER BY post_id ASC LIMIT %d OFFSET %d", self::BATCH_SIZE, $offset ) ); $map = [ 'rank_math_title' => 'title', 'rank_math_description' => 'description', 'rank_math_canonical_url' => 'canonical', 'rank_math_facebook_title' => 'og_title', 'rank_math_facebook_description' => 'og_description', 'rank_math_twitter_title' => 'twitter_title', 'rank_math_twitter_description' => 'twitter_desc', ]; foreach ( $post_ids as $post_id ) { foreach ( $map as $from => $to ) { $val = get_post_meta( $post_id, $from, true ); if ( $val !== '' && $val !== false ) { update_post_meta( $post_id, '_awb_seo_' . $to, $val ); } } // Focus keyword - RankMath stores comma-separated; take first. $kw = get_post_meta( $post_id, 'rank_math_focus_keyword', true ); if ( $kw ) { $parts = explode( ',', $kw ); update_post_meta( $post_id, '_awb_seo_focus_keyword', trim( $parts[0] ) ); } // OG image stored as attachment ID in newer RankMath. $img_id = get_post_meta( $post_id, 'rank_math_facebook_image_id', true ); if ( $img_id ) { $img_url = wp_get_attachment_url( $img_id ); if ( $img_url ) update_post_meta( $post_id, '_awb_seo_og_image', $img_url ); } // Robots - stored as array. $robots = get_post_meta( $post_id, 'rank_math_robots', true ); if ( is_array( $robots ) ) { if ( in_array( 'noindex', $robots, true ) ) update_post_meta( $post_id, '_awb_seo_robots_index', 'noindex' ); if ( in_array( 'index', $robots, true ) ) update_post_meta( $post_id, '_awb_seo_robots_index', 'index' ); if ( in_array( 'nofollow',$robots, true ) ) update_post_meta( $post_id, '_awb_seo_robots_follow', 'nofollow' ); if ( in_array( 'follow', $robots, true ) ) update_post_meta( $post_id, '_awb_seo_robots_follow', 'follow' ); } } $total = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} WHERE meta_key IN ('rank_math_title','rank_math_description','rank_math_focus_keyword') AND meta_value != ''" ); return self::progress( count( $post_ids ), $offset, $total ); } // ── Import: AIOSEO ──────────────────────────────────────────────────────── private static function import_aioseo( $offset ) { global $wpdb; $table = $wpdb->prefix . 'aioseo_posts'; if ( $wpdb->get_var( "SHOW TABLES LIKE '{$table}'" ) === $table ) { return self::import_aioseo_v4( $offset ); } return self::import_aioseo_legacy( $offset ); } private static function import_aioseo_v4( $offset ) { global $wpdb; $table = $wpdb->prefix . 'aioseo_posts'; $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} ORDER BY post_id ASC LIMIT %d OFFSET %d", self::BATCH_SIZE, $offset ) ); foreach ( $rows as $row ) { $pid = (int) $row->post_id; if ( ! empty( $row->title ) ) update_post_meta( $pid, '_awb_seo_title', $row->title ); if ( ! empty( $row->description ) ) update_post_meta( $pid, '_awb_seo_description', $row->description ); if ( ! empty( $row->canonical_url ) ) update_post_meta( $pid, '_awb_seo_canonical', $row->canonical_url ); if ( ! empty( $row->og_title ) ) update_post_meta( $pid, '_awb_seo_og_title', $row->og_title ); if ( ! empty( $row->og_description ) )update_post_meta( $pid, '_awb_seo_og_description', $row->og_description ); // Keywords: JSON array like [{"keyphrase":"seo tips","score":6}] if ( ! empty( $row->keyphrases ) ) { $kps = json_decode( $row->keyphrases, true ); if ( isset( $kps['focus']['keyphrase'] ) ) { update_post_meta( $pid, '_awb_seo_focus_keyword', sanitize_text_field( $kps['focus']['keyphrase'] ) ); } } if ( ! empty( $row->robots_noindex ) ) update_post_meta( $pid, '_awb_seo_robots_index', 'noindex' ); if ( ! empty( $row->robots_nofollow) ) update_post_meta( $pid, '_awb_seo_robots_follow', 'nofollow' ); } $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" ); return self::progress( count( $rows ), $offset, $total ); } private static function import_aioseo_legacy( $offset ) { global $wpdb; $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT post_id FROM {$wpdb->postmeta} WHERE meta_key IN ('_aioseo_title','_aioseop_description','_aioseo_description') AND meta_value != '' ORDER BY post_id ASC LIMIT %d OFFSET %d", self::BATCH_SIZE, $offset ) ); foreach ( $post_ids as $pid ) { $title = get_post_meta( $pid, '_aioseo_title', true ) ?: get_post_meta( $pid, '_aioseop_title', true ); $desc = get_post_meta( $pid, '_aioseo_description', true ) ?: get_post_meta( $pid, '_aioseop_description', true ); $kw = get_post_meta( $pid, '_aioseo_keywords', true ) ?: get_post_meta( $pid, '_aioseop_keywords', true ); $canon = get_post_meta( $pid, '_aioseo_canonical_url', true ); if ( $title ) update_post_meta( $pid, '_awb_seo_title', $title ); if ( $desc ) update_post_meta( $pid, '_awb_seo_description', $desc ); if ( $canon ) update_post_meta( $pid, '_awb_seo_canonical', $canon ); if ( $kw ) { $decoded = json_decode( $kw, true ); if ( is_array( $decoded ) ) { $first = reset( $decoded ); $kw = is_array( $first ) ? ( $first['keyphrase'] ?? '' ) : (string) $first; } else { $kw = trim( explode( ',', $kw )[0] ); } if ( $kw ) update_post_meta( $pid, '_awb_seo_focus_keyword', sanitize_text_field( $kw ) ); } if ( get_post_meta( $pid, '_aioseo_robots_noindex', true ) ) update_post_meta( $pid, '_awb_seo_robots_index', 'noindex' ); if ( get_post_meta( $pid, '_aioseo_robots_nofollow', true ) ) update_post_meta( $pid, '_awb_seo_robots_follow', 'nofollow' ); } $total = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} WHERE meta_key IN ('_aioseo_title','_aioseop_description','_aioseo_description') AND meta_value != ''" ); return self::progress( count( $post_ids ), $offset, $total ); } // ── AJAX: save local SEO ────────────────────────────────────────────────── public static function ajax_save_local() { check_ajax_referer( 'awb_wizard_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Unauthorized.' ); $data = [ 'name' => sanitize_text_field( $_POST['name'] ?? '' ), 'street' => sanitize_text_field( $_POST['street'] ?? '' ), 'city' => sanitize_text_field( $_POST['city'] ?? '' ), 'region' => sanitize_text_field( $_POST['region'] ?? '' ), 'postal_code' => sanitize_text_field( $_POST['postal_code'] ?? '' ), 'country' => sanitize_text_field( $_POST['country'] ?? 'US' ), 'phone' => sanitize_text_field( $_POST['phone'] ?? '' ), 'email' => sanitize_email( $_POST['email'] ?? '' ), 'is_primary' => 1, ]; if ( empty( $data['name'] ) ) { wp_send_json_error( 'Business name is required.' ); } $result = AWBSEO_Local_SEO::save_location( $data ); if ( is_wp_error( $result ) ) { wp_send_json_error( $result->get_error_message() ); } wp_send_json_success( [ 'location_id' => $result ] ); } // ── AJAX: complete wizard ───────────────────────────────────────────────── public static function ajax_complete() { check_ajax_referer( 'awb_wizard_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Unauthorized.' ); update_option( self::OPTION_DONE, '1', false ); wp_send_json_success( [ 'redirect' => admin_url( 'admin.php?page=adworksbid-seo' ) ] ); } // ── Helpers ─────────────────────────────────────────────────────────────── private static function progress( $batch_count, $offset, $total ) { $done = $batch_count === 0 || ( $offset + $batch_count ) >= $total; return [ 'imported' => $batch_count, 'offset' => $offset + $batch_count, 'total' => $total, 'done' => $done, 'pct' => $total > 0 ? min( 100, (int) round( ( $offset + $batch_count ) / $total * 100 ) ) : 100, ]; } }