PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.83
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.83
51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 All 40 releases
← All changes | includes/extensions/alt-text-generator/Alt_Text_Generator.php +92 -28 51.1.39 → 51.1.83 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;