alttextlab_attachment = new AltTextLab_Attachment(); $this->wc_integration = new AltTextLab_WC_Integration(); $this->attachment_helper = new AltTextLab_Attachment_Helper(); $this->assets_storage = new AltTextLab_Storage(); } /** * Processes cleanup when WordPress posts are removed. * */ public function on_post_deleted($post_id) { $this->assets_storage->remove_alttextlab_asset($post_id); } /** * Registers ALT text generation option in the bulk actions dropdown menu * */ public function add_bulk_select_action( $actions ) { $actions[ 'alttextlab_options' ] = esc_html__( '↓ AltTextLab', 'alttextlab' ); $actions[ 'alttextlab_generate_alt' ] = esc_html__( 'Generate alt texts', 'alttextlab' ); $actions[ 'alttextlab_sync_alt' ] = esc_html__( 'Sync alt text', 'alttextlab' ); return $actions; } /** * Executes the selected bulk operation on multiple items * */ public function bulk_select_action_handler( $redirect, $do_action, $items ) { if ( $do_action === 'alttextlab_sync_alt' ) { $redirect = remove_query_arg( array( 'alttextlab_sync_alt' ), $redirect ); $batch_id = uniqid(); set_transient( 'alttextlab_sync_alt_selected_posts_' . $batch_id, array_map( 'intval', $items ), 2048 ); set_transient( 'alttextlab_sync_alt_redirect_' . $batch_id, $redirect, 2048 ); return add_query_arg( array( 'page' => 'alttextlab-sync-alt-in-posts', 'alttextlab_action' => 'sync-selected', 'alttextlab_batch_id' => $batch_id, ), admin_url( 'admin.php' ) ); } // Bail early if action is not alttextlab_generate_alt if ( $do_action !== 'alttextlab_generate_alt' ) { return $redirect; } // remove the query arg from URL because we do not need them anymore $redirect = remove_query_arg( array( 'alttextlab_generate_alt' ), $redirect ); $batch_id = uniqid(); $all_media_ids = []; // Extract all media file IDs from the selected posts foreach ($items as $post_id) { $media_ids = $this->get_media_ids_from_post($post_id); $all_media_ids = array_merge($all_media_ids, $media_ids); } // Writing to transient storage set_transient('alttextlab_bulk_select_generate_' . $batch_id, array_unique($all_media_ids), 2048); set_transient('alttextlab_bulk_select_generate_redirect_' . $batch_id, $redirect, 2048); return admin_url('admin.php?page=alttextlab-bulk-generate&alttextlab_action=bulk-select-generate&alttextlab_batch_id=' . $batch_id . '&alttextlab_from_attachments=1&alttextlab_mode=all'); return $redirect; } /** * Updates alt text for product images * */ public function update_product_images( $post_id, $overwrite, $keywords, $is_ajax ) { $metadata = get_post_meta( $post_id ); if ( empty($metadata) ) { return false; } // Create list of image IDs to process (featured image + gallery images): $image_attachment_ids = []; if ( array_key_exists('_thumbnail_id', $metadata) && is_array($metadata['_thumbnail_id']) ) { array_push( $image_attachment_ids, $metadata['_thumbnail_id'][0] ); } if ( array_key_exists('_product_image_gallery', $metadata) && is_array($metadata['_product_image_gallery']) ) { $gallery_images = $metadata['_product_image_gallery'][0]; $image_attachment_ids = array_merge( $image_attachment_ids, explode(",", $gallery_images) ); } // Process each image attachment: // Check if there are any images if ( empty($image_attachment_ids) ) { if ( $is_ajax ) { // Set a transient to show a success notice after page reload set_transient( 'alttextlab_enrich_post_content_success', __( '[AltTextLab] No images were found to update.', 'alttextlab' ), 60 ); wp_send_json_success(); } return true; } $total_images_found = count($image_attachment_ids); $num_alttext_generated = 0; $no_credits = false; $api_key_is_invalid = false; $monthly_limit = false; foreach ( $image_attachment_ids as $attachment_id ) { $alt_text = $this->get_attachment_alt_for_post_language( $attachment_id, $post_id ); $should_generate = false; // If the alt text is empty or need to overwrite, generate it: if ( $overwrite || empty( $alt_text ) ) { $should_generate = true; $product_data = AltTextLab_Utility::has_woocommerce() ? $this->wc_integration->get_product_info($attachment_id, $post_id) : []; $alt_text = $this->alttextlab_attachment->generate_alt( $attachment_id, null, array( 'keywords' => $keywords, 'explicit_post_id' => $post_id, 'ecommerce' => $product_data ) ); } // Check if generate_alt returned false or an error if ( empty($alt_text) || ! is_string( $alt_text ) ) { continue; } elseif ($alt_text === self::ERROR_INVALID_API_KEY) { $api_key_is_invalid = true; break; } else if ( $alt_text === self::ERROR_NO_CREDITS ) { $no_credits = true; break; } else if ( $alt_text === self::ERROR_MONTHLY_LIMIT ) { $monthly_limit = true; break; } else if ( $should_generate ) { $num_alttext_generated = $num_alttext_generated + 1; } } if ( $is_ajax ) { $success_msg = $this->return_success_message($api_key_is_invalid, $no_credits, $monthly_limit, $total_images_found, $num_alttext_generated); set_transient( 'alttextlab_enrich_post_content_success', $success_msg, 60 ); // Return success wp_send_json_success(); } return array( 'status' => 'success', 'total_images_found' => $total_images_found, 'num_alttext_generated' => $num_alttext_generated ); } /** * Function get ids of attachment images from different abstract entity * */ private function get_media_ids_from_post($post_id) { $post = get_post($post_id); if (!$post) { return []; } //Get images from post content (both embedded and external) $content = $post->post_content; $img_src_attr = get_option('alttextlab_refresh_src_attr', 'src'); // Modern WP (6.2+) HTML processor if (version_compare(get_bloginfo('version'), '6.2') >= 0) { $media_ids = $this->collect_images_with_tag_processor($content, $img_src_attr, $post_id); } else { $media_ids = $this->collect_images_with_regex($content, $img_src_attr, $post_id); } // Get WooCommerce product images (if applicable) if ($post->post_type === 'product' && class_exists('WooCommerce')) { if ($thumbnail_id = get_post_thumbnail_id($post_id)) { $media_ids[] = $thumbnail_id; } $gallery_ids = get_post_meta($post_id, '_product_image_gallery', true); if (!empty($gallery_ids)) { $media_ids = array_merge($media_ids, explode(',', $gallery_ids)); } $media_ids = array_filter(array_unique(array_map('intval', $media_ids))); $media_ids = array_filter($media_ids, function($id) { return get_post_status($id) !== false; }); } // Return unique IDs only return array_unique(array_filter(array_map('intval', $media_ids))); } /** * Extracts image attachment IDs from post content using WP_HTML_Tag_Processor (WordPress 6.2+) * */ private function collect_images_with_tag_processor($content, $img_src_attr, $post_id) { $media_ids = []; $tags = new WP_HTML_Tag_Processor($content); while ($tags->next_tag('img')) { $img_url = $img_url_original = $tags->get_attribute( $img_src_attr ); if (empty($img_url)) continue; // Normalize URL if ( substr($img_url, 0, 1) == "/" ) { $img_url = $img_url_original = home_url() . $img_url; } if ( strpos( $img_url, home_url() ) === 0 ) { $img_url_original = preg_replace( '/-\d+x\d+(?=\.[a-zA-Z]{3,4}$)/', '', $img_url ); } if ( substr($img_url_original, 0, 2) == "//" ) { $img_url_original = "https:" . $img_url_original; } // Get attachment ID $attachment_id = $this->attachment_helper->lookup_attachment_id($img_url_original, $post_id); $attachment_id = $attachment_id ?? $this->attachment_helper->lookup_attachment_id($img_url_original); if ($attachment_id) { $media_ids[] = $attachment_id; } } return $media_ids; } /** * Extracts image attachment IDs from post content using regex pattern matching * */ private function collect_images_with_regex($content, $img_src_attr, $post_id) { $media_ids = []; $src_regex = sprintf('/]*?>/i', $img_src_attr); preg_match_all($src_regex, $content, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $img_url = $match[1]; if (isset($img_url) && substr($img_url, 0, 1) == "/") { $img_url = home_url() . $img_url; } if (strpos($img_url, home_url()) === 0) { $img_url = preg_replace('/-\d+x\d+(?=\.[a-zA-Z]{3,4}$)/', '', $img_url); } if (substr($img_url, 0, 2) == "//") { $img_url = "https:" . $img_url; } $attachment_id = $this->attachment_helper->lookup_attachment_id($img_url, $post_id) ?: $this->attachment_helper->lookup_attachment_id($img_url); if ($attachment_id) { $media_ids[] = $attachment_id; } } return $media_ids; } /** * Register bulk action for post types * */ public function register_bulk_action() { $post_types = array( 'post', 'page' ); if ( AltTextLab_Utility::has_woocommerce() ) { array_push($post_types, 'product'); // Add WooCommerce products if available } $post_types = apply_filters( 'alttextlab_bulk_action_post_types', $post_types ); // Allow user-defined custom types foreach ($post_types as $post_type) { add_filter( "bulk_actions-edit-{$post_type}", [$this, 'add_bulk_select_action'], 100, 1 ); add_filter( "handle_bulk_actions-edit-{$post_type}", [$this, 'bulk_select_action_handler'], 100, 3 ); } } /** * Collect list of the post id. * */ public function collect_list_enrich_post_id( $post_id = null, $overwrite = false, $process_external = false, $keywords = [] ) { $is_ajax = $this->collect_list_enrich_post_id_is_ajax($post_id, $overwrite, $process_external); $post_id_array = []; $post = get_post( $post_id ); // Check if post exists if ( $post === null ) { if ($is_ajax){ wp_send_json_error( array( 'message' => __( 'Post not found.', 'alttextlab' ), 'total_count' => 0, ) ); } return []; } elseif ( $post->post_type === 'product' && AltTextLab_Utility::has_woocommerce() ) { $post_id_array['PRODUCT'] = $this->collect_product_images_id( $post_id, $overwrite ); } $content = $post->post_content; // Check if content is empty if ( empty( $content ) ) { if ( $post_id_array['PRODUCT'] ) { $empty_content_response = array( 'status' => 'success', 'total_count' => count( $post_id_array['PRODUCT']), ); set_transient('alttextlab_enric_post_generate_' . $post_id, $post_id_array, 2048); if($is_ajax){ return wp_send_json_error($empty_content_response); }else{ return $empty_content_response; } } elseif ( $is_ajax ) { return wp_send_json_error( array( 'message' => __( 'Content is empty.', 'alttextlab' ), 'total_count' => 0, ) ); } return array( 'total_count' => 0 ); } $img_src_attr = get_option( 'alttextlab_refresh_src_attr', 'src' ); if ( version_compare( get_bloginfo( 'version' ), '6.2') >= 0 ) { $tags = new WP_HTML_Tag_Processor( $content ); while ( $tags->next_tag( 'img' ) ) { $img_url = $img_url_original = $tags->get_attribute( $img_src_attr ); if ( empty($img_url) ) { continue; } // If relative path, convert to full URL: if ( substr($img_url, 0, 1) == "/" ) { $img_url = $img_url_original = home_url() . $img_url; } // Remove the dimensions from the URL to get the URL of the original image, // only if the image is hosted on the same site if ( strpos( $img_url, home_url() ) === 0 ) { $img_url_original = preg_replace( '/-\d+x\d+(?=\.[a-zA-Z]{3,4}$)/', '', $img_url ); } // Prepend protocol if missing: if ( substr($img_url_original, 0, 2) == "//" ) { $img_url_original = "https:" . $img_url_original; } // Get the attachment ID from the image URL $attachment_id = $this->attachment_helper->lookup_attachment_id($img_url_original, $post_id); $attachment_id = $attachment_id ?? $this->attachment_helper->lookup_attachment_id($img_url_original); if ( $attachment_id ) { $alt_text = $this->get_attachment_alt_for_post_language( $attachment_id, $post_id ); // If the alt text is empty, generate it if ( $overwrite || empty( $alt_text ) ) { $post_id_array['POST'][] = ['type'=>'ID', 'value' => $attachment_id]; } } elseif ( $process_external ) { // Extract alt text from the image tag $alt_text = trim( $tags->get_attribute( 'alt' ) ) ?? ''; if ( $overwrite || empty( $alt_text ) ) { $post_id_array['POST'][] = ['type'=>'URL', 'value' => $img_url_original]; } } } } else { $src_regex = sprintf('/]*?>/i', $img_src_attr); $updated_content = preg_replace_callback( $src_regex, function( $matches ) use ( $overwrite, $process_external, $keywords, &$total_images_found, &$num_alttext_generated, &$no_credits, &$post_id_array, $attachment_helper) { $img_tag = $matches[0]; $img_url = $img_url_original = $matches[2]; // The src URL is captured in the second group. $total_images_found = $total_images_found + 1; // If relative path, convert to full URL: if ( isset($img_url) && substr($img_url, 0, 1) == "/" ) { $img_url = $img_url_original = home_url() . $img_url; } // Remove the dimensions from the URL to get the URL of the original image, // only if the image is hosted on the same site if ( strpos( $img_url, home_url() ) === 0 ) { $img_url_original = preg_replace( '/-\d+x\d+(?=\.[a-zA-Z]{3,4}$)/', '', $img_url ); } // Prepend protocol if missing: if ( substr($img_url_original, 0, 2) == "//" ) { $img_url_original = "https:" . $img_url_original; } // Get the attachment ID from the image URL $attachment_id = $attachment_helper->lookup_attachment_id($img_url_original, $post_id); $attachment_id = $attachment_id ?? $attachment_helper->lookup_attachment_id($img_url_original); $alt_text = false; if ( $attachment_id ) { $alt_text = $this->get_attachment_alt_for_post_language( $attachment_id, $post_id ); // If the alt text is empty, generate it if ( $overwrite || empty( $alt_text ) ) { $post_id_array['POST'][] = ['type'=>'ID', 'value' => $attachment_id]; } } elseif ( $process_external ) { // Extract alt text from the image tag preg_match('/alt="([^"]*)"/i', $img_tag, $matches); $alt_text = trim( $matches[1] ) ?? ''; if ( $overwrite || empty( $alt_text ) ) { $post_id_array['POST'][] = ['type'=>'URL', 'value' => $img_url_original]; } } // Check if generate_alt returned false or an error if ( empty( $alt_text ) || ! is_string( $alt_text ) ) { return $img_tag; } if ( false === strpos( $img_tag, ' alt=' ) ) { // If there's no alt attribute, add one return str_replace( '' . esc_attr( $alt_text ) . ' 'success', 'total_count' => count($post_id_array['POST'] ? : []) + count($post_id_array['PRODUCT']? : []), ]; if ($is_ajax) { wp_send_json_success($response); } else { return $response; } } /** * AJAX: return next batch of post IDs that contain images (for Sync Alt in Posts). * */ public function ajax_sync_posts_batch() { check_ajax_referer( 'alttextlab_sync_posts_batch', 'security' ); $last_post_id = isset( $_REQUEST['last_post_id'] ) ? absint( $_REQUEST['last_post_id'] ) : 0; $per_page = max( 1, min( 10, (int) ( $_REQUEST['posts_per_page'] ?? 2 ) ) ); $batch_id = isset( $_REQUEST['batch_id'] ) ? sanitize_text_field( $_REQUEST['batch_id'] ) : ''; $sync_selected = ! empty( $_REQUEST['sync_selected'] ); if ( $sync_selected && $batch_id !== '' ) { $selected_ids = get_transient( 'alttextlab_sync_alt_selected_posts_' . $batch_id ); if ( ! is_array( $selected_ids ) ) { $selected_ids = array(); } $selected_ids = array_values( array_filter( $selected_ids, function( $id ) use ( $last_post_id ) { return $id > $last_post_id; } ) ); sort( $selected_ids, SORT_NUMERIC ); $chunk = array_slice( $selected_ids, 0, $per_page ); $posts = array_map( function( $id ) { return array( 'id' => (int) $id ); }, $chunk ); $recursive = count( $chunk ) > 0; $new_last = $chunk ? (int) end( $chunk ) : $last_post_id; if ( count( $chunk ) === 0 ) { delete_transient( 'alttextlab_sync_alt_selected_posts_' . $batch_id ); delete_transient( 'alttextlab_sync_alt_redirect_' . $batch_id ); } wp_send_json( array( 'status' => 'success', 'posts' => $posts, 'recursive' => $recursive, 'last_post_id' => $new_last, ) ); } global $wpdb; $post_types = array( 'post', 'page' ); if ( AltTextLab_Utility::has_woocommerce() ) { $post_types[] = 'product'; } $post_types = apply_filters( 'alttextlab_sync_alt_post_types', $post_types ); $placeholders = implode( ',', array_fill( 0, count( $post_types ), '%s' ) ); $query = $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE ID > %d AND post_status IN ( 'publish', 'draft', 'private' ) AND post_type IN ($placeholders) AND post_content LIKE %s ORDER BY ID ASC LIMIT %d", array_merge( array( $last_post_id ), $post_types, array( '%get_col( $query ); $posts = array_map( function( $id ) { return array( 'id' => (int) $id ); }, $ids ); $recursive = count( $posts ) > 0; $new_last = $ids ? (int) end( $ids ) : $last_post_id; wp_send_json( array( 'status' => 'success', 'posts' => $posts, 'recursive' => $recursive, 'last_post_id' => $new_last, ) ); } /** * AJAX: process one post — find img tags, set alt from media library, update post. */ public function ajax_sync_posts_process_post() { check_ajax_referer( 'alttextlab_sync_posts_process', 'security' ); $post_id = isset( $_REQUEST['post_id'] ) ? absint( $_REQUEST['post_id'] ) : 0; if ( ! $post_id ) { wp_send_json( array( 'status' => 'error', 'message' => __( 'Invalid post ID', 'alttextlab' ), 'process_count' => 0, 'success_count' => 0, 'images_updated' => 0 ) ); } $post = get_post( $post_id ); if ( ! $post || empty( $post->post_content ) ) { wp_send_json( array( 'status' => 'success', 'process_count' => 1, 'success_count' => 0, 'images_updated' => 0 ) ); } $only_missing = ! empty( $_REQUEST['only_missing'] ); $images_updated = 0; $updated_content = $this->sync_alt_in_post_content( $post->post_content, $post_id, $only_missing, $images_updated ); $content_changed = ( $updated_content !== $post->post_content ); if ( $content_changed ) { wp_update_post( array( 'ID' => $post_id, 'post_content' => $updated_content, ) ); } $success_count = ( $content_changed || $images_updated > 0 ) ? 1 : 0; wp_send_json( array( 'status' => 'success', 'process_count' => 1, 'success_count' => $success_count, 'images_updated' => $images_updated ) ); } /** * In post content, for each img: resolve attachment (by wp-image-ID class or URL), get alt from media library, set/replace alt. No API calls. * */ private function sync_alt_in_post_content( $content, $post_id, $only_missing = false, &$images_updated = 0 ) { $img_src_attr = get_option( 'alttextlab_refresh_src_attr', 'src' ); if ( version_compare( get_bloginfo( 'version' ), '6.2', '>=' ) && class_exists( 'WP_HTML_Tag_Processor' ) ) { return $this->sync_alt_in_post_content_tag_processor( $content, $post_id, $img_src_attr, $only_missing, $images_updated ); } return $this->sync_alt_in_post_content_regex( $content, $post_id, $img_src_attr, $only_missing, $images_updated ); } private function sync_alt_in_post_content_tag_processor( $content, $post_id, $img_src_attr, $only_missing = false, &$images_updated = 0 ) { $tags = new WP_HTML_Tag_Processor( $content ); while ( $tags->next_tag( 'img' ) ) { if ( $only_missing ) { $current_alt = $tags->get_attribute( 'alt' ); if ( $current_alt !== null && trim( (string) $current_alt ) !== '' ) { continue; } } $alt_text = $this->get_alt_for_img_in_post( $tags, $post_id, $img_src_attr ); if ( $alt_text !== null && $alt_text !== '' ) { $tags->set_attribute( 'alt', $alt_text ); ++$images_updated; } } return $tags->get_updated_html(); } /** * Returns the WPML language code for a post. */ private function get_wpml_post_language( $post_id ) { if ( ! AltTextLab_Utility::is_wpml_active() || ! $post_id ) { return null; } $language_details = apply_filters( 'wpml_post_language_details', null, $post_id ); if ( ! is_array( $language_details ) || empty( $language_details['language_code'] ) ) { return null; } return (string) $language_details['language_code']; } /** * Returns the attachment matching the post language. */ private function get_attachment_for_post_language( $attachment_id, $post_id ) { $attachment_id = (int) $attachment_id; if ( ! $attachment_id || get_post_type( $attachment_id ) !== 'attachment' ) { return 0; } if ( ! AltTextLab_Utility::is_wpml_active() ) { return $attachment_id; } $post_lang = $this->get_wpml_post_language( $post_id ); if ( ! $post_lang ) { return $attachment_id; } $translated_id = apply_filters( 'wpml_object_id', $attachment_id, 'attachment', false, $post_lang ); if ( $translated_id && (int) $translated_id > 0 && get_post_type( $translated_id ) === 'attachment' && get_post_status( $translated_id ) !== 'trash' ) { return (int) $translated_id; } return $attachment_id; } /** * Returns the attachment alt text for the post language. */ private function get_attachment_alt_for_post_language( $attachment_id, $post_id ) { $localized_attachment_id = $this->get_attachment_for_post_language( $attachment_id, $post_id ); if ( ! $localized_attachment_id ) { return null; } $alt = get_post_meta( $localized_attachment_id, '_wp_attachment_image_alt', true ); if ( is_string( $alt ) && $alt !== '' ) { return $alt; } if ( (int) $localized_attachment_id !== (int) $attachment_id ) { $fallback_alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); if ( is_string( $fallback_alt ) && $fallback_alt !== '' ) { return $fallback_alt; } } return null; } private function get_alt_for_img_in_post( $tags, $post_id, $img_src_attr ) { $class = $tags->get_attribute( 'class' ); if ( $class && preg_match( '/\bwp-image-(\d+)\b/', $class, $m ) ) { $attachment_id = (int) $m[1]; if ( get_post_type( $attachment_id ) === 'attachment' ) { $alt = $this->get_attachment_alt_for_post_language( $attachment_id, $post_id ); if ( is_string( $alt ) && $alt !== '' ) { return $alt; } } } $img_url = $tags->get_attribute( $img_src_attr ); if ( empty( $img_url ) ) { return null; } if ( substr( $img_url, 0, 1 ) === '/' ) { $img_url = home_url() . $img_url; } if ( strpos( $img_url, home_url() ) === 0 ) { $img_url = preg_replace( '/-\d+x\d+(?=\.[a-zA-Z]{3,4}$)/', '', $img_url ); } if ( substr( $img_url, 0, 2 ) === '//' ) { $img_url = 'https:' . $img_url; } $attachment_id = $this->attachment_helper->lookup_attachment_id( $img_url, $post_id ) ?: $this->attachment_helper->lookup_attachment_id( $img_url ); if ( ! $attachment_id ) { return null; } $alt = $this->get_attachment_alt_for_post_language( $attachment_id, $post_id ); return ( is_string( $alt ) && $alt !== '' ) ? $alt : null; } private function sync_alt_in_post_content_regex( $content, $post_id, $img_src_attr, $only_missing = false, &$images_updated = 0 ) { $attachment_helper = $this->attachment_helper; $regex = sprintf( '/]*)(%s="([^"]*)")([^>]*)>/i', preg_quote( $img_src_attr, '/' ) ); return preg_replace_callback( $regex, function ( $matches ) use ( $attachment_helper, $post_id, $img_src_attr, $only_missing, &$images_updated ) { $before_src = $matches[1]; $img_url = $matches[3]; $after_src = $matches[4]; $full_tag = $matches[0]; if ( $only_missing && preg_match( '/\balt\s*=\s*["\']([^"\']*)["\']/i', $full_tag, $alt_m ) && trim( $alt_m[1] ) !== '' ) { return $full_tag; } if ( substr( $img_url, 0, 1 ) === '/' ) { $img_url = home_url() . $img_url; } if ( strpos( $img_url, home_url() ) === 0 ) { $img_url = preg_replace( '/-\d+x\d+(?=\.[a-zA-Z]{3,4}$)/', '', $img_url ); } if ( substr( $img_url, 0, 2 ) === '//' ) { $img_url = 'https:' . $img_url; } $attachment_id = null; if ( preg_match( '/\bwp-image-(\d+)\b/', $before_src . $after_src, $m ) ) { $aid = (int) $m[1]; if ( get_post_type( $aid ) === 'attachment' ) { $attachment_id = $aid; } } if ( ! $attachment_id ) { $attachment_id = $attachment_helper->lookup_attachment_id( $img_url, $post_id ) ?: $attachment_helper->lookup_attachment_id( $img_url ); } if ( ! $attachment_id ) { return $full_tag; } $alt = $this->get_attachment_alt_for_post_language( $attachment_id, $post_id ); if ( ! is_string( $alt ) || $alt === '' ) { return $full_tag; } ++$images_updated; $esc_alt = esc_attr( $alt ); if ( preg_match( '/\balt\s*=\s*["\'][^"\']*["\']/i', $full_tag ) ) { return preg_replace( '/\balt\s*=\s*["\'][^"\']*["\']/i', 'alt="' . $esc_alt . '"', $full_tag ); } return str_replace( '' . $esc_alt . 'get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status IN ( 'publish', 'draft', 'private' ) AND post_type IN ($placeholders) AND post_content LIKE %s", array_merge( $post_types, array( '%get_attachment_alt_for_post_language( $attachment_id, $post_id ); // If the alt text is empty or need to overwrite, generate it: if ( $overwrite || empty( $alt_text ) ) { $product_id_array[] = $attachment_id; } } return $product_id_array; } /** * Handles AJAX request for bulk alt text generation * */ public function post_bulk_generate_alt_text_ajax() { check_ajax_referer('alttextlab_security_enrich_post_generate', 'security'); $post_id = intval($_REQUEST['post_id'] ?? 0); $keywords = ( isset( $_REQUEST['keywords'] ) && is_array( $_REQUEST['keywords'] ) ) ? array_map( 'sanitize_text_field', $_REQUEST['keywords'] ) : []; $recursive = true; $images_successful_current = 0; $images_successful_general = intval($_REQUEST['count_successful_images'] ?? 0); $images_total = intval($_REQUEST['count_total_images'] ?? 0); $images_to_update_transient = get_transient('alttextlab_enric_post_generate_' . $post_id); if (empty($images_to_update_transient) || count($images_to_update_transient) == 0) { $success_msg = sprintf( __('[AltTextLab] No images to process.', 'alttextlab' ) ); set_transient( 'alttextlab_enrich_post_content_success', $success_msg, 60 ); $recursive = false; wp_send_json(array( 'status' => 'success', 'message' => __('No images to process.', 'alttextlab'), 'success_count' => $images_successful_current, 'recursive' => $recursive, )); } if (!empty($images_to_update_transient['PRODUCT'])) { $attachment_id = $images_to_update_transient['PRODUCT'][0]; $product_data = AltTextLab_Utility::has_woocommerce() ? $this->wc_integration->get_product_info($attachment_id, $post_id) : []; $response = $this->alttextlab_attachment->generate_alt( $attachment_id, null, array( 'keywords' => $keywords, 'explicit_post_id' => $post_id, 'ecommerce' => $product_data ) ); unset($images_to_update_transient['PRODUCT'][0]); $images_to_update_transient['PRODUCT'] = array_values($images_to_update_transient['PRODUCT']); } elseif (!empty($images_to_update_transient['POST'])) { $attachment_type = $images_to_update_transient['POST'][0]['type']; $attachment_value = $images_to_update_transient['POST'][0]['value']; if ($attachment_type === 'ID'){ $response = $this->alttextlab_attachment->generate_alt($attachment_value, null , array( 'keywords' => $keywords, 'explicit_post_id' => $post_id ) ); }else{ $response = $this->alttextlab_attachment->generate_alt(null, $attachment_value, array( 'keywords' => $keywords ) ); } unset($images_to_update_transient['POST'][0]); $images_to_update_transient['POST'] = array_values($images_to_update_transient['POST']); } if ($response === self::ERROR_NO_CREDITS) { $success_msg = sprintf( __(self::MSG_NO_CREDITS, 'alttextlab' ) ); set_transient( 'alttextlab_enrich_post_content_success', $success_msg, 60 ); $recursive = false; } else if ($response === self::ERROR_INVALID_API_KEY) { $success_msg = sprintf( __(self::MSG_INVALID_API_KEY, 'alttextlab' ) ); set_transient( 'alttextlab_enrich_post_content_success', $success_msg, 60 ); $recursive = false; } else if ($response === self::ERROR_MONTHLY_LIMIT) { $success_msg = sprintf( __( self::MSG_MONTHLY_LIMIT, 'alttextlab' ) ); set_transient( 'alttextlab_enrich_post_content_success', $success_msg, 60 ); $recursive = false; } else if (!is_array($response) && $response !== false) { $images_successful_current++; } // Check if all items are processed $all_empty = true; foreach ($images_to_update_transient as $type => $items) { if (!empty($items)) { $all_empty = false; break; } } if ($all_empty) { delete_transient('alttextlab_enric_post_generate_' . $post_id); delete_transient('alttextlab_enric_post_generate__redirect_' . $post_id); $recursive = false; $images_successful_general += $images_successful_current; /* translators: 1: Total images processed count, 2: New alt texts generated count */ $success_msg = sprintf( __(self::MSG_SUCCESS_TEMPLATE, 'alttextlab'), $images_total, $images_successful_general ); set_transient( 'alttextlab_enrich_post_content_success', $success_msg, 60 ); } else { set_transient('alttextlab_enric_post_generate_' . $post_id, $images_to_update_transient, 2048); } wp_send_json(array( 'status' => 'success', 'message' => __('Images successfully updated.', 'alttextlab'), 'success_count' => $images_successful_current, 'recursive' => $recursive )); } /** * Generates a localized success/error message based on image processing results * */ private function return_success_message($api_key_is_invalid, $no_credits, $monthly_limit, $total_images_found, $num_alttext_generated) { // Set a transient to show a success notice after page reload if ( $api_key_is_invalid ){ $success_msg = sprintf( __(self::MSG_INVALID_API_KEY, 'alttextlab' ) ); } elseif ( $no_credits ) { $success_msg = sprintf( __(self::MSG_NO_CREDITS, 'alttextlab' ) ); } elseif ( $monthly_limit ) { $success_msg = sprintf( __( self::MSG_MONTHLY_LIMIT, 'alttextlab' ) ); } else { /* translators: 1: Total number of images processed, 2: Number of new alt texts generated */ $success_msg = sprintf( __(self::MSG_SUCCESS_TEMPLATE, 'alttextlab'), $total_images_found, $num_alttext_generated ); } return $success_msg; } }