$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'; // 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); error_log("CSS Debug - Saving PAGE CSS. Page length: " . strlen($css_content) . ", Block length: " . strlen($block_css) . ", Final length: " . strlen($final_css)); } else if ($css_type === 'block') { // Pour les blocs, ajouter au CSS existant $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 = ''; } // Ajouter le nouveau CSS de bloc $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); error_log("CSS Debug - Saving BLOCK CSS. Page length: " . strlen($page_css) . ", Block length: " . strlen($block_css) . ", Final length: " . strlen($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); // Log pour debug error_log("CSS Debug - Page CSS length: " . strlen($page_css)); error_log("CSS Debug - Block CSS length: " . strlen($block_css)); error_log("CSS Debug - Combined CSS length: " . strlen($combined_css)); wp_send_json_success(array( 'pageCss' => $page_css, 'blockCss' => $block_css, 'combinedCss' => $combined_css )); } 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); if (defined('WP_DEBUG') && WP_DEBUG) { error_log('[AIBUI] wp_mail_failed: ' . (is_object($wp_error) && method_exists($wp_error, 'get_error_message') ? $wp_error->get_error_message() : print_r($wp_error, true))); } } 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 .= '' . esc_html($f['label']) . '' . $val . ''; } $message = '
' . '

' . esc_html__('Nouveau message de contact', 'ai-builder') . '

' . '' . $rows . '
' . '
'; $headers = []; $headers[] = 'Content-Type: text/html; charset=UTF-8'; $domain = parse_url(home_url(), PHP_URL_HOST); $default_from = 'no-reply@' . $domain; $user_from = isset($_POST['from_email']) ? sanitize_email(wp_unslash($_POST['from_email'])) : ''; $from_email = $default_from; if ($user_from && is_email($user_from)) { // Use as From only if same domain (avoid SPF/DMARC issues) $user_domain = substr(strrchr($user_from, '@'), 1); if ($user_domain && strtolower($user_domain) === strtolower($domain)) { $from_email = $user_from; } } $headers[] = 'From: ' . get_bloginfo('name') . ' <' . $from_email . '>'; if ($sender_email && is_email($sender_email)) { $headers[] = 'Reply-To: ' . $sender_email; } $sent = wp_mail($recipient, $subject, $message, $headers); if (!$sent) { $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; $key_err = 'aibui_cf_mailerr_' . md5($ip); $last_err = get_transient($key_err); wp_send_json_error($last_err ? $last_err : 'Failed to send'); } wp_send_json_success('Sent'); } public function create_page() { // Vérifier que les données POST existent if (!isset($_POST['nonce']) || !isset($_POST['content_type']) || !isset($_POST['title']) || !isset($_POST['content'])) { wp_send_json_error('Missing required data'); } // Déséchapper et assainir les données $nonce = sanitize_text_field(wp_unslash($_POST['nonce'])); $content_type = sanitize_text_field(wp_unslash($_POST['content_type'])); $title = sanitize_text_field(wp_unslash($_POST['title'])); $content = wp_unslash($_POST['content']); $css_content = isset($_POST['css_content']) ? wp_unslash($_POST['css_content']) : ''; $meta_description = isset($_POST['meta_description']) ? sanitize_textarea_field(wp_unslash($_POST['meta_description'])) : ''; // Vérifier le nonce if (!wp_verify_nonce($nonce, 'aibui_nonce')) { wp_die('Security check failed'); } // Vérifier que l'utilisateur peut créer des posts/pages if (!current_user_can('publish_posts')) { wp_send_json_error('Insufficient permissions'); } // Déséchapper les JSON de commentaires de blocs si l'API a échappé les guillemets // Exemple: -> $original_content = $content; $replacement_count = 0; $debug_log = array(); // Stocker les logs pour debug $content = preg_replace_callback( '//', function ($matches) use (&$replacement_count, &$debug_log) { $block_name = $matches[1]; $json_str = $matches[2]; $fixed_json = stripslashes($json_str); // Log pour debug $debug_info = array( 'block' => $block_name, 'original_json' => substr($json_str, 0, 200), 'fixed_json' => substr($fixed_json, 0, 200), 'success' => false ); // Ne remplacer que si le JSON corrigé est valide $decoded = json_decode($fixed_json, true); if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) { $debug_info['error'] = json_last_error_msg(); $debug_info['original_json_full'] = $json_str; $debug_log[] = $debug_info; return $matches[0]; } $replacement_count++; $debug_info['success'] = true; $debug_log[] = $debug_info; return ""; }, $content ); // Créer un fichier de log dans le plugin $log_file = plugin_dir_path(__FILE__) . '../debug-unescape.log'; $log_content = "=== DEBUG UNESCAPE - " . date('Y-m-d H:i:s') . " ===\n"; $log_content .= "Total corrections appliquées: " . $replacement_count . "\n\n"; $log_content .= "Contenu original (premiers 500 chars):\n" . substr($original_content, 0, 500) . "\n\n"; $log_content .= "Contenu corrigé (premiers 500 chars):\n" . substr($content, 0, 500) . "\n\n"; if (strlen($content) > 500) { $log_content .= "Contenu corrigé (derniers 500 chars):\n" . substr($content, -500) . "\n\n"; } $log_content .= "\n=== Détails par bloc ===\n"; foreach ($debug_log as $log) { $log_content .= "\nBloc: " . $log['block'] . "\n"; $log_content .= "Original JSON: " . $log['original_json'] . "\n"; $log_content .= "Fixed JSON: " . $log['fixed_json'] . "\n"; if (isset($log['error'])) { $log_content .= "ERREUR: " . $log['error'] . "\n"; $log_content .= "JSON complet: " . $log['original_json_full'] . "\n"; } else { $log_content .= "SUCCÈS\n"; } } $log_content .= "\n=== FIN DEBUG ===\n\n"; file_put_contents($log_file, $log_content, FILE_APPEND); // Vérifier que le contenu est au format HTML sérialisé WordPress // Le contenu doit commencer par un commentaire de bloc WordPress if (empty($content) || strpos(trim($content), '