seo_keywords = new AltTextLab_SEO_Keywords(); $this->wc_integration = new AltTextLab_WC_Integration(); $this->assets_storage = new AltTextLab_Storage(); $this->attachment_helper = new AltTextLab_Attachment_Helper(); $this->database_helper = new AltTextLab_Database(); $this->wpml_helper = new AltTextLab_Wpml_Helper(); } /** * Generates descriptive alt text for media files and images. * */ public function generate_alt($attachment_id, $attachment_url = null, $options = []) { $api_key = AltTextLab_Utility::get_api_key(); // Exit immediately if API key is missing if (empty($api_key)) { return false; } // Terminate process if attachment doesn't meet requirements if ($attachment_id && $this->is_attachment_eligible($attachment_id) === false) { return false; } if ($attachment_id){ $attachment_url = wp_get_attachment_image_url($attachment_id, 'full'); $attachment_url = apply_filters('alttextlab_attachment_url', $attachment_url, $attachment_id); } // Combine provided options with default settings $api_options = $this->prepare_api_options($attachment_id, $options); $api = new AltTextLab_API($api_key); $response = $this->call_api_with_retry($api, $attachment_id, $attachment_url, $api_options); if (!is_array($response)) { return $response; } $alt_text = $response['result']; $alt_prefix = get_option('alttextlab_alt_prefix', ''); $alt_suffix = get_option('alttextlab_alt_suffix', ''); if (!empty($alt_prefix)) { $alt_text = trim($alt_prefix) . ' ' . $alt_text; } if (!empty($alt_suffix)) { $alt_text = $alt_text . ' ' . trim($alt_suffix); } $this->assets_storage->record_alttextlab_asset($attachment_id, $response['id'], $alt_text); $this->wpml_helper->apply_alt_text_to_attachment($attachment_id, $alt_text); do_action('alttextlab_alttext_generated', $attachment_id, $alt_text); return $alt_text; } /** * Calls the AltTextLab API to generate alt text with exponential backoff on rate limiting. * */ private function call_api_with_retry($api, $attachment_id, $attachment_url, $api_options) { $max_retries = 5; $delay = 1; $response_code = null; for ($i = 0; $i < $max_retries; $i++) { $response = $api->generate_alt_text($attachment_id, $attachment_url, $api_options, $response_code); if ($response_code !== '429') { return $response; } if ($i < $max_retries - 1) { sleep($delay); $delay *= 2; } } return null; } /** * Resolve post ID for API context. */ private function get_context_post_id($attachment_id, array $api_options) { $post_id = null; if (!empty($attachment_id)) { $attachment = get_post($attachment_id); if ($attachment && !empty($attachment->post_parent)) { $post_id = absint($attachment->post_parent); } } if (empty($post_id) && isset($api_options['explicit_post_id']) && !empty($api_options['explicit_post_id'])) { $post_id = absint($api_options['explicit_post_id']); } return $post_id ? $post_id : 0; } /** * Build context payload for the alt text generation API. */ private function get_api_context($attachment_id, array $api_options) { if (get_option('alttextlab_use_post_title_as_context', 'no') !== 'yes') { return array(); } if (empty($attachment_id)) { return array(); } $post_id = $this->get_context_post_id($attachment_id, $api_options); if (!$post_id) { return array(); } $page_title = get_the_title($post_id); if (!is_string($page_title) || $page_title === '') { return array(); } return array( 'pageTitle' => wp_strip_all_tags($page_title), ); } /** * Builds the full set of API request options for generating alt text. * */ private function prepare_api_options($attachment_id, array $options) { $api_options = wp_parse_args( $options, array( 'ecommerce' => [], 'keywords' => [], 'lang' => $this->attachment_helper->get_attachment_language( $attachment_id ) ) ); if ( empty( $options['lang'] ) && ! empty( $api_options['explicit_post_id'] ) && AltTextLab_Utility::is_wpml_active() ) { $language_details = apply_filters( 'wpml_post_language_details', null, absint( $api_options['explicit_post_id'] ) ); if ( is_array( $language_details ) && ! empty( $language_details['language_code'] ) ) { $api_options['lang'] = strtolower( (string) $language_details['language_code'] ); } } if ( get_option( 'alttextlab_use_single_lang', 'no' ) === 'yes' ) { $api_options['lang'] = $this->attachment_helper->get_attachment_language( $attachment_id ); } $model_name = get_option('alttextlab_model_name'); if (!empty($model_name)) { $api_options['style'] = $model_name; } if ($attachment_id) { if ( empty($api_options['ecommerce']) && AltTextLab_Utility::has_woocommerce()) { $product_data = $this->wc_integration->get_product_info($attachment_id); if ($product_data) { $api_options['ecommerce'] = $product_data; } } $api_options['ecommerce'] = $this->wc_integration->filtered_ecomm_data( $attachment_id, $api_options['ecommerce'] ); if (!count($api_options['keywords'])) { if (isset($api_options['explicit_post_id']) && !empty($api_options['explicit_post_id'])) { $api_options['keywords'] = $this->seo_keywords->get_seo_keywords($attachment_id, $api_options['explicit_post_id']); } else { $api_options['keywords'] = $this->seo_keywords->get_seo_keywords($attachment_id); } } if (empty($api_options['context'])) { $context = $this->get_api_context($attachment_id, $api_options); if ($context) { $api_options['context'] = $context; } } } return $api_options; } /** * Determines if a media item meets the requirements for automatic alt text creation. * */ public function is_attachment_eligible($attachment_id, $context = 'generate') { /** Verify custom eligibility filter. Skip processing if attachment is filtered out. **/ $custom_skip = apply_filters('alttextlab_skip_attachment', false, $attachment_id); if ($custom_skip) { return false; } $meta = wp_get_attachment_metadata($attachment_id); $upload_info = wp_get_upload_dir(); $file = (is_array($meta) && array_key_exists('file', $meta)) ? ($upload_info['basedir'] . '/' . $meta['file']) : get_attached_file($attachment_id); if (empty($meta) && file_exists($file)) { $meta = wp_generate_attachment_metadata($attachment_id, $file); } $size = null; // Examine local file existence if (file_exists($file)) { $size = filesize($file); } // First try to get size from metadata if (!$size && isset($meta['filesize'])) { $size = $meta['filesize']; } // Look for size in cloud storage plugin metadata if (!$size) { $offload_meta = get_post_meta($attachment_id, 'amazonS3_info', true) ?: get_post_meta($attachment_id, 'cloudinary_info', true); if (isset($offload_meta['key'])) { $external_url = wp_get_attachment_url($attachment_id); $size = $this->attachment_helper->get_attachment_size($external_url); } } $width = $meta['width'] ?? 0; $height = $meta['height'] ?? 0; $size = $size ? ($size / pow(1024, 2)) : null; // Convert to megabytes $type = wp_check_filetype($file) ?: []; $extension = $type['ext'] ?? pathinfo($file, PATHINFO_EXTENSION); // When WordPress extension detection fails, extract extension from filename directly: if (empty($extension)) { $extension = pathinfo($file, PATHINFO_EXTENSION); } $attachment_edit_url = get_edit_post_link($attachment_id); return $this->validate_image_for_generation($attachment_id, $attachment_edit_url, $context, $size, $extension, $width, $height); } /** * Validates all file parameters for alt text generation eligibility. * */ private function validate_image_for_generation($attachment_id, $attachment_edit_url, $context, $size, $extension, $width, $height) { $use_base64 = get_option('alttextlab_public') !== 'yes'; if (!in_array(strtolower($extension), self::ALLOWED_EXTENSIONS)) { $this->log_error('Unsupported extension.', $attachment_id, $attachment_edit_url, $context, $extension); return false; } if ($size === null || $size === false) { $this->log_error('File not found.', $attachment_id, $attachment_edit_url, $context); return false; } if ($use_base64 && $size > self::MAX_FILE_SIZE_MB) { $this->log_error('File size exceeds 16MB limit.', $attachment_id, $attachment_edit_url, $context, sprintf('%.2f MB', $size)); return false; } if ($width < self::MIN_DIMENSION_PX || $height < self::MIN_DIMENSION_PX) { $this->log_error('Image dimensions too small.', $attachment_id, $attachment_edit_url, $context, "{$width}x{$height}"); return false; } return true; } /* * Records structured error information when an attachment fails validation checks or processing. * */ private function log_error($message, $attachment_id, $edit_url, $context, $details = '') { if ($context === 'generate') { AltTextLab_Logger::log_error( sprintf( 'Image #%d: %s%s', esc_url($edit_url), (int)$attachment_id, esc_html__($message, 'alttextlab'), $details ? " ($details)" : '' ) ); } } /** * Automatically creates alt text for newly uploaded media files * */ public function add_attachment($attachment_id) { if (get_option('alttextlab_auto_generation_enabled') === 'no') { return; } if ( ! $this->is_attachment_eligible( $attachment_id ) ) { return; } $options = array(); $post_id = intval($_REQUEST['post_id'] ?? 0); if ($post_id > 0) { $options['explicit_post_id'] = $post_id; } $generated_alt_text = null; if ( $this->wpml_helper->is_wpml_generation_allowed_for_attachment( $attachment_id ) ) { $generated_alt_text = $this->generate_alt($attachment_id, null, $options); } if ( is_string( $generated_alt_text ) && $generated_alt_text !== '' && ! in_array( $generated_alt_text, array( self::ERROR_NO_CREDITS, self::ERROR_INVALID_API_KEY, self::ERROR_MONTHLY_LIMIT ), true )) { $options['generated_alt_text'] = $generated_alt_text; } $this->generate_wpml_translation_alts( $attachment_id, $options ); } /** * Process a batch of images for alt text generation. * */ public function alttextlab_bulk_generate() { check_ajax_referer('security_alttextlab_bulk_generate', 'security'); $last_post_id = intval($_REQUEST['last_post_id'] ?? 0); $useMediaIDS = sanitize_text_field($_REQUEST['modeUseMediaIds'] ?? 0); $batch_id = sanitize_text_field($_REQUEST['batchId'] ?? '0'); $redirect_url = admin_url('admin.php?page=alttextlab-bulk-generate'); $recursive = true; $images_to_update_transient = []; if ($useMediaIDS){ $images_to_update_transient = get_transient('alttextlab_bulk_select_generate_' . $batch_id); } $images_to_update = $this->query_attachments_for_bulk_alt($last_post_id, $useMediaIDS, $batch_id, $images_to_update_transient, $redirect_url); $raw_attachment_ids = array_map( static function ($image) { return (int) $image->post_id; }, $images_to_update ); $last_post_id = ! empty($raw_attachment_ids) ? max($raw_attachment_ids) : $last_post_id; if (count($images_to_update) == 0) { if (empty($raw_attachment_ids)) { if ($useMediaIDS) { delete_transient('alttextlab_bulk_select_generate_' . $batch_id); delete_transient('alttextlab_bulk_select_generate_redirect_' . $batch_id); } $message = $last_post_id > 0 ? __('Update complete!', 'alttextlab') : __('No images to process.', 'alttextlab'); wp_send_json(array( 'status' => 'success', 'message' => $message, 'recursive' => false, 'redirect_url' => $redirect_url, 'last_post_id' => $last_post_id )); } wp_send_json(array( 'status' => 'success', 'message' => __('No images to process.', 'alttextlab'), 'recursive' => true, 'images' => array(), 'redirect_url' => $redirect_url, 'last_post_id' => $last_post_id )); } if ($useMediaIDS) { $processed_ids = array_map( static function ($image) { return (int) $image->post_id; }, $images_to_update ); // Remove only the attachments that were actually queued for this bulk step. $images_to_update_transient = array_diff($images_to_update_transient, $processed_ids); set_transient('alttextlab_bulk_select_generate_' . $batch_id, $images_to_update_transient, 2048); } wp_send_json(array( 'status' => 'success', 'message' => __('Images successfully retrieved.', 'alttextlab'), 'recursive' => $recursive, 'redirect_url' => $redirect_url, 'images' => $images_to_update, 'last_post_id' => $last_post_id )); } /** * Retrieves a filtered list of image attachments for bulk alt text generation. * */ private function query_attachments_for_bulk_alt($last_post_id, $useMediaIDS, $batch_id, $images_to_update_transient, &$redirect_url) { global $wpdb; $is_bulk_empty = false; $query_limit = max(intval($_REQUEST['posts_per_page'] ?? 0), 1); $mode = sanitize_text_field($_REQUEST['mode'] ?? 'missing'); $only_attached = sanitize_text_field($_REQUEST['onlyAttached'] ?? '0'); $only_new = sanitize_text_field($_REQUEST['onlyNew'] ?? '0'); if ($mode === 'all') { $images_to_update_sql = " SELECT post_table.ID as post_id FROM {$wpdb->posts} post_table WHERE post_table.ID > %d AND (post_table.post_status = 'inherit') AND (post_table.post_mime_type LIKE 'image/%') AND post_table.post_type = 'attachment' "; } else { // Use 'missing' mode as default // Handles only images without alt text attributes $images_to_update_sql = " SELECT post_table.ID as post_id FROM {$wpdb->posts} post_table LEFT JOIN {$wpdb->postmeta} AS postmeta_talbe ON (post_table.ID = postmeta_talbe.post_id AND postmeta_talbe.meta_key = '_wp_attachment_image_alt') LEFT JOIN {$wpdb->postmeta} AS empty_postmeta_table ON (post_table.ID = empty_postmeta_table.post_id) WHERE post_table.ID > %d AND (post_table.post_mime_type LIKE 'image/%') AND ((empty_postmeta_table.meta_key = '_wp_attachment_image_alt' AND empty_postmeta_table.meta_value = '') OR postmeta_talbe.post_id IS NULL) AND (post_table.post_status = 'inherit') AND post_table.post_type = 'attachment' "; } if ($only_attached === '1') { $images_to_update_sql = $images_to_update_sql . " AND (post_table.post_parent > 0)"; } if ($only_new === '1') { $alttextlab_asset_table = $wpdb->prefix . ALT_TEXT_LAB_DB_ASSET_TABLE; $images_to_update_sql = $images_to_update_sql . " AND (NOT EXISTS(SELECT 1 FROM {$alttextlab_asset_table} WHERE wp_post_id = post_table.ID))"; } $params = [$last_post_id]; if ($useMediaIDS) { if (!empty($images_to_update_transient) && is_array($images_to_update_transient)) { $placeholders = implode(',', array_fill(0, count($images_to_update_transient), '%d')); $images_to_update_sql .= " AND post_table.ID IN ($placeholders)"; $params = array_merge($params, array_map('intval', $images_to_update_transient)); } else { $is_bulk_empty = true; } if ($url = get_transient('alttextlab_bulk_select_generate_redirect_' . $batch_id)) { $redirect_url = $url; } } $images_to_update_sql = $images_to_update_sql . " GROUP BY post_table.ID ORDER BY post_table.ID LIMIT %d"; $params[] = $query_limit; if ($useMediaIDS && $is_bulk_empty){ $images_to_update = []; }else{ $images_to_update = $wpdb->get_results($wpdb->prepare($images_to_update_sql, ...$params)); } return $images_to_update; } /** * Process image to create alt text * */ public function alttextlab_bulk_generate_for_image() { check_ajax_referer('security_alttextlab_generate_for_image', 'security'); $is_successful = true; $post_id = intval($_REQUEST['postId'] ?? 0); if (!$post_id) { wp_send_json(array( 'status' => 'success', 'message' => __('No image to process.', 'alttextlab'), 'process_count' => 0, 'success_count' => 0 )); } if (defined('alttextlab_BULK_DEBUG')) { AltTextLab_Logger::log_error(sprintf("BulkGenerate: Attachment ID %d", $post_id)); } if ( ! $this->wpml_helper->is_wpml_generation_allowed_for_attachment( $post_id ) ) { wp_send_json(array( 'status' => 'success', 'message' => __('Image skipped by WPML language filter.', 'alttextlab'), 'skip_reason' => 'wpml_language_filtered', 'process_count' => 1, 'success_count' => 0 )); } $run_id = sanitize_text_field( $_REQUEST['runId'] ?? '' ); if ( $this->wpml_helper->apply_wpml_single_lang_bulk_cached_alt( $post_id, $run_id ) ) { wp_send_json( array( 'status' => 'success', 'message' => __( 'Images successfully updated.', 'alttextlab' ), 'skip_reason' => 'wpml_single_lang_cached', 'process_count' => 1, 'success_count' => 1, ) ); } $response = $this->generate_alt($post_id, null, array('keywords' => [])); if (!is_array($response) && $response === false) { $is_successful = false; } if ($response === self::ERROR_NO_CREDITS || $response === self::ERROR_INVALID_API_KEY || $response === self::ERROR_MONTHLY_LIMIT) { if ( $response === self::ERROR_NO_CREDITS ) { $message = __( 'Images partially updated (no more tokens).', 'alttextlab' ); } elseif ( $response === self::ERROR_INVALID_API_KEY ) { $message = __( 'Your API key is invalid.', 'alttextlab' ); } else { $message = __( 'Images partially updated (API key\'s monthly limit reached).', 'alttextlab' ); } wp_send_json( array( 'status' => 'accountError', 'message' => $message, 'process_count' => 1, 'success_count' => 0 )); } $this->wpml_helper->remember_wpml_single_lang_bulk_group_origin( $post_id, $response, $run_id ); wp_send_json(array( 'status' => 'success', 'message' => __('Images successfully updated.', 'alttextlab'), 'process_count' => 1, 'success_count' => $is_successful ? 1 : 0 )); } /** * Creates filtering option in media library for alt text status * */ public function render_media_alt_filter_dropdown( $post_type ) { if ( $post_type !== 'attachment' ) { return; }; $alttextlab_filter = sanitize_text_field( $_GET['alttextlab_filter'] ?? 'all' ); echo ''; } /** * Modifies media library queries to display only images without alt text * */ public function filter_media_library_query_by_alt( $query ) { if ( function_exists( 'get_current_screen' ) && get_current_screen() ) { $is_media_screen = get_current_screen()->base === 'upload'; } else { $is_media_screen = ( isset($query->query['post_type'] ) && ( $query->query['post_type'] === 'attachment' ) ); } $alttextlab_filter = sanitize_text_field( $_GET['alttextlab_filter'] ?? 'all' ); if ( ! is_admin() || ! $query->is_main_query() || ! $is_media_screen || $alttextlab_filter === 'all' ) { return; } $meta_query = $query->get('meta_query') ? $query->get('meta_query') : array(); $meta_query[] = array( 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=', ), array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS', ), ); $query->set( 'meta_query', $meta_query ); } /** * Modifies ALT text through AJAX request * */ public function ajax_edit_history() { check_ajax_referer('alttextlab_edit_history', 'security'); $attachment_id = intval(wp_unslash($_REQUEST['attachment_id']) ?? 0); $alt_text = sanitize_text_field($_REQUEST['alt_text'] ?? ''); if (!$attachment_id) { wp_send_json(array( 'status' => 'error', 'message' => __('Invalid request.', 'alttextlab' ) )); } $this->wpml_helper->apply_alt_text_to_attachment($attachment_id, $alt_text); wp_send_json(array( 'status' => 'success', 'message' => __('Alt text updated.', 'alttextlab' ) )); } /** * Creates ALT text for individual image through AJAX call * */ public function ajax_single_generate() { // Exit if attachment ID is missing or not a valid number if (!isset($_REQUEST['attachment_id']) || empty($_REQUEST['attachment_id']) || !is_numeric($_REQUEST['attachment_id'])) { return; } check_ajax_referer('alttextlab_single_generate', 'security'); $attachment_id = intval(wp_unslash($_REQUEST['attachment_id']) ?? 0); if (isset($_REQUEST['keywords']) && is_array($_REQUEST['keywords'])) { $keywords = array_map('sanitize_text_field', $_REQUEST['keywords']); } else { $keywords = []; } // Create alternative text for the image $response = $this->generate_alt($attachment_id, null, array('keywords' => $keywords)); if ($response === self::ERROR_NO_CREDITS) { wp_send_json(array( 'status' => 'error', 'message' => 'You have no more tokens available. Go to your account on AltTextLab to get more tokens.', )); } if ($response === self::ERROR_INVALID_API_KEY) { wp_send_json(array( 'status' => 'error', 'message' => 'Your API key is invalid. Update it in your account settings.', )); } if ($response === self::ERROR_MONTHLY_LIMIT) { wp_send_json(array( 'status' => 'error', 'message' => __( "You've reached the monthly limit for this API key. Increase it in your AltTextLab dashboard, or wait until it resets next month.", 'alttextlab' ), )); } if (!is_array($response) && $response !== false) { $this->generate_wpml_translation_alts( $attachment_id, array( 'keywords' => $keywords, 'respect_wpml_language_selection' => false, ) ); wp_send_json(array( 'status' => 'success', 'alt_text' => $response, )); } wp_send_json(array( 'status' => 'error', )); } /** * Includes alt text generation in media library bulk actions menu * */ public function add_bulk_select_action($actions) { $actions['alttextlab_options'] = esc_html__('↓ AltTextLab', 'alttextlab' ); $actions['alttextlab_generate_alt'] = esc_html__('Generate Alt Text', 'alttextlab' ); return $actions; } /** * Handles bulk selection operations for alt text generation * */ public function bulk_select_action_handler($redirect, $do_action, $items) { // Skip processing if this isn't our alt text generation action if ($do_action !== 'alttextlab_generate_alt') { return $redirect; } // Create unique identifier for this batch processing request $batch_id = uniqid(); // Save selected image IDs in temporary WordPress storage set_transient('alttextlab_bulk_select_generate_' . $batch_id, $items, 2048); // Preserve return URL in temporary storage for later use set_transient('alttextlab_bulk_select_generate_redirect_' . $batch_id, $redirect, 2048); // Send user to the bulk processing page with appropriate parameters return admin_url('admin.php?page=alttextlab-bulk-generate&alttextlab_action=bulk-select-generate&alttextlab_batch_id=' . $batch_id . '&alttextlab_mode=all'); } /** * Verifies image suitability for automatic alt text generation via AJAX * */ public function ajax_is_attachment_eligible() { check_ajax_referer('alttextlab_check_attachment_eligibility', 'security'); $attachment_id = intval($_POST['attachment_id'] ?? 0); // Return error response if attachment ID is invalid if (!$attachment_id) { wp_send_json(array( 'status' => 'error', 'message' => __('Invalid post ID.', 'alttextlab' ) )); } if (!$this->is_attachment_eligible($attachment_id, 'check')) { wp_send_json(array( 'status' => 'error', 'message' => __('Image is not eligible for auto-generating ALT text.', 'alttextlab' ) )); } wp_send_json(array( 'status' => 'success', 'message' => __('Image is eligible for auto-generating ALT text.', 'alttextlab' ) )); } /** * Retrieves the total count of all image attachments in the media library * */ public function get_all_images_count() { global $wpdb; $all_images_query = $this->database_helper->query_all_images($wpdb); $all_images_count = (int)$wpdb->get_results($wpdb->prepare($all_images_query))[0]->total_images; return $all_images_count; } /** * Retrieves the count of image attachments that have missing or empty alt text * */ public function get_images_without_alt_text_count() { global $wpdb; $images_missing_alt_text_query = $this->database_helper->query_images_without_alt_text($wpdb); $images_missing_alt_text_count = (int)$wpdb->get_results($wpdb->prepare($images_missing_alt_text_query))[0]->total_images; return $images_missing_alt_text_count; } /** * Retrieves image attachments that have missing or empty alt text. * */ public function get_images_without_alt_text($limit = 20) { global $wpdb; $limit = absint( $limit ) < 1 ? 20 : ( absint( $limit ) > 100 ? 100 : absint( $limit ) ); $images_to_update_sql = " SELECT post_table.ID as post_id FROM {$wpdb->posts} post_table LEFT JOIN {$wpdb->postmeta} AS postmeta_talbe ON (post_table.ID = postmeta_talbe.post_id AND postmeta_talbe.meta_key = '_wp_attachment_image_alt') LEFT JOIN {$wpdb->postmeta} AS empty_postmeta_table ON (post_table.ID = empty_postmeta_table.post_id) WHERE (post_table.post_mime_type LIKE 'image/%') AND ((empty_postmeta_table.meta_key = '_wp_attachment_image_alt' AND empty_postmeta_table.meta_value = '') OR postmeta_talbe.post_id IS NULL) AND (post_table.post_status = 'inherit') AND post_table.post_type = 'attachment' GROUP BY post_table.ID ORDER BY post_table.ID LIMIT %d"; $rows = $wpdb->get_results($wpdb->prepare($images_to_update_sql, $limit)); $images = array(); foreach ( $rows as $image ) { $attachment_id = (int) $image->post_id; $images[] = array( 'attachment_id' => $attachment_id, 'title' => get_the_title( $attachment_id ) ); } return $images; } /** * Retrieves count of images based on multiple filtering criteria * */ public function get_filtered_images_count($mode, $only_attached, $only_new, $action, $selected_images) { global $wpdb; $alttextlab_asset_table = $wpdb->prefix . ALT_TEXT_LAB_DB_ASSET_TABLE; $is_bulk_empty = false; $params = []; if ($mode === 'all') { $result_query = $this->database_helper->query_all_images($wpdb); } else if ($mode === 'missing') { $result_query = $this->database_helper->query_images_without_alt_text($wpdb); } if ($only_attached === '1') { $result_query .= " AND post_table.post_parent > %d"; $params[] = 0; } if ($only_new === '1') { $result_query .= " AND (NOT EXISTS(SELECT 1 FROM {$alttextlab_asset_table} WHERE wp_post_id = post_table.ID))"; } if ($action === 'bulk-select-generate') { //Add filter by selected id if (!empty($selected_images) && is_array($selected_images)) { $placeholders = implode(',', array_fill(0, count($selected_images), '%d')); $result_query .= " AND post_table.ID IN ($placeholders)"; $params = array_merge($params, array_map('intval', $selected_images)); } else{ $is_bulk_empty = true; } } if ($action === 'bulk-select-generate' && $is_bulk_empty) { $images_count = 0; } else { $prepared_query = $wpdb->prepare($result_query, ...$params); $images_count = (int)$wpdb->get_results($prepared_query)[0]->total_images; } return $images_count; } /** * Generates alt text for multiple image attachments. * */ public function bulk_generate_alt_text_for_attachments( array $attachment_ids, array $options = array() ) { $attachment_ids = array_values( array_unique( array_filter( array_map( 'absint', $attachment_ids ) ) ) ); $results = array(); $success_count = 0; $error_count = 0; foreach ( $attachment_ids as $attachment_id ) { if ( ! $attachment_id || get_post_type( $attachment_id ) !== 'attachment' ) { $error_count++; $results[] = array( 'attachment_id' => $attachment_id, 'success' => false, 'error_code' => 'invalid_attachment', 'message' => __( 'Invalid attachment ID.', 'alttextlab' ), ); continue; } if ( ! wp_attachment_is_image( $attachment_id ) ) { $error_count++; $results[] = array( 'attachment_id' => $attachment_id, 'success' => false, 'error_code' => 'not_image', 'message' => __( 'Attachment is not an image.', 'alttextlab' ), ); continue; } if ( ! $this->wpml_helper->is_wpml_generation_allowed_for_attachment( $attachment_id ) ) { $error_count++; $results[] = array( 'attachment_id' => $attachment_id, 'success' => false, 'error_code' => 'wpml_language_filtered', 'message' => __( 'Image skipped by WPML language filter.', 'alttextlab' ), ); continue; } if ( $this->wpml_helper->apply_wpml_single_lang_bulk_cached_alt( $attachment_id, $options['run_id'] ?? '' ) ) { $success_count++; $results[] = array( 'attachment_id' => $attachment_id, 'success' => true, 'skip_reason' => 'wpml_single_lang_cached', 'message' => __( 'Image updated from cached WPML alt text.', 'alttextlab' ), ); continue; } $alt_text = $this->generate_alt( $attachment_id, null, array() ); if ( $alt_text === self::ERROR_NO_CREDITS ) { $error_count++; $results[] = array( 'attachment_id' => $attachment_id, 'success' => false, 'error_code' => 'alttextlab_no_credits', 'message' => __( 'No AltTextLab credits available.', 'alttextlab' ), ); break; } if ( $alt_text === self::ERROR_INVALID_API_KEY ) { $error_count++; $results[] = array( 'attachment_id' => $attachment_id, 'success' => false, 'error_code' => 'alttextlab_invalid_api_key', 'message' => __( 'The AltTextLab API key is invalid.', 'alttextlab' ), ); break; } if ( $alt_text === self::ERROR_MONTHLY_LIMIT ) { $error_count++; $results[] = array( 'attachment_id' => $attachment_id, 'success' => false, 'error_code' => 'alttextlab_monthly_limit', 'message' => __( 'Monthly credit limit reached for this API key. Update it anytime in your AltTextLab account settings.', 'alttextlab' ), ); break; } if ( ! is_string( $alt_text ) || $alt_text === '' ) { $error_count++; $results[] = array( 'attachment_id' => $attachment_id, 'success' => false, 'error_code' => 'alttextlab_generation_failed', 'message' => __( 'Alt text generation failed.', 'alttextlab' ), ); continue; } $this->wpml_helper->remember_wpml_single_lang_bulk_group_origin( $attachment_id, $alt_text, $options['run_id'] ?? '' ); $success_count++; $results[] = array( 'attachment_id' => $attachment_id, 'success' => true, 'alt_text' => $alt_text, ); } return array( 'success' => true, 'processed' => count( $results ), 'success_count' => $success_count, 'error_count' => $error_count, 'results' => $results, ); } /** * Apply one generated alt text value to multiple attachment translations. */ private function apply_alt_text_to_translations( $attachment_ids, $alt_text ) { if ( ! is_string( $alt_text ) || $alt_text === '' ) { return; } foreach ( $attachment_ids as $attachment_id ) { $attachment_id = (int) $attachment_id; if ( ! $attachment_id ) { continue; } $this->wpml_helper->apply_alt_text_to_attachment( $attachment_id, $alt_text ); do_action( 'alttextlab_alttext_generated', $attachment_id, $alt_text ); } } /** * Generate alt text for an attachment's WPML translations. */ private function generate_wpml_translation_alts( $attachment_id, $options = array() ) { $generated_alt_text = null; $respect_language_selection = true; if ( array_key_exists( 'generated_alt_text', $options ) ) { $generated_alt_text = $options['generated_alt_text']; unset( $options['generated_alt_text'] ); } if ( array_key_exists( 'respect_wpml_language_selection', $options ) ) { $respect_language_selection = ! empty( $options['respect_wpml_language_selection'] ); unset( $options['respect_wpml_language_selection'] ); } if ( ! AltTextLab_Utility::is_wpml_active() ) { return; } $target_languages = apply_filters( 'wpml_active_languages', null ); if ( empty( $target_languages ) || ! is_array( $target_languages ) ) { return; } $target_languages = $this->wpml_helper->get_target_wpml_languages( $target_languages, $respect_language_selection ); if ( empty( $target_languages ) ) { return; } $translated_attachments = $this->wpml_helper->get_wpml_translation_attachments( $attachment_id, $target_languages ); if ( empty( $translated_attachments ) ) { return; } if ( $this->wpml_helper->is_single_language_mode_enabled() ) { $translated_ids = array_values( $translated_attachments ); if ( ! is_string( $generated_alt_text ) || $generated_alt_text === '' ) { $seed_attachment_id = array_shift( $translated_ids ); $generated_alt_text = $this->generate_alt( $seed_attachment_id, null, $options ); } $this->apply_alt_text_to_translations( $translated_ids, $generated_alt_text ); return; } foreach ( $translated_attachments as $language_code => $translated_id ) { $this->generate_alt( $translated_id, null, array_merge( $options, array( 'lang' => strtolower( (string) $language_code ) ) )); } } }