settings = $settings; $this->api_key = $settings['ai_api_key'] ?? ''; if ( empty( $this->api_key ) && ( ! defined( 'AITM_DEBUG_MODE' ) || ! AITM_DEBUG_MODE ) ) { throw new Exception( esc_html__( 'OpenAI API key is not configured', 'ai-thumbnails-maker' ) ); } } /** * Generate placeholder image for debug mode * * @param string $size Image size (e.g., '1024x1024'). * @return array Image data with path and URL. */ private function generate_debug_placeholder( $size ) { // Parse size list($width, $height) = explode( 'x', $size ); $width = (int) $width; $height = (int) $height; // Create image using GD $image = imagecreatetruecolor( $width, $height ); // Set colors $bg_color = imagecolorallocate( $image, 100, 100, 255 ); $text_color = imagecolorallocate( $image, 255, 255, 255 ); // Fill background imagefill( $image, 0, 0, $bg_color ); // Add text "AI Debug Mode" $text = 'AI Debug Mode'; $font_size = 5; $text_width = imagefontwidth( $font_size ) * strlen( $text ); $text_height = imagefontheight( $font_size ); $x = ( $width - $text_width ) / 2; $y = ( $height - $text_height ) / 2; imagestring( $image, $font_size, $x, $y, $text, $text_color ); // Save to temp file $upload_dir = wp_upload_dir(); $filename = 'ai-debug-' . time() . '-' . wp_generate_password( 8, false ) . '.png'; $file_path = $upload_dir['path'] . '/' . $filename; imagepng( $image, $file_path ); imagedestroy( $image ); return array( 'path' => $file_path, 'url' => $upload_dir['url'] . '/' . $filename, 'size' => $size, ); } /** * Attach image file to post * * @param string $file_path Path to image file. * @param int $post_id Post ID. * @return int|false Attachment ID or false on failure. */ private function attach_image_to_post( $file_path, $post_id ) { $file_type = wp_check_filetype( basename( $file_path ), null ); $attachment = array( 'post_mime_type' => $file_type['type'], 'post_title' => sanitize_file_name( basename( $file_path ) ), 'post_content' => '', 'post_status' => 'inherit', ); $attachment_id = wp_insert_attachment( $attachment, $file_path, $post_id ); if ( ! is_wp_error( $attachment_id ) ) { require_once ABSPATH . 'wp-admin/includes/image.php'; $attach_data = wp_generate_attachment_metadata( $attachment_id, $file_path ); wp_update_attachment_metadata( $attachment_id, $attach_data ); return $attachment_id; } return false; } /** * Get available prompt models * * @return array List of prompt models. */ public static function get_prompt_models() { return array( array( 'model' => 'gpt-4o', 'name' => 'GPT-4o', ), array( 'model' => 'gpt-4o-mini', 'name' => 'GPT-4o Mini', ), array( 'model' => 'gpt-4-turbo', 'name' => 'GPT-4 Turbo', ), array( 'model' => 'gpt-4-turbo-preview', 'name' => 'GPT-4 Turbo Preview', ), array( 'model' => 'gpt-4', 'name' => 'GPT-4', ), array( 'model' => 'gpt-4-32k', 'name' => 'GPT-4 32k', ), array( 'model' => 'gpt-3.5-turbo', 'name' => 'GPT-3.5 Turbo', ), array( 'model' => 'gpt-3.5-turbo-16k', 'name' => 'GPT-3.5 Turbo 16k', ), ); } /** * Get available image models * * @return array List of image models. */ public static function get_image_models() { return array( array( 'model' => 'dall-e-3', 'name' => 'DALL-E 3', ), array( 'model' => 'dall-e-2', 'name' => 'DALL-E 2', ), ); } /** * Generate featured image for post * * @param int $post_id Post ID. * @return int Attachment ID of generated image. * @throws Exception If generation fails. */ public function generate_for_post( $post_id ) { $post = get_post( $post_id ); if ( empty( $post ) ) { throw new Exception( sprintf( 'Post %d is not found!', (int) $post_id ) ); } // Debug mode: emulate AI generation without real API calls if ( defined( 'AITM_DEBUG_MODE' ) && AITM_DEBUG_MODE ) { // Random sleep 15-25 seconds to simulate API call sleep( wp_rand( 15, 25 ) ); // Get size from settings $size = $this->settings['ai_resolution'] ?? '1024x1024'; // Generate placeholder image $debug_image = $this->generate_debug_placeholder( $size ); // Attach to post $attachment_id = $this->attach_image_to_post( $debug_image['path'], $post_id ); if ( $attachment_id ) { set_post_thumbnail( $post_id, $attachment_id ); return $attachment_id; } throw new Exception( esc_html__( 'Debug mode: Failed to attach image', 'ai-thumbnails-maker' ) ); } // Get post content for prompt generation $post_content = $this->get_post_content( $post_id ); if ( empty( $post_content ) ) { throw new Exception( esc_html__( 'Post content is empty', 'ai-thumbnails-maker' ) ); } // Generate prompt using GPT $prompt = $this->generate_prompt( $post_content ); // Generate image using DALL-E $image_url = $this->generate_image( $prompt ); // Add image to media library $attachment_id = $this->add_image_from_url( $image_url, $post_id ); return $attachment_id; } /** * Extract content from post based on source setting * * @param int $post_id Post ID. * @return string Extracted content. */ protected function get_post_content( $post_id ) { $post = get_post( $post_id ); $content = $post->post_content; $title = $post->post_title; $content_source = $this->settings['ai_content_source'] ?? 'title_first_paragraph'; $blocks = parse_blocks( $content ); $paragraphs = array(); // Extract paragraphs if ( count( $blocks ) === 1 && $blocks[0]['blockName'] === null ) { // Non-Gutenberg posts $html_content = wpautop( $content ); preg_match_all( '/

(.*?)<\/p>/s', $html_content, $matches ); if ( ! empty( $matches[1] ) ) { foreach ( $matches[1] as $paragraph ) { $clean = wp_strip_all_tags( $paragraph ); if ( strlen( trim( $clean ) ) > 50 ) { $paragraphs[] = $clean; } } } } else { // Gutenberg blocks foreach ( $blocks as $block ) { if ( $block['blockName'] === 'core/paragraph' ) { $clean = wp_strip_all_tags( $block['innerHTML'] ); if ( strlen( trim( $clean ) ) > 50 ) { $paragraphs[] = $clean; } } } } // Build content based on source setting $result = ''; switch ( $content_source ) { case 'title_first_paragraph': $result = $title; if ( ! empty( $paragraphs[0] ) ) { $result .= '. ' . $paragraphs[0]; } break; case 'title_second_paragraph': $result = $title; if ( ! empty( $paragraphs[1] ) ) { $result .= '. ' . $paragraphs[1]; } elseif ( ! empty( $paragraphs[0] ) ) { $result .= '. ' . $paragraphs[0]; } break; case 'title_third_paragraph': $result = $title; if ( ! empty( $paragraphs[2] ) ) { $result .= '. ' . $paragraphs[2]; } elseif ( ! empty( $paragraphs[0] ) ) { $result .= '. ' . $paragraphs[0]; } break; case 'random_paragraphs': $result = $title; if ( count( $paragraphs ) > 0 ) { // Take 2-3 random paragraphs $count = min( 3, count( $paragraphs ) ); $random_keys = array_rand( $paragraphs, $count ); if ( ! is_array( $random_keys ) ) { $random_keys = array( $random_keys ); } foreach ( $random_keys as $key ) { $result .= '. ' . $paragraphs[ $key ]; } } break; case 'full_content': $result = $title . '. ' . wp_strip_all_tags( $content ); // Limit to 3000 characters to avoid too high costs $result = substr( $result, 0, 3000 ); break; default: $result = $title; if ( ! empty( $paragraphs[0] ) ) { $result .= '. ' . $paragraphs[0]; } } return $result; } /** * Generate prompt using GPT * * @param string $post_content Post content. * @return string Generated prompt. * @throws Exception If prompt generation fails. */ protected function generate_prompt( $post_content ) { $model = $this->settings['ai_prompt_model'] ?? 'gpt-4o-mini'; $lang = $this->settings['ai_language'] ?? 'en_US'; $content_source = $this->settings['ai_content_source'] ?? 'title_first_paragraph'; // Build system prompt based on content source $system_instructions = 'You are an expert AI assistant specialized in creating detailed, vivid image generation prompts for DALL-E.'; // Build user prompt based on content source $user_prompt = sprintf( 'Based on the following %s text, create a detailed and descriptive prompt for DALL-E to generate a featured image. The image should be visually appealing, relevant to the content, and suitable as a blog post thumbnail. Focus on creating a professional, eye-catching illustration.', $this->get_language_name( $lang ) ); // Add context about content source switch ( $content_source ) { case 'full_content': $user_prompt .= ' The full article content is provided, so create a comprehensive image that captures the main theme.'; break; case 'random_paragraphs': $user_prompt .= ' Key excerpts from the article are provided to give you context.'; break; default: $user_prompt .= ' The title and introduction are provided.'; } $user_prompt .= "\n\nContent:\n" . $post_content; $user_prompt .= "\n\nRespond ONLY with the DALL-E prompt, no additional text."; $response = $this->request( 'chat/completions', array( 'model' => $model, 'messages' => array( array( 'role' => 'system', 'content' => $system_instructions, ), array( 'role' => 'user', 'content' => $user_prompt, ), ), ) ); if ( ! isset( $response['choices'][0]['message']['content'] ) ) { throw new Exception( esc_html__( 'Invalid response from GPT', 'ai-thumbnails-maker' ) ); } $prompt = trim( $response['choices'][0]['message']['content'] ); $prompt = str_replace( array( 'Prompt for DALL-E: ', 'DALL-E Prompt: ', 'Prompt: ' ), '', $prompt ); return $prompt; } /** * Get language name from code * * @param string $lang_code Language code. * @return string Language name. */ protected function get_language_name( $lang_code ) { $languages = array( 'en_US' => 'English', 'ru_RU' => 'Russian', 'es_ES' => 'Spanish', 'fr_FR' => 'French', 'de_DE' => 'German', 'zh_CN' => 'Chinese', 'ja_JP' => 'Japanese', 'it_IT' => 'Italian', 'ko_KR' => 'Korean', 'pt_PT' => 'Portuguese', 'ar_SA' => 'Arabic', 'hi_IN' => 'Hindi', ); return $languages[ $lang_code ] ?? 'English'; } /** * Generate image using DALL-E * * @param string $prompt Image prompt. * @return string Image URL. * @throws Exception If image generation fails. */ protected function generate_image( $prompt ) { $model = $this->settings['ai_image_model'] ?? 'dall-e-3'; $resolution = $this->settings['ai_image_resolution'] ?? '1024x1024'; $style = $this->settings['ai_image_style'] ?? 'natural'; $body = array( 'model' => $model, 'prompt' => $prompt, 'n' => 1, 'size' => $resolution, ); // Add style only if not 'none' and model supports it if ( $style !== 'none' && $model === 'dall-e-3' ) { $body['style'] = $style; } $response = $this->request( 'images/generations', $body ); if ( ! isset( $response['data'][0]['url'] ) ) { throw new Exception( esc_html__( 'Invalid response from DALL-E', 'ai-thumbnails-maker' ) ); } return $response['data'][0]['url']; } /** * Send request to OpenAI API * * @param string $endpoint API endpoint. * @param array $body Request body. * @return array Response data. * @throws Exception If request fails. */ protected function request( $endpoint, $body ) { $request = wp_remote_post( 'https://api.openai.com/v1/' . $endpoint, array( 'timeout' => 60, 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8', 'Authorization' => sprintf( 'Bearer %s', $this->api_key ), ), 'body' => wp_json_encode( $body ), ) ); if ( is_wp_error( $request ) ) { throw new Exception( esc_html( $request->get_error_message() ) ); } $response = json_decode( wp_remote_retrieve_body( $request ), true ); if ( ! is_object( $response ) && ! is_array( $response ) ) { throw new Exception( esc_html__( 'Request returned an empty result!', 'ai-thumbnails-maker' ) ); } if ( isset( $response['error'] ) ) { throw new Exception( esc_html( $response['error']['message'] ) ); } return $response; } /** * Add image from URL to media library * * @param string $url Image URL. * @param int $post_id Post ID. * @return int Attachment ID. * @throws Exception If image download or upload fails. */ protected function add_image_from_url( $url, $post_id ) { $parsed_url = wp_parse_url( $url ); if ( empty( $parsed_url['path'] ) ) { throw new Exception( 'Invalid image URL.' ); } $path_parts = pathinfo( $parsed_url['path'] ); $url_filename = $path_parts['basename']; $file_type = wp_check_filetype( $url_filename, null ); $allowed_types = get_allowed_mime_types(); if ( ! $file_type || ! in_array( $file_type['type'], $allowed_types, true ) ) { throw new Exception( 'Invalid file type from URL.' ); } $extension = $file_type['ext']; // Download image $image_data = $this->download_image( $url ); if ( ! $image_data ) { throw new Exception( 'Could not download the image.' ); } // Generate filename $filename = $this->get_random_id( 16 ) . '.' . $extension; // Store the file through WordPress upload APIs so paths and permissions follow site settings. $upload = wp_upload_bits( $filename, null, $image_data ); if ( ! empty( $upload['error'] ) ) { throw new Exception( esc_html( $upload['error'] ) ); } $file = $upload['file']; // Create attachment $attachment = array( 'post_mime_type' => $file_type['type'], 'post_title' => '', 'post_content' => '', 'post_status' => 'inherit', ); $attachment_id = wp_insert_attachment( $attachment, $file, $post_id ); require_once ABSPATH . 'wp-admin/includes/image.php'; $attachment_data = wp_generate_attachment_metadata( $attachment_id, $file ); wp_update_attachment_metadata( $attachment_id, $attachment_data ); return $attachment_id; } /** * Download image from URL * * @param string $url Image URL. * @return string Image content. * @throws Exception If download fails. */ protected function download_image( $url ) { $args = array( 'timeout' => 60 ); $response = wp_safe_remote_get( $url, $args ); if ( is_wp_error( $response ) ) { throw new Exception( esc_html( $response->get_error_message() ) ); } $output = wp_remote_retrieve_body( $response ); if ( is_wp_error( $output ) ) { throw new Exception( esc_html( $output->get_error_message() ) ); } return $output; } /** * Generate random alphanumeric ID * * @param int $length ID length. * @return string Random ID. */ protected function get_random_id( $length = 8 ) { $characters = '0123456789abcdefghijklmnopqrstuvwxyz'; $characters_length = strlen( $characters ); $random_id = ''; for ( $i = 0; $i < $length; $i++ ) { $random_id .= $characters[ wp_rand( 0, $characters_length - 1 ) ]; } return $random_id; } }