admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('dzs_amfi_generate_nonce'), )); } elseif ($hook === 'add-missing-featured-image-dzs_page_dzs-ai-featured-image-generator-settings') { wp_enqueue_script( 'dzs-amfi-settings-script', plugins_url('assets/js/settings-script.js', __FILE__), array(), '1.0.3', true ); } } // 2. Register Settings add_action('admin_init', 'dzs_amfi_register_settings'); function dzs_amfi_register_settings() { register_setting('dzs_amfi_settings_group', 'dzs_amfi_openai_api_key', array('sanitize_callback' => 'sanitize_text_field')); register_setting('dzs_amfi_settings_group', 'dzs_amfi_gemini_api_key', array('sanitize_callback' => 'sanitize_text_field')); register_setting('dzs_amfi_settings_group', 'dzs_amfi_resolution', array('sanitize_callback' => 'sanitize_text_field')); register_setting('dzs_amfi_settings_group', 'dzs_amfi_dalle_model', array('sanitize_callback' => 'sanitize_text_field')); register_setting('dzs_amfi_settings_group', 'dzs_amfi_gemini_model', array('sanitize_callback' => 'sanitize_text_field')); register_setting('dzs_amfi_settings_group', 'dzs_amfi_custom_prompt', array('sanitize_callback' => 'sanitize_textarea_field')); } function dzs_amfi_settings_page_html() { require_once plugin_dir_path(__FILE__) . 'settings-page.php'; } // 3. Admin Page HTML (List Posts) function dzs_amfi_admin_page_html() { require_once plugin_dir_path(__FILE__) . 'admin-page.php'; } // 4. AJAX Handler for Generation add_action('wp_ajax_dzs_amfi_generate_image', 'dzs_amfi_generate_image_ajax'); function dzs_amfi_generate_image_ajax() { check_ajax_referer('dzs_amfi_generate_nonce', 'security'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); } if (!isset($_POST['post_id'])) { wp_send_json_error('Missing Post ID'); } $post_id = intval($_POST['post_id']); if (!$post_id) { wp_send_json_error('Invalid Post ID'); } $model = get_option('dzs_amfi_dalle_model', 'dall-e-2'); if ($model === 'nano-banana-2') { $api_key = get_option('dzs_amfi_gemini_api_key'); if (empty($api_key)) { wp_send_json_error('Gemini API Key is missing. Please set it in Settings.'); } } else { $api_key = get_option('dzs_amfi_openai_api_key'); if (empty($api_key)) { wp_send_json_error('OpenAI API Key is missing. Please set it in Settings.'); } } $resolution = get_option('dzs_amfi_resolution', '1024x1024'); // Support custom cropping for non-standard resolutions $supported_dalle2_sizes = array('256x256', '512x512', '1024x1024'); $supported_dalle3_sizes = array('1024x1024', '1024x1792', '1792x1024'); $api_size = '1024x1024'; $custom_crop = false; $target_w = 1024; $target_h = 1024; if ($model === 'dall-e-2') { if (in_array($resolution, $supported_dalle2_sizes)) { $api_size = $resolution; } else { $custom_crop = true; } } elseif ($model === 'nano-banana-2') { // Assume nano-banana-2 works mechanically similarly to DALL-E 3 for base queries if (in_array($resolution, $supported_dalle3_sizes)) { $api_size = $resolution; } else { $custom_crop = true; } } else { if (in_array($resolution, $supported_dalle3_sizes)) { $api_size = $resolution; } else { $custom_crop = true; } } if ($custom_crop) { $parts = explode('x', strtolower(trim($resolution))); if (count($parts) === 2) { $target_w = intval($parts[0]); $target_h = intval($parts[1]); } else { $custom_crop = false; } } $post = get_post($post_id); $custom_prompt_template = get_option('dzs_amfi_custom_prompt'); if (!empty(trim($custom_prompt_template))) { // Use custom prompt and replace placeholders $prompt = str_replace( array('{{post_title}}', '{{post_content}}'), array($post->post_title, wp_strip_all_tags(substr($post->post_content, 0, 500))), $custom_prompt_template ); } else { // Fallback to default behavior $prompt = wp_strip_all_tags($post->post_title . '. ' . substr($post->post_content, 0, 500)); } // Ensure prompt is not too long for OpenAI (max 1000 chars typically) $prompt = substr($prompt, 0, 1000); if (empty(trim($prompt))) { $prompt = "Abstract tech background image"; } // Branch API payload logic depending on Model if ($model === 'nano-banana-2') { // Nano Banana 2 uses the Gemini API protocol $gemini_model = get_option('dzs_amfi_gemini_model', 'gemini-2.5-flash-image'); $gemini_url = 'https://generativelanguage.googleapis.com/v1beta/models/' . $gemini_model . ':generateContent'; $aspect_ratio = "1:1"; if (!$custom_crop) { if ($api_size == '1024x1792') $aspect_ratio = "9:16"; if ($api_size == '1792x1024') $aspect_ratio = "16:9"; } $response = wp_remote_post($gemini_url, array( 'timeout' => 60, 'headers' => array( 'x-goog-api-key' => $api_key, 'Content-Type' => 'application/json' ), 'body' => wp_json_encode(array( 'contents' => array( array( 'parts' => array( array('text' => 'Generate a high quality image representing: ' . $prompt) ) ) ), 'generationConfig' => array( 'imageConfig' => array( 'aspectRatio' => $aspect_ratio ) ) )) )); } else { // Call OpenAI API $response = wp_remote_post('https://api.openai.com/v1/images/generations', array( 'timeout' => 60, 'headers' => array( 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json' ), 'body' => wp_json_encode(array( 'model' => $model, 'prompt' => $prompt, 'n' => 1, 'size' => $api_size )) )); } if (is_wp_error($response)) { wp_send_json_error($response->get_error_message()); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if ($model === 'nano-banana-2') { if (isset($data['error'])) { wp_send_json_error('Gemini API Error: ' . $data['error']['message']); } $base64_data = ''; $mime_type = 'image/png'; if (!empty($data['candidates'][0]['content']['parts']) && is_array($data['candidates'][0]['content']['parts'])) { foreach ($data['candidates'][0]['content']['parts'] as $part) { if (!empty($part['inlineData']['data'])) { $base64_data = $part['inlineData']['data']; if (!empty($part['inlineData']['mimeType'])) { $mime_type = $part['inlineData']['mimeType']; } break; } elseif (!empty($part['inline_data']['data'])) { $base64_data = $part['inline_data']['data']; if (!empty($part['inline_data']['mime_type'])) { $mime_type = $part['inline_data']['mime_type']; } break; } } } if (empty($base64_data)) { wp_send_json_error('No image data returned from Gemini API. Raw response: ' . $body); } $image_data = base64_decode($base64_data); $upload_dir = wp_upload_dir(); $api_ext = (strpos($mime_type, 'jpeg') !== false) ? '.jpg' : '.png'; $filename_temp = 'gemini_raw_' . $post_id . '_' . time() . $api_ext; $file_path_temp = $upload_dir['path'] . '/' . $filename_temp; // Write the raw payload to disk file_put_contents($file_path_temp, $image_data); $filename = 'gemini_' . $post_id . '_' . time() . '.jpg'; $file_path = $upload_dir['path'] . '/' . $filename; // Strictly enforce JPEG conversion for higher efficiency $image_editor = wp_get_image_editor($file_path_temp); if (!is_wp_error($image_editor)) { $saved_image = $image_editor->save($file_path, 'image/jpeg'); if (!is_wp_error($saved_image)) { wp_delete_file($file_path_temp); // Clean up the raw file } else { $file_path = $file_path_temp; // Fallback if edit fails $filename = $filename_temp; } } else { $file_path = $file_path_temp; // Fallback if editor invalid $filename = $filename_temp; } $filetype = wp_check_filetype($filename, null); $attachment = array( 'post_mime_type' => $filetype['type'], 'post_title' => sanitize_file_name($filename), 'post_content' => 'Generated by Nano Banana / Gemini for Post ' . $post_id, 'post_status' => 'inherit' ); require_once(ABSPATH . 'wp-admin/includes/image.php'); require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/media.php'); $attachment_id = wp_insert_attachment($attachment, $file_path, $post_id); if (is_wp_error($attachment_id)) { wp_send_json_error($attachment_id->get_error_message()); } $attach_data = wp_generate_attachment_metadata($attachment_id, $file_path); wp_update_attachment_metadata($attachment_id, $attach_data); } else { if (isset($data['error'])) { wp_send_json_error($data['error']['message']); } if (!isset($data['data'][0]['url'])) { wp_send_json_error('No image URL returned from OpenAI.'); } $image_url = $data['data'][0]['url']; // Sideload Image require_once(ABSPATH . 'wp-admin/includes/media.php'); require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); $desc = 'Generated by OpenAI for Post ' . $post_id; // media_sideload_image returns attachment ID if 'id' is passed as 4th param $attachment_id = media_sideload_image($image_url, $post_id, $desc, 'id'); if (is_wp_error($attachment_id)) { wp_send_json_error($attachment_id->get_error_message()); } } if ($custom_crop && $target_w > 0 && $target_h > 0) { $img_path = get_attached_file($attachment_id); $image = wp_get_image_editor($img_path); if (!is_wp_error($image)) { $image->resize($target_w, $target_h, true); // true is for crop $image->save($img_path); $attach_data = wp_generate_attachment_metadata($attachment_id, $img_path); wp_update_attachment_metadata($attachment_id, $attach_data); } } update_post_meta($attachment_id, '_wp_attachment_image_alt', $post->post_title); set_post_thumbnail($post_id, $attachment_id); wp_send_json_success('Image generated and set successfully.'); }