$token)); } public function save_post_css() { // Vérifier que les données POST existent if (!isset($_POST['nonce']) || !isset($_POST['post_id']) || !isset($_POST['css_content'])) { wp_send_json_error('Missing required data'); } // Déséchapper et assainir les données $nonce = sanitize_text_field(wp_unslash($_POST['nonce'])); $post_id = intval($_POST['post_id']); $css_content = wp_unslash($_POST['css_content']); $css_type = isset($_POST['css_type']) ? sanitize_text_field(wp_unslash($_POST['css_type'])) : 'page'; $replace = isset($_POST['replace']) ? filter_var(wp_unslash($_POST['replace']), FILTER_VALIDATE_BOOLEAN) : false; // Vérifier le nonce if (!wp_verify_nonce($nonce, 'aibui_nonce')) { wp_die('Security check failed'); } // Vérifier que l'utilisateur peut éditer ce post if (!current_user_can('edit_post', $post_id)) { wp_send_json_error('Insufficient permissions'); } if ($css_type === 'page') { // Pour les pages, remplacer complètement le CSS de page update_post_meta($post_id, 'ai_builder_page_css_content', $css_content); // Récupérer le CSS de blocs existant $block_css = get_post_meta($post_id, 'ai_builder_block_css_content', true); // Combiner page CSS + block CSS pour le CSS final $final_css = $css_content; if (!empty($block_css)) { $final_css .= "\n" . $block_css; } update_post_meta($post_id, 'ai_builder_css_content', $final_css); } else if ($css_type === 'block') { $page_css = get_post_meta($post_id, 'ai_builder_page_css_content', true); $block_css = get_post_meta($post_id, 'ai_builder_block_css_content', true); if (empty($page_css)) { $page_css = ''; } if (empty($block_css)) { $block_css = ''; } if ($replace) { // Si c'est une édition manuelle, remplacer complètement le CSS de blocs $block_css = $css_content; } else { // Si c'est une génération IA, ajouter au CSS existant $block_css .= "\n/* Block CSS - " . date('Y-m-d H:i:s') . " */\n" . $css_content . "\n"; } // Sauvegarder le CSS de bloc update_post_meta($post_id, 'ai_builder_block_css_content', $block_css); // Combiner page CSS + block CSS pour le CSS final $final_css = $page_css; if (!empty($block_css)) { $final_css .= "\n" . $block_css; } update_post_meta($post_id, 'ai_builder_css_content', $final_css); } wp_send_json_success('CSS saved successfully'); } public function get_post_css() { // Vérifier que les données POST existent if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) { wp_send_json_error('Missing required data'); } // Déséchapper et assainir les données $nonce = sanitize_text_field(wp_unslash($_POST['nonce'])); $post_id = intval($_POST['post_id']); // Vérifier le nonce if (!wp_verify_nonce($nonce, 'aibui_nonce')) { wp_die('Security check failed'); } // Vérifier que l'utilisateur peut lire ce post if (!current_user_can('read_post', $post_id)) { wp_send_json_error('Insufficient permissions'); } // Récupérer les CSS depuis les meta du post $page_css = get_post_meta($post_id, 'ai_builder_page_css_content', true); $block_css = get_post_meta($post_id, 'ai_builder_block_css_content', true); $combined_css = get_post_meta($post_id, 'ai_builder_css_content', true); wp_send_json_success(array( 'pageCss' => $page_css, 'blockCss' => $block_css, 'combinedCss' => $combined_css )); } public function save_post_js() { // Vérifier que les données POST existent if (!isset($_POST['nonce']) || !isset($_POST['post_id']) || !isset($_POST['js_content'])) { wp_send_json_error('Missing required data'); } // Déséchapper et assainir les données $nonce = sanitize_text_field(wp_unslash($_POST['nonce'])); $post_id = intval($_POST['post_id']); $js_content = wp_unslash($_POST['js_content']); $js_type = isset($_POST['js_type']) ? sanitize_text_field(wp_unslash($_POST['js_type'])) : 'page'; $replace = isset($_POST['replace']) ? filter_var(wp_unslash($_POST['replace']), FILTER_VALIDATE_BOOLEAN) : false; // Vérifier le nonce if (!wp_verify_nonce($nonce, 'aibui_nonce')) { wp_die('Security check failed'); } // Vérifier que l'utilisateur peut éditer ce post if (!current_user_can('edit_post', $post_id)) { wp_send_json_error('Insufficient permissions'); } if ($js_type === 'page') { // Pour les pages, remplacer complètement le JS de page update_post_meta($post_id, 'ai_builder_page_js_content', $js_content); // Récupérer le JS de blocs existant $block_js = get_post_meta($post_id, 'ai_builder_block_js_content', true); // Combiner page JS + block JS pour le JS final $final_js = $js_content; if (!empty($block_js)) { $final_js .= "\n" . $block_js; } update_post_meta($post_id, 'ai_builder_js_content', $final_js); } else if ($js_type === 'block') { $page_js = get_post_meta($post_id, 'ai_builder_page_js_content', true); $block_js = get_post_meta($post_id, 'ai_builder_block_js_content', true); if (empty($page_js)) { $page_js = ''; } if (empty($block_js)) { $block_js = ''; } if ($replace) { // Si c'est une édition manuelle, remplacer complètement le JS de blocs $block_js = $js_content; } else { // Si c'est une génération IA, ajouter au JS existant $block_js .= "\n/* Block JS - " . date('Y-m-d H:i:s') . " */\n" . $js_content . "\n"; } // Sauvegarder le JS de bloc update_post_meta($post_id, 'ai_builder_block_js_content', $block_js); // Combiner page JS + block JS pour le JS final $final_js = $page_js; if (!empty($block_js)) { $final_js .= "\n" . $block_js; } update_post_meta($post_id, 'ai_builder_js_content', $final_js); } wp_send_json_success('JS saved successfully'); } public function get_post_js() { // Vérifier que les données POST existent if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) { wp_send_json_error('Missing required data'); } // Déséchapper et assainir les données $nonce = sanitize_text_field(wp_unslash($_POST['nonce'])); $post_id = intval($_POST['post_id']); // Vérifier le nonce if (!wp_verify_nonce($nonce, 'aibui_nonce')) { wp_die('Security check failed'); } // Vérifier que l'utilisateur peut lire ce post if (!current_user_can('read_post', $post_id)) { wp_send_json_error('Insufficient permissions'); } // Récupérer les JS depuis les meta du post $page_js = get_post_meta($post_id, 'ai_builder_page_js_content', true); $block_js = get_post_meta($post_id, 'ai_builder_block_js_content', true); $combined_js = get_post_meta($post_id, 'ai_builder_js_content', true); wp_send_json_success(array( 'pageJS' => $page_js ?: '', 'blockJS' => $block_js ?: '', 'combinedJS' => $combined_js ?: '' )); } /** * Valide le contexte d'un template (site editor) et retourne un tableau * normalisé [type, id, storage_key] ou déclenche une erreur AJAX. */ private function aibui_resolve_template_context() { if (!isset($_POST['nonce'])) { wp_send_json_error('Missing required data'); } $nonce = sanitize_text_field(wp_unslash($_POST['nonce'])); if (!wp_verify_nonce($nonce, 'aibui_nonce')) { wp_die('Security check failed'); } // L'édition de templates requiert la capacité d'édition du thème. if (!current_user_can('edit_theme_options')) { wp_send_json_error('Insufficient permissions'); } $template_type = isset($_POST['template_type']) ? sanitize_text_field(wp_unslash($_POST['template_type'])) : ''; $template_id = isset($_POST['template_id']) ? sanitize_text_field(wp_unslash($_POST['template_id'])) : ''; if (!in_array($template_type, array('wp_template', 'wp_template_part'), true)) { wp_send_json_error('Invalid template type'); } if ($template_id === '' || strpos($template_id, '//') === false) { wp_send_json_error('Invalid template id'); } return array( 'type' => $template_type, 'id' => $template_id, 'key' => $template_type . ':' . $template_id, ); } private function aibui_get_templates_option($kind) { $option_key = $kind === 'js' ? 'ai_builder_templates_js' : 'ai_builder_templates_css'; $value = get_option($option_key, array()); if (!is_array($value)) { $value = array(); } return array($option_key, $value); } public function save_template_css() { $ctx = $this->aibui_resolve_template_context(); $css_content = isset($_POST['css_content']) ? wp_unslash($_POST['css_content']) : ''; $css_type = isset($_POST['css_type']) ? sanitize_text_field(wp_unslash($_POST['css_type'])) : 'page'; $replace = isset($_POST['replace']) ? filter_var(wp_unslash($_POST['replace']), FILTER_VALIDATE_BOOLEAN) : false; list($option_key, $all) = $this->aibui_get_templates_option('css'); if (!isset($all[$ctx['key']]) || !is_array($all[$ctx['key']])) { $all[$ctx['key']] = array('page' => '', 'block' => '', 'combined' => ''); } if ($css_type === 'page') { $all[$ctx['key']]['page'] = $css_content; } elseif ($css_type === 'block') { $existing = isset($all[$ctx['key']]['block']) ? $all[$ctx['key']]['block'] : ''; if ($replace) { $all[$ctx['key']]['block'] = $css_content; } else { $all[$ctx['key']]['block'] = $existing . "\n/* Block CSS - " . date('Y-m-d H:i:s') . " */\n" . $css_content . "\n"; } } else { wp_send_json_error('Invalid css_type'); } $final = isset($all[$ctx['key']]['page']) ? $all[$ctx['key']]['page'] : ''; if (!empty($all[$ctx['key']]['block'])) { $final .= "\n" . $all[$ctx['key']]['block']; } $all[$ctx['key']]['combined'] = $final; update_option($option_key, $all, false); wp_send_json_success('Template CSS saved successfully'); } public function get_template_css() { $ctx = $this->aibui_resolve_template_context(); list(, $all) = $this->aibui_get_templates_option('css'); $entry = isset($all[$ctx['key']]) && is_array($all[$ctx['key']]) ? $all[$ctx['key']] : array(); wp_send_json_success(array( 'pageCss' => isset($entry['page']) ? $entry['page'] : '', 'blockCss' => isset($entry['block']) ? $entry['block'] : '', 'combinedCss' => isset($entry['combined']) ? $entry['combined'] : '', )); } public function save_template_js() { $ctx = $this->aibui_resolve_template_context(); $js_content = isset($_POST['js_content']) ? wp_unslash($_POST['js_content']) : ''; $js_type = isset($_POST['js_type']) ? sanitize_text_field(wp_unslash($_POST['js_type'])) : 'page'; $replace = isset($_POST['replace']) ? filter_var(wp_unslash($_POST['replace']), FILTER_VALIDATE_BOOLEAN) : false; list($option_key, $all) = $this->aibui_get_templates_option('js'); if (!isset($all[$ctx['key']]) || !is_array($all[$ctx['key']])) { $all[$ctx['key']] = array('page' => '', 'block' => '', 'combined' => ''); } if ($js_type === 'page') { $all[$ctx['key']]['page'] = $js_content; } elseif ($js_type === 'block') { $existing = isset($all[$ctx['key']]['block']) ? $all[$ctx['key']]['block'] : ''; if ($replace) { $all[$ctx['key']]['block'] = $js_content; } else { $all[$ctx['key']]['block'] = $existing . "\n/* Block JS - " . date('Y-m-d H:i:s') . " */\n" . $js_content . "\n"; } } else { wp_send_json_error('Invalid js_type'); } $final = isset($all[$ctx['key']]['page']) ? $all[$ctx['key']]['page'] : ''; if (!empty($all[$ctx['key']]['block'])) { $final .= "\n" . $all[$ctx['key']]['block']; } $all[$ctx['key']]['combined'] = $final; update_option($option_key, $all, false); wp_send_json_success('Template JS saved successfully'); } public function get_template_js() { $ctx = $this->aibui_resolve_template_context(); list(, $all) = $this->aibui_get_templates_option('js'); $entry = isset($all[$ctx['key']]) && is_array($all[$ctx['key']]) ? $all[$ctx['key']] : array(); wp_send_json_success(array( 'pageJS' => isset($entry['page']) ? $entry['page'] : '', 'blockJS' => isset($entry['block']) ? $entry['block'] : '', 'combinedJS' => isset($entry['combined']) ? $entry['combined'] : '', )); } public function save_meta_description() { if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) { wp_send_json_error('Missing required data'); } $nonce = sanitize_text_field(wp_unslash($_POST['nonce'])); if (!wp_verify_nonce($nonce, 'aibui_nonce')) { wp_die('Security check failed'); } $post_id = intval($_POST['post_id']); if (!current_user_can('edit_post', $post_id)) { wp_send_json_error('Insufficient permissions'); } $raw = isset($_POST['meta_desc']) ? wp_unslash($_POST['meta_desc']) : ''; $san = trim(wp_strip_all_tags($raw)); if (strlen($san) > 320) { $san = mb_substr($san, 0, 320); } if ($san === '') { delete_post_meta($post_id, 'aibui_meta_description'); } else { update_post_meta($post_id, 'aibui_meta_description', $san); } wp_send_json_success('Meta description saved'); } // Capture wp_mail() errors and store briefly to surface via AJAX public function capture_mail_error($wp_error) { $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; $key = 'aibui_cf_mailerr_' . md5($ip); set_transient($key, $wp_error instanceof WP_Error ? $wp_error->get_error_message() : 'Unknown mail error', 120); } public function submit_contact_form() { if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aibui_contact_form')) { wp_send_json_error('Invalid nonce'); } // Rate limiting per IP: 1 submission per 30 seconds $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; $key = 'aibui_cf_rl_' . md5($ip); $last = get_transient($key); if ($last) { wp_send_json_error('Too many requests. Please wait.'); } set_transient($key, time(), 30); $recipient = isset($_POST['recipient']) ? sanitize_email(wp_unslash($_POST['recipient'])) : ''; if (empty($recipient) || !is_email($recipient)) { $recipient = sanitize_email(get_option('admin_email')); } if (empty($recipient) || !is_email($recipient)) { wp_send_json_error('No valid recipient configured'); } $subject = sprintf('[%s] Nouveau message de contact', get_bloginfo('name')); $fields = []; $sender_email = ''; foreach ($_POST as $key => $value) { if (strpos($key, 'field_') === 0) { $label_key = 'label_' . $key; $type_key = 'type_' . $key; $req_key = 'required_' . $key; $label = isset($_POST[$label_key]) ? sanitize_text_field(wp_unslash($_POST[$label_key])) : 'Champ'; $type = isset($_POST[$type_key]) ? sanitize_text_field(wp_unslash($_POST[$type_key])) : 'text'; $is_required = isset($_POST[$req_key]) && wp_unslash($_POST[$req_key]) === '1'; $raw = wp_unslash($value); switch ($type) { case 'email': $san = sanitize_email($raw); if (!$sender_email && is_email($san)) { $sender_email = $san; } break; case 'number': $san = is_numeric($raw) ? $raw : ''; break; case 'date': $san = preg_match('/^\\d{4}-\\d{2}-\\d{2}$/', $raw) ? $raw : ''; break; case 'textarea': $san = sanitize_textarea_field($raw); break; default: $san = sanitize_text_field($raw); } if ($is_required && $san === '') { wp_send_json_error(sprintf('%s est requis', $label ? $label : 'Ce champ')); } $fields[] = ['label' => $label, 'type' => $type, 'value' => $san]; } } if (empty($fields)) { wp_send_json_error('No fields provided'); } // Build HTML email content $rows = ''; foreach ($fields as $f) { $val = $f['type'] === 'textarea' ? nl2br(esc_html($f['value'])) : esc_html($f['value']); $rows .= '