prefix . $suffix; return (string) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $t ) ) === $t; }; return array( array( 'group' => __( 'Rooster', 'ai-eshop-optimizer' ), 'name' => __( 'Rooster One Pro — subscription', 'ai-eshop-optimizer' ), 'gives' => __( 'Unlocks the Pro admin features on this site. Managed at rooster.systems, not here.', 'ai-eshop-optimizer' ), 'url' => AIEO_Install_Status::panel_url(), 'check' => static function () { $e = get_option( 'aieo_rooster_entitlement', array() ); if ( ! is_array( $e ) || empty( $e['tier'] ) ) { return array( 'state' => 'off', 'detail' => '' ); } $bits = array( ucfirst( (string) $e['tier'] ) ); $ts = ! empty( $e['expires_at'] ) ? strtotime( (string) $e['expires_at'] . ' UTC' ) : 0; if ( $ts && $ts < time() ) { $bits[] = __( 'expired', 'ai-eshop-optimizer' ); return array( 'state' => 'off', 'detail' => implode( ' · ', $bits ) ); } if ( $ts ) { $bits[] = sprintf( __( '%1$s %2$s left', 'ai-eshop-optimizer' ), empty( $e['trial'] ) ? __( 'renews in', 'ai-eshop-optimizer' ) : __( 'trial —', 'ai-eshop-optimizer' ), human_time_diff( time(), $ts ) ); } return array( 'state' => empty( $e['trial'] ) ? 'on' : 'hold', 'detail' => implode( ' · ', $bits ) ); }, ), array( 'group' => __( 'Rooster', 'ai-eshop-optimizer' ), 'name' => __( 'Rooster One Pro — the Pro plugin (second download)', 'ai-eshop-optimizer' ), 'gives' => __( 'Carries the engines this download does not: invoice scanning, the accounting connectors, the catalogue importer, the product exchanger, the merge engine, semantic linking. Seven screens need it.', 'ai-eshop-optimizer' ), 'url' => AIEO_Install_Status::panel_url(), 'check' => static function () { if ( ! class_exists( 'AIEO_Install_Status' ) ) { return null; } foreach ( AIEO_Install_Status::rows() as $r ) { if ( 'pro' === $r['id'] ) { return array( 'state' => $r['state'], 'detail' => trim( $r['version'] . ' ' . $r['detail'] ) ); } } return null; }, ), array( 'group' => __( 'Rooster', 'ai-eshop-optimizer' ), 'name' => __( 'Rooster One — the front-end runtime (third download)', 'ai-eshop-optimizer' ), 'gives' => __( 'Serves the storefront on its own, without the admin plugin in the request. Not on wordpress.org; included with Pro. Without it your pages load the slower path.', 'ai-eshop-optimizer' ), 'url' => AIEO_Install_Status::panel_url(), 'check' => static function () { if ( ! class_exists( 'AIEO_Install_Status' ) ) { return null; } foreach ( AIEO_Install_Status::rows() as $r ) { if ( 'runtime' === $r['id'] ) { return array( 'state' => $r['state'], 'detail' => trim( $r['version'] . ' ' . $r['detail'] ) ); } } return null; }, ), array( 'group' => __( 'Rooster', 'ai-eshop-optimizer' ), 'name' => __( 'Rooster Content Connector — AI credits', 'ai-eshop-optimizer' ), 'gives' => __( 'One token instead of provider API keys: Claude chat, semantic search and Voyage embeddings all run through rooster.systems and meter against your AI Credits balance.', 'ai-eshop-optimizer' ), 'url' => AIEO_Install_Status::panel_url( 'rooster-content' ), 'check' => static function () { $key = (string) get_option( 'aieo_rooster_api_key', '' ); if ( '' === $key ) { return array( 'state' => 'off', 'detail' => '' ); } $on = (bool) get_option( 'aieo_rooster_enabled', 0 ); $status = (string) get_option( 'aieo_rooster_status', '' ); $who = (string) get_option( 'aieo_rooster_client_label', '' ); $bits = array(); if ( '' !== $who ) { $bits[] = sprintf( __( 'as %s', 'ai-eshop-optimizer' ), $who ); } $host = wp_parse_url( (string) get_option( 'aieo_rooster_hub_url', '' ), PHP_URL_HOST ); if ( $host ) { $bits[] = (string) $host; } if ( ! $on ) { $bits[] = __( 'connection switched off', 'ai-eshop-optimizer' ); return array( 'state' => 'hold', 'detail' => implode( ' · ', $bits ) ); } return array( 'state' => ( 'connected' === $status || '' === $status ) ? 'on' : 'hold', 'detail' => implode( ' · ', $bits ) ); }, ), array( 'group' => __( 'Advertising Spend', 'ai-eshop-optimizer' ), 'name' => __( 'Google Ads, Meta & TikTok — ad spend', 'ai-eshop-optimizer' ), 'gives' => __( 'Pulls what you spent into the ROI reports, so revenue can be measured against cost. Also the account a composed Google Ads campaign is sent to.', 'ai-eshop-optimizer' ), 'tab' => 'ad_connectors', 'check' => static function () { $o = get_option( 'aieo_ad_connectors', array() ); $on = array(); if ( is_array( $o ) ) { foreach ( $o as $k => $v ) { if ( is_array( $v ) && array_filter( $v ) ) { $on[] = ucwords( str_replace( '_', ' ', (string) $k ) ); } } } return $on ? array( 'state' => 'on', 'detail' => implode( ', ', $on ) ) : array( 'state' => 'off', 'detail' => '' ); }, ), array( 'group' => __( 'Publishing', 'ai-eshop-optimizer' ), 'name' => __( 'Google Ads — campaign publishing', 'ai-eshop-optimizer' ), 'gives' => __( 'Sends a composed campaign into your Google Ads account. Campaigns are always created PAUSED — a human enables them in Google, so nothing here can start spending on its own.', 'ai-eshop-optimizer' ), 'tab' => 'ads', 'check' => static function () { if ( ! class_exists( 'AIEO_DMM_Ad_Connectors' ) ) { return null; } $c = AIEO_DMM_Ad_Connectors::credentials( 'google' ); if ( ! is_array( $c ) || ! array_filter( $c ) ) { return array( 'state' => 'off', 'detail' => '' ); } return array( 'state' => 'on', 'detail' => __( 'same account as ad spend · creates paused', 'ai-eshop-optimizer' ) ); }, ), array( 'group' => __( 'Publishing', 'ai-eshop-optimizer' ), 'name' => __( 'Facebook, Instagram, X, LinkedIn, TikTok — posting', 'ai-eshop-optimizer' ), 'gives' => __( 'Publishes posts to your own Pages and profiles. You register your own platform app, so tokens stay on this site.', 'ai-eshop-optimizer' ), 'tab' => 'social', 'check' => static function () { if ( ! class_exists( 'AIEO_Social_Store' ) ) { return null; } $a = array_filter( (array) AIEO_Social_Store::get_accounts(), array( 'AIEO_Social_Store', 'can_post' ) ); $n = count( $a ); if ( $n < 1 ) { return array( 'state' => 'off', 'detail' => '' ); } return array( 'state' => 'on', 'detail' => sprintf( _n( '%d account', '%d accounts', $n, 'ai-eshop-optimizer' ), $n ) ); }, ), array( 'group' => __( 'Publishing', 'ai-eshop-optimizer' ), 'name' => __( 'Meta & Google — lookalike audiences', 'ai-eshop-optimizer' ), 'gives' => __( 'Export a hashed customer list here, then upload it in Meta Ads Manager or Google Ads to seed a lookalike audience. The upload is manual, so this plugin stores no credentials for it — but you still need an advertising account on the platform receiving the file.', 'ai-eshop-optimizer' ), 'tab' => 'lookalike_export', 'check' => static function () { $o = get_option( 'aieo_ad_connectors', array() ); $have = array(); if ( is_array( $o ) ) { foreach ( array( 'meta' => __( 'Meta', 'ai-eshop-optimizer' ), 'google' => __( 'Google Ads', 'ai-eshop-optimizer' ) ) as $k => $label ) { if ( ! empty( $o[ $k ] ) && is_array( $o[ $k ] ) && array_filter( $o[ $k ] ) ) { $have[] = $label; } } } return $have ? array( 'state' => 'on', 'detail' => sprintf( __( 'upload manually into %s', 'ai-eshop-optimizer' ), implode( ', ', $have ) ) ) : array( 'state' => 'off', 'detail' => __( 'needs a Meta or Google Ads account to upload into', 'ai-eshop-optimizer' ) ); }, ), array( 'group' => __( 'Catalogue & orders', 'ai-eshop-optimizer' ), 'name' => __( 'Marketplace & comparison feeds', 'ai-eshop-optimizer' ), 'gives' => __( 'The product feeds this shop PUBLISHES — Skroutz, Google Shopping and the rest read them to list your catalogue.', 'ai-eshop-optimizer' ), 'tab' => 'marketplace_feeds', 'check' => static function () use ( $wpdb, $has ) { if ( ! $has( 'aieo_dmm_feed_configs' ) ) { return null; } $t = $wpdb->prefix . 'aieo_dmm_feed_configs'; $row = $wpdb->get_row( "SELECT SUM(enabled) e, MAX(last_run_at) r FROM `{$t}`", ARRAY_A ); $e = (int) ( $row['e'] ?? 0 ); if ( $e < 1 ) { return array( 'state' => 'off', 'detail' => '' ); } $when = ! empty( $row['r'] ) ? sprintf( __( 'last built %s ago', 'ai-eshop-optimizer' ), human_time_diff( strtotime( $row['r'] . ' UTC' ) ) ) : __( 'never built', 'ai-eshop-optimizer' ); return array( 'state' => 'on', 'detail' => sprintf( __( '%1$d live · %2$s', 'ai-eshop-optimizer' ), $e, $when ) ); }, ), array( 'group' => __( 'Catalogue & orders', 'ai-eshop-optimizer' ), 'name' => __( 'Couriers — vouchers & tracking', 'ai-eshop-optimizer' ), 'gives' => __( 'Books shipments, prints vouchers and pulls tracking checkpoints back in, so delivery status shows against the order.', 'ai-eshop-optimizer' ), 'tab' => 'fulfillment_couriers', 'check' => static function () use ( $has ) { if ( ! $has( 'aieo_dmm_fulfillment_settings' ) ) { return null; } $ff = class_exists( 'AIEO_DMM' ) ? AIEO_DMM::instance()->get_module( 'fulfillment' ) : null; if ( ! $ff || ! method_exists( $ff, 'couriers' ) || ! method_exists( $ff, 'courier_setup_state' ) ) { return null; } $api = array(); $man = array(); foreach ( (array) $ff->couriers() as $code => $driver ) { $state = $ff->courier_setup_state( $code, $driver ); $label = method_exists( $driver, 'label' ) ? (string) $driver->label() : (string) $code; if ( 'api' === $state ) { $api[] = $label; } elseif ( 'manual' === $state ) { $man[] = $label; } } if ( ! $api && ! $man ) { return array( 'state' => 'off', 'detail' => '' ); } sort( $api ); sort( $man ); $detail = array(); if ( $api ) { $detail[] = sprintf( __( 'API: %s', 'ai-eshop-optimizer' ), implode( ', ', $api ) ); } if ( $man ) { $detail[] = sprintf( __( 'manual: %s', 'ai-eshop-optimizer' ), implode( ', ', $man ) ); } return array( 'state' => $api ? 'on' : 'hold', 'detail' => implode( ' · ', $detail ), ); }, ), array( 'group' => __( 'Catalogue & orders', 'ai-eshop-optimizer' ), 'name' => __( 'Catalogue import — marketplaces & your feed sources', 'ai-eshop-optimizer' ), 'gives' => __( 'Brings a marketplace or supplier catalogue INTO WooCommerce — the opposite direction to the feeds above. Covers eFood, Wolt, Etsy, Amazon and every feed source you built yourself.', 'ai-eshop-optimizer' ), 'tab' => 'catalog_importer', 'check' => static function () { $o = get_option( 'aieo_catalog_import_sources', array() ); $on = array(); if ( is_array( $o ) ) { foreach ( $o as $slug => $cfg ) { if ( is_array( $cfg ) && array_filter( $cfg ) ) { $on[] = ucfirst( (string) $slug ); } } } if ( class_exists( 'AIEO_Feed_Source_Store' ) ) { $defs = AIEO_Feed_Source_Store::all(); if ( $defs ) { $scheduled = 0; foreach ( $defs as $d ) { if ( 'manual' !== ( $d['schedule'] ?? 'manual' ) ) { $scheduled++; } } $on[] = sprintf( _n( '%1$s feed source (%2$s scheduled)', '%1$s feed sources (%2$s scheduled)', count( $defs ), 'ai-eshop-optimizer' ), number_format_i18n( count( $defs ) ), number_format_i18n( $scheduled ) ); } } return $on ? array( 'state' => 'on', 'detail' => implode( ', ', $on ) ) : array( 'state' => 'off', 'detail' => __( 'no source connected', 'ai-eshop-optimizer' ) ); }, ), array( 'group' => __( 'Search', 'ai-eshop-optimizer' ), 'name' => __( 'Google Search Console', 'ai-eshop-optimizer' ), 'gives' => __( 'The queries people actually searched to reach you, with clicks, impressions and position per page.', 'ai-eshop-optimizer' ), 'tab' => 'seo_search_queries', 'check' => static function () { $tokens = get_option( 'aieo_gsc_tokens' ); $s = get_option( 'aieo_gsc_settings', array() ); $s = is_array( $s ) ? $s : array(); if ( ! is_array( $tokens ) || empty( $tokens['refresh_token'] ) ) { return array( 'state' => 'off', 'detail' => empty( $s['client_id'] ) ? '' : __( 'credentials saved, Google sign-in not completed', 'ai-eshop-optimizer' ), ); } $property = isset( $s['property'] ) ? (string) $s['property'] : ''; $last = (int) get_option( 'aieo_gsc_history_last_sync', 0 ); $err = get_option( 'aieo_gsc_last_error', array() ); $err = ( is_array( $err ) && ! empty( $err['message'] ) ) ? (string) $err['message'] : ''; if ( '' === $property ) { return array( 'state' => 'error', 'detail' => __( 'no property selected', 'ai-eshop-optimizer' ) ); } if ( $last && ( time() - $last ) < 72 * HOUR_IN_SECONDS ) { $bits = array( $property, sprintf( __( 'synced %s ago', 'ai-eshop-optimizer' ), human_time_diff( $last ) ) ); if ( '' !== $err ) { $bits[] = __( 'last attempt failed', 'ai-eshop-optimizer' ); } return array( 'state' => 'on', 'detail' => implode( ' · ', $bits ) ); } if ( '' === $err ) { $err = $last ? sprintf( __( 'last synced %s ago', 'ai-eshop-optimizer' ), human_time_diff( $last ) ) : __( 'never synced', 'ai-eshop-optimizer' ); } return array( 'state' => 'error', 'detail' => $property . ' · ' . $err ); }, ), array( 'group' => __( 'Search', 'ai-eshop-optimizer' ), 'name' => __( 'DataForSEO — rankings & backlinks', 'ai-eshop-optimizer' ), 'gives' => __( 'Keyword rankings and the sites linking to you. A paid third-party service with its own account.', 'ai-eshop-optimizer' ), 'tab' => 'seo_backlinks', 'check' => static function () { return get_option( 'aieo_dataforseo_auth' ) ? array( 'state' => 'on', 'detail' => '' ) : array( 'state' => 'off', 'detail' => '' ); }, ), array( 'group' => __( 'Customers', 'ai-eshop-optimizer' ), 'name' => __( 'Social login', 'ai-eshop-optimizer' ), 'gives' => __( 'Lets shoppers sign in with an existing account instead of creating a password.', 'ai-eshop-optimizer' ), 'tab' => 'social_login', 'check' => static function () { $o = get_option( 'aieo_social_login_settings', array() ); $on = array(); if ( is_array( $o ) && ! empty( $o['providers'] ) && is_array( $o['providers'] ) ) { foreach ( $o['providers'] as $slug => $cfg ) { if ( is_array( $cfg ) && ! empty( $cfg['enabled'] ) && ! empty( $cfg['client_id'] ) ) { $on[] = ucfirst( (string) $slug ); } } } return $on ? array( 'state' => 'on', 'detail' => implode( ', ', $on ) ) : array( 'state' => 'off', 'detail' => '' ); }, ), array( 'group' => __( 'Customers', 'ai-eshop-optimizer' ), 'name' => __( 'Newsletter platform', 'ai-eshop-optimizer' ), 'gives' => __( 'Syncs cohorts and segments to your email platform so a campaign can be sent to them.', 'ai-eshop-optimizer' ), 'tab' => 'web-push', 'check' => static function () { $o = get_option( 'aieo_newsletter_settings', array() ); if ( ! is_array( $o ) || empty( $o['provider'] ) ) { return array( 'state' => 'off', 'detail' => '' ); } $p = (string) $o['provider']; $cfg = ( isset( $o[ $p ] ) && is_array( $o[ $p ] ) ) ? array_filter( $o[ $p ] ) : array(); return $cfg ? array( 'state' => 'on', 'detail' => strtoupper( $p ) ) : array( 'state' => 'off', 'detail' => strtoupper( $p ) . ' — ' . __( 'no credentials', 'ai-eshop-optimizer' ) ); }, ), array( 'group' => __( 'Customers', 'ai-eshop-optimizer' ), 'name' => __( 'Web push notifications', 'ai-eshop-optimizer' ), 'gives' => __( 'Browser notifications to shoppers who opted in. Collecting subscribers and sending to them are separate switches.', 'ai-eshop-optimizer' ), 'tab' => 'web-push', 'check' => static function () use ( $wpdb, $has ) { if ( ! $has( 'aieo_push_subscriptions' ) ) { return null; } $t = $wpdb->prefix . 'aieo_push_subscriptions'; $n = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$t}`" ); $status = 'live' === (string) get_option( 'aieo_push_engine_status', 'paused' ) ? 'live' : 'paused'; if ( $n < 1 ) { return array( 'state' => 'off', 'detail' => '' ); } $who = sprintf( _n( '%s subscriber', '%s subscribers', $n, 'ai-eshop-optimizer' ), number_format_i18n( $n ) ); return ( 'paused' === $status ) ? array( 'state' => 'hold', 'detail' => $who . ' · ' . __( 'collecting, sending paused', 'ai-eshop-optimizer' ) ) : array( 'state' => 'on', 'detail' => $who ); }, ), array( 'group' => __( 'Measurement', 'ai-eshop-optimizer' ), 'name' => __( 'Conversion tracking & pixels', 'ai-eshop-optimizer' ), 'gives' => __( 'Reports purchases back to the ad platforms so their optimisation has something to learn from.', 'ai-eshop-optimizer' ), 'tab' => 'conversions', 'check' => static function () { $o = get_option( 'aieo_dmm_conversions', array() ); if ( ! is_array( $o ) ) { return array( 'state' => 'off', 'detail' => '' ); } $on = array(); if ( ! empty( $o['fb_pixel_id'] ) ) { $on[] = ! empty( $o['fb_access_token'] ) ? __( 'Meta (pixel + CAPI)', 'ai-eshop-optimizer' ) : __( 'Meta (pixel)', 'ai-eshop-optimizer' ); } if ( ! empty( $o['ga_measurement_id'] ) ) { $on[] = ( ! empty( $o['ga_api_secret'] ) && 'yes' === ( $o['ga_send_server'] ?? '' ) ) ? __( 'GA4 (browser + server)', 'ai-eshop-optimizer' ) : __( 'GA4', 'ai-eshop-optimizer' ); } if ( ! empty( $o['gtm_container_id'] ) ) { $on[] = __( 'Google Tag Manager', 'ai-eshop-optimizer' ); } if ( ! empty( $o['ads_conversion_id'] ) ) { $on[] = __( 'Google Ads', 'ai-eshop-optimizer' ); } if ( ! empty( $o['tiktok_pixel_id'] ) ) { $on[] = ! empty( $o['tiktok_access_token'] ) ? __( 'TikTok (pixel + Events API)', 'ai-eshop-optimizer' ) : __( 'TikTok (pixel)', 'ai-eshop-optimizer' ); } return $on ? array( 'state' => 'on', 'detail' => implode( ' · ', $on ) ) : array( 'state' => 'off', 'detail' => '' ); }, ), array( 'group' => __( 'Storefront', 'ai-eshop-optimizer' ), 'name' => __( 'Google Places — shop locations & reviews', 'ai-eshop-optimizer' ), 'gives' => __( 'Pulls the Google listing for each of your shops and the reviews on it, for the locations pages and their review schema. A different key and a different page from checkout autocomplete.', 'ai-eshop-optimizer' ), 'tab' => 'google_reviews', 'check' => static function () use ( $wpdb, $has ) { $o = get_option( 'aieo_google_reviews_settings', array() ); $key = ( is_array( $o ) && ! empty( $o['global_api_key'] ) ); $n = 0; if ( $has( 'aieo_google_locations' ) ) { $t = $wpdb->prefix . 'aieo_google_locations'; $n = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$t}`" ); } if ( ! $key ) { return array( 'state' => 'off', 'detail' => '' ); } $detail = $n ? sprintf( _n( '%d location', '%d locations', $n, 'ai-eshop-optimizer' ), $n ) : __( 'key set, no locations yet', 'ai-eshop-optimizer' ); return array( 'state' => $n ? 'on' : 'hold', 'detail' => $detail ); }, ), array( 'group' => __( 'Storefront', 'ai-eshop-optimizer' ), 'name' => __( 'Google Places — address autocomplete', 'ai-eshop-optimizer' ), 'gives' => __( 'Completes customer addresses at checkout, which cuts failed deliveries.', 'ai-eshop-optimizer' ), 'tab' => 'address_autocomplete', 'check' => static function () { $o = get_option( 'aieo_dmm_address_autocomplete', array() ); return ( is_array( $o ) && array_filter( $o ) ) ? array( 'state' => 'on', 'detail' => '' ) : array( 'state' => 'off', 'detail' => '' ); }, ), ); } private static function persona_map() { return array( 'ad_connectors' => array( 'growth', 'owner' ), 'ads' => array( 'growth' ), 'social' => array( 'growth', 'content' ), 'lookalike_export' => array( 'growth' ), 'marketplace_feeds' => array( 'catalogue', 'operations' ), 'fulfillment_couriers' => array( 'operations' ), 'catalog_importer' => array( 'catalogue', 'tech' ), 'seo_search_queries' => array( 'content', 'growth' ), 'seo_backlinks' => array( 'content' ), 'social_login' => array( 'tech', 'growth' ), 'web-push' => array( 'growth' ), 'conversions' => array( 'growth', 'tech' ), 'google_reviews' => array( 'content', 'operations' ), 'address_autocomplete' => array( 'operations', 'tech' ), ); } public function rows_for_personas( array $held ) { $map = self::persona_map(); $out = array(); foreach ( $this->integrations() as $r ) { $tab = ! empty( $r['tab'] ) ? (string) $r['tab'] : ''; $who = '' === $tab ? array( 'owner', 'tech' ) : ( isset( $map[ $tab ] ) ? $map[ $tab ] : array() ); $hit = array_values( array_intersect( $who, $held ) ); if ( empty( $hit ) ) { continue; } $res = null; if ( is_callable( $r['check'] ) ) { try { $res = call_user_func( $r['check'] ); } catch ( Throwable $e ) { $res = null; } } $out[] = array( 'name' => wp_strip_all_tags( (string) $r['name'] ), 'gives' => wp_strip_all_tags( (string) $r['gives'] ), 'url' => ! empty( $r['url'] ) ? (string) $r['url'] : add_query_arg( array( 'page' => AIEO_DMM::MENU_SLUG, 'dmm_tab' => $tab ), admin_url( 'admin.php' ) ), 'state' => is_array( $res ) && isset( $res['state'] ) ? (string) $res['state'] : '', 'persona' => (string) $hit[0], ); } return $out; } private static function is_pro( array $row ) { $tier = isset( $row['tier'] ) ? (string) $row['tier'] : ''; if ( '' === $tier && ! empty( $row['tab'] ) && class_exists( 'AIEO_Capability_Registry' ) ) { $resolved = AIEO_Capability_Registry::tier_for( (string) $row['tab'], '' ); $tier = isset( $resolved['tier'] ) ? (string) $resolved['tier'] : ''; } return 'pro' === $tier; } public function render_subtab() { list( $aieo_con_open, $aieo_con_close ) = AIEO_DMM_UI::card( array( 'anchor' => 'connections-overview', 'title' => __( 'Service directory', 'ai-eshop-optimizer' ), 'description' => __( 'Everything this plugin can connect to outside your shop, whether it is live right now, and where each one is set up. The settings themselves stay on the page that uses them — this is the map, so you do not have to remember which section a service lives under.', 'ai-eshop-optimizer' ), 'footnote' => __( 'A dash means this page does not check that service, not that it is disconnected — open it to see. "On hold" means it is configured and deliberately not running.', 'ai-eshop-optimizer' ), ) ); echo $aieo_con_open; $this->render_directory( true ); echo $aieo_con_close; $this->render_usage_consent_card(); } public function render_directory( $sections = false ) { $sections = $sections && function_exists( 'rooster_admin_card_section_html' ); $by = array(); foreach ( $this->integrations() as $r ) { $by[ (string) $r['group'] ][] = $r; } $held = class_exists( 'AIEO_Personas' ) ? AIEO_Personas::for_user( get_current_user_id() ) : array(); if ( ! empty( $held ) ) { $map = self::persona_map(); $mine = array(); foreach ( $by as $g => $items ) { foreach ( $items as $k => $r ) { $tab = ! empty( $r['tab'] ) ? (string) $r['tab'] : ''; $who = '' === $tab ? array( 'owner', 'tech' ) : ( isset( $map[ $tab ] ) ? $map[ $tab ] : array() ); if ( array_intersect( $who, $held ) ) { $mine[] = $r; unset( $by[ $g ][ $k ] ); } } if ( empty( $by[ $g ] ) ) { unset( $by[ $g ] ); } } if ( ! empty( $mine ) ) { $by = array_merge( array( __( 'For you', 'ai-eshop-optimizer' ) => $mine ), $by ); } } $columns = array( array( 'key' => 'name', 'label' => __( 'Service', 'ai-eshop-optimizer' ), 'type' => 'text', 'width' => '28%', 'suffix' => static function ( $row ) { return ! empty( $row['is_pro'] ) ? __( 'PRO', 'ai-eshop-optimizer' ) : ''; }, ), array( 'key' => 'gives', 'label' => __( 'What it gives you', 'ai-eshop-optimizer' ), 'type' => 'text', 'width' => '42%', 'tone' => 'muted', ), array( 'key' => 'state', 'label' => __( 'Status', 'ai-eshop-optimizer' ), 'type' => 'badge', 'width' => '20%', 'subline' => 'detail', 'format' => array( 'map' => array( 'on' => 'success', 'hold' => 'info', 'off' => 'warning', 'na' => 'muted', 'error' => 'destructive', ), 'labels' => array( 'on' => __( 'connected', 'ai-eshop-optimizer' ), 'hold' => __( 'on hold', 'ai-eshop-optimizer' ), 'off' => __( 'not set up', 'ai-eshop-optimizer' ), 'na' => __( 'not needed', 'ai-eshop-optimizer' ), 'error' => __( 'needs attention', 'ai-eshop-optimizer' ), ), ), 'empty' => null, ), ); $actions = array( array( 'id' => 'open', 'label' => __( 'Open', 'ai-eshop-optimizer' ), 'description' => __( 'Opens the screen of this connection.', 'ai-eshop-optimizer' ), 'href' => static function ( $row ) { return isset( $row['url'] ) ? (string) $row['url'] : ''; }, ), ); foreach ( $by as $group => $items ) { if ( $sections ) { echo rooster_admin_card_section_html( (string) $group ); } else { echo '
'; echo '

' . esc_html( $group ) . '

'; } $rows = array(); foreach ( $items as $r ) { $res = null; if ( is_callable( $r['check'] ) ) { try { $res = call_user_func( $r['check'] ); } catch ( Throwable $e ) { $res = null; } } $rows[] = array( 'name' => wp_strip_all_tags( (string) $r['name'] ), 'gives' => wp_strip_all_tags( (string) $r['gives'] ), 'state' => is_array( $res ) && isset( $res['state'] ) ? (string) $res['state'] : '', 'detail' => is_array( $res ) && isset( $res['detail'] ) ? (string) $res['detail'] : '', 'is_pro' => self::is_pro( $r ), 'url' => ! empty( $r['url'] ) ? (string) $r['url'] : add_query_arg( array( 'page' => AIEO_DMM::MENU_SLUG, 'dmm_tab' => $r['tab'] ), admin_url( 'admin.php' ) ), ); } AIEO_Table::render( array( 'columns' => $columns, 'rows' => $rows, 'actions' => $actions, 'page_id' => $this->get_id(), 'row_key' => 'name', 'density' => 'compact', 'sortable' => false, 'empty' => __( 'Nothing in this group is available on this site.', 'ai-eshop-optimizer' ), ) ); if ( ! $sections ) { echo '
'; } } } private function render_usage_consent_card() { if ( ! class_exists( 'AIEO_Action_Telemetry' ) ) { return; } $is_pro = class_exists( 'AIEO_DMM_Module_Base' ) && AIEO_DMM_Module_Base::is_aieo_pro_user(); $consent = get_option( AIEO_Action_Telemetry::OPT_CONSENT ); $has = is_array( $consent ) && ! empty( $consent['at'] ); $collecting = AIEO_Action_Telemetry::is_collecting(); $nonce = wp_create_nonce( 'aieo_usage_sharing_optin' ); $aieo_con_dialog = ''; $aieo_con_foot = ''; if ( $is_pro && $has ) { $aieo_con_ask = class_exists( '\Rooster\Admin\States' ); if ( $aieo_con_ask ) { $aieo_con_dialog = \Rooster\Admin\States::confirm( 'aieo-usage-revoke-confirm', 'replace', __( 'Stop and delete what was collected', 'ai-eshop-optimizer' ), __( 'Stop recording and delete what has been collected so far? This cannot be undone.', 'ai-eshop-optimizer' ) ); $aieo_con_attrs = ''; foreach ( \Rooster\Admin\States::confirm_attrs( 'aieo-usage-revoke-confirm' ) as $aieo_con_k => $aieo_con_v ) { $aieo_con_attrs .= ' ' . esc_attr( $aieo_con_k ) . '="' . esc_attr( $aieo_con_v ) . '"'; } } else { $aieo_con_attrs = ' data-confirm="' . esc_attr__( 'Stop recording and delete what has been collected so far? This cannot be undone.', 'ai-eshop-optimizer' ) . '"'; } $aieo_con_foot = ''; } elseif ( $is_pro ) { $aieo_con_foot = '' . ' ' . esc_html__( 'Recorded against your name and the date, so the agreement survives you leaving.', 'ai-eshop-optimizer' ) . ''; } list( $aieo_con_open, $aieo_con_close ) = AIEO_DMM_UI::card( array( 'anchor' => 'connections-usage-data', 'icon' => 'eye', 'title' => __( 'Share how staff use Rooster One', 'ai-eshop-optimizer' ), 'description' => __( 'Opt in to send us which wp-admin controls your staff press, with mode, screen and time — never orders, customer data or typed input; off by default and Pro only.', 'ai-eshop-optimizer' ), 'meta' => $is_pro && class_exists( '\Rooster\Admin\States' ) ? \Rooster\Admin\States::status( $has ? ( $collecting ? 'on' : 'attention' ) : 'off' ) : '', 'footer' => $aieo_con_foot, ) ); echo $aieo_con_open; $aieo_con_why = __( 'Rooster One can record which controls the people in YOUR OFFICE press, so we can see which features are worth building on and which nobody can find. It is off unless you turn it on here, and the free version never sends anything at all.', 'ai-eshop-optimizer' ); $aieo_con_why = class_exists( '\Rooster\Admin\Guidance' ) ? \Rooster\Admin\Guidance::text( $aieo_con_why ) : '

' . esc_html( $aieo_con_why ) . '

'; echo $aieo_con_why; $gdpr_url = add_query_arg( array( 'page' => AIEO_DMM::MENU_SLUG, 'dmm_tab' => 'consent' ), admin_url( 'admin.php' ) ); $aieo_con_note = \Rooster\Admin\Ui::notice( esc_html__( 'Nothing here concerns your shoppers, their data or GDPR. This is about your own staff\'s clicks in wp-admin. The consent your VISITORS give lives on', 'ai-eshop-optimizer' ) . ' ' . esc_html__( 'Consent / GDPR', 'ai-eshop-optimizer' ) . '.', 'mono', array( 'title' => __( 'This is not your cookie banner.', 'ai-eshop-optimizer' ), 'inline' => true, ) ); echo $aieo_con_note; echo '
'; echo '

' . esc_html__( 'What is recorded', 'ai-eshop-optimizer' ) . '

'; echo '

' . esc_html__( 'What is never recorded', 'ai-eshop-optimizer' ) . '

'; echo '
'; if ( ! $is_pro ) { $aieo_con_pro = class_exists( '\Rooster\Admin\States' ) ? \Rooster\Admin\States::state( 'pro', array( 'pro' => __( 'Rooster One Pro', 'ai-eshop-optimizer' ), 'text' => __( 'Collection is available on Pro only. On the free version this plugin does not phone home, and there is nothing here to switch on.', 'ai-eshop-optimizer' ), 'href' => add_query_arg( array( 'page' => AIEO_DMM::MENU_SLUG, 'dmm_tab' => 'subscription' ), admin_url( 'admin.php' ) ), 'label' => __( 'See Pro', 'ai-eshop-optimizer' ), ) ) : '

' . esc_html__( 'Collection is available on Pro only. On the free version this plugin does not phone home, and there is nothing here to switch on.', 'ai-eshop-optimizer' ) . '

'; echo $aieo_con_pro; echo $aieo_con_close; return; } if ( $has ) { $who = ! empty( $consent['user'] ) ? get_userdata( (int) $consent['user'] ) : null; $name = $who ? $who->display_name : __( 'someone on this site', 'ai-eshop-optimizer' ); echo '

' . esc_html( sprintf( __( 'Agreed by %1$s on %2$s.', 'ai-eshop-optimizer' ), $name, (string) $consent['at'] ) ) . '

'; if ( ! $collecting ) { $aieo_con_held = \Rooster\Admin\Ui::notice( esc_html__( 'Consent is recorded but collection is switched off by a filter on this site.', 'ai-eshop-optimizer' ), 'warning', array( 'inline' => true ) ); echo $aieo_con_held; } } echo $aieo_con_dialog; echo $aieo_con_close; printf( '', wp_json_encode( $nonce ), wp_json_encode( admin_url( 'admin-ajax.php' ) ) ); } }