PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.2.4
AI Builder – Generate pages, blocks, images & translate with AI v2.2.4
2.7.10 2.7.9 2.7.8 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.3.10 All 122 releases
ai-builder / includes / class-ajax-handler.php

class-ajax-handler.php in AI Builder – Generate pages, blocks, images & translate with AI 2.2.4, at includes/class-ajax-handler.php

736 lines 28.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class AIBUI_Ajax_Handler
4 {
5 public function __construct()
6 {
7 add_action('wp_ajax_aibui_save_token', array($this, 'save_token'));
8 add_action('wp_ajax_aibui_set_signup_success', array($this, 'set_signup_success'));
9 add_action('wp_ajax_aibui_signout', array($this, 'signout'));
10 add_action('wp_ajax_aibui_get_token', array($this, 'get_token'));
11 add_action('wp_ajax_aibui_save_post_css', array($this, 'save_post_css'));
12 add_action('wp_ajax_aibui_get_post_css', array($this, 'get_post_css'));
13 add_action('wp_ajax_aibui_save_page_prompt', array($this, 'save_page_prompt'));
14 add_action('wp_ajax_aibui_get_page_prompt', array($this, 'get_page_prompt'));
15 add_action('wp_ajax_aibui_save_meta_description', array($this, 'save_meta_description'));
16 add_action('wp_ajax_aibui_create_page', array($this, 'create_page'));
17 add_action('wp_ajax_nopriv_aibui_submit_contact_form', array($this, 'submit_contact_form'));
18 add_action('wp_ajax_aibui_submit_contact_form', array($this, 'submit_contact_form'));
19 add_action('wp_mail_failed', array($this, 'capture_mail_error'));
20
21 // Multi-page generations storage endpoints
22 add_action('wp_ajax_aibui_save_generation', array($this, 'save_generation'));
23 add_action('wp_ajax_aibui_get_generations', array($this, 'get_generations'));
24 add_action('wp_ajax_aibui_get_generation', array($this, 'get_generation'));
25 add_action('wp_ajax_aibui_mark_generation_applied', array($this, 'mark_generation_applied'));
26
27 // Mark pages created via AI
28 add_action('wp_ajax_aibui_mark_ai_created', array($this, 'mark_ai_created'));
29 }
30
31 public function save_token()
32 {
33 // Vérifier que les données POST existent
34 if (!isset($_POST['nonce']) || !isset($_POST['token'])) {
35 wp_send_json_error('Missing required data');
36 }
37
38 // Déséchapper et assainir les données
39 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
40 $token = sanitize_text_field(wp_unslash($_POST['token']));
41
42 // Vérifier le nonce
43 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
44 wp_die('Security check failed');
45 }
46
47 if (empty($token)) {
48 wp_send_json_error('Token is required');
49 }
50
51 // Sauvegarder le token JWT
52 update_option('aibui_jwt_token', $token);
53
54 wp_send_json_success('Token saved successfully');
55 }
56
57 public function set_signup_success()
58 {
59 // Vérifier que les données POST existent
60 if (!isset($_POST['nonce'])) {
61 wp_send_json_error('Missing required data');
62 }
63
64 // Déséchapper et assainir les données
65 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
66
67 // Vérifier le nonce
68 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
69 wp_die('Security check failed');
70 }
71
72 // Marquer l'inscription comme réussie
73 update_option('aibui_user_successful_signup', true);
74
75 wp_send_json_success('Signup success flag set');
76 }
77
78 public function signout()
79 {
80 // Vérifier que les données POST existent
81 if (!isset($_POST['nonce'])) {
82 wp_send_json_error('Missing required data');
83 }
84
85 // Déséchapper et assainir les données
86 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
87
88 // Vérifier le nonce
89 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
90 wp_die('Security check failed');
91 }
92
93 // Supprimer le token JWT
94 delete_option('aibui_jwt_token');
95
96 wp_send_json_success('Signed out successfully');
97 }
98
99 public function get_token()
100 {
101 // Vérifier que les données POST existent
102 if (!isset($_POST['nonce'])) {
103 wp_send_json_error('Missing required data');
104 }
105
106 // Déséchapper et assainir les données
107 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
108
109 // Vérifier le nonce
110 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
111 wp_die('Security check failed');
112 }
113
114 // Récupérer le token JWT
115 $token = get_option('aibui_jwt_token', '');
116
117 if (empty($token)) {
118 wp_send_json_error('No token found');
119 }
120
121 wp_send_json_success(array('token' => $token));
122 }
123
124 public function save_post_css()
125 {
126 // Vérifier que les données POST existent
127 if (!isset($_POST['nonce']) || !isset($_POST['post_id']) || !isset($_POST['css_content'])) {
128 wp_send_json_error('Missing required data');
129 }
130
131 // Déséchapper et assainir les données
132 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
133 $post_id = intval($_POST['post_id']);
134 $css_content = wp_unslash($_POST['css_content']);
135 $css_type = isset($_POST['css_type']) ? sanitize_text_field(wp_unslash($_POST['css_type'])) : 'page';
136
137 // Vérifier le nonce
138 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
139 wp_die('Security check failed');
140 }
141
142 // Vérifier que l'utilisateur peut éditer ce post
143 if (!current_user_can('edit_post', $post_id)) {
144 wp_send_json_error('Insufficient permissions');
145 }
146
147 if ($css_type === 'page') {
148 // Pour les pages, remplacer complètement le CSS de page
149 update_post_meta($post_id, 'ai_builder_page_css_content', $css_content);
150
151 // Récupérer le CSS de blocs existant
152 $block_css = get_post_meta($post_id, 'ai_builder_block_css_content', true);
153
154 // Combiner page CSS + block CSS pour le CSS final
155 $final_css = $css_content;
156 if (!empty($block_css)) {
157 $final_css .= "\n" . $block_css;
158 }
159 update_post_meta($post_id, 'ai_builder_css_content', $final_css);
160
161 error_log("CSS Debug - Saving PAGE CSS. Page length: " . strlen($css_content) . ", Block length: " . strlen($block_css) . ", Final length: " . strlen($final_css));
162 } else if ($css_type === 'block') {
163 // Pour les blocs, ajouter au CSS existant
164 $page_css = get_post_meta($post_id, 'ai_builder_page_css_content', true);
165 $block_css = get_post_meta($post_id, 'ai_builder_block_css_content', true);
166
167 if (empty($page_css)) {
168 $page_css = '';
169 }
170 if (empty($block_css)) {
171 $block_css = '';
172 }
173
174 // Ajouter le nouveau CSS de bloc
175 $block_css .= "\n/* Block CSS - " . date('Y-m-d H:i:s') . " */\n" . $css_content . "\n";
176
177 // Sauvegarder le CSS de bloc
178 update_post_meta($post_id, 'ai_builder_block_css_content', $block_css);
179
180 // Combiner page CSS + block CSS pour le CSS final
181 $final_css = $page_css;
182 if (!empty($block_css)) {
183 $final_css .= "\n" . $block_css;
184 }
185 update_post_meta($post_id, 'ai_builder_css_content', $final_css);
186
187 error_log("CSS Debug - Saving BLOCK CSS. Page length: " . strlen($page_css) . ", Block length: " . strlen($block_css) . ", Final length: " . strlen($final_css));
188 }
189
190 wp_send_json_success('CSS saved successfully');
191 }
192
193
194 public function get_post_css()
195 {
196 // Vérifier que les données POST existent
197 if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
198 wp_send_json_error('Missing required data');
199 }
200
201 // Déséchapper et assainir les données
202 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
203 $post_id = intval($_POST['post_id']);
204
205 // Vérifier le nonce
206 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
207 wp_die('Security check failed');
208 }
209
210 // Vérifier que l'utilisateur peut lire ce post
211 if (!current_user_can('read_post', $post_id)) {
212 wp_send_json_error('Insufficient permissions');
213 }
214
215 // Récupérer les CSS depuis les meta du post
216 $page_css = get_post_meta($post_id, 'ai_builder_page_css_content', true);
217 $block_css = get_post_meta($post_id, 'ai_builder_block_css_content', true);
218 $combined_css = get_post_meta($post_id, 'ai_builder_css_content', true);
219
220 // Log pour debug
221 error_log("CSS Debug - Page CSS length: " . strlen($page_css));
222 error_log("CSS Debug - Block CSS length: " . strlen($block_css));
223 error_log("CSS Debug - Combined CSS length: " . strlen($combined_css));
224
225 wp_send_json_success(array(
226 'pageCss' => $page_css,
227 'blockCss' => $block_css,
228 'combinedCss' => $combined_css
229 ));
230 }
231
232 public function save_meta_description()
233 {
234 if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
235 wp_send_json_error('Missing required data');
236 }
237
238 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
239 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
240 wp_die('Security check failed');
241 }
242
243 $post_id = intval($_POST['post_id']);
244 if (!current_user_can('edit_post', $post_id)) {
245 wp_send_json_error('Insufficient permissions');
246 }
247
248 $raw = isset($_POST['meta_desc']) ? wp_unslash($_POST['meta_desc']) : '';
249 $san = trim(wp_strip_all_tags($raw));
250 if (strlen($san) > 320) {
251 $san = mb_substr($san, 0, 320);
252 }
253
254 if ($san === '') {
255 delete_post_meta($post_id, 'aibui_meta_description');
256 } else {
257 update_post_meta($post_id, 'aibui_meta_description', $san);
258 }
259
260 wp_send_json_success('Meta description saved');
261 }
262
263 // Capture wp_mail() errors and store briefly to surface via AJAX
264 public function capture_mail_error($wp_error)
265 {
266 $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
267 $key = 'aibui_cf_mailerr_' . md5($ip);
268 set_transient($key, $wp_error instanceof WP_Error ? $wp_error->get_error_message() : 'Unknown mail error', 120);
269 if (defined('WP_DEBUG') && WP_DEBUG) {
270 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)));
271 }
272 }
273
274 public function submit_contact_form()
275 {
276 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aibui_contact_form')) {
277 wp_send_json_error('Invalid nonce');
278 }
279
280 // Rate limiting per IP: 1 submission per 30 seconds
281 $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
282 $key = 'aibui_cf_rl_' . md5($ip);
283 $last = get_transient($key);
284 if ($last) {
285 wp_send_json_error('Too many requests. Please wait.');
286 }
287 set_transient($key, time(), 30);
288
289 $recipient = isset($_POST['recipient']) ? sanitize_email(wp_unslash($_POST['recipient'])) : '';
290 if (empty($recipient) || !is_email($recipient)) {
291 $recipient = sanitize_email(get_option('admin_email'));
292 }
293 if (empty($recipient) || !is_email($recipient)) {
294 wp_send_json_error('No valid recipient configured');
295 }
296
297 $subject = sprintf('[%s] Nouveau message de contact', get_bloginfo('name'));
298
299 $fields = [];
300 $sender_email = '';
301 foreach ($_POST as $key => $value) {
302 if (strpos($key, 'field_') === 0) {
303 $label_key = 'label_' . $key;
304 $type_key = 'type_' . $key;
305 $req_key = 'required_' . $key;
306 $label = isset($_POST[$label_key]) ? sanitize_text_field(wp_unslash($_POST[$label_key])) : 'Champ';
307 $type = isset($_POST[$type_key]) ? sanitize_text_field(wp_unslash($_POST[$type_key])) : 'text';
308 $is_required = isset($_POST[$req_key]) && wp_unslash($_POST[$req_key]) === '1';
309 $raw = wp_unslash($value);
310 switch ($type) {
311 case 'email':
312 $san = sanitize_email($raw);
313 if (!$sender_email && is_email($san)) {
314 $sender_email = $san;
315 }
316 break;
317 case 'number':
318 $san = is_numeric($raw) ? $raw : '';
319 break;
320 case 'date':
321 $san = preg_match('/^\\d{4}-\\d{2}-\\d{2}$/', $raw) ? $raw : '';
322 break;
323 case 'textarea':
324 $san = sanitize_textarea_field($raw);
325 break;
326 default:
327 $san = sanitize_text_field($raw);
328 }
329 if ($is_required && $san === '') {
330 wp_send_json_error(sprintf('%s est requis', $label ? $label : 'Ce champ'));
331 }
332 $fields[] = ['label' => $label, 'type' => $type, 'value' => $san];
333 }
334 }
335
336 if (empty($fields)) {
337 wp_send_json_error('No fields provided');
338 }
339
340 // Build HTML email content
341 $rows = '';
342 foreach ($fields as $f) {
343 $val = $f['type'] === 'textarea' ? nl2br(esc_html($f['value'])) : esc_html($f['value']);
344 $rows .= '<tr><td style="padding:8px 12px;border:1px solid #e5e7eb;font-weight:600;">' . esc_html($f['label']) . '</td><td style="padding:8px 12px;border:1px solid #e5e7eb;">' . $val . '</td></tr>';
345 }
346 $message = '<div style="font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#111827;">'
347 . '<h3 style="margin:0 0 12px;">' . esc_html__('Nouveau message de contact', 'ai-builder') . '</h3>'
348 . '<table cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #e5e7eb;width:100%;max-width:720px;">'
349 . $rows
350 . '</table>'
351 . '</div>';
352
353 $headers = [];
354 $headers[] = 'Content-Type: text/html; charset=UTF-8';
355 $domain = parse_url(home_url(), PHP_URL_HOST);
356 $default_from = 'no-reply@' . $domain;
357 $user_from = isset($_POST['from_email']) ? sanitize_email(wp_unslash($_POST['from_email'])) : '';
358 $from_email = $default_from;
359 if ($user_from && is_email($user_from)) {
360 // Use as From only if same domain (avoid SPF/DMARC issues)
361 $user_domain = substr(strrchr($user_from, '@'), 1);
362 if ($user_domain && strtolower($user_domain) === strtolower($domain)) {
363 $from_email = $user_from;
364 }
365 }
366 $headers[] = 'From: ' . get_bloginfo('name') . ' <' . $from_email . '>';
367 if ($sender_email && is_email($sender_email)) {
368 $headers[] = 'Reply-To: ' . $sender_email;
369 }
370
371 $sent = wp_mail($recipient, $subject, $message, $headers);
372 if (!$sent) {
373 $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
374 $key_err = 'aibui_cf_mailerr_' . md5($ip);
375 $last_err = get_transient($key_err);
376 wp_send_json_error($last_err ? $last_err : 'Failed to send');
377 }
378 wp_send_json_success('Sent');
379 }
380
381 public function create_page()
382 {
383 // Vérifier que les données POST existent
384 if (!isset($_POST['nonce']) || !isset($_POST['content_type']) || !isset($_POST['title']) || !isset($_POST['content'])) {
385 wp_send_json_error('Missing required data');
386 }
387
388 // Déséchapper et assainir les données
389 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
390 $content_type = sanitize_text_field(wp_unslash($_POST['content_type']));
391 $title = sanitize_text_field(wp_unslash($_POST['title']));
392 $content = wp_unslash($_POST['content']);
393 $css_content = isset($_POST['css_content']) ? wp_unslash($_POST['css_content']) : '';
394 $meta_description = isset($_POST['meta_description']) ? sanitize_textarea_field(wp_unslash($_POST['meta_description'])) : '';
395
396 // Vérifier le nonce
397 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
398 wp_die('Security check failed');
399 }
400
401 // Vérifier que l'utilisateur peut créer des posts/pages
402 if (!current_user_can('publish_posts')) {
403 wp_send_json_error('Insufficient permissions');
404 }
405
406 // Déséchapper les JSON de commentaires de blocs si l'API a échappé les guillemets
407 // Exemple: <!-- wp:cover {\"align\":\"full\"} --> -> <!-- wp:cover {"align":"full"} -->
408 $original_content = $content;
409 $replacement_count = 0;
410 $debug_log = array(); // Stocker les logs pour debug
411
412 $content = preg_replace_callback(
413 '/<!--\s*wp:([^\s]+)\s+(\{.*?\})\s*-->/',
414 function ($matches) use (&$replacement_count, &$debug_log) {
415 $block_name = $matches[1];
416 $json_str = $matches[2];
417 $fixed_json = stripslashes($json_str);
418
419 // Log pour debug
420 $debug_info = array(
421 'block' => $block_name,
422 'original_json' => substr($json_str, 0, 200),
423 'fixed_json' => substr($fixed_json, 0, 200),
424 'success' => false
425 );
426
427 // Ne remplacer que si le JSON corrigé est valide
428 $decoded = json_decode($fixed_json, true);
429 if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
430 $debug_info['error'] = json_last_error_msg();
431 $debug_info['original_json_full'] = $json_str;
432 $debug_log[] = $debug_info;
433 return $matches[0];
434 }
435
436 $replacement_count++;
437 $debug_info['success'] = true;
438 $debug_log[] = $debug_info;
439 return "<!-- wp:" . $block_name . " " . $fixed_json . " -->";
440 },
441 $content
442 );
443
444 // Créer un fichier de log dans le plugin
445 $log_file = plugin_dir_path(__FILE__) . '../debug-unescape.log';
446 $log_content = "=== DEBUG UNESCAPE - " . date('Y-m-d H:i:s') . " ===\n";
447 $log_content .= "Total corrections appliquées: " . $replacement_count . "\n\n";
448 $log_content .= "Contenu original (premiers 500 chars):\n" . substr($original_content, 0, 500) . "\n\n";
449 $log_content .= "Contenu corrigé (premiers 500 chars):\n" . substr($content, 0, 500) . "\n\n";
450 if (strlen($content) > 500) {
451 $log_content .= "Contenu corrigé (derniers 500 chars):\n" . substr($content, -500) . "\n\n";
452 }
453 $log_content .= "\n=== Détails par bloc ===\n";
454 foreach ($debug_log as $log) {
455 $log_content .= "\nBloc: " . $log['block'] . "\n";
456 $log_content .= "Original JSON: " . $log['original_json'] . "\n";
457 $log_content .= "Fixed JSON: " . $log['fixed_json'] . "\n";
458 if (isset($log['error'])) {
459 $log_content .= "ERREUR: " . $log['error'] . "\n";
460 $log_content .= "JSON complet: " . $log['original_json_full'] . "\n";
461 } else {
462 $log_content .= "SUCCÈS\n";
463 }
464 }
465 $log_content .= "\n=== FIN DEBUG ===\n\n";
466 file_put_contents($log_file, $log_content, FILE_APPEND);
467
468 // Vérifier que le contenu est au format HTML sérialisé WordPress
469 // Le contenu doit commencer par un commentaire de bloc WordPress
470 if (empty($content) || strpos(trim($content), '<!-- wp:') !== 0) {
471 wp_send_json_error('Invalid content format: Expected WordPress serialized block HTML');
472 }
473
474 // Valider le format des blocs avec parse_blocks
475 $parsed_blocks = parse_blocks($content);
476 if (empty($parsed_blocks) || (count($parsed_blocks) === 1 && empty($parsed_blocks[0]['blockName']))) {
477 wp_send_json_error('Invalid block format: Could not parse blocks');
478 }
479
480 // Créer le post/page
481 $post_data = array(
482 'post_title' => $title,
483 'post_content' => $content, // Contenu HTML sérialisé directement
484 'post_status' => 'publish',
485 'post_type' => $content_type === 'post' ? 'post' : 'page',
486 'post_author' => get_current_user_id(),
487 );
488
489 $post_id = wp_insert_post($post_data);
490
491 if (is_wp_error($post_id)) {
492 wp_send_json_error('Failed to create ' . $content_type);
493 }
494
495 // Sauvegarder le CSS si présent
496 if (!empty($css_content)) {
497 update_post_meta($post_id, 'ai_builder_page_css_content', $css_content);
498 update_post_meta($post_id, 'ai_builder_css_content', $css_content);
499 }
500
501 // Sauvegarder la meta description si présente
502 if (!empty($meta_description)) {
503 update_post_meta($post_id, 'aibui_meta_description', $meta_description);
504 }
505
506 // Récupérer l'URL de la page créée
507 $page_url = get_permalink($post_id);
508
509
510 wp_send_json_success(array(
511 'page_id' => $post_id,
512 'page_url' => $page_url,
513 'page_title' => $title
514 ));
515 }
516
517 // -----------------------------
518 // Multi-Page: Generations store
519 // -----------------------------
520 private function get_generations_option()
521 {
522 $stored = get_option('aibui_multi_page_generations', array());
523 if (!is_array($stored)) {
524 $stored = array();
525 }
526 return $stored;
527 }
528
529 private function persist_generations_option($generations)
530 {
531 if (!is_array($generations)) $generations = array();
532 update_option('aibui_multi_page_generations', $generations, false);
533 }
534
535 // Save a generation item (status: Pending review)
536 public function save_generation()
537 {
538 if (!isset($_POST['nonce'])) {
539 wp_send_json_error('Missing required data');
540 }
541 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
542 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
543 wp_die('Security check failed');
544 }
545 if (!current_user_can('edit_posts')) {
546 wp_send_json_error('Insufficient permissions');
547 }
548
549 $payload_raw = isset($_POST['payload']) ? wp_unslash($_POST['payload']) : '';
550 $payload = json_decode($payload_raw, true);
551 if (!$payload || !is_array($payload)) {
552 wp_send_json_error('Invalid payload');
553 }
554
555 $id = isset($payload['id']) && is_string($payload['id']) ? $payload['id'] : wp_generate_uuid4();
556 $now = current_time('mysql');
557
558 $item = array(
559 'id' => $id,
560 'title' => sanitize_text_field($payload['title'] ?? ''),
561 'metaDesc' => sanitize_textarea_field($payload['metaDesc'] ?? ''),
562 'cssContent' => wp_kses_post($payload['cssContent'] ?? ''),
563 'blocksJson' => isset($payload['blocksJson']) ? $payload['blocksJson'] : array(),
564 'status' => 'Pending review',
565 'createdAt' => $now,
566 'applied' => false,
567 'pageId' => 0,
568 );
569
570 $generations = $this->get_generations_option();
571 $generations[$id] = $item;
572 $this->persist_generations_option($generations);
573
574 wp_send_json_success($item);
575 }
576
577 // List generations
578 public function get_generations()
579 {
580 if (!isset($_POST['nonce'])) {
581 wp_send_json_error('Missing required data');
582 }
583 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
584 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
585 wp_die('Security check failed');
586 }
587 if (!current_user_can('edit_posts')) {
588 wp_send_json_error('Insufficient permissions');
589 }
590 $generations = $this->get_generations_option();
591 // Return newest first
592 $items = array_values($generations);
593 usort($items, function($a, $b) {
594 return strcmp($b['createdAt'] ?? '', $a['createdAt'] ?? '');
595 });
596 wp_send_json_success($items);
597 }
598
599 // Get one generation by id
600 public function get_generation()
601 {
602 if (!isset($_POST['nonce']) || !isset($_POST['id'])) {
603 wp_send_json_error('Missing required data');
604 }
605 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
606 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
607 wp_die('Security check failed');
608 }
609 if (!current_user_can('edit_posts')) {
610 wp_send_json_error('Insufficient permissions');
611 }
612 $id = sanitize_text_field(wp_unslash($_POST['id']));
613 $generations = $this->get_generations_option();
614 if (!isset($generations[$id])) {
615 wp_send_json_error('Not found');
616 }
617 wp_send_json_success($generations[$id]);
618 }
619
620 // Mark generation as applied (optionally attach pageId and change status)
621 public function mark_generation_applied()
622 {
623 if (!isset($_POST['nonce']) || !isset($_POST['id'])) {
624 wp_send_json_error('Missing required data');
625 }
626 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
627 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
628 wp_die('Security check failed');
629 }
630 if (!current_user_can('edit_posts')) {
631 wp_send_json_error('Insufficient permissions');
632 }
633 $id = sanitize_text_field(wp_unslash($_POST['id']));
634 $page_id = isset($_POST['page_id']) ? intval($_POST['page_id']) : 0;
635
636 $generations = $this->get_generations_option();
637 if (!isset($generations[$id])) {
638 wp_send_json_error('Not found');
639 }
640 $generations[$id]['applied'] = true;
641 if ($page_id > 0) {
642 $generations[$id]['pageId'] = $page_id;
643 }
644 $generations[$id]['status'] = 'Applied';
645 $this->persist_generations_option($generations);
646
647 wp_send_json_success($generations[$id]);
648 }
649
650 public function save_page_prompt()
651 {
652 // Vérifier que les données POST existent
653 if (!isset($_POST['nonce']) || !isset($_POST['post_id']) || !isset($_POST['page_prompt'])) {
654 wp_send_json_error('Missing required data');
655 }
656
657 // Déséchapper et assainir les données
658 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
659 $post_id = intval($_POST['post_id']);
660 $page_prompt = sanitize_textarea_field(wp_unslash($_POST['page_prompt']));
661
662 // Vérifier le nonce
663 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
664 wp_die('Security check failed');
665 }
666
667 // Vérifier que l'utilisateur peut modifier ce post
668 if (!current_user_can('edit_post', $post_id)) {
669 wp_send_json_error('Insufficient permissions');
670 }
671
672 // Sauvegarder le prompt de page
673 update_post_meta($post_id, 'ai_builder_page_prompt', $page_prompt);
674
675 wp_send_json_success('Page prompt saved successfully');
676 }
677
678 public function get_page_prompt()
679 {
680 // Vérifier que les données POST existent
681 if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
682 wp_send_json_error('Missing required data');
683 }
684
685 // Déséchapper et assainir les données
686 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
687 $post_id = intval($_POST['post_id']);
688
689 // Vérifier le nonce
690 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
691 wp_die('Security check failed');
692 }
693
694 // Vérifier que l'utilisateur peut lire ce post
695 if (!current_user_can('read_post', $post_id)) {
696 wp_send_json_error('Insufficient permissions');
697 }
698
699 // Récupérer le prompt de page
700 $page_prompt = get_post_meta($post_id, 'ai_builder_page_prompt', true);
701
702 wp_send_json_success(array(
703 'pagePrompt' => $page_prompt ?: ''
704 ));
705 }
706
707 /**
708 * Marquer une page comme créée via IA
709 */
710 public function mark_ai_created()
711 {
712 // Vérifier que les données POST existent
713 if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
714 wp_send_json_error('Missing required data');
715 }
716
717 // Déséchapper et assainir les données
718 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
719 $post_id = intval($_POST['post_id']);
720
721 // Vérifier le nonce
722 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
723 wp_die('Security check failed');
724 }
725
726 // Vérifier que l'utilisateur peut éditer ce post
727 if (!current_user_can('edit_post', $post_id)) {
728 wp_send_json_error('Insufficient permissions');
729 }
730
731 // Marquer la page comme créée via IA
732 update_post_meta($post_id, '_aibui_created_by_ai', '1');
733
734 wp_send_json_success('Page marked as AI-created');
735 }
736 }