'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 = '