← All changes
|
includes/extensions/alt-text-generator/Alt_Text_Generator.php
+92
-28
51.1.36
→
51.1.86
View file →
| @@ -39,9 +39,9 @@ | ||
| 39 | 39 | if (!$button_enabled && !$auto_enabled) { |
| 40 | 40 | return; // Don't initialize if both features are disabled |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | - $has_api_key = !empty($options['openai_api_key']); | |
| 43 | + $has_api_key = '' !== \King_Addons\AI_Provider::getApiKey(); | |
| 44 | 44 | |
| 45 | 45 | // Hook for new attachments (only if auto generation is enabled and API key exists). |
| 46 | 46 | if ($auto_enabled && $has_api_key) { |
| 47 | 47 | if(king_addons_freemius()->can_use_premium_code()){ |
| @@ -107,9 +107,9 @@ | ||
| 107 | 107 | ); |
| 108 | 108 | |
| 109 | 109 | // Check if API key exists |
| 110 | 110 | $options = get_option('king_addons_ai_options', []); |
| 111 | - $has_api_key = !empty($options['openai_api_key']); | |
| 111 | + $has_api_key = '' !== \King_Addons\AI_Provider::getApiKey(); | |
| 112 | 112 | |
| 113 | 113 | // Pass data to JavaScript. |
| 114 | 114 | wp_localize_script('king-addons-media-alt-text', 'kingAddonsMediaAltText', array( |
| 115 | 115 | 'ajax_url' => admin_url('admin-ajax.php'), |
| @@ -126,9 +126,10 @@ | ||
| 126 | 126 | * |
| 127 | 127 | * @param array $columns Existing columns. |
| 128 | 128 | * @return array Modified columns. |
| 129 | 129 | */ |
| 130 | - public function add_alt_text_column(array $columns): array { | |
| 130 | + public function add_alt_text_column($columns): array { | |
| 131 | + $columns = is_array($columns) ? $columns : array(); | |
| 131 | 132 | // Add column before 'Date'. |
| 132 | 133 | $new_columns = array(); |
| 133 | 134 | foreach ($columns as $key => $title) { |
| 134 | 135 | if ('date' === $key) { |
| @@ -161,9 +162,9 @@ | ||
| 161 | 162 | } |
| 162 | 163 | |
| 163 | 164 | $alt_text = get_post_meta($attachment_id, '_wp_attachment_image_alt', true); |
| 164 | 165 | $options = get_option('king_addons_ai_options', []); |
| 165 | - $has_api_key = !empty($options['openai_api_key']); | |
| 166 | + $has_api_key = '' !== \King_Addons\AI_Provider::getApiKey(); | |
| 166 | 167 | |
| 167 | 168 | echo '<div class="king-addons-alt-text-status" data-attachment-id="' . esc_attr($attachment_id) . '">'; |
| 168 | 169 | if (!empty($alt_text)) { |
| 169 | 170 | echo '<span>' . esc_html($alt_text) . '</span>'; |
| @@ -200,9 +201,9 @@ | ||
| 200 | 201 | } |
| 201 | 202 | |
| 202 | 203 | // Check if API key exists |
| 203 | 204 | $options = get_option('king_addons_ai_options', []); |
| 204 | - if (empty($options['openai_api_key'])) { | |
| 205 | + if ('' === \King_Addons\AI_Provider::getApiKey()) { | |
| 205 | 206 | wp_send_json_error(array( |
| 206 | 207 | 'message' => esc_html__('OpenAI API key is not configured. Please set it in the AI settings.', 'king-addons'), |
| 207 | 208 | 'needs_setup' => true |
| 208 | 209 | ), 400); |
| @@ -247,9 +248,9 @@ | ||
| 247 | 248 | |
| 248 | 249 | /** |
| 249 | 250 | * Adds custom cron intervals. |
| 250 | 251 | * |
| 251 | - * Note: Recommended interval is 60+ seconds to avoid OpenAI API rate limits. | |
| 252 | + * Note: Recommended interval is 60+ seconds to avoid provider API rate limits. | |
| 252 | 253 | * Lower intervals may cause API errors during high usage periods. |
| 253 | 254 | * |
| 254 | 255 | * @param array $schedules Existing cron schedules. |
| 255 | 256 | * @return array Modified schedules. |
| @@ -254,11 +255,11 @@ | ||
| 254 | 255 | * @param array $schedules Existing cron schedules. |
| 255 | 256 | * @return array Modified schedules. |
| 256 | 257 | */ |
| 257 | 258 | public function add_custom_cron_intervals(array $schedules): array { |
| 258 | - // Get the interval from settings, default to 60 seconds | |
| 259 | + // Get the interval from settings, default to 20 seconds | |
| 259 | 260 | $options = get_option('king_addons_ai_options', []); |
| 260 | - $interval = isset($options['ai_alt_text_generation_interval']) ? (int) $options['ai_alt_text_generation_interval'] : 60; | |
| 261 | + $interval = isset($options['ai_alt_text_generation_interval']) ? (int) $options['ai_alt_text_generation_interval'] : 20; | |
| 261 | 262 | |
| 262 | 263 | // Ensure interval is within acceptable range |
| 263 | 264 | $interval = max(10, min(3600, $interval)); |
| 264 | 265 | |
| @@ -398,18 +399,22 @@ | ||
| 398 | 399 | // For now, we assume the button won't be shown if alt exists, so this path is mainly for the cron job. |
| 399 | 400 | return $is_ajax ? $existing_alt : true; |
| 400 | 401 | } |
| 401 | 402 | |
| 402 | - // Retrieve OpenAI API key and settings from King Addons AI options. | |
| 403 | + // Retrieve the API key and settings of the selected AI provider. | |
| 403 | 404 | $options = get_option('king_addons_ai_options', []); |
| 404 | - $api_key = $options['openai_api_key'] ?? ''; | |
| 405 | - // Use the text model for vision analysis, not the image generation model | |
| 406 | - $model = $options['openai_model'] ?? 'gpt-4o'; | |
| 405 | + $api_key = \King_Addons\AI_Provider::getApiKey(); | |
| 406 | + // Use dedicated vision model for image analysis (fallback to text model). | |
| 407 | + $model = \King_Addons\AI_Provider::getVisionModel(); | |
| 407 | 408 | // Get image detail level from settings (default to 'low') |
| 408 | 409 | $image_detail_level = $options['ai_alt_text_image_detail_level'] ?? 'low'; |
| 409 | 410 | |
| 410 | 411 | if (empty($api_key)) { |
| 411 | - $error_msg = esc_html__('OpenAI API key is missing.', 'king-addons'); | |
| 412 | + $error_msg = sprintf( | |
| 413 | + /* translators: %s: provider name */ | |
| 414 | + esc_html__('%s API key is missing.', 'king-addons'), | |
| 415 | + \King_Addons\AI_Provider::getLabel() | |
| 416 | + ); | |
| 412 | 417 | return $is_ajax ? new \WP_Error('missing_api_key', $error_msg) : $error_msg; |
| 413 | 418 | } |
| 414 | 419 | |
| 415 | 420 | // Get image path instead of URL. |
| @@ -439,12 +444,21 @@ | ||
| 439 | 444 | |
| 440 | 445 | // Create the data URI. |
| 441 | 446 | $image_data_uri = "data:{$mime_type};base64,{$base64_image}"; |
| 442 | 447 | |
| 443 | - // --- OpenAI API Call --- // | |
| 444 | - $api_endpoint = 'https://api.openai.com/v1/chat/completions'; | |
| 448 | + // --- AI provider API call --- // | |
| 449 | + $api_endpoint = \King_Addons\AI_Provider::getChatEndpoint(); | |
| 445 | 450 | |
| 451 | + $options_for_lang = get_option('king_addons_ai_options', []); | |
| 452 | + $custom_lang_enabled = !empty($options_for_lang['content_language_custom_enable']); | |
| 453 | + $custom_lang = trim($options_for_lang['content_language_custom'] ?? ''); | |
| 454 | + | |
| 446 | 455 | $prompt_text = 'Generate a concise, descriptive alt text for this image, suitable for SEO and accessibility. Focus on the main subject and action. Maximum 125 characters.'; |
| 456 | + if ($custom_lang_enabled && $custom_lang !== '') { | |
| 457 | + $prompt_text .= ' Use language: ' . $custom_lang; | |
| 458 | + } else { | |
| 459 | + $prompt_text .= ' Respond in English only.'; | |
| 460 | + } | |
| 447 | 461 | |
| 448 | 462 | $payload = array( |
| 449 | 463 | 'model' => $model, |
| 450 | 464 | 'messages' => array( |
| @@ -468,13 +482,10 @@ | ||
| 468 | 482 | 'max_tokens' => 50 // Limit the response length |
| 469 | 483 | ); |
| 470 | 484 | |
| 471 | 485 | $args = array( |
| 472 | - 'headers' => array( | |
| 473 | - 'Authorization' => 'Bearer ' . $api_key, | |
| 474 | - 'Content-Type' => 'application/json', | |
| 475 | - ), | |
| 476 | - 'body' => wp_json_encode($payload), | |
| 486 | + 'headers' => \King_Addons\AI_Provider::getHeaders(), | |
| 487 | + 'body' => wp_json_encode(\King_Addons\AI_Provider::prepareChatPayload($payload)), | |
| 477 | 488 | 'timeout' => 60, // Increased timeout |
| 478 | 489 | 'method' => 'POST', |
| 479 | 490 | 'data_format' => 'body', |
| 480 | 491 | ); |
| @@ -491,15 +502,18 @@ | ||
| 491 | 502 | $response_code = wp_remote_retrieve_response_code($response); |
| 492 | 503 | $response_body = wp_remote_retrieve_body($response); |
| 493 | 504 | $decoded_body = json_decode($response_body, true); |
| 494 | 505 | |
| 495 | - if ($response_code !== 200 || !isset($decoded_body['choices'][0]['message']['content'])) { | |
| 496 | - $api_error_message = isset($decoded_body['error']['message']) ? $decoded_body['error']['message'] : esc_html__('API request failed or returned unexpected data.', 'king-addons'); | |
| 506 | + if ($response_code !== 200 || isset($decoded_body['error'])) { | |
| 507 | + $api_error_message = \King_Addons\AI_Provider::extractErrorMessage($decoded_body, esc_html__('API request failed or returned unexpected data.', 'king-addons')); | |
| 497 | 508 | /* translators: 1: HTTP response code, 2: API error message. */ |
| 498 | 509 | return $is_ajax ? new \WP_Error('api_error', $api_error_message, array('status' => $response_code)) : sprintf(esc_html__('API error (%1$d): %2$s', 'king-addons'), $response_code, $api_error_message); |
| 499 | 510 | } |
| 500 | 511 | |
| 501 | - $generated_alt_text = $decoded_body['choices'][0]['message']['content']; | |
| 512 | + $generated_alt_text = \King_Addons\AI_Provider::extractMessageContent($decoded_body); | |
| 513 | + if (is_wp_error($generated_alt_text)) { | |
| 514 | + return $is_ajax ? $generated_alt_text : $generated_alt_text->get_error_message(); | |
| 515 | + } | |
| 502 | 516 | // --- End API Call --- // |
| 503 | 517 | |
| 504 | 518 | // If the response starts with "I'm sorry" (case-insensitive, allow whitespace before), treat as error and do not save |
| 505 | 519 | if (preg_match('/^\s*I\'m sorry/i', $generated_alt_text)) { |
| @@ -514,18 +528,18 @@ | ||
| 514 | 528 | // Update the image alt text meta data. |
| 515 | 529 | if (!empty($sanitized_alt_text)) { |
| 516 | 530 | if (update_post_meta($attachment_id, '_wp_attachment_image_alt', $sanitized_alt_text)) { |
| 517 | 531 | // Log success for debugging |
| 518 | - error_log("King Addons Alt Text: Successfully generated alt text for attachment {$attachment_id}: {$sanitized_alt_text}"); | |
| 532 | + // error_log("King Addons Alt Text: Successfully generated alt text for attachment {$attachment_id}: {$sanitized_alt_text}"); | |
| 519 | 533 | return $is_ajax ? $sanitized_alt_text : true; |
| 520 | 534 | } else { |
| 521 | 535 | $error_msg = esc_html__('Failed to save the generated alt text.', 'king-addons'); |
| 522 | - error_log("King Addons Alt Text: Failed to save alt text for attachment {$attachment_id}"); | |
| 536 | + // error_log("King Addons Alt Text: Failed to save alt text for attachment {$attachment_id}"); | |
| 523 | 537 | return $is_ajax ? new \WP_Error('update_failed', $error_msg) : $error_msg; |
| 524 | 538 | } |
| 525 | 539 | } else { |
| 526 | 540 | $error_msg = esc_html__('AI returned empty or invalid text.', 'king-addons'); |
| 527 | - error_log("King Addons Alt Text: AI returned empty text for attachment {$attachment_id}"); | |
| 541 | + // error_log("King Addons Alt Text: AI returned empty text for attachment {$attachment_id}"); | |
| 528 | 542 | return $is_ajax ? new \WP_Error('empty_alt_text', $error_msg) : $error_msg; |
| 529 | 543 | } |
| 530 | 544 | |
| 531 | 545 | // Should not be reached in normal flow due to checks above |
| @@ -533,8 +547,58 @@ | ||
| 533 | 547 | return $is_ajax ? new \WP_Error('unknown_error', $error_msg) : $error_msg; |
| 534 | 548 | } |
| 535 | 549 | |
| 536 | 550 | /** |
| 551 | + * Retrieves statistics about image alt text in the Media Library. | |
| 552 | + * | |
| 553 | + * @return array{total:int,with_alt:int,without_alt:int} | |
| 554 | + */ | |
| 555 | + public static function get_alt_text_stats(): array | |
| 556 | + { | |
| 557 | + $cache_key = 'king_addons_alt_text_stats'; | |
| 558 | + $cached = wp_cache_get($cache_key, 'king-addons'); | |
| 559 | + if (false !== $cached && is_array($cached)) { | |
| 560 | + return $cached; | |
| 561 | + } | |
| 562 | + | |
| 563 | + $all_images = get_posts([ | |
| 564 | + 'post_type' => 'attachment', | |
| 565 | + 'post_mime_type' => 'image', | |
| 566 | + 'post_status' => 'inherit', | |
| 567 | + 'posts_per_page' => -1, | |
| 568 | + 'fields' => 'ids', | |
| 569 | + 'no_found_rows' => true, | |
| 570 | + ]); | |
| 571 | + | |
| 572 | + $with_alt = get_posts([ | |
| 573 | + 'post_type' => 'attachment', | |
| 574 | + 'post_mime_type' => 'image', | |
| 575 | + 'post_status' => 'inherit', | |
| 576 | + 'posts_per_page' => -1, | |
| 577 | + 'fields' => 'ids', | |
| 578 | + 'no_found_rows' => true, | |
| 579 | + 'meta_query' => [ | |
| 580 | + [ | |
| 581 | + 'key' => '_wp_attachment_image_alt', | |
| 582 | + 'value' => '', | |
| 583 | + 'compare' => '!=', | |
| 584 | + ], | |
| 585 | + ], | |
| 586 | + ]); | |
| 587 | + | |
| 588 | + $total = count($all_images); | |
| 589 | + $with_alt_count = count($with_alt); | |
| 590 | + $result = [ | |
| 591 | + 'total' => $total, | |
| 592 | + 'with_alt' => $with_alt_count, | |
| 593 | + 'without_alt' => max(0, $total - $with_alt_count), | |
| 594 | + ]; | |
| 595 | + | |
| 596 | + wp_cache_set($cache_key, $result, 'king-addons', 300); | |
| 597 | + return $result; | |
| 598 | + } | |
| 599 | + | |
| 600 | + /** | |
| 537 | 601 | * Cleans up cron jobs and options on plugin deactivation. |
| 538 | 602 | */ |
| 539 | 603 | public function cleanup_cron_jobs(): void { |
| 540 | 604 | // Clear scheduled cron jobs |
| @@ -565,10 +629,10 @@ | ||
| 565 | 629 | */ |
| 566 | 630 | public function update_cron_schedule($old_value, $new_value, string $option = 'king_addons_ai_options'): void { |
| 567 | 631 | |
| 568 | 632 | // Check if interval setting changed |
| 569 | - $old_interval = isset($old_value['ai_alt_text_generation_interval']) ? (int) $old_value['ai_alt_text_generation_interval'] : 60; | |
| 570 | - $new_interval = isset($new_value['ai_alt_text_generation_interval']) ? (int) $new_value['ai_alt_text_generation_interval'] : 60; | |
| 633 | + $old_interval = isset($old_value['ai_alt_text_generation_interval']) ? (int) $old_value['ai_alt_text_generation_interval'] : 20; | |
| 634 | + $new_interval = isset($new_value['ai_alt_text_generation_interval']) ? (int) $new_value['ai_alt_text_generation_interval'] : 20; | |
| 571 | 635 | |
| 572 | 636 | // Check if auto generation setting changed |
| 573 | 637 | $old_auto = isset($old_value['enable_ai_alt_text_auto_generation']) ? (bool) $old_value['enable_ai_alt_text_auto_generation'] : false; |
| 574 | 638 | $new_auto = isset($new_value['enable_ai_alt_text_auto_generation']) ? (bool) $new_value['enable_ai_alt_text_auto_generation'] : false; |