PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.7.2
AI Builder – Generate pages, blocks, images & translate with AI v2.7.2
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 2.3.11 All 121 releases
ai-builder / includes / class-ajax-handler.php

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

882 lines 33.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_post_js', array($this, 'save_post_js'));
14 add_action('wp_ajax_aibui_get_post_js', array($this, 'get_post_js'));
15 add_action('wp_ajax_aibui_save_page_prompt', array($this, 'save_page_prompt'));
16 add_action('wp_ajax_aibui_get_page_prompt', array($this, 'get_page_prompt'));
17 add_action('wp_ajax_aibui_save_meta_description', array($this, 'save_meta_description'));
18 add_action('wp_ajax_aibui_create_page', array($this, 'create_page'));
19 add_action('wp_ajax_nopriv_aibui_submit_contact_form', array($this, 'submit_contact_form'));
20 add_action('wp_ajax_aibui_submit_contact_form', array($this, 'submit_contact_form'));
21 add_action('wp_mail_failed', array($this, 'capture_mail_error'));
22
23 // Multi-page generations storage endpoints
24 add_action('wp_ajax_aibui_save_generation', array($this, 'save_generation'));
25 add_action('wp_ajax_aibui_get_generations', array($this, 'get_generations'));
26 add_action('wp_ajax_aibui_get_generation', array($this, 'get_generation'));
27 add_action('wp_ajax_aibui_mark_generation_applied', array($this, 'mark_generation_applied'));
28
29 // Mark pages created via AI
30 add_action('wp_ajax_aibui_mark_ai_created', array($this, 'mark_ai_created'));
31 add_action('wp_ajax_aibui_get_ai_created_status', array($this, 'get_ai_created_status'));
32 }
33
34 public function save_token()
35 {
36 // Vérifier que les données POST existent
37 if (!isset($_POST['nonce']) || !isset($_POST['token'])) {
38 wp_send_json_error('Missing required data');
39 }
40
41 // Déséchapper et assainir les données
42 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
43 $token = sanitize_text_field(wp_unslash($_POST['token']));
44
45 // Vérifier le nonce
46 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
47 wp_die('Security check failed');
48 }
49
50 if (empty($token)) {
51 wp_send_json_error('Token is required');
52 }
53
54 // Sauvegarder le token JWT
55 update_option('aibui_jwt_token', $token);
56
57 wp_send_json_success('Token saved successfully');
58 }
59
60 public function set_signup_success()
61 {
62 // Vérifier que les données POST existent
63 if (!isset($_POST['nonce'])) {
64 wp_send_json_error('Missing required data');
65 }
66
67 // Déséchapper et assainir les données
68 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
69
70 // Vérifier le nonce
71 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
72 wp_die('Security check failed');
73 }
74
75 // Marquer l'inscription comme réussie
76 update_option('aibui_user_successful_signup', true);
77
78 wp_send_json_success('Signup success flag set');
79 }
80
81 public function signout()
82 {
83 // Vérifier que les données POST existent
84 if (!isset($_POST['nonce'])) {
85 wp_send_json_error('Missing required data');
86 }
87
88 // Déséchapper et assainir les données
89 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
90
91 // Vérifier le nonce
92 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
93 wp_die('Security check failed');
94 }
95
96 // Supprimer le token JWT
97 delete_option('aibui_jwt_token');
98
99 wp_send_json_success('Signed out successfully');
100 }
101
102 public function get_token()
103 {
104 // Vérifier que les données POST existent
105 if (!isset($_POST['nonce'])) {
106 wp_send_json_error('Missing required data');
107 }
108
109 // Déséchapper et assainir les données
110 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
111
112 // Vérifier le nonce
113 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
114 wp_die('Security check failed');
115 }
116
117 // Récupérer le token JWT
118 $token = get_option('aibui_jwt_token', '');
119
120 if (empty($token)) {
121 wp_send_json_error('No token found');
122 }
123
124 wp_send_json_success(array('token' => $token));
125 }
126
127 public function save_post_css()
128 {
129 // Vérifier que les données POST existent
130 if (!isset($_POST['nonce']) || !isset($_POST['post_id']) || !isset($_POST['css_content'])) {
131 wp_send_json_error('Missing required data');
132 }
133
134 // Déséchapper et assainir les données
135 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
136 $raw_post_id = wp_unslash($_POST['post_id']);
137 $css_content = wp_unslash($_POST['css_content']);
138 $css_type = isset($_POST['css_type']) ? sanitize_text_field(wp_unslash($_POST['css_type'])) : 'page';
139 $replace = isset($_POST['replace']) ? filter_var(wp_unslash($_POST['replace']), FILTER_VALIDATE_BOOLEAN) : false;
140
141 // Vérifier le nonce
142 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
143 wp_die('Security check failed');
144 }
145
146 // --- Template / template part (Site Editor) ---
147 // ex: "twentytwentyfive//404" ou "twentytwentyfive//header"
148 if (class_exists('AIBUI_Template_Assets') && AIBUI_Template_Assets::is_template_id($raw_post_id)) {
149 if (!AIBUI_Template_Assets::current_user_can_edit()) {
150 wp_send_json_error('Insufficient permissions');
151 }
152 $ok = AIBUI_Template_Assets::instance()->save_css($raw_post_id, $css_content, $css_type, $replace);
153 if ($ok) {
154 wp_send_json_success('CSS saved successfully (template)');
155 }
156 wp_send_json_error('Unable to save template CSS');
157 }
158
159 $post_id = intval($raw_post_id);
160
161 // Vérifier que l'utilisateur peut éditer ce post
162 if (!current_user_can('edit_post', $post_id)) {
163 wp_send_json_error('Insufficient permissions');
164 }
165
166 if ($css_type === 'page') {
167 // Pour les pages, remplacer complètement le CSS de page
168 update_post_meta($post_id, 'ai_builder_page_css_content', $css_content);
169
170 // Récupérer le CSS de blocs existant
171 $block_css = get_post_meta($post_id, 'ai_builder_block_css_content', true);
172
173 // Combiner page CSS + block CSS pour le CSS final
174 $final_css = $css_content;
175 if (!empty($block_css)) {
176 $final_css .= "\n" . $block_css;
177 }
178 update_post_meta($post_id, 'ai_builder_css_content', $final_css);
179
180 } else if ($css_type === 'block') {
181 $page_css = get_post_meta($post_id, 'ai_builder_page_css_content', true);
182 $block_css = get_post_meta($post_id, 'ai_builder_block_css_content', true);
183
184 if (empty($page_css)) {
185 $page_css = '';
186 }
187 if (empty($block_css)) {
188 $block_css = '';
189 }
190
191 if ($replace) {
192 // Si c'est une édition manuelle, remplacer complètement le CSS de blocs
193 $block_css = $css_content;
194 } else {
195 // Si c'est une génération IA, ajouter au CSS existant
196 $block_css .= "\n/* Block CSS - " . date('Y-m-d H:i:s') . " */\n" . $css_content . "\n";
197 }
198
199 // Sauvegarder le CSS de bloc
200 update_post_meta($post_id, 'ai_builder_block_css_content', $block_css);
201
202 // Combiner page CSS + block CSS pour le CSS final
203 $final_css = $page_css;
204 if (!empty($block_css)) {
205 $final_css .= "\n" . $block_css;
206 }
207 update_post_meta($post_id, 'ai_builder_css_content', $final_css);
208
209 }
210
211 wp_send_json_success('CSS saved successfully');
212 }
213
214
215 public function get_post_css()
216 {
217 // Vérifier que les données POST existent
218 if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
219 wp_send_json_error('Missing required data');
220 }
221
222 // Déséchapper et assainir les données
223 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
224 $raw_post_id = wp_unslash($_POST['post_id']);
225
226 // Vérifier le nonce
227 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
228 wp_die('Security check failed');
229 }
230
231 // --- Template / template part (Site Editor) ---
232 if (class_exists('AIBUI_Template_Assets') && AIBUI_Template_Assets::is_template_id($raw_post_id)) {
233 if (!AIBUI_Template_Assets::current_user_can_edit()) {
234 wp_send_json_error('Insufficient permissions');
235 }
236 $entry = AIBUI_Template_Assets::instance()->get_entry($raw_post_id);
237 wp_send_json_success(array(
238 'pageCss' => isset($entry['page_css']) ? (string) $entry['page_css'] : '',
239 'blockCss' => isset($entry['block_css']) ? (string) $entry['block_css'] : '',
240 'combinedCss' => isset($entry['css']) ? (string) $entry['css'] : '',
241 ));
242 }
243
244 $post_id = intval($raw_post_id);
245
246 // Vérifier que l'utilisateur peut lire ce post
247 if (!current_user_can('read_post', $post_id)) {
248 wp_send_json_error('Insufficient permissions');
249 }
250
251 // Récupérer les CSS depuis les meta du post
252 $page_css = get_post_meta($post_id, 'ai_builder_page_css_content', true);
253 $block_css = get_post_meta($post_id, 'ai_builder_block_css_content', true);
254 $combined_css = get_post_meta($post_id, 'ai_builder_css_content', true);
255
256 wp_send_json_success(array(
257 'pageCss' => $page_css,
258 'blockCss' => $block_css,
259 'combinedCss' => $combined_css
260 ));
261 }
262
263 public function save_post_js()
264 {
265 // Vérifier que les données POST existent
266 if (!isset($_POST['nonce']) || !isset($_POST['post_id']) || !isset($_POST['js_content'])) {
267 wp_send_json_error('Missing required data');
268 }
269
270 // Déséchapper et assainir les données
271 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
272 $raw_post_id = wp_unslash($_POST['post_id']);
273 $js_content = wp_unslash($_POST['js_content']);
274 $js_type = isset($_POST['js_type']) ? sanitize_text_field(wp_unslash($_POST['js_type'])) : 'page';
275 $replace = isset($_POST['replace']) ? filter_var(wp_unslash($_POST['replace']), FILTER_VALIDATE_BOOLEAN) : false;
276
277 // Vérifier le nonce
278 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
279 wp_die('Security check failed');
280 }
281
282 // --- Template / template part (Site Editor) ---
283 if (class_exists('AIBUI_Template_Assets') && AIBUI_Template_Assets::is_template_id($raw_post_id)) {
284 if (!AIBUI_Template_Assets::current_user_can_edit()) {
285 wp_send_json_error('Insufficient permissions');
286 }
287 $ok = AIBUI_Template_Assets::instance()->save_js($raw_post_id, $js_content, $js_type, $replace);
288 if ($ok) {
289 wp_send_json_success('JS saved successfully (template)');
290 }
291 wp_send_json_error('Unable to save template JS');
292 }
293
294 $post_id = intval($raw_post_id);
295
296 // Vérifier que l'utilisateur peut éditer ce post
297 if (!current_user_can('edit_post', $post_id)) {
298 wp_send_json_error('Insufficient permissions');
299 }
300
301 if ($js_type === 'page') {
302 // Pour les pages, remplacer complètement le JS de page
303 update_post_meta($post_id, 'ai_builder_page_js_content', $js_content);
304
305 // Récupérer le JS de blocs existant
306 $block_js = get_post_meta($post_id, 'ai_builder_block_js_content', true);
307
308 // Combiner page JS + block JS pour le JS final
309 $final_js = $js_content;
310 if (!empty($block_js)) {
311 $final_js .= "\n" . $block_js;
312 }
313 update_post_meta($post_id, 'ai_builder_js_content', $final_js);
314
315 } else if ($js_type === 'block') {
316 $page_js = get_post_meta($post_id, 'ai_builder_page_js_content', true);
317 $block_js = get_post_meta($post_id, 'ai_builder_block_js_content', true);
318
319 if (empty($page_js)) {
320 $page_js = '';
321 }
322 if (empty($block_js)) {
323 $block_js = '';
324 }
325
326 if ($replace) {
327 // Si c'est une édition manuelle, remplacer complètement le JS de blocs
328 $block_js = $js_content;
329 } else {
330 // Si c'est une génération IA, ajouter au JS existant
331 $block_js .= "\n/* Block JS - " . date('Y-m-d H:i:s') . " */\n" . $js_content . "\n";
332 }
333
334 // Sauvegarder le JS de bloc
335 update_post_meta($post_id, 'ai_builder_block_js_content', $block_js);
336
337 // Combiner page JS + block JS pour le JS final
338 $final_js = $page_js;
339 if (!empty($block_js)) {
340 $final_js .= "\n" . $block_js;
341 }
342 update_post_meta($post_id, 'ai_builder_js_content', $final_js);
343
344 }
345
346 wp_send_json_success('JS saved successfully');
347 }
348
349 public function get_post_js()
350 {
351 // Vérifier que les données POST existent
352 if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
353 wp_send_json_error('Missing required data');
354 }
355
356 // Déséchapper et assainir les données
357 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
358 $raw_post_id = wp_unslash($_POST['post_id']);
359
360 // Vérifier le nonce
361 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
362 wp_die('Security check failed');
363 }
364
365 // --- Template / template part (Site Editor) ---
366 if (class_exists('AIBUI_Template_Assets') && AIBUI_Template_Assets::is_template_id($raw_post_id)) {
367 if (!AIBUI_Template_Assets::current_user_can_edit()) {
368 wp_send_json_error('Insufficient permissions');
369 }
370 $entry = AIBUI_Template_Assets::instance()->get_entry($raw_post_id);
371 wp_send_json_success(array(
372 'pageJS' => isset($entry['page_js']) ? (string) $entry['page_js'] : '',
373 'blockJS' => isset($entry['block_js']) ? (string) $entry['block_js'] : '',
374 'combinedJS' => isset($entry['js']) ? (string) $entry['js'] : '',
375 ));
376 }
377
378 $post_id = intval($raw_post_id);
379
380 // Vérifier que l'utilisateur peut lire ce post
381 if (!current_user_can('read_post', $post_id)) {
382 wp_send_json_error('Insufficient permissions');
383 }
384
385 // Récupérer les JS depuis les meta du post
386 $page_js = get_post_meta($post_id, 'ai_builder_page_js_content', true);
387 $block_js = get_post_meta($post_id, 'ai_builder_block_js_content', true);
388 $combined_js = get_post_meta($post_id, 'ai_builder_js_content', true);
389
390 wp_send_json_success(array(
391 'pageJS' => $page_js ?: '',
392 'blockJS' => $block_js ?: '',
393 'combinedJS' => $combined_js ?: ''
394 ));
395 }
396
397 public function save_meta_description()
398 {
399 if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
400 wp_send_json_error('Missing required data');
401 }
402
403 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
404 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
405 wp_die('Security check failed');
406 }
407
408 $post_id = intval($_POST['post_id']);
409 if (!current_user_can('edit_post', $post_id)) {
410 wp_send_json_error('Insufficient permissions');
411 }
412
413 $raw = isset($_POST['meta_desc']) ? wp_unslash($_POST['meta_desc']) : '';
414 $san = trim(wp_strip_all_tags($raw));
415 if (strlen($san) > 320) {
416 $san = mb_substr($san, 0, 320);
417 }
418
419 if ($san === '') {
420 delete_post_meta($post_id, 'aibui_meta_description');
421 } else {
422 update_post_meta($post_id, 'aibui_meta_description', $san);
423 }
424
425 wp_send_json_success('Meta description saved');
426 }
427
428 // Capture wp_mail() errors and store briefly to surface via AJAX
429 public function capture_mail_error($wp_error)
430 {
431 $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
432 $key = 'aibui_cf_mailerr_' . md5($ip);
433 set_transient($key, $wp_error instanceof WP_Error ? $wp_error->get_error_message() : 'Unknown mail error', 120);
434
435 }
436
437 public function submit_contact_form()
438 {
439 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'aibui_contact_form')) {
440 wp_send_json_error('Invalid nonce');
441 }
442
443 // Rate limiting per IP: 1 submission per 30 seconds
444 $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
445 $key = 'aibui_cf_rl_' . md5($ip);
446 $last = get_transient($key);
447 if ($last) {
448 wp_send_json_error('Too many requests. Please wait.');
449 }
450 set_transient($key, time(), 30);
451
452 $recipient = isset($_POST['recipient']) ? sanitize_email(wp_unslash($_POST['recipient'])) : '';
453 if (empty($recipient) || !is_email($recipient)) {
454 $recipient = sanitize_email(get_option('admin_email'));
455 }
456 if (empty($recipient) || !is_email($recipient)) {
457 wp_send_json_error('No valid recipient configured');
458 }
459
460 $subject = sprintf('[%s] Nouveau message de contact', get_bloginfo('name'));
461
462 $fields = [];
463 $sender_email = '';
464 foreach ($_POST as $key => $value) {
465 if (strpos($key, 'field_') === 0) {
466 $label_key = 'label_' . $key;
467 $type_key = 'type_' . $key;
468 $req_key = 'required_' . $key;
469 $label = isset($_POST[$label_key]) ? sanitize_text_field(wp_unslash($_POST[$label_key])) : 'Champ';
470 $type = isset($_POST[$type_key]) ? sanitize_text_field(wp_unslash($_POST[$type_key])) : 'text';
471 $is_required = isset($_POST[$req_key]) && wp_unslash($_POST[$req_key]) === '1';
472 $raw = wp_unslash($value);
473 switch ($type) {
474 case 'email':
475 $san = sanitize_email($raw);
476 if (!$sender_email && is_email($san)) {
477 $sender_email = $san;
478 }
479 break;
480 case 'number':
481 $san = is_numeric($raw) ? $raw : '';
482 break;
483 case 'date':
484 $san = preg_match('/^\\d{4}-\\d{2}-\\d{2}$/', $raw) ? $raw : '';
485 break;
486 case 'textarea':
487 $san = sanitize_textarea_field($raw);
488 break;
489 default:
490 $san = sanitize_text_field($raw);
491 }
492 if ($is_required && $san === '') {
493 wp_send_json_error(sprintf('%s est requis', $label ? $label : 'Ce champ'));
494 }
495 $fields[] = ['label' => $label, 'type' => $type, 'value' => $san];
496 }
497 }
498
499 if (empty($fields)) {
500 wp_send_json_error('No fields provided');
501 }
502
503 // Build HTML email content
504 $rows = '';
505 foreach ($fields as $f) {
506 $val = $f['type'] === 'textarea' ? nl2br(esc_html($f['value'])) : esc_html($f['value']);
507 $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>';
508 }
509 $message = '<div style="font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#111827;">'
510 . '<h3 style="margin:0 0 12px;">' . esc_html__('Nouveau message de contact', 'ai-builder') . '</h3>'
511 . '<table cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #e5e7eb;width:100%;max-width:720px;">'
512 . $rows
513 . '</table>'
514 . '</div>';
515
516 $headers = [];
517 $headers[] = 'Content-Type: text/html; charset=UTF-8';
518 $domain = parse_url(home_url(), PHP_URL_HOST);
519 $default_from = 'no-reply@' . $domain;
520 $user_from = isset($_POST['from_email']) ? sanitize_email(wp_unslash($_POST['from_email'])) : '';
521 $from_email = $default_from;
522 if ($user_from && is_email($user_from)) {
523 // Use as From only if same domain (avoid SPF/DMARC issues)
524 $user_domain = substr(strrchr($user_from, '@'), 1);
525 if ($user_domain && strtolower($user_domain) === strtolower($domain)) {
526 $from_email = $user_from;
527 }
528 }
529 $headers[] = 'From: ' . get_bloginfo('name') . ' <' . $from_email . '>';
530 if ($sender_email && is_email($sender_email)) {
531 $headers[] = 'Reply-To: ' . $sender_email;
532 }
533
534 $sent = wp_mail($recipient, $subject, $message, $headers);
535 if (!$sent) {
536 $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
537 $key_err = 'aibui_cf_mailerr_' . md5($ip);
538 $last_err = get_transient($key_err);
539 wp_send_json_error($last_err ? $last_err : 'Failed to send');
540 }
541 wp_send_json_success('Sent');
542 }
543
544 public function create_page()
545 {
546 // Vérifier que les données POST existent
547 if (!isset($_POST['nonce']) || !isset($_POST['content_type']) || !isset($_POST['title']) || !isset($_POST['content'])) {
548 wp_send_json_error('Missing required data');
549 }
550
551 // Déséchapper et assainir les données
552 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
553 $content_type = sanitize_text_field(wp_unslash($_POST['content_type']));
554 $title = sanitize_text_field(wp_unslash($_POST['title']));
555 $content = wp_unslash($_POST['content']);
556 $css_content = isset($_POST['css_content']) ? wp_unslash($_POST['css_content']) : '';
557 $meta_description = isset($_POST['meta_description']) ? sanitize_textarea_field(wp_unslash($_POST['meta_description'])) : '';
558
559 // Vérifier le nonce
560 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
561 wp_die('Security check failed');
562 }
563
564 // Vérifier que l'utilisateur peut créer des posts/pages
565 if (!current_user_can('publish_posts')) {
566 wp_send_json_error('Insufficient permissions');
567 }
568
569 // Déséchapper les JSON de commentaires de blocs si l'API a échappé les guillemets
570 // Exemple: <!-- wp:cover {\"align\":\"full\"} --> -> <!-- wp:cover {"align":"full"} -->
571 $original_content = $content;
572 $replacement_count = 0;
573 $debug_log = array(); // Stocker les logs pour debug
574
575 $content = preg_replace_callback(
576 '/<!--\s*wp:([^\s]+)\s+(\{.*?\})\s*-->/',
577 function ($matches) use (&$replacement_count, &$debug_log) {
578 $block_name = $matches[1];
579 $json_str = $matches[2];
580 $fixed_json = stripslashes($json_str);
581
582 // Log pour debug
583 $debug_info = array(
584 'block' => $block_name,
585 'original_json' => substr($json_str, 0, 200),
586 'fixed_json' => substr($fixed_json, 0, 200),
587 'success' => false
588 );
589
590 // Ne remplacer que si le JSON corrigé est valide
591 $decoded = json_decode($fixed_json, true);
592 if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
593 $debug_info['error'] = json_last_error_msg();
594 $debug_info['original_json_full'] = $json_str;
595 $debug_log[] = $debug_info;
596 return $matches[0];
597 }
598
599 $replacement_count++;
600 $debug_info['success'] = true;
601 $debug_log[] = $debug_info;
602 return "<!-- wp:" . $block_name . " " . $fixed_json . " -->";
603 },
604 $content
605 );
606
607 // Vérifier que le contenu est au format HTML sérialisé WordPress
608 // Le contenu doit commencer par un commentaire de bloc WordPress
609 if (empty($content) || strpos(trim($content), '<!-- wp:') !== 0) {
610 wp_send_json_error('Invalid content format: Expected WordPress serialized block HTML');
611 }
612
613 // Valider le format des blocs avec parse_blocks
614 $parsed_blocks = parse_blocks($content);
615 if (empty($parsed_blocks) || (count($parsed_blocks) === 1 && empty($parsed_blocks[0]['blockName']))) {
616 wp_send_json_error('Invalid block format: Could not parse blocks');
617 }
618
619 // Créer le post/page
620 $post_data = array(
621 'post_title' => $title,
622 'post_content' => $content, // Contenu HTML sérialisé directement
623 'post_status' => 'publish',
624 'post_type' => $content_type === 'post' ? 'post' : 'page',
625 'post_author' => get_current_user_id(),
626 );
627
628 $post_id = wp_insert_post($post_data);
629
630 if (is_wp_error($post_id)) {
631 wp_send_json_error('Failed to create ' . $content_type);
632 }
633
634 // Sauvegarder le CSS si présent
635 if (!empty($css_content)) {
636 update_post_meta($post_id, 'ai_builder_page_css_content', $css_content);
637 update_post_meta($post_id, 'ai_builder_css_content', $css_content);
638 }
639
640 // Sauvegarder la meta description si présente
641 if (!empty($meta_description)) {
642 update_post_meta($post_id, 'aibui_meta_description', $meta_description);
643 }
644
645 // Récupérer l'URL de la page créée
646 $page_url = get_permalink($post_id);
647
648
649 wp_send_json_success(array(
650 'page_id' => $post_id,
651 'page_url' => $page_url,
652 'page_title' => $title
653 ));
654 }
655
656 // -----------------------------
657 // Multi-Page: Generations store (using JSON files)
658 // -----------------------------
659 private function get_storage()
660 {
661 static $storage = null;
662 if ($storage === null) {
663 require_once plugin_dir_path(__FILE__) . 'class-generations-storage.php';
664 $storage = new AIBUI_Generations_Storage();
665 }
666 return $storage;
667 }
668
669 // Save a generation item (status: Pending review)
670 public function save_generation()
671 {
672 if (!isset($_POST['nonce'])) {
673 wp_send_json_error('Missing required data');
674 }
675 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
676 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
677 wp_die('Security check failed');
678 }
679 if (!current_user_can('edit_posts')) {
680 wp_send_json_error('Insufficient permissions');
681 }
682
683 $payload_raw = isset($_POST['payload']) ? wp_unslash($_POST['payload']) : '';
684 $payload = json_decode($payload_raw, true);
685 if (!$payload || !is_array($payload)) {
686 wp_send_json_error('Invalid payload');
687 }
688
689 $storage = $this->get_storage();
690 $result = $storage->save($payload);
691
692 if (is_wp_error($result)) {
693 wp_send_json_error($result->get_error_message());
694 }
695
696 wp_send_json_success($result);
697 }
698
699 // List generations
700 public function get_generations()
701 {
702 if (!isset($_POST['nonce'])) {
703 wp_send_json_error('Missing required data');
704 }
705 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
706 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
707 wp_die('Security check failed');
708 }
709 if (!current_user_can('edit_posts')) {
710 wp_send_json_error('Insufficient permissions');
711 }
712
713 $storage = $this->get_storage();
714 $items = $storage->get_all();
715
716 wp_send_json_success($items);
717 }
718
719 // Get one generation by id
720 public function get_generation()
721 {
722 if (!isset($_POST['nonce']) || !isset($_POST['id'])) {
723 wp_send_json_error('Missing required data');
724 }
725 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
726 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
727 wp_die('Security check failed');
728 }
729 if (!current_user_can('edit_posts')) {
730 wp_send_json_error('Insufficient permissions');
731 }
732 $id = sanitize_text_field(wp_unslash($_POST['id']));
733
734 $storage = $this->get_storage();
735 $result = $storage->get($id);
736
737 if (is_wp_error($result)) {
738 wp_send_json_error($result->get_error_message());
739 }
740
741 wp_send_json_success($result);
742 }
743
744 // Mark generation as applied (optionally attach pageId and change status)
745 public function mark_generation_applied()
746 {
747 if (!isset($_POST['nonce']) || !isset($_POST['id'])) {
748 wp_send_json_error('Missing required data');
749 }
750 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
751 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
752 wp_die('Security check failed');
753 }
754 if (!current_user_can('edit_posts')) {
755 wp_send_json_error('Insufficient permissions');
756 }
757 $id = sanitize_text_field(wp_unslash($_POST['id']));
758 $page_id = isset($_POST['page_id']) ? intval($_POST['page_id']) : 0;
759
760 $storage = $this->get_storage();
761 $result = $storage->mark_applied($id, $page_id);
762
763 if (is_wp_error($result)) {
764 wp_send_json_error($result->get_error_message());
765 }
766
767 wp_send_json_success($result);
768 }
769
770 public function save_page_prompt()
771 {
772 // Vérifier que les données POST existent
773 if (!isset($_POST['nonce']) || !isset($_POST['post_id']) || !isset($_POST['page_prompt'])) {
774 wp_send_json_error('Missing required data');
775 }
776
777 // Déséchapper et assainir les données
778 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
779 $post_id = intval($_POST['post_id']);
780 $page_prompt = sanitize_textarea_field(wp_unslash($_POST['page_prompt']));
781
782 // Vérifier le nonce
783 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
784 wp_die('Security check failed');
785 }
786
787 // Vérifier que l'utilisateur peut modifier ce post
788 if (!current_user_can('edit_post', $post_id)) {
789 wp_send_json_error('Insufficient permissions');
790 }
791
792 // Sauvegarder le prompt de page
793 update_post_meta($post_id, 'ai_builder_page_prompt', $page_prompt);
794
795 wp_send_json_success('Page prompt saved successfully');
796 }
797
798 public function get_page_prompt()
799 {
800 // Vérifier que les données POST existent
801 if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
802 wp_send_json_error('Missing required data');
803 }
804
805 // Déséchapper et assainir les données
806 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
807 $post_id = intval($_POST['post_id']);
808
809 // Vérifier le nonce
810 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
811 wp_die('Security check failed');
812 }
813
814 // Vérifier que l'utilisateur peut lire ce post
815 if (!current_user_can('read_post', $post_id)) {
816 wp_send_json_error('Insufficient permissions');
817 }
818
819 // Récupérer le prompt de page
820 $page_prompt = get_post_meta($post_id, 'ai_builder_page_prompt', true);
821
822 wp_send_json_success(array(
823 'pagePrompt' => $page_prompt ?: ''
824 ));
825 }
826
827 /**
828 * Marquer une page comme créée via IA
829 */
830 public function mark_ai_created()
831 {
832 // Vérifier que les données POST existent
833 if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
834 wp_send_json_error('Missing required data');
835 }
836
837 // Déséchapper et assainir les données
838 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
839 $post_id = intval($_POST['post_id']);
840
841 // Vérifier le nonce
842 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
843 wp_die('Security check failed');
844 }
845
846 // Vérifier que l'utilisateur peut éditer ce post
847 if (!current_user_can('edit_post', $post_id)) {
848 wp_send_json_error('Insufficient permissions');
849 }
850
851 // Marquer la page comme créée via IA
852 update_post_meta($post_id, '_aibui_created_by_ai', '1');
853
854 wp_send_json_success('Page marked as AI-created');
855 }
856
857 /**
858 * Get whether a post was created via AI Builder.
859 */
860 public function get_ai_created_status()
861 {
862 if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
863 wp_send_json_error('Missing required data');
864 }
865
866 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
867 $post_id = intval($_POST['post_id']);
868
869 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
870 wp_die('Security check failed');
871 }
872
873 if (!current_user_can('read_post', $post_id)) {
874 wp_send_json_error('Insufficient permissions');
875 }
876
877 $flag = get_post_meta($post_id, '_aibui_created_by_ai', true);
878 wp_send_json_success(array(
879 'isAICreated' => ($flag === '1' || $flag === 1 || $flag === true),
880 ));
881 }
882 }