post_type, Semantic_Settings::post_types(), true ) ) return; wp_enqueue_script( 'semantic-editor-panel', SEMANTIC_URL . 'admin/js/editor-panel.js', [ 'wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data', 'wp-api-fetch' ], SEMANTIC_VERSION, true ); wp_localize_script( 'semantic-editor-panel', 'semanticEditorData', [ 'postTypes' => Semantic_Settings::post_types(), 'autoAnalyse' => Semantic_Settings::auto_analyse(), 'pricingUrl' => 'https://ai-datalayer.com/pricing', 'billingUrl' => admin_url( 'admin.php?page=semantic' ), 'setupUrl' => admin_url( 'admin.php?page=semantic-setup' ), ] ); } // ── WP Dashboard widget ─────────────────────────────────────────────────── public static function register_dashboard_widget() { wp_add_dashboard_widget( 'semantic_ai_data_layer', 'AI Data Layer — Semantic Score', [ __CLASS__, 'render_dashboard_widget' ] ); } /** Register + enqueue the dashboard widget CSS (Dashboard screen only). */ public static function dashboard_widget_assets( $hook ) { if ( 'index.php' !== $hook ) return; wp_register_style( 'semantic-dashboard-widget', false, [], SEMANTIC_VERSION ); wp_enqueue_style( 'semantic-dashboard-widget' ); wp_add_inline_style( 'semantic-dashboard-widget', self::dashboard_widget_css() ); } /** CSS for the at-a-glance dashboard widget (enqueued, not echoed inline). */ private static function dashboard_widget_css() { return ".aidl-widget{font-family:'Inter',system-ui,sans-serif;padding:4px 0;}" . ".aidl-scores{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px;align-items:flex-end;}" . ".aidl-dial{text-align:center;display:inline-block;}" . ".aidl-dial span{display:block;font-size:10px;color:#5A6A7A;text-transform:uppercase;letter-spacing:.06em;margin-top:3px;font-weight:500;}" . ".aidl-btns{display:flex;gap:8px;flex-wrap:wrap;}" . ".aidl-btn{display:inline-block;padding:7px 14px;border-radius:6px;font-size:12px;font-weight:600;text-decoration:none;cursor:pointer;font-family:inherit;}" . ".aidl-btn-primary{background:#0D1F3C;color:#1ECBA4;}" . ".aidl-btn-secondary{background:transparent;color:#0D1F3C;border:1px solid rgba(13,31,60,.2);}"; } public static function render_dashboard_widget() { $c = Semantic_Cohesion::latest() ?: []; $url = admin_url( 'admin.php?page=semantic' ); $scores = [ 'Overall' => (int) ( $c['score'] ?? 0 ), 'Connectivity' => (int) ( $c['connectivity'] ?? 0 ), 'Clusters' => (int) ( $c['clusters'] ?? 0 ), 'Depth' => (int) ( $c['depth'] ?? 0 ), 'Alignment' => (int) ( $c['core_alignment'] ?? 0 ), ]; echo '
'; } // ── Admin bar notification ──────────────────────────────────────────────── public static function admin_bar_item( $wp_admin_bar ) { if ( ! current_user_can( 'manage_options' ) ) return; // Billing problem takes priority — surfaced from the status cached by quota(). // A failed payment blocks analysis, so flag it loudly and skip the usual pill. $billing = get_transient( 'semantic_billing_status' ); if ( $billing === 'past_due' || $billing === 'unpaid' ) { $red_icon = ''; $wp_admin_bar->add_node( [ 'id' => 'semantic-billing', 'title' => '', 'href' => admin_url( 'admin.php?page=semantic' ), 'meta' => [ 'title' => 'Your last payment failed — analysis is paused. Update your billing details.' ], ] ); return; } // Cache count to avoid repeated DB hits on every admin page $cache_key = 'semantic_unanalysed_count'; $unanalysed = get_transient( $cache_key ); if ( $unanalysed === false ) { global $wpdb; $post_types = Semantic_Settings::post_types(); if ( empty( $post_types ) ) return; $placeholders = implode( ',', array_fill( 0, count( $post_types ), '%s' ) ); // Published posts of tracked types that are neither already analysed // nor explicitly excluded from the AI Data Layer. $unanalysed = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} p WHERE p.post_status='publish' AND p.post_type IN ($placeholders) AND p.ID NOT IN ( SELECT post_id FROM {$wpdb->prefix}semantic_analyses WHERE status='completed' ) AND p.ID NOT IN ( SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key=%s AND meta_value='1' )", array_merge( $post_types, [ Semantic_Settings::EXCLUDE_META ] ) ) ); set_transient( $cache_key, $unanalysed, HOUR_IN_SECONDS ); } if ( $unanalysed < 1 ) return; $wp_admin_bar->add_node( [ 'id' => 'semantic-unanalysed', 'title' => '', 'href' => admin_url( 'admin.php?page=semantic' ), 'meta' => [ 'title' => 'Analyse your content in AI Data Layer' ], ] ); } /** * Admin-bar pill styling. Added as inline CSS on core's `admin-bar` stylesheet * handle (which loads on both the front-end and admin toolbars) instead of * echoing a raw