Content Development. * * Row buttons and queued bulk actions intentionally converge here so there is * one content-generation engine and no sidecar/runtime-hotfix dependency. */ class AIWEST_Content_Development_Action_Service { public static function execute($post_id, $action) { $post_id = absint($post_id); $action = sanitize_key($action); if (in_array($action,['canvas_enable','canvas_disable'],true)) { if (!current_user_can('manage_options') || !current_user_can('edit_post',$post_id) || get_post_type($post_id)!=='page') return new WP_Error('canvas_permission',__('Canvas requires an editable page.','ai-webmaster-studio')); $ok=$action==='canvas_enable'?AIWEST_Canvas::enable_for_page($post_id):AIWEST_Canvas::disable_for_page($post_id); return $ok?['message'=>__('Canvas setting saved.','ai-webmaster-studio')]:new WP_Error('canvas_save',__('Canvas could not be changed.','ai-webmaster-studio')); } if (!class_exists('AIWEST_Content_Improvement_Bulk_Processor') || !method_exists('AIWEST_Content_Improvement_Bulk_Processor', 'execute_shared_action')) { return new WP_Error('aiwest_content_development_service_unavailable', __('The Content Development engine is not available.', 'ai-webmaster-studio')); } return AIWEST_Content_Improvement_Bulk_Processor::execute_shared_action($post_id, $action); } } class AIWEST_Content_Improvement_Bulk_Processor { public static function init() { add_action('wp_ajax_aiwest_ci_bulk_process', [__CLASS__, 'process']); } public static function ajax_process() { self::process(); } public static function process() { try { if (!current_user_can('manage_options')) { self::send_error(__('Missing permission.', 'ai-webmaster-studio'), 'missing_permission', 403); } $nonce = AIWEST_Security::post_text('nonce'); if (!$nonce || !wp_verify_nonce($nonce, 'aiwest_ci_bulk_ajax')) { self::send_error(__('The security token expired or is invalid.', 'ai-webmaster-studio'), 'invalid_nonce', 403); } $post_id = AIWEST_Security::post_int('post_id'); $action = AIWEST_Security::post_key('bulk_action'); $allowed_actions = ['expand', 'improve', 'visual', 'faq', 'cta', 'seo', 'links', 'image', 'undo', 'canvas_enable', 'canvas_disable']; if (!in_array($action, $allowed_actions, true)) { self::send_error(__('Unknown or disallowed bulk action.', 'ai-webmaster-studio'), 'disallowed_action', 400); } if (!$post_id || !$action) { self::send_error(__('Missing content ID or action.', 'ai-webmaster-studio'), 'missing_request_data', 400); } $post = get_post($post_id); if (!$post || !in_array($post->post_type, ['page','post'], true)) { self::send_error(__('The page or post was not found.', 'ai-webmaster-studio'), 'invalid_post_id', 404); } if (!class_exists('AIWEST_Content_Development_Action_Service')) { self::send_error(__('The shared Content Development engine is not available.', 'ai-webmaster-studio'), 'shared_engine_missing', 500); } $result = AIWEST_Content_Development_Action_Service::execute($post_id, $action); if (is_wp_error($result)) { self::send_error($result->get_error_message(), $result->get_error_code() ?: 'engine_error', 500); } if (empty($result) || !is_array($result)) { self::send_error(__('The engine returned an empty or invalid result.', 'ai-webmaster-studio'), 'empty_engine_response', 500); } self::send_success($result); } catch (Throwable $e) { self::send_error(__('Bulk action error:', 'ai-webmaster-studio') . ' ' . $e->getMessage(), 'php_exception', 500); } } protected static function send_success($data) { nocache_headers(); wp_send_json_success($data, 200); } protected static function send_error($message, $code = 'bulk_action_failed', $status = 400) { nocache_headers(); if (is_wp_error($message)) { $code = $message->get_error_code() ?: $code; $message = $message->get_error_message(); } $message = sanitize_text_field((string) $message); if ($message === '') { $message = __('Bulk action failed.', 'ai-webmaster-studio'); } wp_send_json_error([ 'message' => $message, 'code' => sanitize_key($code), 'handler' => 'aiwest_ci_bulk_process', ], absint($status) ?: 400); } public static function execute_shared_action($post_id, $action) { return self::run_action($post_id, $action); } protected static function run_action($post_id, $action) { switch ($action) { case 'expand': case 'improve': case 'visual': case 'rewrite': case 'faq': case 'cta': case 'seo': return self::run_existing_content_upgrader_action($post_id, $action); case 'links': self::snapshot_before_bulk_action($post_id, $action); if (class_exists('AIWEST_Internal_Links')) { $res = AIWEST_Internal_Links::link_post($post_id, 10); if (is_wp_error($res)) return $res; return ['message' => __('Internal links have been added or checked.', 'ai-webmaster-studio'), 'details' => $res]; } return new WP_Error('aiwest_no_links', __('The linking module is not available.', 'ai-webmaster-studio')); case 'image': self::snapshot_before_bulk_action($post_id, $action); if (class_exists('AIWEST_Image_Generator')) { $res = AIWEST_Image_Generator::generate_for_post($post_id, 'bulk_content_improvement'); if (is_wp_error($res)) return $res; return ['message' => __('Image generation has been completed or started.', 'ai-webmaster-studio')]; } return new WP_Error('aiwest_no_images', __('The AI images module is not available.', 'ai-webmaster-studio')); case 'undo': if (class_exists('AIWEST_Undo') && method_exists('AIWEST_Undo','restore_last')) { $res = AIWEST_Undo::restore_last($post_id); if (is_wp_error($res)) return $res; return ['message' => __('The latest change has been undone if it was available.', 'ai-webmaster-studio')]; } return new WP_Error('aiwest_no_undo', __('The undo module is not available.', 'ai-webmaster-studio')); } return new WP_Error('aiwest_unknown_action', __('Unknown bulk action.', 'ai-webmaster-studio')); } protected static function snapshot_before_bulk_action($post_id, $action) { if (class_exists('AIWEST_Undo')) { AIWEST_Undo::snapshot($post_id, 'bulk_' . sanitize_key($action), __('Backup before AI Web bulk action: ', 'ai-webmaster-studio') . sanitize_key($action)); } } /** * Use the same proven execution path as the working row buttons in * AI Webmaster Studio -> Content Development. * * v1.5.77 intentionally keeps this queue as an orchestration layer only. * The previous dedicated bulk implementations drifted from the single-row * action behavior and could fail while the visible row buttons worked. */ protected static function run_existing_content_upgrader_action($post_id, $action) { if (!class_exists('AIWEST_Content_Upgrader') || !method_exists('AIWEST_Content_Upgrader', 'improve_post')) { return new WP_Error('aiwest_content_upgrader_missing', __('The Content Development module is not available.', 'ai-webmaster-studio')); } $mode = $action === 'expand' ? 'improve' : $action; $content_limit = self::bulk_content_limit($mode); $extra_prompt = self::bulk_extra_prompt($mode); $result = AIWEST_Content_Upgrader::improve_post($post_id, $mode, $extra_prompt, $content_limit); if (is_wp_error($result)) { return $result; } $summary = ''; if (is_array($result)) { $summary = isset($result['summary']) ? sanitize_textarea_field($result['summary']) : ''; } if ($summary === '') { $summary = self::fallback_success_message($mode); } update_post_meta($post_id, '_aiwest_bulk_last_action', $action); update_post_meta($post_id, '_aiwest_bulk_last_action_at', current_time('mysql')); return [ 'message' => $summary, 'summary' => $summary, 'edit' => get_edit_post_link($post_id, ''), 'view' => get_permalink($post_id), 'post_id' => intval($post_id), 'bulk_action' => sanitize_key($action), 'engine' => 'content_upgrader', ]; } protected static function bulk_content_limit($mode) { $mode = sanitize_key($mode); return 60000; } protected static function bulk_extra_prompt($mode) { $mode = sanitize_key($mode); $base = 'Bulk Content Development run: keep the update WordPress-safe. Preserve existing valuable content, headings and links, avoid duplicating whole sections, and return only the JSON requested by the main instruction. Do not add an H1 or repeat the WordPress page/post title as the first content heading.'; if ($mode === 'visual') { $base .= ' PRESERVE_CONTENT is mandatory: return the complete page, keep every visible word, heading and link in its original order, and change layout markup only. Never summarize, shorten, rewrite or replace text.'; } return $base; } protected static function is_openai_400_error($result) { if (!is_wp_error($result)) return false; $message = $result->get_error_message(); return (stripos($message, 'OpenAI API error [400]') !== false || preg_match('/\bHTTP\s*400\b/i', $message)); } protected static function fallback_success_message($mode) { $mode = sanitize_key($mode); $messages = [ 'improve' => __('The content has been expanded.', 'ai-webmaster-studio'), 'visual' => __('The layout has been improved while preserving existing content.', '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'), ]; return $messages[$mode] ?? __('The content has been updated.', 'ai-webmaster-studio'); } protected static function expand_content($post_id) { $post = get_post($post_id); $title = get_the_title($post_id); $current = $post->post_content; $prompt = self::prompt_expand($title, wp_strip_all_tags($current)); $ai_html = self::ask_ai_for_html($prompt); $warning = ''; if (is_wp_error($ai_html)) { $ai_html = self::fallback_expand_html($title); $warning = __('AI did not return valid content, so a safe fallback expansion was used.', 'ai-webmaster-studio'); } $guarded = self::guard_generated_links($ai_html, $post_id); $ai_html = $guarded['html']; $new_content = self::append_expand_content($current, $ai_html); $updated = wp_update_post(['ID'=>$post_id, 'post_content'=>$new_content], true); if (is_wp_error($updated)) return $updated; update_post_meta($post_id, '_aiwest_bulk_last_action', 'expand'); update_post_meta($post_id, '_aiwest_bulk_last_action_at', current_time('mysql')); $msg = $warning ?: __('The content has been expanded.', 'ai-webmaster-studio'); if (!empty($guarded['created'])) { $msg .= ' ' . __('Link Guardian created missing pages:', 'ai-webmaster-studio') . ' ' . count($guarded['created']) . '.'; update_post_meta($post_id, '_aiwest_link_guardian_created', $guarded['created']); } return ['message' => $msg, 'edit'=>get_edit_post_link($post_id,''), 'view'=>get_permalink($post_id), 'link_guardian_created'=>$guarded['created'] ?? []]; } protected static function append_section($post_id, $type) { $title = get_the_title($post_id); $current = get_post($post_id)->post_content; if ($type === 'faq') { $html = self::fallback_faq_html($title); $marker = 'aiwest-bulk-faq-section'; $message=__('The FAQ section has been added.', 'ai-webmaster-studio'); } else { $html = self::fallback_cta_html($title); $marker = 'aiwest-bulk-cta-section'; $message=__('The CTA section has been added.', 'ai-webmaster-studio'); } $new_content = self::merge_content($current, $html, $marker); $updated = wp_update_post(['ID'=>$post_id, 'post_content'=>$new_content], true); if (is_wp_error($updated)) return $updated; return ['message'=>$message, 'edit'=>get_edit_post_link($post_id,''), 'view'=>get_permalink($post_id)]; } protected static function improve_seo($post_id) { $title = get_the_title($post_id); $post = get_post($post_id); $excerpt = wp_trim_words(wp_strip_all_tags($post->post_content), 28); update_post_meta($post_id, '_aiwest_seo_note', __('The page requires meta title/meta description aligned with the topic: ', 'ai-webmaster-studio') . $title); update_post_meta($post_id, '_aiwest_seo_suggested_description', $excerpt ?: __('Practical guide: ', 'ai-webmaster-studio') . $title); return ['message'=>__('SEO suggestions have been added to AI Web metadata.', 'ai-webmaster-studio'), 'edit'=>get_edit_post_link($post_id,''), 'view'=>get_permalink($post_id)]; } protected static function prompt_expand($title, $plain) { $style = get_option('aiwest_visual_brand_style', ''); if (class_exists('AIWEST_Design_System')) { $style .= "\n\n" . AIWEST_Design_System::style_prompt(); } $plain = mb_substr($plain, 0, 2500); return "Expand an existing WordPress page. Return ONLY ready HTML, without JSON, markdown or comments.\n\nPage title: {$title}\n\nCurrent content:\n{$plain}\n\nWebsite style:\n{$style}\n\nAdd one complete HTML section: a practical introduction, bullet list, mini FAQ and CTA. If you add a button or link, use clear anchor text. AI Webmaster Studio will check links and create missing target pages."; } protected static function ask_ai_for_html($prompt) { if (!class_exists('AIWEST_OpenAI')) return new WP_Error('aiwest_no_openai', __('The OpenAI module is missing.', 'ai-webmaster-studio')); $messages = [ ['role'=>'system','content'=>'You are a WordPress editor. Return only safe HTML for insertion into page content. Do not return JSON or markdown.'], ['role'=>'user','content'=>$prompt], ]; if (method_exists('AIWEST_OpenAI','chat')) $response = AIWEST_OpenAI::chat($messages); elseif (method_exists('AIWEST_OpenAI','complete')) $response = AIWEST_OpenAI::complete($prompt); else return new WP_Error('aiwest_openai_method', __('No supported OpenAI method is available.', 'ai-webmaster-studio')); if (is_wp_error($response)) return $response; if (is_array($response)) $html = $response['content'] ?? $response['message'] ?? $response['text'] ?? ''; else $html = (string)$response; $html = trim($html); $html = preg_replace('/^```(?:html)?/i','',$html); $html = preg_replace('/```$/','',$html); $html = trim($html); if (strlen(wp_strip_all_tags($html)) < 80) return new WP_Error('aiwest_short_html', __('AI returned content that was too short.', 'ai-webmaster-studio')); return wp_kses_post($html); } protected static function merge_content($current, $html, $marker) { if (strpos($current, $marker) !== false) { $pattern = '/]*class=["\'][^"\']*' . preg_quote($marker, '/') . '[^"\']*["\'][\s\S]*?<\/section>/i'; $replaced = preg_replace($pattern, $html, $current, 1); return $replaced ?: (rtrim($current) . "\n\n" . $html); } return rtrim($current) . "\n\n" . $html; } protected static function append_expand_content($current, $html) { $stamp = current_time('timestamp'); if (preg_match('/]*)class=["\']([^"\']*)["\']/i', '' . $html . ''; } return rtrim($current) . "\n\n" . $html; } protected static function guard_generated_links($html, $source_post_id = 0) { $created = []; if (stripos($html, ' $html, 'created' => $created]; } if (!class_exists('DOMDocument')) { return self::guard_generated_links_regex($html, $source_post_id); } $internal_errors = libxml_use_internal_errors(true); $dom = new DOMDocument('1.0', get_bloginfo('charset') ?: 'UTF-8'); $wrapped = ''; $dom->loadHTML('' . $wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); libxml_clear_errors(); libxml_use_internal_errors($internal_errors); $links = $dom->getElementsByTagName('a'); foreach ($links as $a) { $href = trim($a->getAttribute('href')); if (!self::is_internal_missing_href($href)) continue; $label = trim($a->textContent); $target = self::ensure_page_for_href($href, $label, $source_post_id); if ($target && !is_wp_error($target)) { $a->setAttribute('href', get_permalink($target)); $created[] = $target; } } $root = $dom->getElementById('aiwest-link-guardian-root'); $out = ''; if ($root) { foreach ($root->childNodes as $child) { $out .= $dom->saveHTML($child); } } return ['html' => $out ?: $html, 'created' => array_values(array_unique(array_map('intval', $created)))]; } protected static function guard_generated_links_regex($html, $source_post_id = 0) { $created = []; $html = preg_replace_callback('/]*?)href=["\']([^"\']+)["\']([^>]*)>(.*?)<\/a>/is', function($m) use (&$created, $source_post_id) { $href = trim(html_entity_decode($m[2])); if (!self::is_internal_missing_href($href)) return $m[0]; $label = trim(wp_strip_all_tags($m[4])); $target = self::ensure_page_for_href($href, $label, $source_post_id); if (!$target || is_wp_error($target)) return $m[0]; $created[] = $target; return '' . $m[4] . ''; }, $html); return ['html' => $html, 'created' => array_values(array_unique(array_map('intval', $created)))]; } protected static function is_internal_missing_href($href) { if (!$href || strpos($href, '#') === 0 || strpos($href, 'mailto:') === 0 || strpos($href, 'tel:') === 0) return false; $home = home_url('/'); $path = ''; if (strpos($href, '/') === 0) { $path = $href; } elseif (stripos($href, $home) === 0) { $path = '/' . ltrim(str_replace($home, '', $href), '/'); } else { return false; } $path = strtok($path, '?'); if (!$path || $path === '/') return false; $existing = url_to_postid(home_url($path)); return !$existing; } protected static function ensure_page_for_href($href, $label, $source_post_id = 0) { $path = strpos($href, '/') === 0 ? $href : '/' . ltrim(str_replace(home_url('/'), '', $href), '/'); $path = trim(strtok($path, '?'), '/'); if (!$path) return 0; $existing = url_to_postid(home_url('/' . $path . '/')); if ($existing) return $existing; $title = $label ? wp_strip_all_tags($label) : ucwords(str_replace(['-', '_'], ' ', basename($path))); $title = preg_replace('/^(Zobacz|View|See)\s+/iu', '', $title); $title = trim($title) ?: ucwords(str_replace('-', ' ', basename($path))); $slug = sanitize_title(basename($path)); if (!$slug) $slug = sanitize_title($title); $maybe = get_page_by_path($slug, OBJECT, 'page'); if ($maybe) return intval($maybe->ID); $new_id = wp_insert_post([ 'post_type' => 'page', 'post_status' => 'draft', 'post_title' => $title, 'post_name' => $slug, 'post_content' => '

' . esc_html(__('This page was created automatically by AI Webmaster Studio Link Guardian as the destination for a generated link. It requires content completion.', 'ai-webmaster-studio')) . '

', '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($new_id)) return $new_id; update_post_meta($new_id, '_aiwest_link_guardian_created', '1'); update_post_meta($new_id, '_aiwest_needs_content_review', '1'); update_post_meta($new_id, '_aiwest_link_guardian_source_post_id', intval($source_post_id)); if (class_exists('AIWEST_Technical_Operator')) { AIWEST_Technical_Operator::annotate_post($new_id, 'create_link_guardian_target', AIWEST_Technical_Operator::explain_decision('create_page', 'content_page_create', ['post_type'=>'page','post_status'=>'draft'])); } return intval($new_id); } protected static function fallback_expand_html($title) { return '

Practical information: ' . esc_html($title) . '

This section organizes the key information and helps users quickly understand where to start and what to review.

What should be checked?

Verify requirements, costs, timelines and documents needed at this stage.

What needs attention?

Avoid decisions without comparing options, analyzing risks and checking formal requirements.

Next step

Prepare a list of questions, documents and decisions required before moving forward.

FAQ

Is a checklist useful? Yes. A checklist reduces the risk of missing important formalities and costs.

When should options be compared? Before making the decision, so the budget and scope remain under control.

Consult the next step

'; } protected static function fallback_faq_html($title) { return '

FAQ: ' . esc_html($title) . '

Where should you start?

First define the goal, budget, formal requirements and the list of decisions to make.

How can errors be reduced?

Use checklists, compare options and document agreements with contractors and designers.

When should you consult a specialist?

When the decision affects costs, formalities, safety, later use or further development of the website.

'; } protected static function fallback_cta_html($title) { return '
' . '

Need help with: ' . esc_html($title) . '?

' . '

Check related guides on ' . esc_html(get_bloginfo('name')) . ' or get in touch to organize the next steps.

' . 'Ask a question' . '
'; } } AIWEST_Content_Improvement_Bulk_Processor::init();