'system', 'content' => AIWEST_OpenAI::system_prompt()], ['role' => 'user', 'content' => "Write a complete SEO article for WordPress. Default content language: ".AIWEST_Languages::language_name(AIWEST_Languages::content_locale()).". Use a different language only when explicitly requested.\n" . __("Topic: ", 'ai-webmaster-studio') . $topic . "\n" . __("Keyword: ", 'ai-webmaster-studio') . $keyword . "\n" . __("Length: ", 'ai-webmaster-studio') . $length . "\n" . __("Return the result as JSON with fields: title, excerpt, meta_title, meta_description, html_content.\n", 'ai-webmaster-studio') . $taxonomy_instruction . $media_instruction . __("HTML must be clean and semantic, without markdown or ``` fences. Do not add an H1 or repeat the post title as the first heading. Start with an introduction, then use H2/H3.", 'ai-webmaster-studio') . "\n\n" . (class_exists('AIWEST_Search_Optimization') ? AIWEST_Search_Optimization::prompt_contract(['post_type'=>'post']) : '')] ]; $result = AIWEST_OpenAI::chat($messages, 0.5); if (is_wp_error($result)) { AIWEST_Tasks::complete($task_id, ['error' => $result->get_error_message()]); if ($history_id) AIWEST_History::update_content($history_id, 'error', null, $result->get_error_message()); return $result; } $clean = trim($result); $clean = preg_replace('/^```json\s*/i', '', $clean); $clean = preg_replace('/^```\s*/', '', $clean); $clean = preg_replace('/\s*```$/', '', $clean); $data = json_decode($clean, true); if (!is_array($data)) { $first = strpos($clean, '{'); $last = strrpos($clean, '}'); if ($first !== false && $last !== false && $last > $first) { $data = json_decode(substr($clean, $first, $last - $first + 1), true); } } if (!is_array($data) || empty($data['title']) || empty($data['html_content'])) { $msg = __('AI did not return a valid JSON format.', 'ai-webmaster-studio'); AIWEST_Tasks::complete($task_id, ['error' => $msg, 'raw_excerpt' => wp_trim_words(wp_strip_all_tags($result), 80, '...')]); if ($history_id) AIWEST_History::update_content($history_id, 'error', null, $msg); return new WP_Error('invalid_ai_json', $msg); } $allowed_status = in_array($status, ['draft', 'publish'], true) ? $status : 'draft'; if ($allowed_status === 'publish' && get_option('aiwest_auto_publish_posts') !== 'yes') $allowed_status = 'draft'; if (class_exists('AIWEST_Technical_Operator')) { $allowed_status = AIWEST_Technical_Operator::default_post_status($allowed_status); $aiwest_operator_decision = AIWEST_Technical_Operator::explain_decision('create_post', 'content_post_create', ['post_type' => 'post', 'post_status' => $allowed_status]); } else { $aiwest_operator_decision = []; } $post_title = sanitize_text_field($data['title']); $post_content = wp_kses_post($data['html_content']); $heading_normalization = ['content' => $post_content, 'report' => []]; if (class_exists('AIWEST_Page_Title_Heading_Normalizer')) { $heading_normalization = AIWEST_Page_Title_Heading_Normalizer::normalize(0, $post_content, [ 'post_title' => $post_title, 'post_type' => 'post', 'mode' => 'standard', 'theme_title_visible' => true, ]); $post_content = $heading_normalization['content']; } $post_id = wp_insert_post([ 'post_title' => $post_title, 'post_excerpt' => sanitize_textarea_field($data['excerpt'] ?? ''), 'post_content' => $post_content, 'post_status' => $allowed_status, 'post_type' => 'post', 'post_author' => class_exists('AIWEST_Technical_Operator') ? AIWEST_Technical_Operator::post_author_id(get_current_user_id()) : (get_current_user_id() ?: 1), ], true); if (is_wp_error($post_id)) { AIWEST_Tasks::complete($task_id, ['error' => $post_id->get_error_message()]); if ($history_id) AIWEST_History::update_content($history_id, 'error', null, $post_id->get_error_message()); return $post_id; } $taxonomy_result = self::assign_ai_taxonomy($post_id, $data); update_post_meta($post_id, '_aiwest_meta_title', sanitize_text_field($data['meta_title'] ?? '')); update_post_meta($post_id, '_aiwest_meta_description', sanitize_textarea_field($data['meta_description'] ?? '')); update_post_meta($post_id, '_aiwest_generated', 'yes'); update_post_meta($post_id, '_aiwest_source', sanitize_text_field($source)); update_post_meta($post_id, '_aiwest_taxonomy_result', $taxonomy_result); update_post_meta($post_id, '_aiwest_media_options', $media_options); $search_report = class_exists('AIWEST_Search_Optimization') ? AIWEST_Search_Optimization::finalize($post_id, $post_content, ['post_type'=>'post','post_title'=>$post_title], $data) : []; if (class_exists('AIWEST_Page_Title_Heading_Normalizer')) { AIWEST_Page_Title_Heading_Normalizer::record_report($post_id, $heading_normalization['report']); } if (class_exists('AIWEST_Technical_Operator')) { AIWEST_Technical_Operator::annotate_post($post_id, 'create_post', $aiwest_operator_decision); } $media_result = self::handle_media($post_id, $data, $topic, $keyword, $media_options); update_post_meta($post_id, '_aiwest_media_result', $media_result); AIWEST_Tasks::complete($task_id, [ 'post_id' => $post_id, 'status' => $allowed_status, 'source' => $source, 'taxonomy' => $taxonomy_result, 'media' => $media_result, 'search_optimization' => $search_report, ]); AIWEST_Logger::info('AI content generated.', ['post_id' => $post_id, 'topic' => $topic, 'source' => $source, 'taxonomy' => $taxonomy_result, 'media' => $media_result]); if ($history_id) { $msg = $allowed_status === 'publish' ? __('The post was published.', 'ai-webmaster-studio') : __('The post was created as a draft.', 'ai-webmaster-studio'); if (!empty($taxonomy_result['category'])) { $msg .= ' ' . sprintf( /* translators: %s: Assigned WordPress category name. */ __('Category: %s.', 'ai-webmaster-studio'), $taxonomy_result['category'] ); } if (!empty($taxonomy_result['tags'])) { $msg .= ' ' . sprintf( /* translators: %s: Comma-separated list of assigned WordPress tags. */ __('Tags: %s.', 'ai-webmaster-studio'), implode(', ', $taxonomy_result['tags']) ); } if (!empty($media_result['message'])) $msg .= ' ' . $media_result['message']; AIWEST_History::update_content($history_id, 'completed', $post_id, $msg); } return $post_id; } public static function sanitize_media_options($options) { $options = is_array($options) ? $options : []; if (class_exists('AIWEST_Graphics_Rules')) { return AIWEST_Graphics_Rules::resolve($options); } $action = sanitize_key($options['action'] ?? 'site_default'); if (!in_array($action, ['site_default', 'none', 'ai', 'library'], true)) { $action = 'site_default'; } $placement = sanitize_key($options['placement'] ?? 'featured'); if (!in_array($placement, ['featured', 'content_top', 'featured_and_content_top'], true)) { $placement = 'featured'; } $size = sanitize_text_field($options['size'] ?? '1024x1024'); if (!in_array($size, ['1024x1024', '1536x1024', '1024x1536'], true)) { $size = '1024x1024'; } return [ 'action' => $action, 'placement' => $placement, 'size' => $size, 'generation_size' => $size, 'display_size' => 'large', 'custom_width' => 800, 'custom_height' => 0, 'alignment' => 'none', 'mobile_behavior' => 'full_width', 'replace_existing' => 'no', 'attachment_id' => absint($options['attachment_id'] ?? 0), 'auto_select_library' => sanitize_key($options['auto_select_library'] ?? 'no') === 'yes' ? 'yes' : 'no', 'prompt' => sanitize_textarea_field($options['prompt'] ?? ''), ]; } public static function handle_media_for_existing_post($post_id, $media_options, $context = '') { $post_id = absint($post_id); if (!$post_id || !get_post($post_id)) { return new WP_Error('aiwest_missing_post', __('The post or page was not found.', 'ai-webmaster-studio')); } $post = get_post($post_id); $data = [ 'image_prompt' => $context ?: wp_trim_words(wp_strip_all_tags($post->post_content), 60, '...'), ]; $result = self::handle_media($post_id, $data, $post->post_title, '', $media_options); update_post_meta($post_id, '_aiwest_media_result', $result); update_post_meta($post_id, '_aiwest_media_options', self::sanitize_media_options($media_options)); return $result; } protected static function handle_media($post_id, $data, $topic, $keyword, $media_options) { $media_options = self::sanitize_media_options($media_options); $action = $media_options['action']; if ($action === 'none') { return ['status' => 'skipped', 'message' => __('Image: skipped according to administrator rules.', 'ai-webmaster-studio'), 'attachment_id' => 0]; } if ($action === 'site_default') { $action = 'none'; } if ($action === 'library') { $attachment_id = absint($media_options['attachment_id']); $auto_selected = false; if (!$attachment_id && sanitize_key($media_options['auto_select_library'] ?? 'no') === 'yes') { $attachment_id = self::select_library_image($post_id, $topic, $keyword, $data); $auto_selected = $attachment_id > 0; if ($auto_selected) { $media_options['attachment_id'] = $attachment_id; } } if (!$attachment_id) { return ['status' => 'error', 'message' => __('Image: Media Library was selected, but no suitable image is available for automatic use.', 'ai-webmaster-studio'), 'attachment_id' => 0]; } if (!wp_attachment_is_image($attachment_id)) { return ['status' => 'error', 'message' => __('Image: the selected file is not a valid image.', 'ai-webmaster-studio'), 'attachment_id' => 0]; } $applied = self::apply_attachment_to_post($post_id, $attachment_id, $media_options['placement'], array_merge($media_options, ['action' => 'library', 'source' => 'library'])); if (!$applied) { return ['status' => 'error', 'message' => __('Image: the Media Library image could not be applied.', 'ai-webmaster-studio'), 'attachment_id' => 0]; } return [ 'status' => 'attached', 'message' => $auto_selected ? __('A Media Library image was selected automatically and added according to administrator rules.', 'ai-webmaster-studio') : __('The Media Library image was added using the selected source and placement.', 'ai-webmaster-studio'), 'attachment_id' => $attachment_id, 'source' => 'library', 'auto_selected' => $auto_selected ? 'yes' : 'no', 'placement' => $media_options['placement'], 'display_size' => $media_options['display_size'] ?? 'large', ]; } if ($action === 'ai') { if (!class_exists('AIWEST_Image_Generator')) { return ['status' => 'error', 'message' => __('Image: the AI image generation module is unavailable.', 'ai-webmaster-studio'), 'attachment_id' => 0]; } if (!AIWEST_Image_Generator::enabled()) { return ['status' => 'skipped', 'message' => __('Image: AI generation is disabled in settings.', 'ai-webmaster-studio'), 'attachment_id' => 0]; } $image_context = $media_options['prompt'] ?: (($data['image_prompt'] ?? '') ?: ($topic . ' ' . $keyword)); $image = AIWEST_Image_Generator::generate_for_post($post_id, $image_context, $media_options); if (is_wp_error($image)) { return [ 'status' => 'error', 'message' => sprintf( /* translators: %s: Error message returned by the image generation service. */ __('Image error: %s', 'ai-webmaster-studio'), $image->get_error_message() ), 'attachment_id' => 0, ]; } return [ 'status' => 'generated', 'message' => __('The AI image was added according to the image rules.', 'ai-webmaster-studio'), 'attachment_id' => intval($image), 'source' => 'ai', 'placement' => $media_options['placement'], 'size' => $media_options['generation_size'] ?? $media_options['size'], 'display_size' => $media_options['display_size'] ?? 'large', ]; } return ['status' => 'skipped', 'message' => '', 'attachment_id' => 0]; } protected static function select_library_image($post_id, $topic = '', $keyword = '', $data = []) { $post_id = absint($post_id); $search_terms = trim(wp_strip_all_tags((string) ($keyword ?: $topic))); $search_terms = preg_replace('/\s+/', ' ', $search_terms); $base_args = [ 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'numberposts' => 12, 'orderby' => 'date', 'order' => 'DESC', 'fields' => 'ids', ]; $candidate_ids = []; if ($search_terms !== '') { $search_args = $base_args; $search_args['s'] = wp_trim_words($search_terms, 8, ''); $candidate_ids = get_posts($search_args); } if (!$candidate_ids) { $candidate_ids = get_posts($base_args); } if (!$candidate_ids || !is_array($candidate_ids)) { return 0; } $valid_ids = []; foreach ($candidate_ids as $candidate_id) { $candidate_id = absint($candidate_id); if ($candidate_id && wp_attachment_is_image($candidate_id)) { $valid_ids[] = $candidate_id; } } if (!$valid_ids) { return 0; } $index = $post_id ? ($post_id % count($valid_ids)) : 0; return absint($valid_ids[$index]); } public static function apply_attachment_to_post($post_id, $attachment_id, $placement = 'featured', $options = []) { $post_id = absint($post_id); $attachment_id = absint($attachment_id); $placement = sanitize_key($placement); $options = is_array($options) ? $options : []; if (class_exists('AIWEST_Graphics_Rules')) { $options = AIWEST_Graphics_Rules::sanitize(array_merge($options, ['placement' => $placement]), true); $placement = sanitize_key($options['placement'] ?? $placement); } if (!$post_id || !$attachment_id || !wp_attachment_is_image($attachment_id)) { return false; } $replace_existing = sanitize_key($options['replace_existing'] ?? 'no') === 'yes'; if (in_array($placement, ['featured', 'featured_and_content_top'], true)) { if ($replace_existing || !get_post_thumbnail_id($post_id)) { set_post_thumbnail($post_id, $attachment_id); update_post_meta($post_id, '_aiwest_featured_image_id', $attachment_id); } } if (in_array($placement, ['content_top', 'featured_and_content_top', 'after_first_paragraph', 'content_end'], true)) { $post = get_post($post_id); if ($post) { $wp_size = class_exists('AIWEST_Graphics_Rules') ? AIWEST_Graphics_Rules::wp_image_size($options) : 'large'; $attrs = class_exists('AIWEST_Graphics_Rules') ? AIWEST_Graphics_Rules::image_attrs($options) : ['class' => 'aiwest-content-image', 'loading' => 'lazy']; $image_html = wp_get_attachment_image($attachment_id, $wp_size, false, $attrs); if ($image_html) { $figure_class = class_exists('AIWEST_Graphics_Rules') ? AIWEST_Graphics_Rules::figure_class($options) : 'wp-block-image size-large aiwest-generated-media'; $figure = '
' . $image_html . '
' . "\n\n"; $content = $post->post_content; if ($placement === 'content_end') { $content = self::append_inline_media_at_content_end($content, $figure); } elseif ($placement === 'after_first_paragraph') { $parts = preg_split('/(<\/p>)/i', $content, 2, PREG_SPLIT_DELIM_CAPTURE); if (is_array($parts) && count($parts) >= 3) { $content = $parts[0] . $parts[1] . "\n\n" . $figure . $parts[2]; } else { $content = $figure . $content; } } else { $content = $figure . $content; } wp_update_post([ 'ID' => $post_id, 'post_content' => $content, ]); update_post_meta($post_id, '_aiwest_inline_image_id', $attachment_id); update_post_meta($post_id, '_aiwest_inline_image_placement', $placement); update_post_meta($post_id, '_aiwest_inline_image_source', sanitize_key($options['source'] ?? ($options['action'] ?? ''))); } } } return true; } protected static function append_inline_media_at_content_end($content, $figure) { $content = (string) $content; $figure = "\n\n" . trim((string) $figure) . "\n\n"; // Keep AIW images inside the AI-generated wrapper when possible. This makes // "end of content" mean the end of the article text, not a position after // theme/plugin related-post sections rendered outside post_content. if (stripos($content, '
'); if ($last_close !== false) { return substr($content, 0, $last_close) . $figure . substr($content, $last_close); } } return rtrim($content) . $figure; } public static function assign_ai_taxonomy($post_id, $data) { $out = ['enabled' => get_option('aiwest_auto_taxonomy', 'yes') === 'yes', 'category' => '', 'tags' => []]; if (!$out['enabled']) return $out; $category = sanitize_text_field($data['category'] ?? ''); if ($category) { $term = term_exists($category, 'category'); if (!$term) $term = wp_insert_term($category, 'category'); if (!is_wp_error($term)) { $term_id = is_array($term) ? intval($term['term_id']) : intval($term); if ($term_id) { wp_set_post_categories($post_id, [$term_id], false); $out['category'] = $category; } } } $tags = $data['tags'] ?? []; if (is_string($tags)) $tags = array_map('trim', explode(',', $tags)); if (is_array($tags)) { $clean_tags = []; foreach ($tags as $tag) { $tag = sanitize_text_field($tag); if ($tag && mb_strlen($tag) <= 80) $clean_tags[] = $tag; } $clean_tags = array_values(array_unique(array_slice($clean_tags, 0, 8))); if ($clean_tags) { wp_set_post_tags($post_id, $clean_tags, false); $out['tags'] = $clean_tags; } } return $out; } }