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.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 51.1.46 All 39 releases
← All changes | includes/extensions/AI_SEO_Tools/Post_Generator_Module.php +91 -58 51.1.7651.1.83 View file →
@@ -2,9 +2,9 @@
2 2 /**
3 3 * Post Generator module for AI SEO Tools.
4 4 *
5 5 * Generates full blog posts (title, HTML content, excerpt, tags)
6 - * via OpenAI and optionally generates a matching featured image.
6 + * via the configured AI provider and optionally generates a matching featured image.
7 7 *
8 8 * @package King_Addons
9 9 */
10 10
@@ -38,9 +38,9 @@
38 38 wp_send_json_error(['message' => esc_html__('Permission denied.', 'king-addons')], 403);
39 39 }
40 40
41 41 $ka_ai_opts = get_option('king_addons_ai_options', []);
42 - if (empty($ka_ai_opts['openai_api_key'])) {
42 + if ('' === \King_Addons\AI_Provider::getApiKey()) {
43 43 wp_send_json_error(['message' => esc_html__('OpenAI API key is not set. Please add it in AI Settings.', 'king-addons'), 'code' => 'no_api_key'], 400);
44 44 }
45 45
46 46 $description = sanitize_textarea_field(wp_unslash($_POST['description'] ?? ''));
@@ -53,11 +53,13 @@
53 53 : 'medium';
54 54 $category_raw = sanitize_text_field(wp_unslash($_POST['category_id'] ?? 'auto'));
55 55 $category_id = ($category_raw === 'auto' || $category_raw === '0') ? $category_raw : (string) absint($category_raw);
56 56 $gen_image = !empty($_POST['generate_image']) && king_addons_freemius()->can_use_premium_code();
57 - $image_model = in_array($_POST['image_model'] ?? 'dall-e-3', ['dall-e-3', 'gpt-image-1'], true)
58 - ? sanitize_key(wp_unslash($_POST['image_model']))
59 - : 'dall-e-3';
57 + $posted_model = sanitize_text_field(wp_unslash($_POST['image_model'] ?? ''));
58 + $allowed_models = wp_list_pluck(\King_Addons\AI_Provider::getModelsFor('image'), 'id');
59 + $image_model = in_array($posted_model, $allowed_models, true)
60 + ? $posted_model
61 + : \King_Addons\AI_Provider::getImageModel();
60 62 $image_quality = sanitize_key(wp_unslash($_POST['image_quality'] ?? 'standard'));
61 63 $image_size = sanitize_key(wp_unslash($_POST['image_size'] ?? '1024x1024'));
62 64
63 65 if ($description === '') {
@@ -173,9 +175,9 @@
173 175 ),
174 176 ];
175 177 update_option(self::BULK_OPTION_PROGRESS, $progress, false);
176 178
177 - // 1. Generate content via OpenAI.
179 + // 1. Generate content via the configured AI provider.
178 180 $post_data = $this->generate_post_content(
179 181 (string) ($settings['description'] ?? ''),
180 182 $idx,
181 183 (string) ($settings['length'] ?? 'medium'),
@@ -231,9 +233,9 @@
231 233 if (!empty($settings['generate_image'])) {
232 234 $attach_id = $this->generate_and_attach_image(
233 235 $post_data['title'],
234 236 (string) ($settings['description'] ?? ''),
235 - (string) ($settings['image_model'] ?? 'dall-e-3'),
237 + (string) ($settings['image_model'] ?: \King_Addons\AI_Provider::getImageModel()),
236 238 (string) ($settings['image_quality'] ?? 'standard'),
237 239 (string) ($settings['image_size'] ?? '1024x1024'),
238 240 $post_id
239 241 );
@@ -273,9 +275,9 @@
273 275 delete_transient(self::BULK_OPTION_LOCK);
274 276 }
275 277
276 278 /**
277 - * Call OpenAI Chat Completions to generate post fields as JSON.
279 + * Call the provider's Chat Completions endpoint to generate post fields as JSON.
278 280 *
279 281 * @param string $description User-supplied topic/description.
280 282 * @param int $post_num Index within the batch (for uniqueness).
281 283 * @return array|\WP_Error Associative array with keys: title, content, excerpt, tags.
@@ -280,9 +282,9 @@
280 282 * @param int $post_num Index within the batch (for uniqueness).
281 283 * @return array|\WP_Error Associative array with keys: title, content, excerpt, tags.
282 284 */
283 285 /**
284 - * Build the OpenAI prompt string (same logic as generate_post_content) without making an API call.
286 + * Build the prompt string (same logic as generate_post_content) without making an API call.
285 287 * Used to expose the current prompt in the status response.
286 288 */
287 289 private function build_prompt_preview(string $description, int $post_num, string $length = 'medium', string $category_mode = 'auto'): string
288 290 {
@@ -313,13 +315,17 @@
313 315
314 316 private function generate_post_content(string $description, int $post_num, string $length = 'medium', string $category_mode = 'auto')
315 317 {
316 318 $options = get_option('king_addons_ai_options', []);
317 - $api_key = $options['openai_api_key'] ?? '';
318 - $model = $options['openai_model'] ?? 'gpt-4o-mini';
319 + $api_key = \King_Addons\AI_Provider::getApiKey();
320 + $model = \King_Addons\AI_Provider::getTextModel();
319 321
320 322 if ($api_key === '') {
321 - return new \WP_Error('missing_api_key', esc_html__('OpenAI API key is missing.', 'king-addons'));
323 + return new \WP_Error('missing_api_key', sprintf(
324 + /* translators: %s: provider name */
325 + esc_html__('%s API key is missing.', 'king-addons'),
326 + \King_Addons\AI_Provider::getLabel()
327 + ));
322 328 }
323 329
324 330 $word_targets = ['short' => 300, 'medium' => 600, 'long' => 1200];
325 331 $word_count = $word_targets[$length] ?? 600;
@@ -347,18 +353,15 @@
347 353
348 354 $max_tokens_map = ['short' => 700, 'medium' => 1200, 'long' => 2400];
349 355 $max_tokens = $max_tokens_map[$length] ?? 1200;
350 356
351 - $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
352 - 'headers' => [
353 - 'Authorization' => 'Bearer ' . $api_key,
354 - 'Content-Type' => 'application/json',
355 - ],
356 - 'body' => wp_json_encode([
357 + $response = wp_remote_post(\King_Addons\AI_Provider::getChatEndpoint(), [
358 + 'headers' => \King_Addons\AI_Provider::getHeaders(),
359 + 'body' => wp_json_encode(\King_Addons\AI_Provider::prepareChatPayload([
357 360 'model' => $model,
358 361 'messages' => [['role' => 'user', 'content' => $prompt]],
359 362 'max_tokens' => $max_tokens,
360 - ]),
363 + ])),
361 364 'timeout' => 120,
362 365 'data_format' => 'body',
363 366 ]);
364 367
@@ -369,9 +372,9 @@
369 372 $code = wp_remote_retrieve_response_code($response);
370 373 $body = json_decode(wp_remote_retrieve_body($response), true);
371 374
372 375 if ($code !== 200 || empty($body['choices'][0]['message']['content'])) {
373 - $api_error = $body['error']['message'] ?? esc_html__('API request failed.', 'king-addons');
376 + $api_error = \King_Addons\AI_Provider::extractErrorMessage($body, esc_html__('API request failed.', 'king-addons'));
374 377 return new \WP_Error('api_error', $api_error);
375 378 }
376 379
377 380 $raw = trim((string) $body['choices'][0]['message']['content']);
@@ -396,13 +399,13 @@
396 399 ];
397 400 }
398 401
399 402 /**
400 - * Generate an image via OpenAI Images API and attach it to a post.
403 + * Generate an image via the provider's Images API and attach it to a post.
401 404 *
402 405 * @param string $title Post title (used as image prompt context).
403 406 * @param string $description Overall topic description.
404 - * @param string $model 'dall-e-3' or 'gpt-image-1'.
407 + * @param string $model Image model id of the configured AI provider.
405 408 * @param string $quality Quality setting for the selected model.
406 409 * @param string $size Image dimensions string.
407 410 * @param int $post_id Post to attach the image to.
408 411 * @return int|\WP_Error Attachment ID on success, WP_Error on failure.
@@ -408,37 +411,48 @@
408 411 * @return int|\WP_Error Attachment ID on success, WP_Error on failure.
409 412 */
410 413 private function generate_and_attach_image(string $title, string $description, string $model, string $quality, string $size, int $post_id)
411 414 {
412 - $options = get_option('king_addons_ai_options', []);
413 - $api_key = $options['openai_api_key'] ?? '';
415 + $api_key = \King_Addons\AI_Provider::getApiKey();
414 416
415 417 if ($api_key === '') {
416 - return new \WP_Error('missing_api_key', esc_html__('OpenAI API key is missing.', 'king-addons'));
418 + return new \WP_Error('missing_api_key', sprintf(
419 + /* translators: %s: provider name */
420 + esc_html__('%s API key is missing.', 'king-addons'),
421 + \King_Addons\AI_Provider::getLabel()
422 + ));
417 423 }
418 424
419 425 $prompt = 'Professional blog featured image for an article titled: "' . $title . '". Topic: ' . $description . '. Photorealistic style, no text overlays, no watermarks.';
420 426
421 - $body = ['model' => $model, 'prompt' => $prompt, 'size' => $size];
427 + // OpenAI's image models take vendor specific size/quality options;
428 + // OpenRouter fans out to many vendors that do not share them.
429 + $body = ['model' => $model, 'prompt' => $prompt];
422 430
423 - if ($model === 'dall-e-3') {
424 - $body['n'] = 1;
425 - $body['quality'] = ($quality === 'hd') ? 'hd' : 'standard';
426 - } elseif ($model === 'gpt-image-1') {
427 - $body['quality'] = in_array($quality, ['low', 'medium', 'high', 'auto'], true) ? $quality : 'auto';
431 + if (\King_Addons\AI_Provider::isOpenRouter()) {
432 + $body['n'] = 1;
433 + } else {
434 + $body['size'] = $size;
435 +
436 + if ($model === 'dall-e-3') {
437 + $body['n'] = 1;
438 + $body['quality'] = ($quality === 'hd') ? 'hd' : 'standard';
439 + } elseif ($model === 'gpt-image-1') {
440 + $body['quality'] = in_array($quality, ['low', 'medium', 'high', 'auto'], true) ? $quality : 'auto';
441 + }
428 442 }
429 443
430 - $response = wp_remote_post('https://api.openai.com/v1/images/generations', [
431 - 'headers' => [
432 - 'Authorization' => 'Bearer ' . $api_key,
433 - 'Content-Type' => 'application/json',
434 - ],
435 - 'body' => wp_json_encode($body),
436 - 'timeout' => 120,
437 - ]);
444 + $data = \King_Addons\AI_Provider::decodeResponse(
445 + wp_remote_post(\King_Addons\AI_Provider::getImagesEndpoint(), [
446 + 'headers' => \King_Addons\AI_Provider::getHeaders(),
447 + 'body' => wp_json_encode($body),
448 + 'timeout' => 120,
449 + ]),
450 + esc_html__('Image generation failed.', 'king-addons')
451 + );
438 452
439 - if (is_wp_error($response)) {
440 - return $response;
453 + if (is_wp_error($data)) {
454 + return $data;
441 455 }
442 456
443 457 require_once ABSPATH . 'wp-admin/includes/image.php';
444 458 require_once ABSPATH . 'wp-admin/includes/file.php';
@@ -443,41 +457,60 @@
443 457 require_once ABSPATH . 'wp-admin/includes/image.php';
444 458 require_once ABSPATH . 'wp-admin/includes/file.php';
445 459 require_once ABSPATH . 'wp-admin/includes/media.php';
446 460
447 - $code = wp_remote_retrieve_response_code($response);
448 - $data = json_decode(wp_remote_retrieve_body($response), true);
461 + // An image comes back either as a hosted URL or as inline base64.
462 + $item = (isset($data['data'][0]) && is_array($data['data'][0])) ? $data['data'][0] : [];
463 + $base64 = '';
464 + $remote_url = '';
465 + $mime = 'image/png';
449 466
450 - if ($model === 'gpt-image-1') {
451 - $b64 = $data['data'][0]['b64_json'] ?? '';
452 - if ($b64 === '') {
453 - $err = $data['error']['message'] ?? esc_html__('No image data returned.', 'king-addons');
454 - return new \WP_Error('api_error', $err);
467 + if (!empty($item['b64_json']) && is_string($item['b64_json'])) {
468 + $base64 = $item['b64_json'];
469 + if (!empty($item['media_type']) && is_string($item['media_type'])) {
470 + $mime = $item['media_type'];
455 471 }
472 + } elseif (!empty($item['url']) && is_string($item['url'])) {
473 + $remote_url = $item['url'];
474 + } elseif (!empty($item['image_url']['url']) && is_string($item['image_url']['url'])) {
475 + $remote_url = $item['image_url']['url'];
476 + }
456 477
457 - $bytes = base64_decode($b64);
478 + if ($remote_url !== '' && strpos($remote_url, 'data:') === 0 && preg_match('#^data:([^;,]+);base64,(.+)$#s', $remote_url, $matches)) {
479 + $mime = $matches[1];
480 + $base64 = $matches[2];
481 + $remote_url = '';
482 + }
483 +
484 + if ($base64 === '' && $remote_url === '') {
485 + return new \WP_Error('api_error', esc_html__('No image data returned.', 'king-addons'));
486 + }
487 +
488 + if ($base64 !== '') {
489 + $bytes = base64_decode($base64, true);
458 490 if (!$bytes) {
459 491 return new \WP_Error('decode_error', esc_html__('Failed to decode image data.', 'king-addons'));
460 492 }
461 493
462 - $tmp = wp_tempnam('postgen.png');
494 + $type_map = ['image/jpeg' => 'jpg', 'image/jpg' => 'jpg', 'image/webp' => 'webp', 'image/gif' => 'gif'];
495 + $extension = $type_map[$mime] ?? 'png';
496 + $name = substr(sanitize_file_name($title), 0, 80);
497 + if ($name === '') {
498 + $name = 'postgen';
499 + }
500 +
501 + $tmp = wp_tempnam($name . '.' . $extension);
463 502 if (!$tmp || !file_put_contents($tmp, $bytes)) {
464 503 return new \WP_Error('write_error', esc_html__('Failed to write temp image file.', 'king-addons'));
465 504 }
466 505
467 506 return media_handle_sideload([
468 - 'name' => substr(sanitize_file_name($title), 0, 80) . '.png',
507 + 'name' => $name . '.' . $extension,
469 508 'tmp_name' => $tmp,
470 509 ], $post_id, $title);
471 510 }
472 511
473 - // DALL·E 3 (URL-based).
474 - if ($code !== 200 || empty($data['data'][0]['url'])) {
475 - $err = $data['error']['message'] ?? esc_html__('Image generation failed.', 'king-addons');
476 - return new \WP_Error('api_error', $err);
477 - }
478 -
479 - return media_sideload_image(esc_url_raw($data['data'][0]['url']), $post_id, $title, 'id');
512 + return media_sideload_image(esc_url_raw($remote_url), $post_id, $title, 'id');
480 513 }
481 514
482 515 /**
483 516 * Kick the batch processor if cron missed its schedule or went stale.