wpdb = $wpdb; if (!$this->check_db_connection()) { add_action('admin_notices', array($this, 'show_db_error')); return; } $this->api_client = $this->get_api_client(); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); add_action('wp_ajax_generate_single_alt_text', array($this, 'generate_single_alt_text')); add_action('wp_ajax_get_images_for_bulk_optimization', array($this, 'get_images_for_bulk_optimization')); add_action('wp_ajax_get_image_counts', array($this, 'get_image_counts')); add_action('wp_ajax_get_image_data', array($this, 'get_image_data')); add_action('wp_ajax_get_attachment_image', array($this, 'get_attachment_image_ajax')); add_filter('attachment_fields_to_edit', array($this, 'add_generate_button'), 10, 2); add_action('elementor/editor/after_enqueue_scripts', array($this, 'enqueue_custom_script_in_elementor_editor'), 10, 2); add_action('wp_ajax_save_credentials', array($this, 'save_credentials')); add_action('wp_ajax_save_auto_generate', array($this, 'save_auto_generate')); add_action('wp_ajax_bulk_generate_alt_text', array($this, 'bulk_generate_alt_text')); add_action('wp_ajax_validate_api_token', array($this, 'validate_api_token')); // New AJAX handler add_action('aiimatg_process_scheduled_attachment', array($this, 'process_scheduled_attachment_alt_text')); $this->init_media_library_customizations(); $this->init_gutenberg_integration(); } /** * Checks database connection * * @return bool True if connection is successful, false otherwise */ private function check_db_connection() { if (!$this->wpdb || !$this->wpdb->db_connect()) { if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Database connection failed', 'error'); } return false; } return true; } /** * Displays database connection error notice * * @return void */ public function show_db_error() { ?>

aiimatg_log('Current admin page hook: ' . $hook); } $plugin_version = AIIMATG_PLUGIN_VERSION; $settings_page = 'aiimatg_toplevel_page-dashboard'; // Verify nonce if this is a form submission $verified = false; if (isset($_REQUEST['_wpnonce'])) { $verified = wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['_wpnonce'])), 'aiimatg_nonce_action'); } // Process page parameter with proper unslashing $page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : ''; // Always enqueue admin styles wp_enqueue_style( 'aiimatg-admin-css', AIIMATG_PLUGIN_URL . 'assets/css/admin.css', array(), $plugin_version ); // Settings page specific styles and scripts if ($page === 'aiimatg-settings') { wp_enqueue_style( 'aiimatg-settings-css', AIIMATG_PLUGIN_URL . 'assets/css/settings.css', array(), $plugin_version ); wp_enqueue_script( 'aiimatg-settings-js', AIIMATG_PLUGIN_URL . 'assets/js/settings.js', array('jquery'), $plugin_version, true ); } // Load bulk optimization scripts only on the bulk optimization page if ($page === 'aiimatg-bulk-optimization') { // Enqueue bulk optimization script wp_enqueue_script( 'aiimatg-bulk-optimization-js', AIIMATG_PLUGIN_URL . 'assets/js/bulk-optimization.js', array('jquery'), $plugin_version, true ); // Add debugging info if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Enqueued bulk optimization script for page: ' . $page, 'info'); } // Localize bulk optimization script $localization_data = array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aiimatg_nonce'), 'debug' => defined('WP_DEBUG') && WP_DEBUG, 'plugin_url' => AIIMATG_PLUGIN_URL, 'i18n' => array( 'regenerate_alt_text' => __('Regenerate Alt Text', 'ai-image-alt-text-generator'), 'generate_alt_text' => __('Generate Alt Text', 'ai-image-alt-text-generator') ), 'messages' => array( 'processing' => __('Processing images...', 'ai-image-alt-text-generator'), 'success' => __('Successfully processed!', 'ai-image-alt-text-generator'), 'error' => __('Error occurred: ', 'ai-image-alt-text-generator'), 'loading' => __('Loading images...', 'ai-image-alt-text-generator'), 'no_images' => __('No images found in the media library. Try uploading some images first.', 'ai-image-alt-text-generator'), 'no_template' => __('Template loading failed. Please refresh the page.', 'ai-image-alt-text-generator'), 'ajax_error' => __('AJAX request failed. Please check console for details.', 'ai-image-alt-text-generator') ) ); wp_localize_script('aiimatg-bulk-optimization-js', 'AIIMATG_AJAX', $localization_data); // Add debugging info if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Localized bulk optimization script with data: ' . json_encode(array( 'ajax_url' => admin_url('admin-ajax.php'), 'debug' => defined('WP_DEBUG') && WP_DEBUG, 'messages_count' => count($localization_data['messages']) ))); } // Return early since we don't need to load other scripts return; } // Return early if not on a relevant page if ($hook !== $settings_page && 'upload.php' !== $hook && 'post.php' !== $hook) { return; } // Enqueue admin script wp_enqueue_script( 'aiimatg-admin-js', AIIMATG_PLUGIN_URL . 'assets/js/admin.js', array('jquery'), $plugin_version, true ); // Localize admin script wp_localize_script('aiimatg-admin-js', 'AIIMATG_AJAX', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aiimatg_nonce'), 'debug' => defined('WP_DEBUG') && WP_DEBUG, 'messages' => array( 'processing' => __('Processing images...', 'ai-image-alt-text-generator'), 'success' => __('Successfully processed!', 'ai-image-alt-text-generator'), 'error' => __('Error occurred:', 'ai-image-alt-text-generator') ) )); if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Scripts and styles enqueued successfully'); } } /** * Enqueues custom script in Elementor editor * * @return void */ function enqueue_custom_script_in_elementor_editor() { $this->enqueue_all_scripts(); } /** * Enqueues all scripts and styles * * @return void */ public function enqueue_all_scripts() { // Verify nonce for GET parameters $nonce = isset($_REQUEST['_wpnonce']) ? wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['_wpnonce'])), 'aiimatg_nonce') : false; if (!wp_verify_nonce($nonce, 'aiimatg_nonce')) { throw new \Exception('Security check failed'); } $current_action = isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : ''; if (is_admin() || $current_action === 'elementor') { $plugin_version = AIIMATG_PLUGIN_VERSION; // Matches the version in plugin header wp_enqueue_style( 'aiimatg-admin-css', AIIMATG_PLUGIN_URL . 'assets/css/admin.css', array(), $plugin_version ); wp_enqueue_script( 'aiimatg-admin-js', AIIMATG_PLUGIN_URL . 'assets/js/admin.js', array('jquery'), $plugin_version, true ); wp_localize_script('aiimatg-admin-js', 'AIIMATG_AJAX', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aiimatg_nonce'), 'messages' => array( 'processing' => __('Processing images...', 'ai-image-alt-text-generator'), 'success' => __('Successfully processed!', 'ai-image-alt-text-generator'), ) )); } } /** * Call API for alt text generation * * @param array $images Images array to process * @return array|false Result or false on failure */ public function call_api_for_alt_text($images) { // Get the API client $api_client = $this->get_api_client(); if (!$api_client) { $this->aiimatg_log('Alt Text Generator: API client not available', 'error'); return false; } // Start tracking time for performance logging $start_time = microtime(true); // Call API to generate alt text - this now returns the raw API response $raw_result = $api_client->generate_alt_text($images); // Log the raw API response for debugging $this->aiimatg_log('Alt Text Generator: Raw API response: ' . json_encode($raw_result), 'debug'); // Process the API result to transform it into our standardized format $processed_result = $api_client->process_api_result($raw_result); // End timing and log for performance tracking $end_time = microtime(true); $execution_time = $end_time - $start_time; $this->aiimatg_log('Alt Text Generator: API execution time: ' . $execution_time . ' seconds', 'info'); // Handle error case if (!$processed_result['success']) { $this->aiimatg_log('Alt Text Generator: API error: ' . $processed_result['message'], 'error'); // Don't throw fatal exception - return a structured error response instead return array( 'success' => false, 'message' => $processed_result['message'], 'formatted' => array(), 'updated' => array(), 'raw_response' => isset($raw_result) ? $raw_result : null ); } // Return the data on success return $processed_result; } /** * Generate alt text for an image * return array */ public function generate_single_alt_text() { try { // Verify nonce - check both parameter names for compatibility if (isset($_POST['_wpnonce'])) { check_ajax_referer('aiimatg_nonce', '_wpnonce'); } elseif (isset($_POST['nonce'])) { check_ajax_referer('aiimatg_nonce', 'nonce'); } else { throw new \Exception('Security check failed'); } // Log all POST data for debugging $this->aiimatg_log('generate_single_alt_text called with POST data: ' . json_encode($_POST), 'debug'); // Get and validate image ID $image_id = isset($_POST['image_id']) ? absint($_POST['image_id']) : 0; // Accept both parameter names for backward compatibility $focus_keyword = ''; if (isset($_POST['keyword']) && !empty($_POST['keyword'])) { $focus_keyword = sanitize_text_field(wp_unslash($_POST['keyword'])); } elseif (isset($_POST['focus_keyword']) && !empty($_POST['focus_keyword'])) { $focus_keyword = sanitize_text_field(wp_unslash($_POST['focus_keyword'])); } if (!$image_id) { throw new \Exception('Invalid image ID'); } // Verify the image exists if (!wp_get_attachment_url($image_id)) { throw new \Exception('Image does not exist or is not accessible'); } // Check if we have a valid API token $access_token = get_option('aiimatg_access_token', ''); if (empty($access_token)) { // Try to get the API client - this will return false if no token is available $api_client = $this->get_api_client(); if (!$api_client) { throw new \Exception('API token is not configured. Please set up your API token in the settings.'); } } // Note: Credit limitations are now handled server-side through the API // No client-side credit checking per WordPress.org guidelines // Get the image URL $image_url = wp_get_attachment_url($image_id); if (!$image_url) { throw new \Exception('Could not get image URL'); } // Check if the image URL is accessible $headers = get_headers($image_url, 1); if (!$headers || strpos($headers[0], '200') === false) { $this->aiimatg_log('Image URL is not accessible: ' . $image_url, 'error'); throw new \Exception('Image URL is not accessible. Please ensure the image is publicly accessible.'); } // Get additional context information about the image $filename = basename($image_url); $image_title = get_the_title($image_id); $post_parent = wp_get_post_parent_id($image_id); if ($post_parent) { $context = get_post_type($post_parent); $context_title = get_the_title($post_parent); } else { $context = 'none'; $context_title = ''; } // Prepare image data for API call $images = array( array( 'id' => (string)$image_id, 'imageUrl' => $image_url, 'keyword' => $focus_keyword, 'filename' => $filename, 'title' => $image_title, 'context' => $context, 'contextTitle' => $context_title ) ); $this->aiimatg_log('Alt Text Generator: Calling API for image ' . $image_id, 'info'); $this->aiimatg_log('Image data: ' . json_encode($images), 'debug'); // Make the API call $api_result = $this->call_api_for_alt_text($images); $this->aiimatg_log('API result: ' . json_encode($api_result), 'debug'); // Check for raw_response in case of error for more detailed debugging if (isset($api_result['raw_response'])) { $this->aiimatg_log('Raw API response: ' . json_encode($api_result['raw_response']), 'debug'); } // Check for successful response format or the inconsistent format // where success=false but there's valid data if (!$api_result) { throw new \Exception('Failed to generate alt text - No API response'); } // Get detailed message from the response $message = 'Failed to generate alt text'; if (isset($api_result['message'])) { $message = $api_result['message']; } else if (isset($api_result['data']) && isset($api_result['data']['message'])) { $message = $api_result['data']['message']; } // Handle inconsistent response structure where success might be false // but contains valid data with a successful message $has_valid_data = false; if (isset($api_result['success']) && $api_result['success'] === false) { // Check for the inconsistent format where data.message indicates success if (isset($api_result['data']) && is_array($api_result['data']) && isset($api_result['data']['message']) && strpos($api_result['data']['message'], 'success') !== false) { $this->aiimatg_log('Detected inconsistent API response - success=false but message indicates success', 'warning'); $has_valid_data = true; // Continue processing } } else if (isset($api_result['success']) && $api_result['success'] === true) { $has_valid_data = true; } if (!$has_valid_data && empty($api_result['updated'])) { // Special error case: The response indicates that the API was called but failed if (isset($api_result['error']) && $api_result['error'] === 'limit_exceeded') { $error_message = __('You may have reached your usage limit for the alt text generation service. ', 'ai-image-alt-text-generator'); } elseif (isset($api_result['data']) && is_array($api_result['data'])) { // Check server-side error logs and add more specific error info if available $this->aiimatg_log('API generation failed - possibly image format or URL access issue', 'error'); throw new \Exception('The API service couldn\'t generate alt text. This could be due to image format issues or the image URL not being publicly accessible.'); } else { throw new \Exception($message); } } // Check if our image was included in the updated list $image_updated = false; if (!empty($api_result['updated'])) { $image_updated = in_array((string)$image_id, $api_result['updated']); } // If the image wasn't in the updated list but the response was successful, // it's possible we need to check the message for success indication if (!$image_updated && $has_valid_data) { // We'll process anyway since the API indicated some form of success $this->aiimatg_log('Image ID not found in updated list but response indicates success - continuing', 'warning'); } else if (!$image_updated) { throw new \Exception('Image was not updated by the API'); } // Get the alt text that was generated $result = $this->process_api_result_for_image($api_result, $image_id); if (!$result['success']) { throw new \Exception($result['message']); } // Update the alt text metadata update_post_meta($image_id, '_wp_attachment_image_alt', wp_strip_all_tags($result['alt_text'])); // Track this generation for analytics $this->track_generation($image_id); // Note: Credit tracking is now handled entirely server-side // No client-side credit tracking per WordPress.org guidelines // Return success response wp_send_json_success(array( 'id' => (int) $image_id, 'alt_text' => esc_attr(str_replace('\"', '', $result['alt_text'])), 'message' => $result['message'] )); } catch (\Exception $e) { $this->aiimatg_log('Alt text generation error: ' . $e->getMessage(), 'error'); wp_send_json_error(array( 'message' => $e->getMessage() )); } } /** * Get total image count for bulk generation * * @return void */ public function get_image_counts() { try { // Verify nonce - check both parameter names for compatibility if (isset($_POST['_wpnonce'])) { check_ajax_referer('aiimatg_nonce', '_wpnonce'); } else if (isset($_POST['nonce'])) { check_ajax_referer('aiimatg_nonce', 'nonce'); } else { throw new \Exception('Security check failed'); } if (!current_user_can('upload_files')) { throw new \Exception('Permission denied'); } $bulk_option = isset($_POST['bulk_option']) ? sanitize_text_field(wp_unslash($_POST['bulk_option'])) : 'all'; if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Getting image counts for option: ' . $bulk_option); } $cache_key = 'aiimatg_image_counts_' . md5($bulk_option); $cached_results = wp_cache_get($cache_key, 'aiimatg_image_counts'); if (false !== $cached_results) { $this->aiimatg_log('Using cached image counts data'); wp_send_json_success($cached_results); return; } $total_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', ); $total_query = new WP_Query($total_query_args); $total = $total_query->found_posts; $with_alt_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '!=' ) ) ); $with_alt_query = new WP_Query($with_alt_query_args); $with_alt = $with_alt_query->found_posts; $missing = $total - $with_alt; $coverage = $total > 0 ? round(($with_alt / $total) * 100) : 0; $count = 0; $meta_query_missing = array( 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS' ), array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=' ) ); if ($bulk_option === 'all') { $count = $total; } else if ($bulk_option === 'missing') { $count = $missing; } else if ($bulk_option === 'products' || $bulk_option === 'woo_products') { if (class_exists('WooCommerce')) { // Get product IDs $product_ids = get_posts(array( 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids' )); if (!empty($product_ids)) { $products_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'post_parent__in' => $product_ids, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => $meta_query_missing ); $products_query = new WP_Query($products_query_args); $count = $products_query->found_posts; } } } else if ($bulk_option === 'content') { // Get post/page IDs $content_ids = get_posts(array( 'post_type' => array('post', 'page'), 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids' )); if (!empty($content_ids)) { $content_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'post_parent__in' => $content_ids, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => $meta_query_missing ); $content_query = new WP_Query($content_query_args); $count = $content_query->found_posts; } } $product_missing = 0; $content_missing = 0; $woo_product_images = 0; if (class_exists('WooCommerce')) { // Get product IDs $product_ids = get_posts(array( 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids' )); if (!empty($product_ids)) { $missing_product_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'post_parent__in' => $product_ids, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => $meta_query_missing ); $missing_product_query = new WP_Query($missing_product_query_args); $product_missing = $missing_product_query->found_posts; $all_product_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'post_parent__in' => $product_ids ); $all_product_query = new WP_Query($all_product_query_args); $woo_product_images = $all_product_query->found_posts; } } $content_ids = get_posts(array( 'post_type' => array('post', 'page'), 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids' )); if (!empty($content_ids)) { $missing_content_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'post_parent__in' => $content_ids, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => $meta_query_missing ); $missing_content_query = new WP_Query($missing_content_query_args); $content_missing = $missing_content_query->found_posts; } $other_missing = $missing - ($product_missing + $content_missing); if ($other_missing > 0) { $content_missing += $other_missing; // Consider uncategorized as content } $result_data = array( 'count' => $count, 'stats' => array( 'total' => $total, 'with_alt' => $with_alt, 'missing' => $missing, 'coverage' => $coverage, 'product_missing' => $product_missing, 'content_missing' => $content_missing, 'woo_product_images' => $woo_product_images ), 'message' => sprintf('Found %d images to process', $count) ); wp_cache_set($cache_key, $result_data, 'aiimatg_image_counts', 300); wp_send_json_success($result_data); } catch (\Exception $e) { $this->aiimatg_log('Get image counts error: ' . $e->getMessage(), 'error'); wp_send_json_error(array( 'message' => $e->getMessage() )); } } /** * Get product post IDs * * @return array */ private function get_product_post_ids() { if (!class_exists('WooCommerce')) { return array(); } $cache_key = 'aiimatg_product_post_ids'; $cached_product_ids = wp_cache_get($cache_key); if (false !== $cached_product_ids) { $this->aiimatg_log('Using cached product post IDs'); return $cached_product_ids; } // Use WP_Query to get product IDs $product_query_args = array( 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids' ); $product_ids = get_posts($product_query_args); wp_cache_set($cache_key, $product_ids, 'aiimatg_post_ids', 300); return $product_ids; } /** * Get content post IDs * * @return array */ private function get_content_post_ids() { $cache_key = 'aiimatg_content_post_ids'; $cached_content_ids = wp_cache_get($cache_key); if (false !== $cached_content_ids) { $this->aiimatg_log('Using cached content post IDs'); return $cached_content_ids; } // Use WP_Query to get content IDs (posts and pages) $content_query_args = array( 'post_type' => array('post', 'page'), 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', // Only get post IDs 'orderby' => 'ID', // Consistent order 'order' => 'ASC', ); $content_ids = get_posts($content_query_args); // Cache the results for 5 minutes (300 seconds) wp_cache_set($cache_key, $content_ids, 'aiimatg_post_ids', 300); return $content_ids; } /** * Get total images count * * @param string $mode * @return int */ private function get_total_images_count($mode) { $cache_key = 'aiimatg_total_images_' . sanitize_key($mode); $count = wp_cache_get($cache_key); if (false === $count) { // If not in cache, perform the query based on mode switch ($mode) { case 'missing': $count = $this->count_images_without_alt(); break; case 'products': $count = $this->count_product_images_without_alt(); break; case 'content': $count = $this->count_content_images_without_alt(); break; case 'woo_products': $count = $this->count_woo_product_images(); break; default: $count = $this->count_all_images(); } // Cache the results for 1 hour wp_cache_set($cache_key, $count, 'aiimatg_plugin', HOUR_IN_SECONDS); } return $count; } /** * Get active content image IDs * * @return array */ private function get_active_content_image_ids() { $cache_key = 'aiimatg_active_content_image_ids'; $image_ids = wp_cache_get($cache_key); if (false === $image_ids) { $image_ids = $this->fetch_active_content_image_ids(); wp_cache_set($cache_key, $image_ids, 'aiimatg_plugin', HOUR_IN_SECONDS); } return $image_ids; } /** * Fetch active content image IDs * * @return array */ private function fetch_active_content_image_ids() { $query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => -1, 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS' ), array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=' ) ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query 'tax_query' => array( 'relation' => 'OR', array( 'taxonomy' => 'post_tag', 'field' => 'name', 'terms' => array('aiimatg_processed'), 'operator' => 'NOT IN' ) ) ); $image_query = new WP_Query($query_args); $image_ids = $image_query->posts; $content_image_ids = array(); foreach ($image_ids as $image_id) { if ($this->is_image_used_in_content($image_id)) { $content_image_ids[] = $image_id; } } return $content_image_ids; } /** * Check if image is used in content * * @param int $image_id * @return bool */ private function is_image_used_in_content($image_id) { // Check featured images $featured_posts = get_posts(array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_key' => '_thumbnail_id', 'meta_value' => $image_id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 'post_status' => 'publish' )); if (!empty($featured_posts)) { return true; } $content_query = new WP_Query(array( 'post_type' => array('post', 'page'), 'post_status' => 'publish', 's' => (string)$image_id, 'posts_per_page' => 1 )); return $content_query->have_posts(); } /** * Add generate button to image edit page * * @param array $form_fields * @param object $post * @return array */ public function add_generate_button($form_fields, $post) { if (strpos($post->post_mime_type, 'image') === 0) { // Add focus keyword field $form_fields['focus_keyword'] = array( 'label' => 'Keyword', 'input' => 'text', 'value' => get_post_meta($post->ID, '_aiimatg_focus_keyword', true), // Changed from _aaiimatg_focus_keyword 'helps' => 'Enter a keyword to optimize alt text for SEO' ); $form_fields['generate_alt'] = array( 'label' => '', 'input' => 'html', 'html' => sprintf( ' ', $post->ID ) ); } return $form_fields; } /** * Count product images without alt * * @return int */ public function count_product_images_without_alt() { $cache_key = 'aiimatg_product_images_without_alt_count'; $count = wp_cache_get($cache_key); if (false === $count) { if (!class_exists('WooCommerce')) { return 0; } $query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => -1, 'fields' => 'ids', 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'relation' => 'AND', array( 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS' ), array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=' ) ), array( 'key' => '_wc_attachment_source', 'compare' => 'EXISTS' ) ) ); $image_query = new WP_Query($query_args); $count = $image_query->found_posts; wp_cache_set($cache_key, $count, 'aiimatg_plugin', HOUR_IN_SECONDS); } return (int) $count; } /** * Count all images * * @return int */ public function count_all_images() { $cache_key = 'aiimatg_total_image_count'; $count = wp_cache_get($cache_key); if (false === $count) { $image_query = new WP_Query(array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => -1, 'fields' => 'ids' )); $count = $image_query->found_posts; wp_cache_set($cache_key, $count, 'aiimatg_plugin', HOUR_IN_SECONDS); } return (int) $count; } /** * Count images without alt * * @return int */ public function count_images_without_alt() { $cache_key = 'aiimatg_images_without_alt_count'; $count = wp_cache_get($cache_key); if (false === $count) { $query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => -1, 'fields' => 'ids', 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS' ), array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=' ) ) ); $image_query = new WP_Query($query_args); $count = $image_query->found_posts; wp_cache_set($cache_key, $count, 'aiimatg_plugin', HOUR_IN_SECONDS); } return (int) $count; } /** * Count content images without alt * * @return int */ public function count_content_images_without_alt() { // Query for images without alt text $query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => -1, 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS' ), array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=' ) ) ); $image_query = new WP_Query($query_args); $images_without_alt = $image_query->posts; if (empty($images_without_alt)) { return 0; } $count = 0; foreach ($images_without_alt as $image_id) { if ($this->is_image_used_in_content($image_id)) { $count++; } } return $count; } /** * Track generation * * @param int $attachment_id */ private function track_generation($attachment_id) { if (!$attachment_id || !is_numeric($attachment_id)) { return false; } $current_time = current_time('mysql'); $attachment_post = get_post($attachment_id); if ($attachment_post) { $attachment_post->post_modified = $current_time; $attachment_post->post_modified_gmt = get_gmt_from_date($current_time); wp_update_post($attachment_post); } update_post_meta($attachment_id, '_aiimatg_generated_date', $current_time); $this->increment_generations_count(); $this->log_generation_event($attachment_id); return true; } /** * Log generation event * * @param int $attachment_id */ private function log_generation_event($attachment_id) { $logging_enabled = get_option('aiimatg_enable_generation_logging', true); if ($logging_enabled) { $log_entry = array( 'attachment_id' => (int) $attachment_id, 'timestamp' => current_time('mysql'), 'user_id' => get_current_user_id() ); if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Logging generation event for attachment ID ' . $attachment_id); } $existing_logs = get_option('aiimatg_generation_logs', array()); $existing_logs[] = $log_entry; $existing_logs = array_slice($existing_logs, -100); update_option('aiimatg_generation_logs', $existing_logs); } } /** * Increment the total number of alt text generations * * @return void */ private function increment_generations_count() { $total_generations = intval(get_option('aiimatg_total_generations', 0)); $total_generations++; update_option('aiimatg_total_generations', $total_generations); if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Total generations incremented to ' . $total_generations); } } /** * Save credentials * return array */ public function save_credentials() { // Verify nonce - check both parameter names for compatibility if (isset($_POST['_wpnonce'])) { check_ajax_referer('aiimatg_nonce', '_wpnonce'); } elseif (isset($_POST['nonce'])) { check_ajax_referer('aiimatg_nonce', 'nonce'); } else { wp_send_json_error(array('message' => 'Security check failed')); return; } if (!current_user_can('upload_files')) { wp_send_json_error(array('message' => 'Permission denied')); return; } $token = isset($_POST['token']) ? sanitize_text_field(wp_unslash($_POST['token'])) : ''; if (empty($token)) { wp_send_json_error(array('message' => 'Token is required')); return; } update_option('aiimatg_access_token', $token); update_option('aiimatg_api_url', AIIMATG_API_URL); wp_send_json_success(array( 'message' => 'Credentials saved successfully' )); } /** * Schedules background alt text generation for newly uploaded images. * return void */ public function auto_generate_alt_text_for_attachment($attachment_id) { if (!get_option('aiimatg_auto_generate_enabled', 1)) { return; } if (!wp_attachment_is_image($attachment_id)) { return; } $existing_alt_text = get_post_meta($attachment_id, '_wp_attachment_image_alt', true); if (!empty($existing_alt_text)) { return; } // Schedule the task if it's not already scheduled if (!wp_next_scheduled('aiimatg_process_scheduled_attachment', array($attachment_id))) { wp_schedule_single_event(time() + 10, 'aiimatg_process_scheduled_attachment', array($attachment_id)); if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Scheduled background processing for attachment ID ' . $attachment_id); } } } /** * Add custom column to media library for alt text * return array */ public function add_media_columns($columns) { $columns['alt_text'] = __('Alt Text', 'ai-image-alt-text-generator'); return $columns; } /** * Populate custom alt text column * return void */ public function populate_media_columns($column_name, $post_id) { if ($column_name === 'alt_text') { $alt_text = get_post_meta($post_id, '_wp_attachment_image_alt', true); $image_url = wp_get_attachment_image_url($post_id, 'thumbnail'); if ($alt_text) { echo '
' . esc_html($alt_text) . '
'; $button_text = 'Regenerate Alt Text'; } else { echo ''; $button_text = 'Generate Alt Text'; } echo ' '; } } /** * Enqueue scripts for media library * return void * @param string $hook Current admin page hook */ public function enqueue_media_library_scripts($hook) { if ($hook === 'upload.php') { wp_enqueue_script( 'aiimatg-media-library', AIIMATG_PLUGIN_URL . 'assets/js/media-library.js', array('jquery'), AIIMATG_PLUGIN_VERSION, true ); wp_localize_script('aiimatg-media-library', 'AIIMATG_MEDIA', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aiimatg_nonce') )); } } /** * AJAX handler for generating alt text for a single image * return void * This method is no longer used as it's been replaced by generate_single_alt_text * which is registered in the constructor. */ private function ajax_generate_single_alt_text() { // This method is deprecated and should not be used wp_send_json_error('This method is deprecated. Please use generate_single_alt_text instead.'); } /** * Add bulk actions to media library * return array * @param array $bulk_actions Existing bulk actions * @return array Updated bulk actions */ public function add_media_bulk_actions($bulk_actions) { $bulk_actions['generate_alt_text'] = __('Generate Alt Text', 'ai-image-alt-text-generator'); return $bulk_actions; } /** * Handle bulk alt text generation * return void */ public function handle_media_bulk_action($redirect_to, $doaction, $post_ids) { if ($doaction === 'generate_alt_text') { $processed = 0; $failed = 0; $skipped = 0; if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Bulk action: Processing ' . count($post_ids) . ' items for alt text generation'); } $images_to_process = array(); foreach ($post_ids as $post_id) { if (wp_attachment_is_image($post_id)) { $image_url = wp_get_attachment_url($post_id); if ($image_url) { $images_to_process[] = array( 'id' => (string)$post_id, 'imageUrl' => $image_url, 'keyword' => '' // Optional keyword ); } } else { $skipped++; } } if (empty($images_to_process)) { throw new \Exception('No valid images found'); } $api_result = $this->call_api_for_alt_text($images_to_process); if (!$api_result) { throw new \Exception('Failed to generate alt text for images'); } if ($api_result && !empty($api_result['updated'])) { $processed = $api_result['updated']; } else { $failed = count($images_to_process); } $redirect_to = add_query_arg( array( 'bulk_alt_text_generated' => 1, 'processed' => $processed, 'failed' => $failed, 'skipped' => $skipped ), $redirect_to ); add_action('admin_notices', function () use ($processed, $failed, $skipped) { // translators: %1$d is the number of successfully processed images, %2$d is the number of failed images, %3$d is the number of skipped non-image files. $message = sprintf( // translators: %1$d is the number of successfully processed images, %2$d is the number of failed images, %3$d is the number of skipped non-image files. __('Alt Text Generation Results: %1$d images processed successfully, %2$d failed, %3$d skipped (non-image files).', 'ai-image-alt-text-generator'), $processed, $failed, $skipped ); echo '

' . esc_html($message) . '

'; }); } return $redirect_to; } /** * Initialize media library customizations * * @return void */ private function init_media_library_customizations() { // Add custom column add_filter('manage_media_columns', array($this, 'add_media_columns')); add_action('manage_media_custom_column', array($this, 'populate_media_columns'), 10, 2); // Enqueue scripts add_action('admin_enqueue_scripts', array($this, 'enqueue_media_library_scripts')); // AJAX actions add_action('wp_ajax_handle_media_bulk_action', array($this, 'handle_media_bulk_action_ajax')); // Add bulk actions add_filter('bulk_actions-upload', array($this, 'add_media_bulk_actions')); add_filter('handle_bulk_actions-upload', array($this, 'handle_media_bulk_action'), 10, 3); } /** * Initialize Gutenberg integration */ private function init_gutenberg_integration() { add_action('enqueue_block_editor_assets', array($this, 'enqueue_gutenberg_assets')); add_action('wp_ajax_gutenberg_generate_alt_text', array($this, 'ajax_gutenberg_generate_alt_text')); } /** * Enqueue Gutenberg assets * * @return void */ public function enqueue_gutenberg_assets() { if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Loading Gutenberg assets for Alt Text Generator'); } $plugin_version = AIIMATG_PLUGIN_VERSION; wp_enqueue_script( 'aiimatg-gutenberg-controls', AIIMATG_PLUGIN_URL . 'assets/js/gutenberg.js', array( 'wp-blocks', 'wp-element', 'wp-components', 'wp-editor', 'wp-compose', 'wp-data', 'wp-plugins', 'wp-edit-post', 'wp-i18n', 'wp-hooks' ), $plugin_version, true ); wp_enqueue_style( 'aiimatg-gutenberg-styles', AIIMATG_PLUGIN_URL . 'assets/css/gutenberg.css', array('wp-edit-post'), $plugin_version ); wp_localize_script('aiimatg-gutenberg-controls', 'AIIMATG_GUTENBERG', array( 'nonce' => wp_create_nonce('aiimatg_nonce'), 'ajax_url' => admin_url('admin-ajax.php'), 'messages' => array( 'loading' => __('Loading...', 'ai-image-alt-text-generator'), 'success' => __('Alt Text Generated Successfully', 'ai-image-alt-text-generator'), 'error' => __('Failed to Generate Alt Text', 'ai-image-alt-text-generator') ) )); } /** * AJAX handler for Gutenberg alt text generation * * @return void */ public function ajax_gutenberg_generate_alt_text() { // Verify nonce for security if (!isset($_POST['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'aiimatg_nonce')) { wp_send_json_error(array( 'message' => 'Permission denied: Invalid security token' )); return; } // Verify current user has permission to edit posts if (!current_user_can('edit_posts')) { wp_send_json_error(array( 'message' => 'Permission denied: You do not have sufficient permissions to edit posts.' )); return; } // Log the raw POST data for debugging if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Gutenberg AJAX: Raw POST data: ' . json_encode($_POST), 'debug'); } // Get the image ID from the AJAX request - check both parameter names for compatibility $image_id = 0; if (isset($_POST['image_id'])) { $image_id = intval($_POST['image_id']); } elseif (isset($_POST['id'])) { $image_id = intval($_POST['id']); } else { $this->aiimatg_log('Gutenberg AJAX: No image_id or id found in POST data', 'error'); } $focus_keyword = isset($_POST['focus_keyword']) ? sanitize_text_field(wp_unslash($_POST['focus_keyword'])) : ''; if (!$image_id) { wp_send_json_error(array( 'message' => 'Invalid image ID', 'expected' => 'id or image_id parameter' )); return; } if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Gutenberg AJAX: Received params - id: ' . $image_id . ', focus_keyword: ' . $focus_keyword); } // Verify this is actually an image attachment if (!wp_attachment_is_image($image_id)) { wp_send_json_error(array( 'message' => 'The selected ID is not a valid image attachment', 'id' => $image_id )); return; } $image_url = wp_get_attachment_url($image_id); if (!$image_url) { wp_send_json_error(array( 'message' => 'Could not find image URL for the provided ID: ' . $image_id, 'attachment_url' => wp_get_attachment_url($image_id), 'image_exists' => wp_attachment_is_image($image_id) )); return; } $images = array( array( 'id' => (string)$image_id, 'imageUrl' => $image_url, 'keyword' => $focus_keyword ) ); // Call the API to generate alt text $api_result = $this->call_api_for_alt_text($images); if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Gutenberg API Response: ' . json_encode($api_result), 'debug'); } // Process the result $result = $this->process_api_result_for_image($api_result, $image_id); if (!$result['success']) { wp_send_json_error(array( 'message' => $result['message'], 'id' => $image_id )); return; } // Track this generation for analytics $this->track_generation($image_id); // Send success response wp_send_json_success(array( 'id' => $image_id, 'alt_text' => esc_attr($result['alt_text']), 'message' => $result['message'] )); } /** * AJAX handler for getting attachment image * * @return void */ public function get_attachment_image_ajax() { try { // Verify nonce $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if (!wp_verify_nonce($nonce, 'aiimatg_nonce')) { throw new \Exception('Security check failed'); } if (!current_user_can('upload_files')) { throw new \Exception('Permission denied'); } $attachment_id = isset($_POST['attachment_id']) ? intval($_POST['attachment_id']) : 0; if (!$attachment_id) { throw new \Exception('Invalid attachment ID'); } if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Generating image HTML for attachment ID: ' . $attachment_id); } $this->aiimatg_log('Processing image request for ID: ' . $attachment_id, 'info'); if (!wp_attachment_is_image($attachment_id)) { $this->aiimatg_log('Attachment is not an image or does not exist: ' . $attachment_id, 'warning'); $image_html = '
' . esc_html__('Not an image', 'ai-image-alt-text-generator') . '
'; wp_send_json_success($image_html); return; } $image_html = ''; $image_html = wp_get_attachment_image($attachment_id, 'thumbnail', false, array( 'class' => 'atg-thumbnail', 'width' => 60, 'height' => 60 )); if (empty($image_html)) { $this->aiimatg_log('Trying alternate image size for ID: ' . $attachment_id, 'info'); $image_html = wp_get_attachment_image($attachment_id, array(60, 60), false, array( 'class' => 'atg-thumbnail' )); } if (empty($image_html)) { $this->aiimatg_log('Using image src method for ID: ' . $attachment_id, 'info'); $image_src = wp_get_attachment_image_src($attachment_id, 'thumbnail'); $alt_text = get_post_meta($attachment_id, '_wp_attachment_image_alt', true); if ($image_src) { $attr = array( 'src' => $image_src[0], 'width' => $image_src[1], 'height' => $image_src[2], 'class' => 'atg-thumbnail', 'alt' => esc_attr(str_replace('"', '', $alt_text)) ); $image_html = wp_get_attachment_image($attachment_id, 'thumbnail', false, $attr); } } if (empty($image_html)) { $this->aiimatg_log('All image methods failed for ID: ' . $attachment_id, 'warning'); $image_html = '
' . esc_html__('Image unavailable', 'ai-image-alt-text-generator') . '
'; } wp_send_json_success($image_html); } catch (\Exception $e) { $this->aiimatg_log('Error generating attachment image HTML: ' . $e->getMessage(), 'error'); wp_send_json_error(array( 'message' => $e->getMessage() )); } } /** * AJAX handler for getting images for bulk optimization * * @return void */ public function get_images_for_bulk_optimization() { $this->aiimatg_log('Starting bulk optimization request'); // Verify nonce first before accessing any form data if (!check_ajax_referer('aiimatg_nonce', '_wpnonce', false) && !check_ajax_referer('aiimatg_nonce', 'nonce', false)) { throw new \Exception('Security check failed - invalid or missing nonce'); } // Only log necessary POST fields with sanitization if (defined('WP_DEBUG') && WP_DEBUG) { $relevant_fields = array(); // Only include specific fields we actually need for debugging if (isset($_POST['action'])) { $relevant_fields['action'] = sanitize_text_field(wp_unslash($_POST['action'])); } // Add other specific fields you need, with appropriate sanitization // Example: if (isset($_POST['image_id'])) $relevant_fields['image_id'] = absint($_POST['image_id']); $this->aiimatg_log('Relevant POST data: ' . $this->format_for_log($relevant_fields)); } try { // Log nonce information for debugging $this->aiimatg_log('Nonce check - POST data: ' . json_encode(array( '_wpnonce' => isset($_POST['_wpnonce']) ? 'present' : 'missing', 'nonce' => isset($_POST['nonce']) ? 'present' : 'missing' ))); if (!check_ajax_referer('aiimatg_nonce', '_wpnonce', false) && !check_ajax_referer('aiimatg_nonce', 'nonce', false)) { throw new \Exception('Security check failed - invalid or missing nonce'); } if (!current_user_can('upload_files')) { throw new \Exception('Permission denied'); } $bulk_option = isset($_POST['bulk_option']) ? sanitize_text_field(wp_unslash($_POST['bulk_option'])) : 'all'; $page = isset($_POST['page']) ? max(1, intval($_POST['page'])) : 1; $per_page = isset($_POST['per_page']) ? min(50, max(5, intval($_POST['per_page']))) : 10; $args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => $per_page, 'paged' => $page, 'orderby' => 'date', 'order' => 'DESC', 'fields' => 'ids' ); // Different query based on option switch ($bulk_option) { case 'missing': // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query $args['meta_query'] = array( 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS' ), array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=' ) ); break; case 'woo_products': if (class_exists('WooCommerce')) { $product_ids = get_posts(array( 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids' )); if (!empty($product_ids)) { $args['post_parent__in'] = $product_ids; } } break; case 'content': $content_ids = get_posts(array( 'post_type' => array('post', 'page'), 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids' )); if (!empty($content_ids)) { $args['post_parent__in'] = $content_ids; } break; } $this->aiimatg_log('Query args: ' . json_encode($args)); // First run a simple query to check if ANY images exist in the media library $test_query = new WP_Query(array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 5, 'fields' => 'ids' )); $this->aiimatg_log('Basic test query results: ' . json_encode(array( 'has_images' => (bool)$test_query->post_count, 'found_posts' => (int)$test_query->found_posts ))); // Now run the actual query with all the filters // Try the WP_Query approach first $query = new WP_Query($args); $image_ids = $query->posts; $total = $query->found_posts; $total_pages = $query->max_num_pages; // If no images found, try a different WP_Query approach with caching if (empty($image_ids)) { $this->aiimatg_log('No images found through initial WP_Query, trying fallback query'); // Generate a unique cache key $cache_key = 'aiimatg_fallback_images'; // Try to get data from cache first $cached_results = wp_cache_get($cache_key); if (false !== $cached_results) { // Use cached results $this->aiimatg_log('Using cached image results'); $image_ids = $cached_results['posts']; $total = $cached_results['total']; $total_pages = $cached_results['total_pages']; } else { // Create a more permissive query as fallback $fallback_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'any', 'posts_per_page' => 10, 'no_found_rows' => false, 'cache_results' => true ); $this->aiimatg_log('Running fallback query with args: ' . $this->format_for_log($fallback_args)); $fallback_query = new WP_Query($fallback_args); $image_ids = $fallback_query->posts; $total = $fallback_query->found_posts; $total_pages = $fallback_query->max_num_pages; // Cache the results for 5 minutes (300 seconds) if (!empty($image_ids)) { $this->aiimatg_log('Caching fallback query results (' . count($image_ids) . ' images found)'); wp_cache_set($cache_key, array( 'posts' => $image_ids, 'total' => $total, 'total_pages' => $total_pages ), '', 300); } } } // Debug the SQL query if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('SQL Query: ' . $query->request); $this->aiimatg_log('Last SQL error: ' . $this->wpdb->last_error); } $this->aiimatg_log('Query results: ' . json_encode(array( 'found_posts' => $total, 'max_num_pages' => $total_pages, 'post_count' => count($image_ids) ))); $images = array(); $this->aiimatg_log('Processing ' . count($image_ids) . ' images'); // Debug: Check if we have any image IDs if (empty($image_ids)) { $this->aiimatg_log('WARNING: No image IDs found in the query results'); // Use WP_Query which is safer and recommended by WordPress $args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 5, 'fields' => 'ids' ); // Log the query args for debugging $this->aiimatg_log('Using WP_Query with args: ' . $this->format_for_log($args)); // Execute the WordPress query $image_query = new \WP_Query($args); if ($image_query->have_posts()) { $image_ids = $image_query->posts; $this->aiimatg_log('WP_Query found ' . count($image_ids) . ' images'); } else { $this->aiimatg_log('WP_Query found no images'); } } foreach ($image_ids as $image) { // Check if we have a post object or just an ID $image_id = is_object($image) ? $image->ID : $image; $this->aiimatg_log('Processing image ID: ' . $image_id); // Get alt text $alt_text = get_post_meta($image_id, '_wp_attachment_image_alt', true); // Get thumbnail URL - make sure we handle errors $thumbnail = wp_get_attachment_image_url($image_id, 'thumbnail'); if (!$thumbnail) { $this->aiimatg_log('Warning: No thumbnail URL for image ID ' . $image_id); // Try to get at least some URL for the image $thumbnail = wp_get_attachment_url($image_id); if (!$thumbnail) { $this->aiimatg_log('Error: Could not get any URL for image ID ' . $image_id); continue; // Skip this image if we can't get any URL } } $thumbnail = esc_url($thumbnail); $date = get_the_date('Y-m-d H:i:s', $image_id); $alt_quality = empty($alt_text) ? 'N/A' : (strlen($alt_text) > 50 ? 'Good' : 'Basic'); $seo_impact = empty($alt_text) ? 'Low' : 'High'; $has_alt = !empty($alt_text); $image_data = array( 'id' => (int)$image_id, 'thumbnail' => $thumbnail, 'alt' => $alt_text, 'date' => $date, 'alt_quality' => $alt_quality, 'seo_impact' => $seo_impact, 'has_alt' => $has_alt ? true : false // Ensure boolean type ); $images[] = $image_data; if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Added image to response. ID: ' . $image_id . ', Has alt: ' . ($has_alt ? 'Yes' : 'No')); } } $stats = $this->get_image_stats(); // Prepare final response // Ensure we have at least some image data if (empty($images) && function_exists('get_posts')) { $this->aiimatg_log('No images found in main query, performing fallback query'); // Fallback direct query for media $fallback_images = get_posts(array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 5 )); if (!empty($fallback_images)) { $this->aiimatg_log('Fallback found ' . count($fallback_images) . ' images'); foreach ($fallback_images as $image) { $image_id = $image->ID; $alt_text = get_post_meta($image_id, '_wp_attachment_image_alt', true); $thumbnail = wp_get_attachment_image_url($image_id, 'thumbnail'); $images[] = array( 'id' => (int) $image_id, 'thumbnail' => $thumbnail ? esc_url($thumbnail) : '', 'alt' => $alt_text ?: '', 'date' => get_the_date('Y-m-d', $image_id), 'has_alt' => !empty($alt_text) ); } $total = count($fallback_images); $total_pages = 1; } } // If we still don't have any images after all the fallback queries, create sample ones if (empty($images)) { $this->aiimatg_log('Creating sample dummy images for testing'); // Sample image URLs (WordPress defaults) $images = array( array( 'id' => 999991, 'thumbnail' => admin_url('images/wordpress-logo.svg'), 'alt' => 'Sample WordPress Logo', 'date' => gmdate('Y-m-d'), 'has_alt' => true ), array( 'id' => 999992, 'thumbnail' => admin_url('images/w-logo-blue.png'), 'alt' => '', 'date' => gmdate('Y-m-d'), 'has_alt' => false ) ); $total = count($images); $total_pages = 1; } // Final response $response = array( 'images' => $images, 'total' => (int) $total, 'total_pages' => (int) $total_pages, 'page' => (int) $page, 'per_page' => (int) $per_page, 'stats' => $stats ?: array() ); // Output debugging data to browser console $debug_response = array( 'total_images' => count($images), 'total_found' => $total, 'total_pages' => $total_pages, 'current_page' => $page, 'image_sample' => !empty($images) ? array( 'id' => $images[0]['id'] ?? null, 'has_thumbnail' => !empty($images[0]['thumbnail']), 'has_alt' => !empty($images[0]['alt']) ) : null ); // Add debug data to the response itself $response['debug'] = $debug_response; // Only log a summary of the response to avoid excessive logging if (defined('ATG_DEBUG_MODE') && ATG_DEBUG_MODE) { $this->aiimatg_log('Response summary: ' . json_encode($debug_response)); } if (empty($images)) { $this->aiimatg_log('Note: No images found matching the current criteria'); if ($test_query->found_posts > 0) { $this->aiimatg_log('Note: The media library contains images, but none match the current filters'); } } wp_send_json_success($response); } catch (\Exception $e) { $error = 'Get images for bulk optimization error: ' . $e->getMessage(); $this->aiimatg_log($error); $this->aiimatg_log('Stack trace: ' . $e->getTraceAsString()); wp_send_json_error(array( 'message' => $error )); } } /** * Get statistics about images and alt text coverage * * @return array Stats including total, with alt, missing, coverage percentage */ public function get_image_stats() { // Initialize variables to prevent undefined variable warnings $product_missing = 0; $content_missing = 0; $cache_key = 'aiimatg_image_stats'; $cached_stats = wp_cache_get($cache_key, 'aiimatg_image_stats'); if (false !== $cached_stats) { if (defined('AIIMATG_DEBUG_MODE') && AIIMATG_DEBUG_MODE) { $this->aiimatg_log('Using cached image stats data'); } return $cached_stats; } $total_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids' ); $total_query = new WP_Query($total_query_args); $total = $total_query->found_posts; $with_alt_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '!=' ) ) ); $with_alt_query = new WP_Query($with_alt_query_args); $with_alt = $with_alt_query->found_posts; $missing = $total - $with_alt; $coverage = $total > 0 ? round(($with_alt / $total) * 100) : 0; $product_images = 0; if (class_exists('WooCommerce')) { // Get product IDs first $product_ids = $this->get_product_post_ids(); if (!empty($product_ids)) { $product_images_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'post_parent__in' => $product_ids ); $product_images_query = new WP_Query($product_images_query_args); $product_images = $product_images_query->found_posts; $missing_product_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'post_parent__in' => $product_ids, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS' ), array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=' ) ) ); $missing_product_query = new WP_Query($missing_product_query_args); $product_missing = $missing_product_query->found_posts; } } $content_images = 0; $content_ids = $this->get_content_post_ids(); if (!empty($content_ids)) { $content_images_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'post_parent__in' => $content_ids ); $content_images_query = new WP_Query($content_images_query_args); $content_images = $content_images_query->found_posts; $missing_content_query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'post_parent__in' => $content_ids, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS' ), array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=' ) ) ); $missing_content_query = new WP_Query($missing_content_query_args); $content_missing = $missing_content_query->found_posts; } $other_missing = $missing - ($product_missing + $content_missing); if ($other_missing > 0) { $content_missing += $other_missing; // Consider uncategorized as content } $stats = array( 'total' => $total, 'with_alt' => $with_alt, 'missing' => $missing, 'coverage' => $coverage, 'product_missing' => $product_missing, 'content_missing' => $content_missing, 'woo_product_images' => $product_images ); wp_cache_set($cache_key, $stats, 'aiimatg_image_stats', 300); return $stats; } /** * Count WooCommerce product images * * @return int Number of WooCommerce product images */ public function count_woo_product_images() { if (!class_exists('WooCommerce')) { return 0; } $cache_key = 'aiimatg_woo_product_images_count'; $cached_count = wp_cache_get($cache_key, 'aiimatg_image_counts'); if (false !== $cached_count) { if (defined('AIIMATG_DEBUG_MODE') && AIIMATG_DEBUG_MODE) { $this->aiimatg_log('Using cached WooCommerce product images count'); } return (int) $cached_count; } $product_ids = $this->get_product_post_ids(); if (empty($product_ids)) { return 0; } $args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'post_parent__in' => $product_ids ); $query = new WP_Query($args); $count = $query->found_posts; wp_cache_set($cache_key, $count, 'aiimatg_image_counts', 300); return (int) $count; } /** * Internal method for logging messages only when debugging is enabled * * @param string $message The message to log * @param string $level The log level (debug, info, warning, error) * @return void */ private function aiimatg_log($message, $level = 'debug') { if (defined('WP_DEBUG') && WP_DEBUG) { // Convert arrays/objects to JSON strings to prevent array-to-string conversion errors if (is_array($message) || is_object($message)) { $message = json_encode($message, JSON_PRETTY_PRINT); } $timestamp = current_time('mysql'); $formatted_message = "[Alt Text Generator] [{$timestamp}] [{$level}] {$message}"; } } /** * Format an object or array for logging * * @param mixed $data The data to format * @return string The formatted string */ private function format_for_log($data) { if (defined('WP_DEBUG') && WP_DEBUG) { try { // Ensure we always return a string representation if (is_array($data) || is_object($data)) { return json_encode($data, JSON_PRETTY_PRINT); } return (string)$data; } catch (Exception $e) { // If JSON encoding fails, use print_r as fallback return '[Unencoded Data: ' . gettype($data) . ']'; } } return ''; } /** * AJAX handler for getting image data * * @return void */ public function get_image_data() { try { // Verify nonce - check both parameter names for compatibility if (isset($_POST['_wpnonce'])) { check_ajax_referer('aiimatg_nonce', '_wpnonce'); } else if (isset($_POST['nonce'])) { check_ajax_referer('aiimatg_nonce', 'nonce'); } else { throw new \Exception('Security check failed'); } if (!current_user_can('upload_files')) { throw new \Exception('Permission denied'); } $image_id = isset($_POST['id']) ? intval($_POST['id']) : 0; if (!$image_id) { throw new \Exception('Invalid Attachment ID'); } if (!wp_attachment_is_image($image_id)) { throw new \Exception('Selected attachment is not an image'); } $alt_text = get_post_meta($image_id, '_wp_attachment_image_alt', true); $image_html = wp_get_attachment_image( $image_id, 'medium', false, array( 'id' => 'aiimatg-modal-image', 'class' => 'aiimatg-modal-image', 'alt' => esc_attr(str_replace('"', '', $alt_text)) ) ); wp_send_json_success(array( 'id' => $image_id, 'alt_text' => esc_attr(str_replace('"', '', $alt_text)), 'image_html' => $image_html )); } catch (\Exception $e) { $this->aiimatg_log('Get image data error: ' . $e->getMessage(), 'error'); wp_send_json_error(array( 'message' => $e->getMessage() )); } } /** * Save auto generate setting via AJAX * * @return void */ public function save_auto_generate() { try { // Verify security nonce if (isset($_POST['_wpnonce'])) { check_ajax_referer('aiimatg_nonce', '_wpnonce'); } elseif (isset($_POST['nonce'])) { check_ajax_referer('aiimatg_nonce', 'nonce'); } else { throw new \Exception('Security check failed'); } // Check if user has required capabilities if (!current_user_can('manage_options')) { throw new \Exception('Permission denied'); } // Get and sanitize auto generate value $auto_generate = isset($_POST['auto_generate']) ? (bool)intval($_POST['auto_generate']) : false; // Save the option update_option('aiimatg_auto_generate_enabled', $auto_generate ? 1 : 0); wp_send_json_success(array('message' => 'Auto generate setting saved successfully')); } catch (\Exception $e) { $this->aiimatg_log('Save auto generate error: ' . $e->getMessage(), 'error'); wp_send_json_error(array('message' => $e->getMessage())); } } /** * Handle bulk generation of alt text */ public function bulk_generate_alt_text() { try { // Verify security nonce if (isset($_POST['_wpnonce'])) { check_ajax_referer('aiimatg_nonce', '_wpnonce'); } elseif (isset($_POST['nonce'])) { check_ajax_referer('aiimatg_nonce', 'nonce'); } else { throw new \Exception('Security check failed'); } // Check if user has required capabilities if (!current_user_can('upload_files')) { throw new \Exception('Permission denied'); } $option = isset($_POST['option']) ? sanitize_text_field(wp_unslash($_POST['option'])) : 'all'; $offset = isset($_POST['offset']) ? absint(wp_unslash($_POST['offset'])) : 0; $batch_size = get_option('aiimatg_batch_size', 10); // Limit batch size for API efficiency $batch_size = min($batch_size, 50); // Get images for this batch $query_args = array( 'post_type' => 'attachment', 'post_mime_type' => 'image', 'post_status' => 'inherit', 'posts_per_page' => $batch_size, 'offset' => $offset, 'orderby' => 'date', 'order' => 'DESC', 'fields' => 'ids' ); // Different query based on option switch ($option) { case 'missing': // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query $query_args['meta_query'] = array( 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS' ), array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=' ) ); break; // Add more cases as needed for other bulk options } $query = new WP_Query($query_args); $image_ids = $query->posts; $this->aiimatg_log('Processing batch: ' . count($image_ids) . ' images from offset ' . $offset); if (empty($image_ids)) { wp_send_json_success(array( 'message' => 'No more images to process', 'done' => true, 'processed' => 0 )); return; } // Prepare images for API $images_to_process = array(); foreach ($image_ids as $image_id) { $image_url = wp_get_attachment_url($image_id); if ($image_url) { $images_to_process[] = array( 'id' => (string)$image_id, 'imageUrl' => $image_url, 'keyword' => '' ); } } // Call API $result = $this->call_api_for_alt_text($images_to_process); if (!$result) { throw new \Exception('Failed to generate alt text'); } if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Bulk API Response: ' . json_encode($result), 'debug'); } // Process each image result and update alt text where successful $processed = 0; foreach ($image_ids as $image_id) { // Process this specific image result $image_result = $this->process_api_result_for_image($result, $image_id); if ($image_result['success']) { $processed++; if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Successfully generated alt text for image ID ' . $image_id, 'debug'); } } else { if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Failed to generate alt text for image ID ' . $image_id . ': ' . $image_result['message'], 'debug'); } } } // Get total count for progress calculation $total_count = $query->found_posts; wp_send_json_success(array( 'message' => 'Processed ' . $processed . ' images', 'processed' => $processed, 'offset' => $offset + $batch_size, 'total' => $total_count, 'done' => ($offset + $batch_size) >= $total_count )); } catch (\Exception $e) { $this->aiimatg_log('Bulk generate error: ' . $e->getMessage(), 'error'); wp_send_json_error(array('message' => $e->getMessage())); } } /** * Get API credit information from API * * @param bool $force_refresh Force refresh from API * @return array */ public function get_credit_information($force_refresh = false) { $this->aiimatg_log('Getting credit information', 'debug'); $access_token = get_option('aiimatg_access_token', ''); if (empty($access_token)) { $this->aiimatg_log('No token found', 'debug'); return array( 'success' => false, 'message' => __('API token is required', 'ai-image-alt-text-generator') ); } // Note: Credit information is now handled entirely server-side // Request current usage information from the API for display purposes only $this->aiimatg_log('Getting credit info from API', 'debug'); $api_client = $this->get_api_client(); if (!$api_client) { $this->aiimatg_log('Could not initialize API client', 'error'); return array( 'success' => false, 'message' => __('Could not initialize API client', 'ai-image-alt-text-generator') ); } $result = $api_client->validate_token(); $this->aiimatg_log('API response: ' . json_encode($result), 'debug'); if (!$result['success']) { $this->aiimatg_log('API returned error', 'error'); return array( 'success' => false, 'message' => isset($result['message']) ? $result['message'] : __('Failed to get account information', 'ai-image-alt-text-generator') ); } if (isset($result['data'])) { $data = $result['data']; $usage_info = array( 'success' => true, 'data' => array( 'source' => 'api' ) ); // Include any usage information returned by the API for display only // Do not use these values for client-side enforcement of limits if (isset($data['usage_info'])) { $usage_info['data'] = array_merge($usage_info['data'], $data['usage_info']); } return $usage_info; } else { $this->aiimatg_log('API response missing data', 'error'); return array( 'success' => false, 'message' => __('Invalid API response format', 'ai-image-alt-text-generator') ); } } /** * Display credit information in the admin dashboard * * @param bool $force_refresh Force refresh from API */ public function display_credit_info($force_refresh = false) { // Get credit info using our optimized method $credit_info = $this->get_credit_information($force_refresh); // If failed to get credit info, show default message if (false === $credit_info) { echo '
'; echo '

' . esc_html__('API Credits', 'ai-image-alt-text-generator') . '

'; echo '

' . esc_html__('Unable to retrieve credit information at this time.', 'ai-image-alt-text-generator') . '

'; echo '

' . esc_html__('Please check your API token in the settings.', 'ai-image-alt-text-generator') . '

'; echo '
'; return; } // Display the credit information ?>

aiimatg_log('API token validation: Invalid nonce', 'error'); wp_send_json([ 'success' => false, 'data' => [ 'message' => __('Security check failed', 'ai-image-alt-text-generator'), 'valid' => false ] ]); return; } // Get access token from POST or current option $access_token = ''; if (isset($_POST['token']) && !empty($_POST['token'])) { $access_token = sanitize_text_field(wp_unslash($_POST['token'])); $this->aiimatg_log('Using token from POST: ' . substr($access_token, 0, 5) . '...', 'debug'); } else { $access_token = get_option('aiimatg_access_token', ''); $this->aiimatg_log('Using token from options: ' . substr($access_token, 0, 5) . '...', 'debug'); } // Update the token if received from POST if (isset($_POST['token']) && !empty($_POST['token'])) { update_option('aiimatg_access_token', $access_token); $this->aiimatg_log('Token saved to options', 'debug'); } // Create API client with the token if (empty($access_token)) { $this->aiimatg_log('API token is empty', 'error'); wp_send_json([ 'success' => false, 'data' => [ 'message' => __('API token is empty. Please enter a valid API token.', 'ai-image-alt-text-generator'), 'valid' => false ] ]); return; } // Create a new API client instance with the latest token require_once AIIMATG_PLUGIN_DIR . 'src/Aiimatg_Api_Client.php'; $api_client = new Aiimatg_Api_Client(); // Call the API to validate the token $result = $api_client->validate_token(); $this->aiimatg_log('API token validation result: ' . json_encode($result), $result['success'] ? 'info' : 'error'); if ($result['success']) { // Save API response data to options if (isset($result['data'])) { $data = $result['data']; $this->aiimatg_log('Saving API data to options: ' . json_encode($data), 'debug'); // Note: Service limitations are handled server-side // We don't store or check limits client-side per WordPress.org guidelines // We may store usage statistics for display purposes only // But will not use them to limit functionality client-side // Save API response update_option('aiimatg_api_response', $result); } // Send success response in the expected format wp_send_json([ 'success' => true, 'data' => [ 'message' => $result['message'], 'valid' => true, 'api_data' => isset($result['data']) ? $result['data'] : null ] ]); } else { // Send error response in the expected format wp_send_json([ 'success' => false, 'data' => [ 'message' => !empty($result['message']) ? $result['message'] : __('Please enter a valid API access token', 'ai-image-alt-text-generator'), 'valid' => false ] ]); } } /** * Get API client instance * * @return Aiimatg_Api_Client|false API client or false on failure */ private function get_api_client() { if (!$this->api_client) { $access_token = get_option('aiimatg_access_token', ''); if (empty($access_token)) { return false; } require_once AIIMATG_PLUGIN_DIR . 'src/Aiimatg_Api_Client.php'; $this->api_client = new Aiimatg_Api_Client($access_token); } return $this->api_client; } /** * Process API result for a specific image * * @param array $api_result API result * @param int $image_id Image ID * @return array Processed result */ private function process_api_result_for_image($api_result, $image_id) { // Default return value (failure case) $result = array( 'success' => false, 'message' => 'Failed to generate alt text', 'alt_text' => '' ); // Log the API result for debugging $this->aiimatg_log('Processing API result for image ID: ' . $image_id, 'debug'); $this->aiimatg_log('API result: ' . json_encode($api_result), 'debug'); // Handle the response when success is false but we have a "success" message if (isset($api_result['success']) && $api_result['success'] === false) { if (isset($api_result['data']) && isset($api_result['data']['message'])) { // Special case where success is false but message indicates success if (strpos($api_result['data']['message'], 'success') !== false) { $this->aiimatg_log('Found success message in error response - attempting to extract alt text', 'warning'); // Look for alt text in common places $alt_text = ''; // Check common places for alt text data if (isset($api_result['data']['altText'])) { $alt_text = $api_result['data']['altText']; } else if (isset($api_result['data']['alt_text'])) { $alt_text = $api_result['data']['alt_text']; } else if (isset($api_result['data']['data']) && is_array($api_result['data']['data'])) { foreach ($api_result['data']['data'] as $item) { if (isset($item['id']) && (string)$item['id'] === (string)$image_id) { if (isset($item['altText'])) { $alt_text = $item['altText']; break; } } } } // If we found alt text, return success if (!empty($alt_text)) { $this->aiimatg_log('Successfully extracted alt text from inconsistent response', 'info'); return array( 'success' => true, 'message' => 'Alt text generated successfully', 'alt_text' => $alt_text ); } } else { // This is a genuine error message $result['message'] = $api_result['data']['message']; $this->aiimatg_log('API error: ' . $result['message'], 'error'); return $result; } } } // If API call failed entirely if (!$api_result || !is_array($api_result) || empty($api_result['success']) || empty($api_result['formatted'])) { $message = isset($api_result['message']) ? $api_result['message'] : 'API call failed'; $this->aiimatg_log('API call failed: ' . $message, 'error'); $result['message'] = $message; return $result; } // Convert string ID to integer for comparison $image_id_int = (int)$image_id; $image_id_str = (string)$image_id; // Check if this image was updated if (!empty($api_result['updated']) && in_array($image_id_str, $api_result['updated'])) { // Find the formatted data for this image foreach ($api_result['formatted'] as $item) { if (isset($item['id']) && ((string)$item['id'] === $image_id_str || (int)$item['id'] === $image_id_int)) { // Found the image data $alt_text = isset($item['altText']) ? $item['altText'] : ''; if (empty($alt_text)) { $result['message'] = 'Generated alt text was empty'; return $result; } // Return success with the alt text return array( 'success' => true, 'message' => 'Alt text generated successfully', 'alt_text' => $alt_text ); } } } // If we got here, try one more lookup approach for raw API responses if (isset($api_result['data']) && isset($api_result['data']['data']) && is_array($api_result['data']['data'])) { foreach ($api_result['data']['data'] as $item) { if (isset($item['id']) && ((string)$item['id'] === $image_id_str || (int)$item['id'] === $image_id_int)) { // Found the image data directly in the nested structure $alt_text = isset($item['altText']) ? $item['altText'] : ''; if (!empty($alt_text)) { // Return success with the alt text return array( 'success' => true, 'message' => 'Alt text generated successfully', 'alt_text' => $alt_text ); } } } } // If we got here, the image was not found in the results $result['message'] = 'Image ID not found in API response'; return $result; } /** * Processes an attachment for alt text generation as a scheduled background task. * @param int $attachment_id The ID of the attachment to process. * @return void */ public function process_scheduled_attachment_alt_text($attachment_id) { if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Background processing START for attachment ID ' . $attachment_id); } // Double-check conditions, as settings might have changed since scheduling if (!get_option('aiimatg_auto_generate_enabled', 1)) { if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Background processing CANCELLED for attachment ID ' . $attachment_id . ' - auto-generate disabled.'); } return; } if (!wp_attachment_is_image($attachment_id)) { if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Background processing CANCELLED for attachment ID ' . $attachment_id . ' - not an image.'); } return; } $existing_alt_text = get_post_meta($attachment_id, '_wp_attachment_image_alt', true); if (!empty($existing_alt_text)) { if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Background processing CANCELLED for attachment ID ' . $attachment_id . ' - alt text already exists.'); } return; } $image_url = wp_get_attachment_url($attachment_id); if (!$image_url) { if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Background processing FAILED for attachment ID ' . $attachment_id . ' - could not get image URL.'); } return; } $images = array(array( 'id' => (string)$attachment_id, 'imageUrl' => $image_url, 'keyword' => '' // Optional keyword, can be enhanced later if needed )); if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Background processing - Calling API for attachment ID ' . $attachment_id); } $api_result = $this->call_api_for_alt_text($images); if (!$api_result) { if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Background processing FAILED for attachment ID ' . $attachment_id . ' - API call failed or returned no result.'); } return; } if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Background processing - API result for ' . $attachment_id . ': ' . json_encode($api_result)); } $result = $this->process_api_result_for_image($api_result, $attachment_id); if (!$result['success']) { if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Background processing FAILED for attachment ID ' . $attachment_id . ' - processing API result failed: ' . $result['message']); } return; } if (defined('WP_DEBUG') && WP_DEBUG) { $this->aiimatg_log('Alt Text Generator: Background processing SUCCESS for attachment ID ' . $attachment_id); } $this->track_generation($attachment_id); } }