post_type, ['page', 'post'], true)) { return new WP_Error('aiwest_bad_post_type', __('This content type is not supported.', 'ai-webmaster-studio')); } $mode = sanitize_key($mode); $extra_prompt = sanitize_textarea_field($extra_prompt); $allowed_modes = ['improve', 'seo', 'faq', 'cta', 'visual', 'rewrite', 'custom']; if (!in_array($mode, $allowed_modes, true)) $mode = 'improve'; if (!class_exists('AIWEST_Content_Preservation_Gate')) { return new WP_Error('aiwest_preservation_gate_missing', __('The Content Preservation Gate is missing. The update was stopped.', 'ai-webmaster-studio')); } $intent = AIWEST_Content_Preservation_Gate::detect_intent($mode, $extra_prompt); $current = (string)$post->post_content; $lovable = class_exists('AIWEST_Memory') ? AIWEST_Memory::get('lovable_project_summary', '') : ''; $brand = get_option('aiwest_visual_brand_style', ''); $design_contract = class_exists('AIWEST_Design_Contract') ? AIWEST_Design_Contract::resolve() : []; $language = AIWEST_Languages::language_name(AIWEST_Languages::content_locale()); $summary_language = AIWEST_Languages::language_name(AIWEST_Languages::locale()); $snapshot = AIWEST_Content_Preservation_Gate::capture_snapshot($post_id, $mode, $extra_prompt, $intent); if (is_wp_error($snapshot)) return $snapshot; if ($intent === AIWEST_Content_Preservation_Gate::VISUAL_ONLY) { return self::apply_visual_layout($post, $mode, $extra_prompt, $design_contract, $snapshot); } // Preservation modes require the complete source. Never send a shortened // fragment and later replace the full post with a response based on it. $content_limit = 60000; $content_length = function_exists('mb_strlen') ? mb_strlen($current) : strlen($current); if ($content_length > $content_limit) { return new WP_Error( 'aiwest_content_too_long_for_safe_update', __('The content is too long to send to AI safely without truncation. The original remains unchanged.', 'ai-webmaster-studio') ); } $instruction = [ 'improve' => 'Expand the content: add practical sections, examples, lists, tips and a short summary. Preserve the meaning of the existing content and do not remove valuable parts.', 'seo' => 'Improve SEO: improve the H2/H3 heading structure, add a lead, semantic phrases, a meta-style excerpt, SEO FAQ and readable sections. Do not keyword-stuff.', 'faq' => 'Add a FAQ section at the end with 5–8 questions and answers that match the topic and user intent.', 'cta' => 'Add a strong CTA section and short boxes encouraging further reading, contact or checking related guides.', 'visual' => 'Preserve every existing visible word, sentence, heading, link and their order. Improve presentation only by adding or adjusting safe layout wrappers, sections, columns, cards, spacing classes and visual grouping. Do not summarize, shorten, rewrite, replace or add textual content. Return the complete page HTML.', 'rewrite' => 'Rewrite the content according to the administrator instruction. This explicit mode may replace existing text, but the result must remain complete, accurate and WordPress-safe.', 'custom' => 'Follow the administrator instruction from the additional prompt field. Keep the output WordPress-safe and do not add scripts.', ][$mode] ?? 'Improve the content.'; $intent_rule = $intent === AIWEST_Content_Preservation_Gate::CONTENT_REWRITE ? "CONTENT_REWRITE is active because the administrator explicitly selected rewrite. Text replacement is allowed." : "CONTENT_EXPANSION is active. Preserve all existing valuable content, headings and links; additions are allowed, deletion is not."; $heading_rule = "The WordPress theme normally displays the page/post title as the only H1. Do not add an H1 or repeat the WordPress title as the first content heading. Standard content should start with an introduction and continue with H2/H3."; $title_rule = "The existing WordPress title is administrator-approved and locked. Keep it exactly as provided. Do not rename the page or post."; $messages = [ ['role' => 'system', 'content' => AIWEST_OpenAI::system_prompt()], ['role' => 'user', 'content' => "Update an existing WordPress content item.\n" . "Default language for excerpt and HTML content: {$language}; an explicit language in the administrator prompt takes precedence. Language for change_summary: {$summary_language}.\n" . "Use only the administrator interface language inside change_summary.\n" . "Title: {$post->post_title}\n" . "Type: {$post->post_type}\n" . "Mode: {$mode}\n" . "Intent contract: {$intent_rule}\n" . "Heading contract: {$heading_rule}\n" . "Title contract: {$title_rule}\n" . "Main instruction: {$instruction}\n\n" . "Additional administrator prompt:\n" . ($extra_prompt ?: 'None.') . "\n\n" . "Visual website style:\n" . ($brand ?: 'No separate style description.') . "\n\n" . "Imported design reference / project style:\n" . ($lovable ?: 'No imported design reference.') . "\n\n" . (class_exists('AIWEST_Design_Contract') ? AIWEST_Design_Contract::planner_context($design_contract) . "\n\n" : '') . (class_exists('AIWEST_Search_Optimization') ? AIWEST_Search_Optimization::prompt_contract(['post_type'=>$post->post_type,'mode'=>$mode]) . "\n\n" : '') . "Current complete WordPress HTML (never truncate it in the response):\n" . $current . "\n\n" . "Return JSON only, without markdown:\n" . "{ \"excerpt\":\"...\", \"meta_title\":\"...\", \"meta_description\":\"...\", \"html_content\":\"clean WordPress HTML with sections, cards, FAQ/CTA when relevant\", \"change_summary\":\"short summary in {$summary_language} only\" }\n" . "Do not add JS scripts, iframe tags or CSS in style tags. You may use simple inline styles, sections, cards, h2/h3, lists, details/summary and safe HTML." ], ]; $result = AIWEST_OpenAI::chat($messages, 0.35); if (is_wp_error($result)) return $result; $data = self::decode_json($result); if (!is_array($data) || empty($data['html_content'])) { if (class_exists('AIWEST_Logger')) AIWEST_Logger::warning('Content upgrader bad JSON.', ['post_id'=>$post_id,'mode'=>$mode,'raw'=>mb_substr((string)$result,0,1000)]); return new WP_Error('aiwest_bad_upgrade_json', __('AI did not return a valid update JSON. Try again or use a shorter instruction.', 'ai-webmaster-studio')); } $candidate_html = wp_kses_post($data['html_content']); if (function_exists('parse_blocks') && function_exists('serialize_blocks')) { $parsed_blocks = parse_blocks($candidate_html); $serialized_blocks = serialize_blocks($parsed_blocks); if (is_string($serialized_blocks) && trim($serialized_blocks) !== '') { $candidate_html = $serialized_blocks; } } if (trim(wp_strip_all_tags($candidate_html)) === '' && trim(wp_strip_all_tags($current)) !== '') { return new WP_Error('aiwest_empty_content_candidate', __('AI returned an empty content candidate. The original content and title were preserved.', 'ai-webmaster-studio')); } $heading_normalization = ['content' => $candidate_html, 'report' => []]; if (class_exists('AIWEST_Page_Title_Heading_Normalizer')) { $heading_normalization = AIWEST_Page_Title_Heading_Normalizer::normalize($post_id, $candidate_html, [ 'post_title' => (string)$post->post_title, 'post_type' => (string)$post->post_type, 'mode' => 'standard', ]); $candidate_html = $heading_normalization['content']; } $gate_report = AIWEST_Content_Preservation_Gate::validate($post_id, $snapshot, $candidate_html, $intent); if (is_wp_error($gate_report)) return $gate_report; if (class_exists('AIWEST_Undo')) { AIWEST_Undo::snapshot($post_id, 'content_upgrader', __('Backup before AI change in Content Development.', 'ai-webmaster-studio')); } update_post_meta($post_id, '_aiwest_previous_content_' . time(), $post->post_content); $update = [ 'ID' => $post_id, 'post_content' => $candidate_html, ]; if (!empty($data['excerpt'])) $update['post_excerpt'] = sanitize_textarea_field($data['excerpt']); $updated = wp_update_post($update, true); if (is_wp_error($updated)) return $updated; if (class_exists('AIWEST_Page_Title_Heading_Normalizer')) { AIWEST_Page_Title_Heading_Normalizer::record_report($post_id, $heading_normalization['report']); } $search_report = class_exists('AIWEST_Search_Optimization') ? AIWEST_Search_Optimization::finalize($post_id, $candidate_html, ['post_type'=>$post->post_type,'post_title'=>$post->post_title,'mode'=>$mode], $data) : []; $summary = self::normalize_change_summary($data['change_summary'] ?? '', $mode); if (!empty($heading_normalization['report']['message'])) { $summary = trim($summary . ' ' . $heading_normalization['report']['message']); } update_post_meta($post_id, '_aiwest_last_upgrade', current_time('mysql')); update_post_meta($post_id, '_aiwest_last_upgrade_mode', $mode); update_post_meta($post_id, '_aiwest_last_upgrade_summary', sanitize_textarea_field($summary)); if (class_exists('AIWEST_History')) { $hid = AIWEST_History::add_content('content_upgrader', $post->post_title, '', 'Update: ' . $mode . ($extra_prompt ? ' / custom prompt' : '')); AIWEST_History::update_content($hid, 'completed', $post_id, sanitize_textarea_field($summary)); } if (class_exists('AIWEST_Logger')) { AIWEST_Logger::info('AI updated existing content.', ['post_id'=>$post_id,'mode'=>$mode,'summary'=>$summary]); } return ['post_id'=>intval($post_id),'mode'=>$mode,'intent'=>$intent,'preservation_gate'=>$gate_report,'duplicate_title_heading'=>$heading_normalization['report'],'search_optimization'=>$search_report,'summary'=>sanitize_textarea_field($summary)]; } protected static function apply_visual_layout($post, $mode, $extra_prompt, $design_contract, $snapshot) { if (!class_exists('AIWEST_Visual_Layout_Engine')) { return new WP_Error('aiwest_visual_layout_engine_missing', __('The safe Visual Layout Engine is missing.', 'ai-webmaster-studio')); } $plan = AIWEST_Visual_Layout_Engine::select_plan($post, $extra_prompt, $design_contract); $protected_content = isset($snapshot['content']) ? (string)$snapshot['content'] : (string)$post->post_content; $candidate_html = AIWEST_Visual_Layout_Engine::apply($protected_content, $plan); $heading_normalization = ['content' => $candidate_html, 'report' => []]; if (class_exists('AIWEST_Page_Title_Heading_Normalizer')) { $heading_normalization = AIWEST_Page_Title_Heading_Normalizer::normalize($post->ID, $candidate_html, [ 'post_title' => (string)$post->post_title, 'post_type' => (string)$post->post_type, 'mode' => 'standard', ]); $candidate_html = $heading_normalization['content']; } $gate_report = AIWEST_Content_Preservation_Gate::validate($post->ID, $snapshot, $candidate_html, AIWEST_Content_Preservation_Gate::VISUAL_ONLY); if (is_wp_error($gate_report)) return $gate_report; if ($candidate_html !== (string)$post->post_content) { update_post_meta($post->ID, '_aiwest_previous_content_' . time(), $post->post_content); $updated = wp_update_post([ 'ID' => $post->ID, 'post_content' => $candidate_html, ], true); if (is_wp_error($updated)) return $updated; } if (class_exists('AIWEST_Page_Title_Heading_Normalizer')) { AIWEST_Page_Title_Heading_Normalizer::record_report($post->ID, $heading_normalization['report']); } $search_report = class_exists('AIWEST_Search_Optimization') ? AIWEST_Search_Optimization::finalize($post->ID, $candidate_html, ['post_type'=>$post->post_type,'post_title'=>$post->post_title,'mode'=>'visual']) : []; $safe_plan = AIWEST_Visual_Layout_Engine::save_plan($post->ID, $plan); if (!is_array($safe_plan)) { return new WP_Error('aiwest_visual_plan_not_saved', __('The visual layout plan could not be saved. Existing content remains unchanged.', 'ai-webmaster-studio')); } $summary = self::fallback_summary('visual'); if (!empty($heading_normalization['report']['message'])) { $summary = trim($summary . ' ' . $heading_normalization['report']['message']); } update_post_meta($post->ID, '_aiwest_last_visual_layout_plan', $safe_plan); update_post_meta($post->ID, '_aiwest_last_upgrade', current_time('mysql')); update_post_meta($post->ID, '_aiwest_last_upgrade_mode', $mode); update_post_meta($post->ID, '_aiwest_last_upgrade_summary', $summary); if (class_exists('AIWEST_History')) { $hid = AIWEST_History::add_content('visual_layout_engine', $post->post_title, '', 'Safe visual layout: ' . $safe_plan['profile']); AIWEST_History::update_content($hid, 'completed', $post->ID, $summary); } if (class_exists('AIWEST_Logger')) { AIWEST_Logger::info('Visual layout applied without content regeneration.', [ 'post_id' => intval($post->ID), 'plan' => $safe_plan, ]); } return [ 'post_id' => intval($post->ID), 'mode' => $mode, 'intent' => AIWEST_Content_Preservation_Gate::VISUAL_ONLY, 'visual_plan' => $safe_plan, 'preservation_gate' => $gate_report, 'duplicate_title_heading' => $heading_normalization['report'], 'search_optimization' => $search_report, 'summary' => $summary, ]; } public static function normalize_change_summary($summary, $mode = '') { $summary = sanitize_textarea_field((string)$summary); if ($summary === '') { return self::fallback_summary($mode); } if ((!class_exists('AIWEST_Product') || AIWEST_Product::is_en())) { $legacy = [ 'Przebudowano stronę kontaktową jako wizualną stronę docelową' => 'The contact page was rebuilt as a visual landing page with clearer sections, contact cards, FAQ, CTA and readable content blocks.', 'Treść została zaktualizowana.' => 'The content has been updated.', ]; foreach ($legacy as $needle => $replacement) { if (stripos($summary, $needle) !== false) { return $replacement; } } if (preg_match('/\b(została|został|dodano|przebudowano|ulepszono|rozbudowano|treść|stronę|sekcję|sekcje|zmiany|zwiększono)\b/iu', $summary)) { return self::fallback_summary($mode); } } return $summary; } protected static function fallback_summary($mode = '') { $mode = sanitize_key($mode); $map = [ 'improve' => __('The content has been expanded.', 'ai-webmaster-studio'), 'seo' => __('The content has been improved for SEO.', 'ai-webmaster-studio'), 'faq' => __('The FAQ section has been added or improved.', 'ai-webmaster-studio'), 'cta' => __('The CTA section has been added or improved.', 'ai-webmaster-studio'), 'visual' => __('The layout has been improved while preserving existing content.', 'ai-webmaster-studio'), 'rewrite' => __('The content has been rewritten at the administrator’s explicit request.', 'ai-webmaster-studio'), 'custom' => __('The custom instruction has been completed.', 'ai-webmaster-studio'), ]; return $map[$mode] ?? __('The content has been updated.', 'ai-webmaster-studio'); } public static function decode_json($result) { $clean = trim((string)$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); } return $data; } }