$row ) { $v = self::validate( $row, $id ); if ( null !== $v ) { $memo[ $v['id'] ] = $v; } } } } $cat = get_option( self::OPTION_CATALOGUE, array() ); if ( is_array( $cat ) && ! empty( $cat['rows'] ) && is_array( $cat['rows'] ) ) { foreach ( $cat['rows'] as $id => $row ) { $id = sanitize_key( (string) $id ); if ( '' === $id || ! is_array( $row ) ) { continue; } if ( isset( $memo[ $id ] ) ) { $memo[ $id ] = self::merge_row( $memo[ $id ], $row ); } else { $v = self::validate( $row, $id ); if ( null !== $v ) { $memo[ $id ] = $v; } } } } self::$rows_memo = $memo; return $memo; } private static $rows_memo = null; private static $active_memo = null; public static function flush() { self::$rows_memo = null; self::$active_memo = null; } public static function validate( $row, $id ) { $id = sanitize_key( (string) $id ); if ( '' === $id || ! is_array( $row ) ) { return null; } $raw = isset( $row['kind'] ) ? $row['kind'] : ( isset( $row['family'] ) ? $row['family'] : array() ); $kinds = array(); foreach ( (array) $raw as $k ) { $k = sanitize_key( (string) $k ); if ( in_array( $k, self::KINDS, true ) && ! in_array( $k, $kinds, true ) ) { $kinds[] = $k; } } $detect = self::clean_detect( isset( $row['detect'] ) ? $row['detect'] : null ); $fault = isset( $row['fault'] ) && is_string( $row['fault'] ) ? trim( $row['fault'] ) : ''; if ( in_array( 'conflicts', $kinds, true ) && ( empty( $detect ) || '' === $fault ) ) { $kinds = array_values( array_diff( $kinds, array( 'conflicts' ) ) ); } if ( empty( $kinds ) ) { return null; } if ( in_array( 'conflicts', $kinds, true ) ) { $kinds = array_merge( array( 'conflicts' ), array_values( array_diff( $kinds, array( 'conflicts' ) ) ) ); } $group = isset( $row['group'] ) ? sanitize_key( (string) $row['group'] ) : ''; if ( ! in_array( $group, self::GROUPS, true ) ) { return null; } $label = isset( $row['label'] ) && is_scalar( $row['label'] ) ? trim( (string) $row['label'] ) : ''; $plugins = array(); $listed = array_merge( (array) ( isset( $row['plugin'] ) ? $row['plugin'] : array() ), (array) ( isset( $row['plugins'] ) ? $row['plugins'] : array() ) ); foreach ( $listed as $b ) { $b = is_scalar( $b ) ? ltrim( trim( (string) $b ), '/' ) : ''; if ( '' !== $b && ! in_array( $b, $plugins, true ) ) { $plugins[] = $b; } } if ( '' === $label || empty( $plugins ) ) { return null; } $row['id'] = $id; $row['kind'] = $kinds; $row['family'] = $kinds[0]; $row['group'] = $group; $row['label'] = $label; $row['plugin'] = $plugins[0]; $row['plugins'] = $plugins; $row['tier'] = ( isset( $row['tier'] ) && in_array( $row['tier'], self::TIERS, true ) ) ? $row['tier'] : 'free'; if ( '' !== $fault ) { $row['fault'] = $fault; } else { unset( $row['fault'] ); } if ( ! empty( $detect ) ) { $row['detect'] = $detect; } else { unset( $row['detect'] ); } $aliases = array(); foreach ( (array) ( isset( $row['aliases'] ) ? $row['aliases'] : array() ) as $a ) { if ( is_scalar( $a ) && '' !== trim( (string) $a ) ) { $aliases[] = trim( (string) $a ); } } $row['aliases'] = array_values( array_unique( $aliases ) ); if ( isset( $row['slug'] ) ) { $row['slug'] = sanitize_title( (string) $row['slug'] ); } if ( isset( $row['ours'] ) ) { $ours = is_array( $row['ours'] ) ? $row['ours'] : array(); $tabs = array(); foreach ( (array) ( isset( $ours['tabs'] ) ? $ours['tabs'] : array() ) as $t ) { $t = sanitize_key( (string) $t ); if ( '' !== $t && ! in_array( $t, $tabs, true ) ) { $tabs[] = $t; } } $ours['tabs'] = $tabs; $ours['label'] = isset( $ours['label'] ) && is_scalar( $ours['label'] ) ? (string) $ours['label'] : ''; $row['ours'] = $ours; } return $row; } private static function clean_detect( $detect ) { if ( ! is_array( $detect ) ) { return array(); } $out = array(); foreach ( array( 'function', 'class', 'constant', 'callable' ) as $k ) { if ( isset( $detect[ $k ] ) && is_string( $detect[ $k ] ) && '' !== trim( $detect[ $k ] ) ) { $out[ $k ] = trim( $detect[ $k ] ); } } return $out; } private static function merge_row( array $bundled, array $portal ) { foreach ( $portal as $k => $v ) { if ( in_array( $k, self::PROTECTED_KEYS, true ) ) { continue; } if ( 'aliases' === $k ) { $bundled['aliases'] = array_values( array_unique( array_merge( $bundled['aliases'], array_map( 'strval', array_filter( (array) $v, 'is_scalar' ) ) ) ) ); } elseif ( 'detect' === $k ) { $bundled['detect'] = array_merge( self::clean_detect( $v ), isset( $bundled['detect'] ) ? $bundled['detect'] : array() ); } elseif ( ! array_key_exists( $k, $bundled ) ) { $bundled[ $k ] = $v; } } $v = self::validate( $bundled, $bundled['id'] ); return null === $v ? $bundled : $v; } public static function row( $id ) { $rows = self::rows(); return isset( $rows[ $id ] ) ? $rows[ $id ] : null; } public static function search_terms() { $out = array(); foreach ( self::rows() as $row ) { $out[] = (string) $row['label']; foreach ( (array) ( $row['aliases'] ?? array() ) as $a ) { $out[] = (string) $a; } } return array_values( array_unique( array_filter( $out ) ) ); } const CRON_REFRESH = 'aieo_overlap_catalogue_refresh'; public static function init() { add_action( self::CRON_REFRESH, array( __CLASS__, 'refresh_catalogue' ) ); add_action( 'admin_init', array( __CLASS__, 'maybe_schedule' ) ); } public static function catalogue_allowed() { $paired = 'connected' === (string) get_option( 'aieo_rooster_status', '' ) && '' !== (string) get_option( 'aieo_rooster_api_key', '' ); if ( $paired ) { return true; } return class_exists( 'AIEO_Simplify_Scan' ) && AIEO_Simplify_Scan::consented(); } public static function maybe_schedule() { if ( ! self::catalogue_allowed() ) { return; } if ( ! wp_next_scheduled( self::CRON_REFRESH ) ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', self::CRON_REFRESH ); } } public static function refresh_catalogue() { if ( ! self::catalogue_allowed() ) { return array( 'ok' => false, 'changed' => false, 'count' => 0, 'code' => 0, 'reason' => 'not_allowed' ); } $cat = get_option( self::OPTION_CATALOGUE, array() ); $cat = is_array( $cat ) ? $cat : array(); $base = rtrim( (string) get_option( 'aieo_rooster_hub_url', 'https://rooster.systems' ), '/' ); $hdr = array( 'Accept' => 'application/json', 'X-OM-API-Key' => (string) get_option( 'aieo_rooster_api_key', '' ) ); if ( ! empty( $cat['etag'] ) ) { $hdr['If-None-Match'] = (string) $cat['etag']; } $r = wp_remote_get( $base . '/wp-json/om-portal/v1/one/overlaps', array( 'headers' => $hdr, 'timeout' => 20, 'sslverify' => (bool) apply_filters( 'aieo_portal_sslverify', true ) ) ); if ( is_wp_error( $r ) ) { return array( 'ok' => false, 'changed' => false, 'count' => count( (array) ( $cat['rows'] ?? array() ) ), 'code' => 0, 'reason' => $r->get_error_message() ); } $code = (int) wp_remote_retrieve_response_code( $r ); if ( 304 === $code ) { $cat['fetched_at'] = time(); update_option( self::OPTION_CATALOGUE, $cat, false ); return array( 'ok' => true, 'changed' => false, 'count' => count( (array) ( $cat['rows'] ?? array() ) ), 'code' => 304, 'reason' => '' ); } if ( 200 !== $code ) { return array( 'ok' => false, 'changed' => false, 'count' => count( (array) ( $cat['rows'] ?? array() ) ), 'code' => $code, 'reason' => 404 === $code ? 'no_catalogue_yet' : 'http_' . $code ); } $body = json_decode( (string) wp_remote_retrieve_body( $r ), true ); $rows = is_array( $body ) && isset( $body['rows'] ) && is_array( $body['rows'] ) ? $body['rows'] : array(); $kept = array(); foreach ( $rows as $id => $row ) { $id = sanitize_key( (string) $id ); if ( '' !== $id && is_array( $row ) ) { $kept[ $id ] = $row; } } $etag = trim( (string) wp_remote_retrieve_header( $r, 'etag' ) ); update_option( self::OPTION_CATALOGUE, array( 'etag' => $etag, 'fetched_at' => time(), 'rows' => $kept ), false ); self::flush(); return array( 'ok' => true, 'changed' => true, 'count' => count( $kept ), 'code' => 200, 'reason' => '' ); } public static function catalogue_state() { $cat = get_option( self::OPTION_CATALOGUE, array() ); $cat = is_array( $cat ) ? $cat : array(); return array( 'fetched_at' => (int) ( $cat['fetched_at'] ?? 0 ), 'count' => count( (array) ( $cat['rows'] ?? array() ) ), 'etag' => (string) ( $cat['etag'] ?? '' ) ); } public static function decisions() { $v = get_option( self::OPTION, array() ); return is_array( $v ) ? $v : array(); } public static function decision( $key ) { $key = sanitize_key( (string) $key ); $rows = self::rows(); if ( ! isset( $rows[ $key ] ) ) { $active = self::active_in_group( $key ); if ( '' === $active ) { return ''; } $key = $active; } $all = self::decisions(); $d = isset( $all[ $key ]['decision'] ) ? (string) $all[ $key ]['decision'] : ''; return in_array( $d, self::DECISIONS, true ) ? $d : ''; } public static function decide( $id, $decision, $uid ) { $id = sanitize_key( (string) $id ); $decision = sanitize_key( (string) $decision ); if ( null === self::row( $id ) ) { return false; } $all = self::decisions(); if ( 'undo' === $decision ) { unset( $all[ $id ] ); } elseif ( in_array( $decision, self::DECISIONS, true ) ) { $all[ $id ] = array( 'decision' => $decision, 'at' => time(), 'by' => (int) $uid ); } else { return false; } if ( empty( $all ) ) { delete_option( self::OPTION ); } else { update_option( self::OPTION, $all, false ); } return true; } public static function active_in_group( $group ) { if ( 'seo' === $group ) { $store = self::competitor(); if ( '' === $store ) { return ''; } foreach ( self::rows() as $id => $row ) { if ( 'seo' === ( $row['group'] ?? '' ) && isset( $row['store'] ) && $row['store'] === $store ) { return $id; } } return ''; } foreach ( self::rows() as $id => $row ) { if ( $group === ( $row['group'] ?? '' ) && 'unrelated' !== $row['family'] && self::plugin_active( $row ) ) { return $id; } } return ''; } public static function present() { $out = array(); foreach ( self::rows() as $id => $row ) { if ( 'unrelated' === $row['family'] ) { continue; } $c = self::card( $id ); if ( self::STATE_NOT_PRESENT !== $c['state'] ) { $out[] = $id; } } return $out; } public static function plugin_active( array $row ) { if ( ! empty( $row['store'] ) ) { $active = ( self::competitor() === (string) $row['store'] ); } elseif ( ! empty( $row['cmp'] ) ) { $active = self::cmp_active( $row ); } else { $active = (bool) array_intersect( (array) ( $row['plugins'] ?? array( $row['plugin'] ) ), self::raw_active() ); } $active = (bool) apply_filters( 'aieo_overlap_plugin_active', $active, $row ); if ( $active && ! empty( $row['detect'] ) ) { $active = ( null !== self::detect( $row['detect'] ) ); } return $active; } public static function detect( array $detect ) { $got = true; if ( isset( $detect['function'] ) && ! function_exists( $detect['function'] ) ) { return null; } if ( isset( $detect['class'] ) && ! class_exists( $detect['class'] ) ) { return null; } if ( isset( $detect['constant'] ) && ! defined( $detect['constant'] ) ) { return null; } if ( isset( $detect['callable'] ) ) { if ( ! is_callable( $detect['callable'] ) ) { return null; } try { $got = call_user_func( $detect['callable'] ); } catch ( \Throwable $e ) { return null; } if ( empty( $got ) ) { return null; } } return $got; } private static function raw_active() { if ( null !== self::$active_memo ) { return self::$active_memo; } global $wpdb; $list = array(); $raw = $wpdb->get_var( "SELECT option_value FROM {$wpdb->options} WHERE option_name = 'active_plugins' LIMIT 1" ); $val = maybe_unserialize( (string) $raw ); if ( is_array( $val ) ) { foreach ( $val as $b ) { if ( is_string( $b ) && '' !== $b ) { $list[] = $b; } } } if ( is_multisite() ) { foreach ( array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ) as $b ) { if ( is_string( $b ) && '' !== $b ) { $list[] = $b; } } } self::$active_memo = array_values( array_unique( $list ) ); return self::$active_memo; } public static function card( $id ) { $row = self::row( $id ); if ( ! $row || 'unrelated' === $row['family'] ) { return array( 'id' => $id, 'state' => self::STATE_NOT_PRESENT ); } $is_seo = ( 'seo' === $row['group'] && ! empty( $row['store'] ) ); $kinds = (array) $row['kind']; $active = self::plugin_active( $row ); $detected = ( $active && ! empty( $row['detect'] ) ) ? self::detect( $row['detect'] ) : null; $installed = false; foreach ( (array) ( $row['plugins'] ?? array( $row['plugin'] ) ) as $b ) { if ( '' !== (string) $b && file_exists( WP_PLUGIN_DIR . '/' . ltrim( (string) $b, '/' ) ) ) { $installed = true; break; } } $ours_on = self::ours_on( $row ); $fault_live = $active && in_array( 'conflicts', $kinds, true ) && ! empty( $row['fault'] ) && false !== $ours_on; $family = (string) $row['family']; if ( 'conflicts' === $family && ! $fault_live ) { $rest = array_values( array_diff( $kinds, array( 'conflicts' ) ) ); $family = empty( $rest ) ? 'conflicts' : $rest[0]; if ( empty( $rest ) ) { $active = false; } } $is_along = ( 'alongside' === $family ); $counts = array( 'posts' => 0, 'terms' => 0 ); $has_data = false; if ( $is_seo && ! empty( $row['import'] ) ) { foreach ( self::seo_sources() as $s ) { if ( $s['id'] === $row['import'] ) { $counts = array( 'posts' => (int) ( $s['counts']['posts'] ?? 0 ), 'terms' => (int) ( $s['counts']['terms'] ?? 0 ) ); $has_data = true; } } } $decision = self::decision( $id ); $engine_on = class_exists( 'AIEO_DMM_Settings' ) ? (bool) AIEO_DMM_Settings::get( 'seo.enabled', false ) : false; $we_own = class_exists( 'AIEO_SEO_Store' ) && method_exists( 'AIEO_SEO_Store', 'is_active_provider' ) ? (bool) AIEO_SEO_Store::is_active_provider() : false; $last = (array) get_option( 'aieo_seo_import_last', array() ); $import_done = $is_seo && ! empty( $last['finished_at'] ) && ( $last['source'] ?? '' ) === ( $row['import'] ?? '' ); $import_when = $import_done ? (int) $last['finished_at'] : 0; $import_skip = $import_done ? (int) ( $last['skipped'] ?? 0 ) : 0; $import_n = $import_done ? (int) ( $last['posts_imported'] ?? 0 ) + (int) ( $last['terms_imported'] ?? 0 ) : 0; $verify = get_option( class_exists( 'AIEO_Setup_Checklist' ) ? AIEO_Setup_Checklist::OPTION_VERIFY_LAST : 'aieo_seo_verify_last', array() ); $parity = array( 'ran' => is_array( $verify ) && ! empty( $verify['at'] ), 'checked' => is_array( $verify ) ? (int) ( $verify['checked'] ?? 0 ) : 0, 'mismatches' => is_array( $verify ) ? (int) ( $verify['mismatches'] ?? 0 ) : 0, 'at' => is_array( $verify ) ? (int) ( $verify['at'] ?? 0 ) : 0, ); if ( ! $is_seo && ! $active && 'switch' === $decision && ! $is_along && $installed ) { $state = self::STATE_RETIRED; } elseif ( ! $is_seo && ! $active ) { $state = self::STATE_NOT_PRESENT; } elseif ( ! $is_seo && $active && 'switch' === $decision && ! $is_along ) { $state = self::STATE_MIGRATING; } elseif ( ! $active && ! $installed && ! $has_data ) { $state = self::STATE_NOT_PRESENT; } elseif ( 'kept' === $decision && $active ) { $state = self::STATE_KEPT; } elseif ( 'later' === $decision && $active ) { $state = self::STATE_LATER; } elseif ( ! $active && $is_seo && ( $has_data || 'switch' === $decision ) && $we_own ) { $state = self::STATE_RETIRED; } elseif ( $active && $is_seo && ( 'switch' === $decision || $import_done || $engine_on ) ) { $state = ( $import_done && $import_skip > 0 ) ? self::STATE_PARTIAL : self::STATE_MIGRATING; } else { $state = self::STATE_UNDECIDED; } $nothing_to_lose = false; if ( $active && $is_seo && self::STATE_UNDECIDED === $state && 0 === $counts['posts'] + $counts['terms'] && class_exists( 'AIEO_Shop_Shape' ) ) { $f = AIEO_Shop_Shape::facts(); $nothing_to_lose = AIEO_Shop_Shape::is_fresh() && (int) $f['products'] < AIEO_Shop_Shape::BLANK_PRODUCT_CEILING; } $evidence = array(); if ( $is_seo ) { $evidence[] = sprintf( __( '%1$s products and %2$s terms carry %3$s data', 'ai-eshop-optimizer' ), number_format_i18n( $counts['posts'] ), number_format_i18n( $counts['terms'] ), $row['label'] ); if ( $active ) { $evidence[] = __( 'Rooster One is in augment-only mode: it adds ratings into the other plugin\'s schema and writes nothing to the page head', 'ai-eshop-optimizer' ); } elseif ( $we_own ) { $evidence[] = __( 'Rooster One owns the page head', 'ai-eshop-optimizer' ); } elseif ( $installed ) { $evidence[] = __( 'Installed but not active', 'ai-eshop-optimizer' ); } $evidence[] = $parity['ran'] ? sprintf( __( 'Head parity: checked %1$d pages, %2$d mismatches, on %3$s', 'ai-eshop-optimizer' ), $parity['checked'], $parity['mismatches'], wp_date( get_option( 'date_format' ), $parity['at'] ) ) : __( 'Head parity: not yet run', 'ai-eshop-optimizer' ); if ( $import_done ) { $evidence[] = sprintf( __( 'Imported %1$s objects on %2$s', 'ai-eshop-optimizer' ), number_format_i18n( $import_n ), wp_date( get_option( 'date_format' ), $import_when ) ); } } elseif ( ! $is_along ) { $quoted = false; if ( ! empty( $row['evidence'] ) && is_callable( $row['evidence'] ) ) { try { foreach ( (array) call_user_func( $row['evidence'], $detected, $row ) as $line ) { if ( is_scalar( $line ) && '' !== trim( (string) $line ) ) { $evidence[] = (string) $line; $quoted = true; } } } catch ( \Throwable $e ) { } } if ( empty( $evidence ) ) { $evidence[] = $active ? sprintf( __( '%s is active', 'ai-eshop-optimizer' ), $row['label'] ) : __( 'Installed but not active', 'ai-eshop-optimizer' ); } $ours_label = isset( $row['ours']['label'] ) ? (string) $row['ours']['label'] : ''; if ( ! $quoted && null !== $ours_on && '' !== $ours_label ) { $evidence[] = $ours_on ? sprintf( __( 'Rooster One\'s %s is on', 'ai-eshop-optimizer' ), $ours_label ) : sprintf( __( 'Rooster One\'s %s is off', 'ai-eshop-optimizer' ), $ours_label ); } } $others = array(); $best = ''; if ( $is_seo ) { $sources = self::seo_sources(); if ( count( $sources ) > 1 && class_exists( 'AIEO_SEO_Import_Registry' ) ) { $best_id = (string) AIEO_SEO_Import_Registry::best(); foreach ( $sources as $s ) { if ( $s['id'] === $best_id ) { $best = (string) $s['label']; } elseif ( $s['id'] !== ( $row['import'] ?? '' ) ) { $others[] = (string) $s['label']; } } } } return array( 'id' => $id, 'label' => (string) $row['label'], 'family' => $family, 'group' => (string) ( $row['group'] ?? '' ), 'active' => $active, 'installed' => $installed, 'has_data' => $has_data, 'counts' => $counts, 'decision' => $decision, 'state' => $state, 'nothing_to_lose' => $nothing_to_lose, 'engine_on' => $engine_on, 'we_own_head' => $we_own, 'import_done' => $import_done, 'import_when' => $import_when, 'import_skipped' => $import_skip, 'parity' => $parity, 'evidence' => $evidence, 'ours' => (array) ( $row['ours'] ?? array() ), 'lines' => (array) ( $row['lines'] ?? array() ), 'others' => $others, 'best' => $best, 'sequence' => $is_seo ? self::seo_sequence( $row, $active, $import_done, $parity['ran'], $engine_on ) : ( $is_along ? array() : self::plain_sequence( $row, $active ) ), 'rollback' => $is_seo ? sprintf( __( 'If you change your mind: nothing we import is written back to %1$s. Switch our engine off and %1$s takes the head back on the next page load.', 'ai-eshop-optimizer' ), $row['label'] ) : ( $is_along ? '' : sprintf( __( 'If you change your mind: Rooster One writes nothing into %s. Reactivate it and it carries on with its own settings.', 'ai-eshop-optimizer' ), $row['label'] ) ), 'kinds' => $kinds, 'fault' => isset( $row['fault'] ) ? (string) $row['fault'] : '', 'fault_live' => $fault_live, 'tier' => (string) $row['tier'], 'slug' => isset( $row['slug'] ) ? (string) $row['slug'] : '', 'detected' => $detected, ); } private static function ours_on( array $row ) { if ( empty( $row['ours']['on'] ) || ! is_callable( $row['ours']['on'] ) ) { return null; } try { return (bool) call_user_func( $row['ours']['on'] ); } catch ( \Throwable $e ) { return null; } } private static function plain_sequence( array $row, $active ) { $tab = ! empty( $row['ours']['tabs'] ) ? (string) $row['ours']['tabs'][0] : ''; $ours_on = self::ours_on( $row ); $ours_label = isset( $row['ours']['label'] ) ? (string) $row['ours']['label'] : ''; $steps = array( array( 'n' => 1, 'label' => '' !== $ours_label ? sprintf( __( 'Check Rooster One\'s %s settings', 'ai-eshop-optimizer' ), $ours_label ) : __( 'Check the Rooster One settings', 'ai-eshop-optimizer' ), 'done' => true === $ours_on, 'tab' => $tab, 'anchor' => '', 'url' => '', 'yours' => false, ), array( 'n' => 2, 'label' => sprintf( __( 'Deactivate %s. Last, and yours to do', 'ai-eshop-optimizer' ), $row['label'] ), 'done' => ! $active, 'tab' => '', 'anchor' => '', 'url' => admin_url( 'plugins.php' ), 'yours' => true, ), ); if ( '' !== $tab && class_exists( 'AIEO_DMM' ) ) { $steps[0]['url'] = AIEO_DMM::instance()->get_subtab_url( $tab ); } return $steps; } public static function notes_for_tab( $tab ) { $tab = sanitize_key( (string) $tab ); if ( '' === $tab ) { return array(); } $out = array(); foreach ( self::rows() as $id => $row ) { if ( 'unrelated' === $row['family'] || ! in_array( $tab, (array) ( $row['ours']['tabs'] ?? array() ), true ) ) { continue; } if ( in_array( $tab, array_map( 'sanitize_key', (array) ( $row['note_except'] ?? array() ) ), true ) ) { continue; } $c = self::card( $id ); if ( ! $c['active'] || ! in_array( $c['state'], array( self::STATE_UNDECIDED, self::STATE_LATER, self::STATE_PARTIAL, self::STATE_MIGRATING ), true ) ) { continue; } if ( $c['fault_live'] ) { $text = sprintf( __( '%1$s and Rooster One both do this. %2$s', 'ai-eshop-optimizer' ), $c['label'], $c['fault'] ); } elseif ( ! empty( $row['note'] ) && is_string( $row['note'] ) ) { $text = (string) $row['note']; } elseif ( 'replaces' === $c['family'] ) { $text = sprintf( __( '%s also does this and owns it for now. Rooster One stands back until you decide.', 'ai-eshop-optimizer' ), $c['label'] ); } elseif ( 'supersedes' === $c['family'] ) { $text = sprintf( __( '%s also does this. Rooster One can take it over without moving any data.', 'ai-eshop-optimizer' ), $c['label'] ); } else { $text = ! empty( $c['lines'][0] ) ? (string) $c['lines'][0] : sprintf( __( '%s also does this, and the two work together.', 'ai-eshop-optimizer' ), $c['label'] ); } $out[] = array( 'id' => $id, 'text' => $text, 'url' => ( class_exists( 'AIEO_DMM' ) ? AIEO_DMM::instance()->get_subtab_url( 'overlaps' ) : '' ) . '#aieo-ov-card-' . $id, 'link' => __( 'Overlaps', 'ai-eshop-optimizer' ), ); } return $out; } private static function seo_sequence( array $row, $active, $import_done, $parity_ran, $engine_on ) { $steps = array( array( 'n' => 1, 'label' => sprintf( __( 'Import %s\'s settings. Read-only: nothing in it is changed', 'ai-eshop-optimizer' ), $row['label'] ), 'done' => (bool) $import_done, 'tab' => 'seo_import', 'anchor' => 'import-source', 'yours' => false, ), array( 'n' => 2, 'label' => __( 'Compare the two page heads on real pages', 'ai-eshop-optimizer' ), 'done' => (bool) $parity_ran, 'tab' => 'seo_import', 'anchor' => 'import-pre-cutover-verify-head-parity', 'yours' => false, ), array( 'n' => 3, 'label' => __( 'Turn on the Rooster One engine', 'ai-eshop-optimizer' ), 'done' => (bool) $engine_on, 'tab' => 'seo_general', 'anchor' => 'general-seo-engine', 'yours' => false, ), array( 'n' => 4, 'label' => sprintf( __( 'Deactivate %s. Last, and yours to do', 'ai-eshop-optimizer' ), $row['label'] ), 'done' => ! $active, 'tab' => '', 'anchor' => '', 'url' => admin_url( 'plugins.php' ), 'yours' => true, ), ); foreach ( $steps as &$s ) { if ( empty( $s['url'] ) ) { $s['url'] = class_exists( 'AIEO_DMM' ) ? AIEO_DMM::instance()->get_subtab_url( $s['tab'] ) . ( '' !== $s['anchor'] ? '#aieo-card-' . $s['anchor'] : '' ) : ''; } } unset( $s ); return $steps; } public static function enable_engine() { if ( ! class_exists( 'AIEO_DMM_Settings' ) ) { return false; } AIEO_DMM_Settings::update( array( 'seo' => array( 'enabled' => true ) ) ); return (bool) AIEO_DMM_Settings::get( 'seo.enabled', false ); } private static function competitor() { if ( ! class_exists( 'AIEO_SEO_Store' ) && defined( 'AIEO_DIR' ) && file_exists( AIEO_DIR . 'includes/seo/class-aieo-seo-store.php' ) ) { require_once AIEO_DIR . 'includes/seo/class-aieo-seo-store.php'; } return ( class_exists( 'AIEO_SEO_Store' ) && method_exists( 'AIEO_SEO_Store', 'competitor_seo_plugin' ) ) ? (string) AIEO_SEO_Store::competitor_seo_plugin() : ''; } private static function cmp_active( array $row ) { if ( empty( $row['cmp'] ) || ! class_exists( 'AIEO_Consent' ) || ! method_exists( 'AIEO_Consent', 'external_cmp_active' ) ) { return false; } return (string) AIEO_Consent::external_cmp_active() === (string) $row['cmp']; } private static function seo_sources() { $sources = class_exists( 'AIEO_Setup_Checklist' ) ? AIEO_Setup_Checklist::seo_sources_detected() : array(); $filtered = apply_filters( 'aieo_overlap_seo_sources', $sources ); return is_array( $filtered ) ? $filtered : $sources; } }