init_hooks(); } private function init_hooks() { add_action('add_meta_boxes', array($this, 'add_meta_box')); add_action('admin_enqueue_scripts', array($this, 'enqueue_meta_box_assets')); } /** * Enqueue meta box styles and scripts ONLY on post edit screens * WordPress.org compliance: Use proper enqueue APIs instead of inline tags */ public function enqueue_meta_box_assets($hook_suffix) { // Only load on post edit screens where our meta box appears if (!in_array($hook_suffix, array('post.php', 'post-new.php'))) { return; } $screen = get_current_screen(); if (!$screen || !in_array($screen->post_type, array('post', 'page'))) { return; } // Get post ID global $post; if (!$post) { return; } // Enqueue meta box script wp_enqueue_script( 'aeo-bob-lite-meta-box', AEO_BOB_LITE_URL . 'assets/js/meta-box.js', array( 'jquery' ), aeo_bob_lite_asset_ver( 'assets/js/meta-box.js' ), true ); // Localize script with translatable strings wp_localize_script( 'aeo-bob-lite-meta-box', 'aeoBobLiteMetaBox', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'postId' => absint( $post->ID ), 'nonce' => wp_create_nonce( 'aeo_bob_lite_nonce' ), // v1.5.81: the box switches to OPTIMIZE + RE-ANALYZE after the first ANALYZE without a // reload, and must render them exactly like the PHP does (incl. the no-key warning). 'hasApiKey' => '' !== trim( (string) get_option( 'aeo_bob_lite_openai_key', '' ) ), 'settingsUrl' => admin_url( 'admin.php?page=aeo-bob-lite-settings' ), 'sourceLabels' => self::source_labels(), // v1.5.85 'debug' => AEO_Bob_Lite_Admin::is_debug_mode(), // v1.5.86: console.log only in debug mode // v1.5.88: Generate llms.txt from the box (administrators only) 'llmsNonce' => current_user_can( 'manage_options' ) && class_exists( 'AEO_Bob_Lite_Llms' ) ? wp_create_nonce( AEO_Bob_Lite_Llms::AJAX ) : '', 'i18n' => array( 'analyze' => __( 'ANALYZE', 'aeo-bob-lite' ), 'analyzing' => __( 'Analyzing...', 'aeo-bob-lite' ), 'optimize' => __( 'OPTIMIZE', 'aeo-bob-lite' ), 'optimizeContent' => __( 'OPTIMIZE CONTENT', 'aeo-bob-lite' ), 'reanalyze' => __( 'RE-ANALYZE', 'aeo-bob-lite' ), 'rescoreContent' => __( 'RE-SCORE CONTENT', 'aeo-bob-lite' ), 'analysisFailed' => __( 'Analysis failed', 'aeo-bob-lite' ), 'rescoreFailed' => __( 'Re-score failed', 'aeo-bob-lite' ), 'networkError' => __( 'Network error. Please try again.', 'aeo-bob-lite' ), 'networkErrorRescore' => __( 'Network error during re-score. Please try again.', 'aeo-bob-lite' ), 'unknownError' => __( 'Unknown error', 'aeo-bob-lite' ), 'invalidScores' => __( 'Analysis returned invalid scores. Please try again.', 'aeo-bob-lite' ), 'addContent' => __( 'Please add some content before analyzing.', 'aeo-bob-lite' ), 'scrollingToPanel' => __( 'Scrolling to Optimization Panel', 'aeo-bob-lite' ), 'pointsTotal' => __( 'points total', 'aeo-bob-lite' ), 'points' => __( 'points', 'aeo-bob-lite' ), 'noChange' => __( 'No change', 'aeo-bob-lite' ), 'afterEdits' => __( 'After manual edits', 'aeo-bob-lite' ), 'addApiKey' => __( 'Add API key', 'aeo-bob-lite' ), 'forAiOptimization' => __( 'for AI optimization', 'aeo-bob-lite' ), 'unsavedNote' => __( 'Scores are for the saved post. You have unsaved changes - click Update, then RE-ANALYZE.', 'aeo-bob-lite' ), /* translators: 1: date and time, 2: what triggered the scoring */ 'scoredAt' => __( 'Scored %1$s, source: %2$s', 'aeo-bob-lite' ), 'generateLlms' => __( 'Generate llms.txt', 'aeo-bob-lite' ), 'generatingLlms' => __( 'Generating llms.txt...', 'aeo-bob-lite' ), 'view' => __( 'View', 'aeo-bob-lite' ), 'llmsFailed' => __( 'llms.txt could not be generated. Please try again.', 'aeo-bob-lite' ), ), ) ); } public function add_meta_box() { // v1.5.86: the boxes follow Screen Options and the collapse toggle like any other box // (1.5.x reset the user's choice on every page load). // Sidebar meta box with scores add_meta_box( 'aeo-bob-lite-score', __('🤖 AEO BOB Score', 'aeo-bob-lite'), array($this, 'render_meta_box'), array('post', 'page'), 'side', 'default' // Changed from 'high' to 'default' - don't compete with Publish box ); // Optimization panel meta box (below editor) - Works in both Classic and Block Editor add_meta_box( 'aeo-bob-lite-optimizer', __('🤖 AEO Bob LITE - Content Optimization', 'aeo-bob-lite'), array($this, 'render_optimization_panel'), array('post', 'page'), 'normal', // Position below editor 'default' ); } public function render_meta_box($post) { // Get stored data AEO_Bob_Lite_Admin::get_instance()->fresh_scores($post->ID); // v1.5.84: never a stale score $seo_score_raw = get_post_meta($post->ID, '_aeo_bob_seo_score', true); $aeo_score_raw = get_post_meta($post->ID, '_aeo_bob_aeo_score', true); $last_analysis = get_post_meta($post->ID, '_aeo_bob_last_analyzed', true); // Use actual scores or null $seo_score = ($seo_score_raw !== '' && $seo_score_raw !== false) ? (int)$seo_score_raw : null; $aeo_score = ($aeo_score_raw !== '' && $aeo_score_raw !== false) ? (int)$aeo_score_raw : null; // Check if ANY scores exist $has_any_scores = ($seo_score !== null || $aeo_score !== null); // Show OPTIMIZE button ONLY if BOTH scores exist AND at least one is > 0 $show_optimize = false; if ($seo_score !== null && $aeo_score !== null) { if ($seo_score > 0 || $aeo_score > 0) { $show_optimize = true; } } // ✅ v1.5.43: Don't use 0 as placeholder for missing scores // If scores don't exist, we'll hide the scores section or show a message $seo_score_display = $seo_score; $aeo_score_display = $aeo_score; // Check if API key is configured $api_key = get_option('aeo_bob_lite_openai_key', ''); $connection_status = !empty($api_key); // WP.org Guideline 5: No usage tracking for feature gating. $logo_url = aeo_bob_lite_logo_url(); ?>
__( 'saved (Update)', 'aeo-bob-lite' ), 'apply' => __( 'Apply Selected Suggestions', 'aeo-bob-lite' ), 'analyze' => __( 'ANALYZE', 'aeo-bob-lite' ), 're-analyze' => __( 'RE-ANALYZE', 'aeo-bob-lite' ), 're-score' => __( 'RE-SCORE', 'aeo-bob-lite' ), 'elementor' => __( 'Elementor ANALYZE', 'aeo-bob-lite' ), 'view' => __( 'refreshed (the post had changed)', 'aeo-bob-lite' ), ); } /** * v1.5.85: collapsible "Score details": every AEO and SEO factor with its points and maximum, * and "Scored