init(); do_action( 'bdthemes_ai_image_init' ); } return self::$instance; } /** * Admin screens that render this plugin’s own UI (always load assets). * * @return string[] */ private function ai_image_plugin_admin_hooks() { return array( 'settings_page_ai-image-settings', 'media_page_bdt-ai-media-tab' ); } /** * Whether wp.media (Image Generator tab) is expected on this admin screen. * Uses WP_Screen so we do not load on unrelated pages (e.g. plugins, users). * * @param string $hook_suffix Passed from admin_enqueue_scripts (usually WP_Screen::$id). * @return bool */ private function is_ai_image_media_modal_context_screen( $hook_suffix ) { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( $screen instanceof \WP_Screen ) { // Post / page / any CPT editor. if ( 'post' === $screen->base ) { return true; } // Media library, add media, upload flows. if ( in_array( $screen->base, array( 'upload', 'media' ), true ) ) { return true; } // List tables (featured image, bulk actions, etc.). if ( 'edit' === $screen->base ) { return true; } $modal_screen_ids = array( 'site-editor', 'widgets', 'nav-menus', 'theme-editor', 'customize', 'media-upload', ); if ( in_array( $screen->id, $modal_screen_ids, true ) ) { return true; } } // CPT list: hook id is often `edit-{post_type}` when screen object is unavailable. if ( preg_match( '/^edit-[a-z0-9_-]+$/', $hook_suffix ) ) { return true; } return false; } /** * Whether to load the main admin bundle (CSS/JS) on this screen. * * @param string $hook_suffix Passed from admin_enqueue_scripts. * @return bool */ private function should_enqueue_ai_image_admin_bundle( $hook_suffix ) { if ( in_array( $hook_suffix, $this->ai_image_plugin_admin_hooks(), true ) ) { return true; } if ( get_option( 'bdthemes_ai_image_hide_media_modal_tab', '0' ) === '1' ) { return false; } $load = $this->is_ai_image_media_modal_context_screen( $hook_suffix ); /** * Override whether the Image Generator admin bundle loads (media modal tab enabled). * * @param bool $load Default decision. * @param string $hook_suffix Current admin screen hook suffix / screen id. */ return (bool) apply_filters( 'bdthemes_ai_image_should_enqueue_admin_bundle', $load, $hook_suffix ); } /** * Admin Styles * * @since 1.0.0 */ public function enqueue_admin_styles( $hook_suffix ) { if ( ! $this->should_enqueue_ai_image_admin_bundle( $hook_suffix ) ) { return; } wp_register_style( 'ai-image', BDT_AI_IMAGE_URL . 'build/admin/index.css', array(), BDT_AI_IMAGE_VERSION ); wp_enqueue_style( 'ai-image' ); } /** * Enqueue admin scripts * * @since 1.0.0 * @return void */ public function enqueue_admin_scripts( $hook_suffix ) { if ( ! $this->should_enqueue_ai_image_admin_bundle( $hook_suffix ) ) { return; } $is_plugin_page = in_array( $hook_suffix, $this->ai_image_plugin_admin_hooks(), true ); $asset_file = plugin_dir_path( __FILE__ ) . 'build/admin/index.asset.php'; if ( ! file_exists( $asset_file ) ) { return; } $asset = include $asset_file; wp_register_script( 'ai-image', BDT_AI_IMAGE_URL . 'build/admin/index.js', $asset['dependencies'], $asset['version'], true ); wp_enqueue_script( 'ai-image' ); $provider_ids = array( 'global', 'pexels', 'pixabay', 'openverse', 'wikimedia', 'unsplash', 'giphy', 'openai', 'gemini' ); $provider_enabled = array(); $enabled_by_default = array( 'global', 'pexels', 'pixabay', 'openverse', 'wikimedia', 'unsplash', 'giphy' ); foreach ( $provider_ids as $id ) { $default_value = in_array( $id, $enabled_by_default, true ) ? '1' : '0'; $val = get_option( 'bdthemes_ai_image_provider_' . $id, $default_value ); $provider_enabled[ $id ] = ( $val === '1' || $val === true ); } $default_order = array( 'pexels', 'pixabay', 'openverse', 'wikimedia', 'unsplash', 'giphy', 'openai', 'gemini' ); $saved_order = get_option( 'bdthemes_ai_image_provider_order', $default_order ); if ( ! is_array( $saved_order ) || empty( $saved_order ) ) { $saved_order = $default_order; } $provider_order = array_values( array_unique( array_merge( $saved_order, $default_order ) ) ); $max_w = get_option( 'bdthemes_ai_image_max_upload_width', 1600 ); $max_h = get_option( 'bdthemes_ai_image_max_upload_height', 1200 ); $items_per_page = get_option( 'bdthemes_ai_image_items_per_page', 20 ); $general = array( 'max_upload_width' => is_numeric( $max_w ) ? (int) $max_w : 1600, 'max_upload_height' => is_numeric( $max_h ) ? (int) $max_h : 1200, 'default_provider' => get_option( 'bdthemes_ai_image_default_provider', 'pexels' ), 'image_attribution' => get_option( 'bdthemes_ai_image_attribution', '0' ) === '1', 'auto_alt_text' => get_option( 'bdthemes_ai_image_auto_alt_text', '0' ) === '1', 'auto_title' => get_option( 'bdthemes_ai_image_auto_title', '0' ) === '1', 'hide_media_modal_tab' => get_option( 'bdthemes_ai_image_hide_media_modal_tab', '0' ) === '1', 'default_view_mode' => get_option( 'bdthemes_ai_image_default_view_mode', 'grid' ), 'items_per_page' => is_numeric( $items_per_page ) ? (int) $items_per_page : 20, 'thumbnail_size' => get_option( 'bdthemes_ai_image_thumbnail_size', 'small' ), 'load_more_mode' => get_option( 'bdthemes_ai_image_load_more_mode', 'manual' ), ); $script_config = array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'assets_url' => BDT_AI_IMAGE_ASSETS, 'rest_url' => rest_url( 'bdthemes/v1/' ), 'version' => BDT_AI_IMAGE_VERSION, 'settings_url' => admin_url( 'options-general.php?page=ai-image-settings' ), 'generator_url' => admin_url( 'upload.php?page=bdt-ai-media-tab' ), 'support_url' => 'https://bdthemes.com/support/?utm_source=WordPress_Repository&utm_medium=Plugin_Page&utm_campaign=WordPress_to_Instant_Image_Generator', 'docs_url' => 'https://bdthemes.com/all-knowledge-base-of-instant-image-generator/?utm_source=WordPress_Repository&utm_medium=Plugin_Page&utm_campaign=WordPress_to_Instant_Image_Generator', 'provider_enabled' => $provider_enabled, 'provider_order' => $provider_order, 'general_settings' => $general, 'image_sizes' => $this->get_all_image_sizes(), ); wp_localize_script( 'ai-image', 'AI_IMAGE_AdminConfig', $script_config ); // Add media modal integration script only on screens where the tab is used (not on plugin settings pages). if ( ! $is_plugin_page && get_option( 'bdthemes_ai_image_hide_media_modal_tab', '0' ) !== '1' ) { wp_enqueue_media(); $this->enqueue_media_modal_script(); } } /** * Enqueue block editor assets for the Image Generator button * * @since 2.0.0 */ public function enqueue_block_editor_assets() { // Load the main admin bundle which includes block-toolbar functionality $asset_file = BDT_AI_IMAGE_PATH . 'build/admin/index.asset.php'; if ( ! file_exists( $asset_file ) ) { return; } $asset = include $asset_file; // Enqueue the admin script (includes block toolbar) wp_enqueue_script( 'ai-image', BDT_AI_IMAGE_URL . 'build/admin/index.js', $asset['dependencies'], $asset['version'], true ); // Enqueue the admin styles wp_enqueue_style( 'ai-image', BDT_AI_IMAGE_URL . 'build/admin/index.css', array(), BDT_AI_IMAGE_VERSION ); // Check if any provider is enabled and build enabled providers list $provider_ids = array( 'pexels', 'pixabay', 'unsplash', 'openverse', 'wikimedia', 'giphy', 'openai', 'gemini' ); $has_enabled_provider = false; $enabled_providers = array(); $enabled_by_default = array( 'pexels', 'pixabay', 'openverse', 'wikimedia', 'unsplash', 'giphy' ); foreach ( $provider_ids as $id ) { $default_value = in_array( $id, $enabled_by_default, true ) ? '1' : '0'; $enabled = get_option( 'bdthemes_ai_image_provider_' . $id, $default_value ) === '1'; $enabled_providers[ $id ] = $enabled; if ( $enabled ) { $has_enabled_provider = true; } } // Get provider order from settings $default_order = array( 'pexels', 'pixabay', 'openverse', 'wikimedia', 'unsplash', 'giphy', 'openai', 'gemini' ); $saved_order = get_option( 'bdthemes_ai_image_provider_order', $default_order ); if ( ! is_array( $saved_order ) || empty( $saved_order ) ) { $saved_order = $default_order; } $provider_order = array_values( array_unique( array_merge( $saved_order, $default_order ) ) ); // Localize script with config for block toolbar wp_localize_script( 'ai-image', 'AI_IMAGE_BlockToolbar', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'hasEnabledProviders' => $has_enabled_provider, 'enabledProviders' => $enabled_providers, 'providerOrder' => $provider_order, ) ); } /** * Enqueue the inline script that adds the "Image Generator" tab to the new wp.media modal. */ private function enqueue_media_modal_script() { $inline_js = <<<'MEDIAJS' (function( $, wp ){ if ( typeof wp === 'undefined' || ! wp.media || ! wp.media.view ) return; var TAB_ID = 'ai-image-tab'; var TAB_TEXT = 'Image Generator'; /** * Custom Backbone view that renders our React app. */ var AiImageContent = wp.media.View.extend({ className: 'ai-image-wrap ai-image-media-modal', initialize: function() { this.$el.attr('style', 'height:100%;overflow-y:auto;padding:16px 20px;background:#f0f0f1;'); }, render: function() { this.$el.html( '
' ); var self = this; // Small delay to ensure DOM is ready before React renders setTimeout(function(){ var root = self.$el.find('#ai-image-generator-modal')[0]; if ( root && window.aiImageRenderApp ) { window.aiImageRenderApp( root ); } }, 50); return this; } }); /** * Add the tab to router for any frame type. */ function addTab( routerView ) { routerView.set( TAB_ID, { text: TAB_TEXT, priority: 200 }); } /** * Bind content creation handler to a frame. */ function bindContentHandler( frame ) { if ( frame._aiImageBound ) return; frame._aiImageBound = true; frame.on( 'content:create:' + TAB_ID, function() { var view = new AiImageContent(); frame.content.set( view ); }); } // Override Select frame router (used in Gutenberg, featured image, etc.) var origSelectRouter = wp.media.view.MediaFrame.Select.prototype.browseRouter; wp.media.view.MediaFrame.Select.prototype.browseRouter = function( routerView ) { origSelectRouter.apply( this, arguments ); addTab( routerView ); bindContentHandler( this ); }; // Override Post frame router (used in classic editor "Add Media") if ( wp.media.view.MediaFrame.Post ) { var origPostRouter = wp.media.view.MediaFrame.Post.prototype.browseRouter; wp.media.view.MediaFrame.Post.prototype.browseRouter = function( routerView ) { origPostRouter.apply( this, arguments ); addTab( routerView ); bindContentHandler( this ); }; } })( jQuery, wp ); MEDIAJS; wp_add_inline_script( 'ai-image', $inline_js, 'after' ); } /** * Add AI Image settings page under Settings menu. */ public function add_settings_menu() { add_submenu_page( 'options-general.php', __( 'Image Generator', 'ai-image' ), __( 'Image Generator', 'ai-image' ), 'manage_options', 'ai-image-settings', array( $this, 'render_settings_page' ) ); } /** * Settings page callback: output root for React. */ public function render_settings_page() { echo '
'; } /** * Summary of upload_image_to_wp */ public function upload_image_to_wp() { // Verify nonce for security if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_rest' ) ) { wp_send_json_error( array( 'message' => 'Invalid nonce.' ) ); } if ( ! current_user_can( 'upload_files' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to upload files.', 'ai-image' ) ), 403 ); } // Check if image URL is provided if ( ! isset( $_POST['image_url'] ) || empty( $_POST['image_url'] ) ) { wp_send_json_error( array( 'message' => 'No image URL provided.' ) ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized based on type below $raw_image_url = wp_unslash( $_POST['image_url'] ); $image_title = isset( $_POST['image_title'] ) ? sanitize_text_field( wp_unslash( $_POST['image_title'] ) ) : ''; $image_author = isset( $_POST['image_author'] ) ? sanitize_text_field( wp_unslash( $_POST['image_author'] ) ) : ''; $upload_dir = wp_upload_dir(); // Check if this is a base64 data URI (e.g., from Gemini) $is_base64 = strpos( $raw_image_url, 'data:image/' ) === 0; if ( $is_base64 ) { // Handle base64 data URI // Extract the image data from the data URI // Format: data:image/png;base64,iVBORw0KGgoAAAANS... if ( ! preg_match( '/^data:image\/(\w+);base64,(.+)$/i', $raw_image_url, $matches ) ) { wp_send_json_error( array( 'message' => 'Invalid base64 image data.' ) ); } $extension = strtolower( $matches[1] ); $base64_str = $matches[2]; // Validate extension if ( ! in_array( $extension, array( 'png', 'jpg', 'jpeg', 'gif', 'webp' ) ) ) { wp_send_json_error( array( 'message' => 'Unsupported image format: ' . $extension ) ); } // Decode base64 data $image_data = base64_decode( $base64_str ); if ( $image_data === false ) { wp_send_json_error( array( 'message' => 'Failed to decode base64 image data.' ) ); } if ( empty( $image_data ) ) { wp_send_json_error( array( 'message' => 'Decoded base64 image data is empty.' ) ); } } else { // Handle regular URL (reject unsafe / local targets to reduce SSRF risk). $image_url = esc_url_raw( $raw_image_url ); if ( empty( $image_url ) || ! wp_http_validate_url( $image_url ) ) { wp_send_json_error( array( 'message' => __( 'Invalid or disallowed image URL.', 'ai-image' ) ) ); } $response = wp_safe_remote_get( $image_url, array( 'timeout' => 60, 'redirection' => 3, ) ); // Check if the request failed if ( is_wp_error( $response ) ) { $error_message = $response->get_error_message(); wp_send_json_error( array( 'message' => 'Failed to fetch image.', 'error' => $error_message ) ); } // Check if the status code is 200 (success) $status_code = wp_remote_retrieve_response_code( $response ); if ( $status_code !== 200 ) { wp_send_json_error( array( 'message' => 'Failed to fetch image. Status code: ' . $status_code ) ); } // Get the image data $image_data = wp_remote_retrieve_body( $response ); if ( empty( $image_data ) ) { wp_send_json_error( array( 'message' => 'Failed to retrieve image data.' ) ); } // Detect file extension from content-type or URL $content_type = wp_remote_retrieve_header( $response, 'content-type' ); $extension = 'jpg'; if ( ! empty( $content_type ) ) { if ( strpos( $content_type, 'png' ) !== false ) { $extension = 'png'; } elseif ( strpos( $content_type, 'gif' ) !== false ) { $extension = 'gif'; } elseif ( strpos( $content_type, 'webp' ) !== false ) { $extension = 'webp'; } } } // Build filename from image title if available, otherwise use a domain-based name if ( ! empty( $image_title ) ) { // Sanitize the title into a clean slug for the filename $slug = sanitize_title( $image_title ); $slug = preg_replace( '/[^a-z0-9\-]/', '', $slug ); // Limit to 80 chars to avoid extremely long filenames if ( strlen( $slug ) > 80 ) { $slug = substr( $slug, 0, 80 ); } $slug = rtrim( $slug, '-' ); $filename = $slug . '.' . $extension; } else { // Fallback: domain-based random filename $wp_domain_name = get_site_url(); $wp_domain_name = str_replace( array( 'http://', 'https://' ), '', strtolower( $wp_domain_name ) ); $wp_domain_name = preg_replace( '/[^a-z0-9]/', '-', $wp_domain_name ); $filename = $wp_domain_name . '-' . time() . '.' . $extension; } // Ensure unique filename in uploads directory $filename = wp_unique_filename( $upload_dir['path'], $filename ); // Check if the upload directory exists if ( wp_mkdir_p( $upload_dir['path'] ) ) { $file_path = $upload_dir['path'] . '/' . $filename; } else { $file_path = $upload_dir['basedir'] . '/' . $filename; } // Write the image data to the file $write_result = file_put_contents( $file_path, $image_data ); if ( ! $write_result ) { wp_send_json_error( array( 'message' => 'Failed to save image to disk.' ) ); } // Check the file type $wp_filetype = wp_check_filetype( $filename, null ); if ( ! in_array( $wp_filetype['type'], array( 'image/jpeg', 'image/png', 'image/gif' ) ) ) { wp_send_json_error( array( 'message' => 'Invalid image type.' ) ); } // Resize image if it exceeds max dimensions $max_width = get_option( 'bdthemes_ai_image_max_upload_width', 1600 ); $max_height = get_option( 'bdthemes_ai_image_max_upload_height', 1200 ); $max_width = is_numeric( $max_width ) ? (int) $max_width : 1600; $max_height = is_numeric( $max_height ) ? (int) $max_height : 1200; if ( $max_width > 0 && $max_height > 0 ) { require_once( ABSPATH . 'wp-admin/includes/image.php' ); $image_editor = wp_get_image_editor( $file_path ); if ( ! is_wp_error( $image_editor ) ) { $size = $image_editor->get_size(); // Only resize if image exceeds max dimensions if ( $size['width'] > $max_width || $size['height'] > $max_height ) { $image_editor->resize( $max_width, $max_height, false ); $saved = $image_editor->save( $file_path ); } } } // Read user settings for auto-populating metadata $auto_alt_text = get_option( 'bdthemes_ai_image_auto_alt_text', '0' ) === '1'; $auto_title = get_option( 'bdthemes_ai_image_auto_title', '0' ) === '1'; $image_attribution = get_option( 'bdthemes_ai_image_attribution', '0' ) === '1'; // Determine the WP attachment title if ( $auto_title && ! empty( $image_title ) ) { $attachment_title = $image_title; } else { // When auto title is off, leave empty so WordPress doesn't show a generated name $attachment_title = $auto_title ? sanitize_file_name( $filename ) : ''; } // Build caption from author name if attribution is enabled $caption = ''; if ( $image_attribution && ! empty( $image_author ) ) { $caption = 'Photo by ' . $image_author; } $attachment = array( 'post_mime_type' => $wp_filetype['type'], 'post_title' => $attachment_title, 'post_content' => '', 'post_excerpt' => $caption, 'post_status' => 'inherit', ); // Insert the attachment into the media library $attach_id = wp_insert_attachment( $attachment, $file_path ); if ( ! $attach_id ) { wp_send_json_error( array( 'message' => 'Failed to upload image.' ) ); } // Set alternative text from the image title if enabled if ( $auto_alt_text && ! empty( $image_title ) ) { update_post_meta( $attach_id, '_wp_attachment_image_alt', $image_title ); } // Generate metadata for the attachment require_once( ABSPATH . 'wp-admin/includes/image.php' ); $attach_data = wp_generate_attachment_metadata( $attach_id, $file_path ); wp_update_attachment_metadata( $attach_id, $attach_data ); // Return success response wp_send_json_success( array( 'attach_id' => $attach_id, 'attach_url' => wp_get_attachment_url( $attach_id ), ) ); } /** * Media Sub Menu */ public function media_sub_menu() { add_media_page( 'Image Generator', 'Image Generator', 'read', 'bdt-ai-media-tab', function () { ?>
$size ) { if ( ! empty( $size['width'] ) && ! empty( $size['height'] ) ) { add_image_size( $name, (int) $size['width'], (int) $size['height'], ! empty( $size['crop'] ) ); } } } /** * Get all image sizes (WP defaults + custom). */ private function get_all_image_sizes() { global $_wp_additional_image_sizes; $sizes = array(); $default_names = array( 'thumbnail', 'medium', 'medium_large', 'large' ); foreach ( $default_names as $name ) { $w = get_option( $name . '_size_w' ); $h = get_option( $name . '_size_h' ); $crop = get_option( $name . '_crop' ); if ( $w || $h ) { $label = ucfirst( str_replace( '_', ' ', $name ) ); $sizes[] = array( 'label' => $label, 'name' => $name, 'width' => (int) $w, 'height' => (int) $h, 'crop' => ! empty( $crop ), 'source' => 'wordpress', ); } } // Get custom sizes from database to verify they still exist $custom_sizes = get_option( 'bdthemes_ai_image_custom_sizes', array() ); if ( ! is_array( $custom_sizes ) ) $custom_sizes = array(); if ( is_array( $_wp_additional_image_sizes ) ) { foreach ( $_wp_additional_image_sizes as $name => $data ) { // Only include custom sizes that exist in our database if ( isset( $custom_sizes[ $name ] ) ) { $label = ucfirst( str_replace( array( '_', '-' ), ' ', $name ) ); $sizes[] = array( 'label' => $label, 'name' => $name, 'width' => isset( $data['width'] ) ? (int) $data['width'] : 0, 'height' => isset( $data['height'] ) ? (int) $data['height'] : 0, 'crop' => ! empty( $data['crop'] ), 'source' => 'custom', ); } } } return $sizes; } /** * AJAX: add a custom image size. */ public function ajax_add_custom_size() { $this->api_key_ajax_permission_check(); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $name = isset( $_POST['name'] ) ? sanitize_title( wp_unslash( $_POST['name'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $width = isset( $_POST['width'] ) ? absint( $_POST['width'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $height = isset( $_POST['height'] ) ? absint( $_POST['height'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $crop = ! empty( $_POST['crop'] ); // Validate name and dimensions if ( empty( $name ) || $width < 1 || $height < 1 ) { wp_send_json_error( array( 'message' => __( 'Invalid name or dimensions.', 'ai-image' ) ) ); } // Check maximum dimensions if ( $width > 1920 ) { wp_send_json_error( array( 'message' => __( 'Width must not exceed 1920 pixels.', 'ai-image' ) ) ); } if ( $height > 3000 ) { wp_send_json_error( array( 'message' => __( 'Height must not exceed 3000 pixels.', 'ai-image' ) ) ); } // Check for reserved WordPress image size names $reserved = array( 'thumbnail', 'medium', 'medium_large', 'large' ); if ( in_array( $name, $reserved, true ) ) { wp_send_json_error( array( 'message' => __( 'This name is reserved by WordPress. Please use a different name.', 'ai-image' ) ) ); } // Check for duplicate custom size names $custom = get_option( 'bdthemes_ai_image_custom_sizes', array() ); if ( ! is_array( $custom ) ) $custom = array(); if ( isset( $custom[ $name ] ) ) { wp_send_json_error( array( 'message' => __( 'A size with this name already exists. Please use a different name.', 'ai-image' ) ) ); } $custom[ $name ] = array( 'width' => $width, 'height' => $height, 'crop' => $crop ); update_option( 'bdthemes_ai_image_custom_sizes', $custom ); add_image_size( $name, $width, $height, $crop ); wp_send_json_success( array( 'message' => __( 'Image size added.', 'ai-image' ), 'sizes' => $this->get_all_image_sizes(), ) ); } /** * AJAX: delete a custom image size. */ public function ajax_delete_custom_size() { $this->api_key_ajax_permission_check(); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $name = isset( $_POST['name'] ) ? sanitize_title( wp_unslash( $_POST['name'] ) ) : ''; if ( empty( $name ) ) { wp_send_json_error( array( 'message' => __( 'Invalid name.', 'ai-image' ) ) ); } $custom = get_option( 'bdthemes_ai_image_custom_sizes', array() ); if ( ! is_array( $custom ) ) $custom = array(); if ( ! isset( $custom[ $name ] ) ) { wp_send_json_error( array( 'message' => __( 'Size not found.', 'ai-image' ) ) ); } unset( $custom[ $name ] ); update_option( 'bdthemes_ai_image_custom_sizes', $custom ); wp_send_json_success( array( 'message' => __( 'Image size deleted.', 'ai-image' ), 'sizes' => $this->get_all_image_sizes(), ) ); } /** * Verify nonce and manage_options for API key AJAX handlers. */ private function api_key_ajax_permission_check() { $nonce = isset( $_REQUEST['nonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { wp_send_json_error( array( 'message' => 'Invalid nonce.' ), 403 ); } if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Forbidden.' ), 403 ); } } /** * AJAX: return OpenAI API key (JSON). */ public function ajax_get_openai_key() { $this->api_key_ajax_permission_check(); $key = get_option( 'bdthemes_openai_api_key' ); $key = is_string( $key ) ? trim( $key ) : ''; wp_send_json_success( array( 'api_key' => $key ? $key : null ) ); } /** * AJAX: return Gemini API key (JSON). */ public function ajax_get_gemini_key() { $this->api_key_ajax_permission_check(); $key = get_option( 'bdthemes_gemini_api_key' ); $key = is_string( $key ) ? trim( $key ) : ''; wp_send_json_success( array( 'api_key' => $key ? $key : null ) ); } /** * AJAX: return Pexels API key (JSON). */ public function ajax_get_pexels_key() { $this->api_key_ajax_permission_check(); $key = get_option( 'bdthemes_pexels_api_key' ); $key = is_string( $key ) ? trim( $key ) : ''; wp_send_json_success( array( 'api_key' => $key ? $key : null ) ); } /** * AJAX: return Unsplash API key (JSON). */ public function ajax_get_unsplash_key() { $this->api_key_ajax_permission_check(); $key = get_option( 'bdthemes_unsplash_access_key' ); $key = is_string( $key ) ? trim( $key ) : ''; wp_send_json_success( array( 'api_key' => $key ? $key : null ) ); } /** * AJAX: return Pixabay API key (JSON). */ public function ajax_get_pixabay_key() { $this->api_key_ajax_permission_check(); $key = get_option( 'bdthemes_pixabay_api_key' ); $key = is_string( $key ) ? trim( $key ) : ''; wp_send_json_success( array( 'api_key' => $key ? $key : null ) ); } /** * AJAX: return Giphy API key (JSON). */ public function ajax_get_giphy_key() { $this->api_key_ajax_permission_check(); $key = get_option( 'bdthemes_giphy_api_key' ); $key = is_string( $key ) ? trim( $key ) : ''; wp_send_json_success( array( 'api_key' => $key ? $key : null ) ); } /** * Collect all plugin settings for export. * * @param bool $include_api_keys Whether to include API keys in the export. * @return array */ private function collect_settings_for_export( $include_api_keys = true ) { $provider_ids = array( 'global', 'pexels', 'pixabay', 'unsplash', 'openverse', 'wikimedia', 'giphy', 'openai', 'gemini' ); $settings = array(); if ( $include_api_keys ) { $settings['openai_api_key'] = (string) get_option( 'bdthemes_openai_api_key', '' ); $settings['gemini_api_key'] = (string) get_option( 'bdthemes_gemini_api_key', '' ); $settings['pexels_api_key'] = (string) get_option( 'bdthemes_pexels_api_key', '' ); $settings['unsplash_access_key'] = (string) get_option( 'bdthemes_unsplash_access_key', '' ); $settings['pixabay_api_key'] = (string) get_option( 'bdthemes_pixabay_api_key', '' ); $settings['giphy_api_key'] = (string) get_option( 'bdthemes_giphy_api_key', '' ); } foreach ( $provider_ids as $id ) { $settings[ 'provider_' . $id ] = get_option( 'bdthemes_ai_image_provider_' . $id, '1' ) === '1'; } $settings['max_upload_width'] = (int) get_option( 'bdthemes_ai_image_max_upload_width', 1600 ); $settings['max_upload_height'] = (int) get_option( 'bdthemes_ai_image_max_upload_height', 1200 ); $settings['default_provider'] = (string) get_option( 'bdthemes_ai_image_default_provider', 'pexels' ); $settings['image_attribution'] = get_option( 'bdthemes_ai_image_attribution', '0' ) === '1'; $settings['auto_alt_text'] = get_option( 'bdthemes_ai_image_auto_alt_text', '0' ) === '1'; $settings['auto_title'] = get_option( 'bdthemes_ai_image_auto_title', '0' ) === '1'; $settings['hide_media_modal_tab'] = get_option( 'bdthemes_ai_image_hide_media_modal_tab', '0' ) === '1'; $settings['default_view_mode'] = (string) get_option( 'bdthemes_ai_image_default_view_mode', 'grid' ); $settings['items_per_page'] = (int) get_option( 'bdthemes_ai_image_items_per_page', 20 ); $settings['thumbnail_size'] = (string) get_option( 'bdthemes_ai_image_thumbnail_size', 'small' ); $settings['load_more_mode'] = (string) get_option( 'bdthemes_ai_image_load_more_mode', 'manual' ); $settings['provider_order'] = get_option( 'bdthemes_ai_image_provider_order', array() ); $custom_sizes = get_option( 'bdthemes_ai_image_custom_sizes', array() ); $settings['custom_sizes'] = is_array( $custom_sizes ) ? $custom_sizes : array(); if ( ! is_array( $settings['provider_order'] ) ) { $settings['provider_order'] = array(); } return $settings; } /** * Apply dashboard settings from an array (save / import). * * @param array $input Settings payload. * @param bool $apply_api_keys Whether API key fields should be updated. */ private function apply_dashboard_settings( array $input, $apply_api_keys = true ) { if ( $apply_api_keys ) { if ( isset( $input['openai_api_key'] ) ) { update_option( 'bdthemes_openai_api_key', sanitize_text_field( is_string( $input['openai_api_key'] ) ? trim( $input['openai_api_key'] ) : '' ) ); } if ( isset( $input['gemini_api_key'] ) ) { update_option( 'bdthemes_gemini_api_key', sanitize_text_field( is_string( $input['gemini_api_key'] ) ? trim( $input['gemini_api_key'] ) : '' ) ); } if ( isset( $input['unsplash_access_key'] ) ) { update_option( 'bdthemes_unsplash_access_key', sanitize_text_field( is_string( $input['unsplash_access_key'] ) ? trim( $input['unsplash_access_key'] ) : '' ) ); } if ( isset( $input['giphy_api_key'] ) ) { update_option( 'bdthemes_giphy_api_key', sanitize_text_field( is_string( $input['giphy_api_key'] ) ? trim( $input['giphy_api_key'] ) : '' ) ); } if ( isset( $input['pexels_api_key'] ) ) { update_option( 'bdthemes_pexels_api_key', sanitize_text_field( is_string( $input['pexels_api_key'] ) ? trim( $input['pexels_api_key'] ) : '' ) ); } if ( isset( $input['pixabay_api_key'] ) ) { update_option( 'bdthemes_pixabay_api_key', sanitize_text_field( is_string( $input['pixabay_api_key'] ) ? trim( $input['pixabay_api_key'] ) : '' ) ); } } $provider_ids = array( 'global', 'pexels', 'pixabay', 'unsplash', 'openverse', 'wikimedia', 'giphy', 'openai', 'gemini' ); foreach ( $provider_ids as $id ) { $key = 'provider_' . $id; if ( array_key_exists( $key, $input ) ) { $val = $input[ $key ]; $enabled = ( $val === true || $val === '1' || $val === 1 ); update_option( 'bdthemes_ai_image_provider_' . $id, $enabled ? '1' : '0' ); } } if ( array_key_exists( 'max_upload_width', $input ) ) { update_option( 'bdthemes_ai_image_max_upload_width', absint( $input['max_upload_width'] ) ?: 1600 ); } if ( array_key_exists( 'max_upload_height', $input ) ) { update_option( 'bdthemes_ai_image_max_upload_height', absint( $input['max_upload_height'] ) ?: 1200 ); } if ( array_key_exists( 'default_provider', $input ) ) { update_option( 'bdthemes_ai_image_default_provider', sanitize_text_field( $input['default_provider'] ) ); } if ( array_key_exists( 'image_attribution', $input ) ) { $v = $input['image_attribution']; update_option( 'bdthemes_ai_image_attribution', ( $v === true || $v === '1' || $v === 1 ) ? '1' : '0' ); } if ( array_key_exists( 'auto_alt_text', $input ) ) { $v = $input['auto_alt_text']; update_option( 'bdthemes_ai_image_auto_alt_text', ( $v === true || $v === '1' || $v === 1 ) ? '1' : '0' ); } if ( array_key_exists( 'auto_title', $input ) ) { $v = $input['auto_title']; update_option( 'bdthemes_ai_image_auto_title', ( $v === true || $v === '1' || $v === 1 ) ? '1' : '0' ); } if ( array_key_exists( 'hide_media_modal_tab', $input ) ) { $v = $input['hide_media_modal_tab']; update_option( 'bdthemes_ai_image_hide_media_modal_tab', ( $v === true || $v === '1' || $v === 1 ) ? '1' : '0' ); } if ( array_key_exists( 'default_view_mode', $input ) ) { $mode = sanitize_text_field( $input['default_view_mode'] ); update_option( 'bdthemes_ai_image_default_view_mode', $mode === 'list' ? 'list' : 'grid' ); } if ( array_key_exists( 'items_per_page', $input ) ) { $items = absint( $input['items_per_page'] ); update_option( 'bdthemes_ai_image_items_per_page', ( $items >= 20 && $items <= 100 ) ? $items : 30 ); } if ( array_key_exists( 'thumbnail_size', $input ) ) { $size = sanitize_text_field( $input['thumbnail_size'] ); $allowed_sizes = array( 'small', 'medium', 'large' ); update_option( 'bdthemes_ai_image_thumbnail_size', in_array( $size, $allowed_sizes, true ) ? $size : 'medium' ); } if ( array_key_exists( 'load_more_mode', $input ) ) { $mode = sanitize_text_field( $input['load_more_mode'] ); $allowed_modes = array( 'auto', 'manual' ); update_option( 'bdthemes_ai_image_load_more_mode', in_array( $mode, $allowed_modes, true ) ? $mode : 'auto' ); } if ( array_key_exists( 'provider_order', $input ) && is_array( $input['provider_order'] ) ) { $valid_ids = array( 'pexels', 'pixabay', 'unsplash', 'openverse', 'wikimedia', 'giphy', 'openai', 'gemini' ); $order = array_values( array_intersect( array_map( 'sanitize_text_field', $input['provider_order'] ), $valid_ids ) ); if ( ! empty( $order ) ) { update_option( 'bdthemes_ai_image_provider_order', $order ); } } } /** * Import custom image sizes from export payload. * * @param mixed $custom_sizes Custom sizes from export file. */ private function apply_custom_sizes_import( $custom_sizes ) { if ( ! is_array( $custom_sizes ) ) { return; } $sanitized = array(); $reserved = array( 'thumbnail', 'medium', 'medium_large', 'large' ); foreach ( $custom_sizes as $name => $size ) { $name = sanitize_title( $name ); if ( empty( $name ) || in_array( $name, $reserved, true ) || ! is_array( $size ) ) { continue; } $width = absint( $size['width'] ?? 0 ); $height = absint( $size['height'] ?? 0 ); if ( $width < 1 || $height < 1 || $width > 1920 || $height > 3000 ) { continue; } $sanitized[ $name ] = array( 'width' => $width, 'height' => $height, 'crop' => ! empty( $size['crop'] ), ); } update_option( 'bdthemes_ai_image_custom_sizes', $sanitized ); } /** * AJAX: export dashboard settings as JSON. */ public function ajax_export_settings() { $this->api_key_ajax_permission_check(); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $include_api_keys = ! isset( $_POST['include_api_keys'] ) || $_POST['include_api_keys'] === '1' || $_POST['include_api_keys'] === 'true'; $bundle = array( 'plugin_version' => defined( 'BDT_AI_IMAGE_VERSION' ) ? BDT_AI_IMAGE_VERSION : '', 'exported_at' => gmdate( 'c' ), 'include_api_keys' => (bool) $include_api_keys, 'settings' => $this->collect_settings_for_export( $include_api_keys ), ); wp_send_json_success( array( 'bundle' => $bundle ) ); } /** * AJAX: import dashboard settings from JSON. */ public function ajax_import_settings() { $this->api_key_ajax_permission_check(); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput -- Nonce verified in api_key_ajax_permission_check() $raw = isset( $_POST['data'] ) && is_string( $_POST['data'] ) ? stripslashes( $_POST['data'] ) : ''; if ( $raw === '' ) { wp_send_json_error( array( 'message' => __( 'No import data provided.', 'ai-image' ) ) ); } $bundle = json_decode( $raw, true ); if ( ! is_array( $bundle ) ) { wp_send_json_error( array( 'message' => __( 'Invalid JSON file.', 'ai-image' ) ) ); } $settings = isset( $bundle['settings'] ) && is_array( $bundle['settings'] ) ? $bundle['settings'] : null; if ( ! is_array( $settings ) ) { wp_send_json_error( array( 'message' => __( 'Export file is missing settings.', 'ai-image' ) ) ); } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $import_api_keys = ! isset( $_POST['import_api_keys'] ) || $_POST['import_api_keys'] === '1' || $_POST['import_api_keys'] === 'true'; $has_api_keys = isset( $settings['openai_api_key'] ) || isset( $settings['gemini_api_key'] ) || isset( $settings['pexels_api_key'] ) || isset( $settings['unsplash_access_key'] ) || isset( $settings['pixabay_api_key'] ) || isset( $settings['giphy_api_key'] ); $apply_api_keys = $import_api_keys && ( $has_api_keys || ! empty( $bundle['include_api_keys'] ) ); $custom_sizes = $settings['custom_sizes'] ?? null; unset( $settings['custom_sizes'] ); $this->apply_dashboard_settings( $settings, $apply_api_keys ); if ( null !== $custom_sizes ) { $this->apply_custom_sizes_import( $custom_sizes ); } wp_send_json_success( array( 'message' => __( 'Settings imported successfully.', 'ai-image' ) ) ); } /** * AJAX: save dashboard settings (API keys). */ public function ajax_save_settings() { $this->api_key_ajax_permission_check(); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput -- Nonce verified in api_key_ajax_permission_check(), data is JSON decoded and sanitized per field $input = isset( $_POST['data'] ) && is_string( $_POST['data'] ) ? json_decode( stripslashes( $_POST['data'] ), true ) : null; if ( ! is_array( $input ) ) { wp_send_json_error( array( 'message' => __( 'Invalid request.', 'ai-image' ) ) ); } $this->apply_dashboard_settings( $input, true ); wp_send_json_success( array( 'message' => __( 'Settings saved.', 'ai-image' ) ) ); } /** * AJAX: test API key for a provider. */ public function ajax_test_api_key() { $this->api_key_ajax_permission_check(); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $provider = isset( $_POST['provider'] ) ? sanitize_text_field( wp_unslash( $_POST['provider'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $api_key = isset( $_POST['api_key'] ) ? sanitize_text_field( wp_unslash( $_POST['api_key'] ) ) : ''; if ( empty( $provider ) ) { wp_send_json_error( array( 'message' => __( 'Provider required.', 'ai-image' ) ) ); } if ( empty( $api_key ) ) { if ( 'openai' === $provider ) { $api_key = get_option( 'bdthemes_openai_api_key' ); } elseif ( 'unsplash' === $provider ) { $api_key = get_option( 'bdthemes_unsplash_access_key' ); // For Unsplash, if no custom key is saved, use default key for testing if ( empty( trim( $api_key ) ) ) { $api_key = \BDT_AI_IMG\decrypt_key( AI_IMAGE_UNSPLASH_DEFAULT_KEY ); } } elseif ( 'pexels' === $provider ) { $api_key = get_option( 'bdthemes_pexels_api_key' ); // For Pexels, if no custom key is saved, use default key for testing if ( empty( trim( $api_key ) ) ) { $api_key = \BDT_AI_IMG\decrypt_key( AI_IMAGE_PEXELS_DEFAULT_KEY ); } } elseif ( 'pixabay' === $provider ) { $api_key = get_option( 'bdthemes_pixabay_api_key' ); // For Pixabay, if no custom key is saved, use default key for testing if ( empty( trim( $api_key ) ) ) { $api_key = \BDT_AI_IMG\decrypt_key( AI_IMAGE_PIXABAY_DEFAULT_KEY ); } } elseif ( 'giphy' === $provider ) { $api_key = get_option( 'bdthemes_giphy_api_key' ); // For Giphy, if no custom key is saved, use default key for testing if ( empty( trim( $api_key ) ) ) { $api_key = \BDT_AI_IMG\decrypt_key( AI_IMAGE_GIPHY_DEFAULT_KEY ); } } elseif ( 'gemini' === $provider ) { $api_key = get_option( 'bdthemes_gemini_api_key' ); } $api_key = is_string( $api_key ) ? trim( $api_key ) : ''; } // If user provides a key via POST, test that exact key (don't fallback) // The fallback only applies when api_key POST param is completely empty if ( empty( $api_key ) ) { wp_send_json_error( array( 'message' => __( 'No API key provided.', 'ai-image' ) ) ); } $result = $this->test_provider_key( $provider, $api_key ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Key valid.', 'ai-image' ) ) ); } /** * Minimal check for provider API key. * * @param string $provider openai|unsplash|giphy * @param string $api_key * @return true|WP_Error */ private function test_provider_key( $provider, $api_key ) { if ( 'openai' === $provider ) { $response = wp_remote_get( 'https://api.openai.com/v1/models', array( 'headers' => array( 'Authorization' => 'Bearer ' . $api_key ), 'timeout' => 15, ) ); if ( is_wp_error( $response ) ) { return $response; } $code = wp_remote_retrieve_response_code( $response ); if ( $code === 200 ) { return true; } $body = json_decode( wp_remote_retrieve_body( $response ), true ); $msg = isset( $body['error']['message'] ) ? $body['error']['message'] : __( 'OpenAI key invalid.', 'ai-image' ); return new \WP_Error( 'openai_test_failed', $msg ); } if ( 'unsplash' === $provider ) { $response = wp_remote_get( 'https://api.unsplash.com/photos?per_page=1', array( 'headers' => array( 'Authorization' => 'Client-ID ' . $api_key ), 'timeout' => 15, ) ); if ( is_wp_error( $response ) ) { return $response; } $code = wp_remote_retrieve_response_code( $response ); if ( $code === 200 ) { return true; } $body = json_decode( wp_remote_retrieve_body( $response ), true ); $msg = isset( $body['errors'][0] ) ? $body['errors'][0] : __( 'Unsplash Access key not valid. Please pass a valid Access key.', 'ai-image' ); return new \WP_Error( 'unsplash_test_failed', $msg ); } if ( 'giphy' === $provider ) { $response = wp_remote_get( 'https://api.giphy.com/v1/gifs/trending?api_key=' . urlencode( $api_key ) . '&limit=1', array( 'timeout' => 15 ) ); if ( is_wp_error( $response ) ) { return $response; } $code = wp_remote_retrieve_response_code( $response ); if ( $code === 200 ) { return true; } return new \WP_Error( 'giphy_test_failed', __( 'Giphy API key not valid. Please pass a valid API key.', 'ai-image' ) ); } if ( 'gemini' === $provider ) { $response = wp_remote_get( 'https://generativelanguage.googleapis.com/v1beta/models?key=' . urlencode( $api_key ), array( 'timeout' => 15 ) ); if ( is_wp_error( $response ) ) { return $response; } $code = wp_remote_retrieve_response_code( $response ); if ( $code === 200 ) { return true; } $body = json_decode( wp_remote_retrieve_body( $response ), true ); $msg = isset( $body['error']['message'] ) ? $body['error']['message'] : __( 'Gemini key invalid.', 'ai-image' ); return new \WP_Error( 'gemini_test_failed', $msg ); } if ( 'pexels' === $provider ) { function generateRandomWords($count = 5) { $letters = 'abcdefghijklmnopqrstuvwxyz'; function createWord($letters) { $length = wp_rand(4, 8); // word length between 4–8 characters $word = ''; for ($i = 0; $i < $length; $i++) { $word .= $letters[wp_rand(0, strlen($letters) - 1)]; } return $word; } $words = []; for ($i = 0; $i < $count; $i++) { $words[] = createWord($letters); } return $words; } $response = wp_remote_get( 'https://api.pexels.com/v1/search?query=' . urlencode(implode(' ', generateRandomWords())) . '&per_page=1', array( 'headers' => array( 'Authorization' => $api_key ), 'timeout' => 15, ) ); if ( is_wp_error( $response ) ) { return $response; } $code = wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); // Check for error responses if ( $code === 401 || $code === 403 ) { return new \WP_Error( 'pexels_test_failed', __( 'Pexels API key not valid. Invalid or unauthorized key.', 'ai-image' ) ); } if ( $code !== 200 ) { return new \WP_Error( 'pexels_test_failed', __( 'Pexels API key not valid. Please pass a valid API key.', 'ai-image' ) ); } // Parse and validate response structure $data = json_decode( $body, true ); // Check JSON decode errors if ( json_last_error() !== JSON_ERROR_NONE ) { return new \WP_Error( 'pexels_test_failed', __( 'Pexels API returned invalid JSON.', 'ai-image' ) ); } // Check for error field in response if ( isset( $data['error'] ) ) { /* translators: %s: error message from Pexels API */ return new \WP_Error( 'pexels_test_failed', sprintf( __( 'Pexels API key not valid: %s', 'ai-image' ), $data['error'] ) ); } // Verify response has the expected photos array structure // A valid API key should return a response with a photos array (even if empty) if ( ! isset( $data['photos'] ) || ! is_array( $data['photos'] ) ) { return new \WP_Error( 'pexels_test_failed', __( 'Pexels API key not valid. Unexpected response format.', 'ai-image' ) ); } return true; } if ( 'pixabay' === $provider ) { $response = wp_remote_get( 'https://pixabay.com/api/?key=' . urlencode( $api_key ) . '&q=nature&per_page=3', array( 'timeout' => 15 ) ); if ( is_wp_error( $response ) ) { return $response; } $code = wp_remote_retrieve_response_code( $response ); if ( $code === 200 ) { return true; } return new \WP_Error( 'pixabay_test_failed', __( 'Pixabay API key not valid. Please pass a valid API key.', 'ai-image' ) ); } return new \WP_Error( 'unknown_provider', __( 'Unknown provider.', 'ai-image' ) ); } /** * AJAX: Generate Gemini image (proxy to avoid CORS). */ public function ajax_generate_gemini_image() { $this->api_key_ajax_permission_check(); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $prompt = isset( $_POST['prompt'] ) ? sanitize_text_field( wp_unslash( $_POST['prompt'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $num_images = isset( $_POST['number_of_images'] ) ? absint( $_POST['number_of_images'] ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in api_key_ajax_permission_check() $aspect_ratio = isset( $_POST['aspect_ratio'] ) ? sanitize_text_field( wp_unslash( $_POST['aspect_ratio'] ) ) : '1:1'; if ( empty( $prompt ) ) { wp_send_json_error( array( 'message' => __( 'Prompt is required.', 'ai-image' ) ) ); } $api_key = get_option( 'bdthemes_gemini_api_key' ); $api_key = is_string( $api_key ) ? trim( $api_key ) : ''; if ( empty( $api_key ) ) { wp_send_json_error( array( 'message' => __( 'No Gemini API key configured.', 'ai-image' ) ) ); } // Use Imagen 4.0 model which is available in the API // Available models: imagen-4.0-generate-001 (stable), imagen-4.0-fast-generate-001, imagen-4.0-ultra-generate-001 // These models use :predict method $url = 'https://generativelanguage.googleapis.com/v1beta/models/imagen-4.0-generate-001:predict?key=' . urlencode( $api_key ); // Correct request format for predict API $body = wp_json_encode( array( 'instances' => array( array( 'prompt' => $prompt ) ), 'parameters' => array( 'sampleCount' => min( max( 1, $num_images ), 4 ), 'aspectRatio' => $aspect_ratio, 'safetyFilterLevel' => 'block_some', 'personGeneration' => 'allow_adult' ) ) ); $response = wp_remote_post( $url, array( 'headers' => array( 'Content-Type' => 'application/json', ), 'body' => $body, 'timeout' => 60, ) ); if ( is_wp_error( $response ) ) { wp_send_json_error( array( 'message' => $response->get_error_message() ) ); } $code = wp_remote_retrieve_response_code( $response ); $response_body = wp_remote_retrieve_body( $response ); $data = json_decode( $response_body, true ); if ( $code !== 200 ) { $error_msg = isset( $data['error']['message'] ) ? $data['error']['message'] : __( 'Gemini API request failed.', 'ai-image' ); $error_details = isset( $data['error'] ) ? $data['error'] : null; wp_send_json_error( array( 'message' => $error_msg, 'code' => $code, 'raw_response' => $data, 'error_details' => $error_details ) ); } wp_send_json_success( array( 'data' => $data ) ); } /** * Init * * @since 1.0.0 */ public function init() { $this->setup_hooks(); } } if ( class_exists( 'BDT_AI_IMG\Plugin' ) ) { \BDT_AI_IMG\Plugin::instance(); }