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_VERSION, 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' ), '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' ), ), ) ); } public function add_meta_box() { // Force the meta boxes to always be visible (prevent users from accidentally closing them) add_filter('postbox_classes_post_aeo-bob-lite-score', array($this, 'force_meta_box_open')); add_filter('postbox_classes_page_aeo-bob-lite-score', array($this, 'force_meta_box_open')); add_filter('postbox_classes_post_aeo-bob-lite-optimizer', array($this, 'force_meta_box_open')); add_filter('postbox_classes_page_aeo-bob-lite-optimizer', array($this, 'force_meta_box_open')); // 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' ); // Reset hidden meta boxes for this user to ensure optimizer is visible $this->ensure_metabox_visible(); } /** * Ensure the optimizer meta box is not hidden in user preferences */ private function ensure_metabox_visible() { $user_id = get_current_user_id(); if (!$user_id) { return; } $our_boxes = array('aeo-bob-lite-optimizer', 'aeo-bob-lite-score'); // Check both post and page screens foreach (array('post', 'page') as $post_type) { // Check hidden meta boxes (Screen Options unchecked) $hidden_boxes = get_user_meta($user_id, 'metaboxhidden_' . $post_type, true); if (is_array($hidden_boxes)) { $updated = false; foreach ($our_boxes as $box_id) { if (($key = array_search($box_id, $hidden_boxes)) !== false) { unset($hidden_boxes[$key]); $updated = true; } } if ($updated) { update_user_meta($user_id, 'metaboxhidden_' . $post_type, array_values($hidden_boxes)); } } // Check closed meta boxes (collapsed) $closed_boxes = get_user_meta($user_id, 'closedpostboxes_' . $post_type, true); if (is_array($closed_boxes)) { $updated = false; foreach ($our_boxes as $box_id) { if (($key = array_search($box_id, $closed_boxes)) !== false) { unset($closed_boxes[$key]); $updated = true; } } if ($updated) { update_user_meta($user_id, 'closedpostboxes_' . $post_type, array_values($closed_boxes)); } } } } /** * Force meta box to always be open (cannot be closed) */ public function force_meta_box_open($classes) { // Remove 'closed' class if it exists $classes = array_diff($classes, array('closed')); return $classes; } public function render_meta_box($post) { // Get stored data $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_URL . 'assets/images/aeoboblogo.png'; ?>