PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.7.10
AI Builder – Generate pages, blocks, images & translate with AI v2.7.10
2.8.0 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 All 123 releases
← All changes | includes/class-ajax-handler.php +359 -51 2.3.102.7.10 View file →
@@ -9,8 +9,10 @@
9 9 add_action('wp_ajax_aibui_signout', array($this, 'signout'));
10 10 add_action('wp_ajax_aibui_get_token', array($this, 'get_token'));
11 11 add_action('wp_ajax_aibui_save_post_css', array($this, 'save_post_css'));
12 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'));
13 15 add_action('wp_ajax_aibui_save_page_prompt', array($this, 'save_page_prompt'));
14 16 add_action('wp_ajax_aibui_get_page_prompt', array($this, 'get_page_prompt'));
15 17 add_action('wp_ajax_aibui_save_meta_description', array($this, 'save_meta_description'));
16 18 add_action('wp_ajax_aibui_create_page', array($this, 'create_page'));
@@ -25,8 +27,9 @@
25 27 add_action('wp_ajax_aibui_mark_generation_applied', array($this, 'mark_generation_applied'));
26 28
27 29 // Mark pages created via AI
28 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'));
29 32 }
30 33
31 34 public function save_token()
32 35 {
@@ -97,15 +100,11 @@
97 100 }
98 101
99 102 public function get_token()
100 103 {
101 - // Increase timeout for this request (in case DB is slow)
102 - // Default is usually 30s, but some hosts have shorter limits
103 - @set_time_limit(60); // Allow up to 60 seconds
104 -
105 104 // Vérifier que les données POST existent
106 105 if (!isset($_POST['nonce'])) {
107 - wp_send_json_error('Missing required data');
106 + wp_send_json_error('Missing required data', 400);
108 107 }
109 108
110 109 // Déséchapper et assainir les données
111 110 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
@@ -111,9 +110,19 @@
111 110 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
112 111
113 112 // Vérifier le nonce
114 113 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
115 - wp_die('Security check failed');
114 + // wp_die() répondrait une page HTML avec un statut 200 : le client JS
115 + // ne peut ni la parser ni la distinguer d'un vrai échec. On renvoie du
116 + // JSON + 403 pour qu'il affiche « session expirée, rechargez la page »
117 + // plutôt qu'une erreur générique.
118 + wp_send_json_error(
119 + array(
120 + 'code' => 'nonce_expired',
121 + 'message' => 'Security check failed: the WordPress nonce has expired.',
122 + ),
123 + 403
124 + );
116 125 }
117 126
118 127 // Récupérer le token JWT
119 128 $token = get_option('aibui_jwt_token', '');
@@ -124,8 +133,139 @@
124 133
125 134 wp_send_json_success(array('token' => $token));
126 135 }
127 136
137 + /**
138 + * Résout l'ID numérique de post cible pour les endpoints CSS/JS.
139 + *
140 + * Accepte soit :
141 + * - un post_id numérique classique (pages/articles, et templates déjà
142 + * matérialisés en base), soit
143 + * - un template_id composite "theme//slug" + template_type
144 + * ("wp_template" ou "wp_template_part"), utilisé par le Site Editor.
145 + *
146 + * Pour les templates/parts file-based non encore en base, le post
147 + * correspondant est créé à la volée (même stratégie que le Site Editor
148 + * quand l'utilisateur clique sur Save).
149 + *
150 + * @return int Post ID positif, ou 0 si non résoluble.
151 + */
152 + private function resolve_target_post_id($raw_post_id, $template_id, $template_type)
153 + {
154 + // Chemin rapide : un post_id numérique valide est accepté tel quel.
155 + $post_id = 0;
156 + if ($raw_post_id !== '' && is_numeric($raw_post_id)) {
157 + $post_id = intval($raw_post_id);
158 + if ($post_id > 0 && get_post($post_id)) {
159 + return $post_id;
160 + }
161 + $post_id = 0;
162 + }
163 +
164 + // Sinon, on tente la résolution via template_id "theme//slug".
165 + if (!is_string($template_id) || $template_id === '') {
166 + return 0;
167 + }
168 + if (!in_array($template_type, array('wp_template', 'wp_template_part'), true)) {
169 + return 0;
170 + }
171 + if (strpos($template_id, '//') === false) {
172 + return 0;
173 + }
174 + if (!function_exists('get_block_template')) {
175 + return 0;
176 + }
177 +
178 + try {
179 + $tpl = get_block_template($template_id, $template_type);
180 + } catch (\Throwable $e) {
181 + return 0;
182 + }
183 + if (!$tpl) {
184 + return 0;
185 + }
186 +
187 + // Déjà en base → on réutilise.
188 + if (!empty($tpl->wp_id) && (int) $tpl->wp_id > 0) {
189 + return (int) $tpl->wp_id;
190 + }
191 +
192 + // Pas encore en base : on matérialise le template file-based en post
193 + // de la même manière que le Site Editor. Cela nécessite la capability
194 + // edit_theme_options (vérifiée ici, en plus de la vérif au niveau
195 + // endpoint) pour ne jamais créer d'entrée theme à cause d'un save JS.
196 + if (!current_user_can('edit_theme_options')) {
197 + return 0;
198 + }
199 +
200 + list($theme_slug, $slug) = array_pad(explode('//', $template_id, 2), 2, '');
201 + if ($theme_slug === '' || $slug === '') {
202 + return 0;
203 + }
204 +
205 + $title = isset($tpl->title) && $tpl->title !== '' ? (string) $tpl->title : $slug;
206 + $content = isset($tpl->content) ? (string) $tpl->content : '';
207 +
208 + try {
209 + $new_post_id = wp_insert_post(array(
210 + 'post_type' => $template_type,
211 + 'post_status' => 'publish',
212 + 'post_title' => $title,
213 + 'post_name' => $slug,
214 + 'post_content' => $content,
215 + ), true);
216 + } catch (\Throwable $e) {
217 + return 0;
218 + }
219 + if (is_wp_error($new_post_id) || !$new_post_id) {
220 + return 0;
221 + }
222 +
223 + // Rattacher au theme courant (taxonomy wp_theme).
224 + try {
225 + wp_set_object_terms((int) $new_post_id, $theme_slug, 'wp_theme');
226 + } catch (\Throwable $e) {
227 + // non bloquant
228 + }
229 +
230 + // Pour les template parts, rattacher la zone (header/footer/uncategorized...).
231 + if ($template_type === 'wp_template_part') {
232 + $area = isset($tpl->area) && is_string($tpl->area) && $tpl->area !== ''
233 + ? $tpl->area
234 + : 'uncategorized';
235 + try {
236 + wp_set_object_terms((int) $new_post_id, $area, 'wp_template_part_area');
237 + } catch (\Throwable $e) {
238 + // non bloquant
239 + }
240 + }
241 +
242 + return (int) $new_post_id;
243 + }
244 +
245 + /**
246 + * Vérif de capability adaptée au type de cible.
247 + * - wp_template / wp_template_part : edit_theme_options (Site Editor)
248 + * - autres posts : edit_post sur l'ID
249 + */
250 + private function current_user_can_edit_target($post_id)
251 + {
252 + $post = get_post($post_id);
253 + if ($post && in_array($post->post_type, array('wp_template', 'wp_template_part'), true)) {
254 + return current_user_can('edit_theme_options');
255 + }
256 + return current_user_can('edit_post', $post_id);
257 + }
258 +
259 + private function current_user_can_read_target($post_id)
260 + {
261 + $post = get_post($post_id);
262 + if ($post && in_array($post->post_type, array('wp_template', 'wp_template_part'), true)) {
263 + return current_user_can('edit_theme_options');
264 + }
265 + return current_user_can('read_post', $post_id);
266 + }
267 +
128 268 public function save_post_css()
129 269 {
130 270 // Vérifier que les données POST existent
131 271 if (!isset($_POST['nonce']) || !isset($_POST['post_id']) || !isset($_POST['css_content'])) {
@@ -133,19 +273,29 @@
133 273 }
134 274
135 275 // Déséchapper et assainir les données
136 276 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
137 - $post_id = intval($_POST['post_id']);
277 + $raw_post_id = wp_unslash($_POST['post_id']);
138 278 $css_content = wp_unslash($_POST['css_content']);
139 279 $css_type = isset($_POST['css_type']) ? sanitize_text_field(wp_unslash($_POST['css_type'])) : 'page';
280 + $replace = isset($_POST['replace']) ? filter_var(wp_unslash($_POST['replace']), FILTER_VALIDATE_BOOLEAN) : false;
140 281
282 + // Paramètres optionnels pour le Site Editor (templates / template parts)
283 + $template_id = isset($_POST['template_id']) ? sanitize_text_field(wp_unslash($_POST['template_id'])) : '';
284 + $template_type = isset($_POST['template_type']) ? sanitize_text_field(wp_unslash($_POST['template_type'])) : '';
285 +
141 286 // Vérifier le nonce
142 287 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
143 288 wp_die('Security check failed');
144 289 }
145 290
146 - // Vérifier que l'utilisateur peut éditer ce post
147 - if (!current_user_can('edit_post', $post_id)) {
291 + $post_id = $this->resolve_target_post_id($raw_post_id, $template_id, $template_type);
292 + if ($post_id <= 0) {
293 + wp_send_json_error('Invalid target (save the template once in the Site Editor first)');
294 + }
295 +
296 + // Vérifier que l'utilisateur peut éditer cette cible
297 + if (!$this->current_user_can_edit_target($post_id)) {
148 298 wp_send_json_error('Insufficient permissions');
149 299 }
150 300
151 301 if ($css_type === 'page') {
@@ -161,11 +311,9 @@
161 311 $final_css .= "\n" . $block_css;
162 312 }
163 313 update_post_meta($post_id, 'ai_builder_css_content', $final_css);
164 314
165 - error_log("CSS Debug - Saving PAGE CSS. Page length: " . strlen($css_content) . ", Block length: " . strlen($block_css) . ", Final length: " . strlen($final_css));
166 315 } else if ($css_type === 'block') {
167 - // Pour les blocs, ajouter au CSS existant
168 316 $page_css = get_post_meta($post_id, 'ai_builder_page_css_content', true);
169 317 $block_css = get_post_meta($post_id, 'ai_builder_block_css_content', true);
170 318
171 319 if (empty($page_css)) {
@@ -174,10 +322,15 @@
174 322 if (empty($block_css)) {
175 323 $block_css = '';
176 324 }
177 325
178 - // Ajouter le nouveau CSS de bloc
179 - $block_css .= "\n/* Block CSS - " . date('Y-m-d H:i:s') . " */\n" . $css_content . "\n";
326 + if ($replace) {
327 + // Si c'est une édition manuelle, remplacer complètement le CSS de blocs
328 + $block_css = $css_content;
329 + } else {
330 + // Si c'est une génération IA, ajouter au CSS existant
331 + $block_css .= "\n/* Block CSS - " . date('Y-m-d H:i:s') . " */\n" . $css_content . "\n";
332 + }
180 333
181 334 // Sauvegarder le CSS de bloc
182 335 update_post_meta($post_id, 'ai_builder_block_css_content', $block_css);
183 336
@@ -187,12 +340,14 @@
187 340 $final_css .= "\n" . $block_css;
188 341 }
189 342 update_post_meta($post_id, 'ai_builder_css_content', $final_css);
190 343
191 - error_log("CSS Debug - Saving BLOCK CSS. Page length: " . strlen($page_css) . ", Block length: " . strlen($block_css) . ", Final length: " . strlen($final_css));
192 344 }
193 345
194 - wp_send_json_success('CSS saved successfully');
346 + wp_send_json_success(array(
347 + 'message' => 'CSS saved successfully',
348 + 'post_id' => $post_id,
349 + ));
195 350 }
196 351
197 352
198 353 public function get_post_css()
@@ -203,17 +358,32 @@
203 358 }
204 359
205 360 // Déséchapper et assainir les données
206 361 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
207 - $post_id = intval($_POST['post_id']);
362 + $raw_post_id = wp_unslash($_POST['post_id']);
208 363
364 + // Paramètres optionnels pour le Site Editor (templates / template parts)
365 + $template_id = isset($_POST['template_id']) ? sanitize_text_field(wp_unslash($_POST['template_id'])) : '';
366 + $template_type = isset($_POST['template_type']) ? sanitize_text_field(wp_unslash($_POST['template_type'])) : '';
367 +
209 368 // Vérifier le nonce
210 369 if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
211 370 wp_die('Security check failed');
212 371 }
213 372
214 - // Vérifier que l'utilisateur peut lire ce post
215 - if (!current_user_can('read_post', $post_id)) {
373 + $post_id = $this->resolve_target_post_id($raw_post_id, $template_id, $template_type);
374 + if ($post_id <= 0) {
375 + // Rien à retourner mais on ne bloque pas l'UI : réponse vide neutre.
376 + wp_send_json_success(array(
377 + 'pageCss' => '',
378 + 'blockCss' => '',
379 + 'combinedCss' => '',
380 + 'post_id' => 0,
381 + ));
382 + }
383 +
384 + // Vérifier que l'utilisateur peut lire cette cible
385 + if (!$this->current_user_can_read_target($post_id)) {
216 386 wp_send_json_error('Insufficient permissions');
217 387 }
218 388
219 389 // Récupérer les CSS depuis les meta du post
@@ -220,20 +390,158 @@
220 390 $page_css = get_post_meta($post_id, 'ai_builder_page_css_content', true);
221 391 $block_css = get_post_meta($post_id, 'ai_builder_block_css_content', true);
222 392 $combined_css = get_post_meta($post_id, 'ai_builder_css_content', true);
223 393
224 - // Log pour debug
225 - error_log("CSS Debug - Page CSS length: " . strlen($page_css));
226 - error_log("CSS Debug - Block CSS length: " . strlen($block_css));
227 - error_log("CSS Debug - Combined CSS length: " . strlen($combined_css));
228 -
229 394 wp_send_json_success(array(
230 395 'pageCss' => $page_css,
231 396 'blockCss' => $block_css,
232 - 'combinedCss' => $combined_css
397 + 'combinedCss' => $combined_css,
398 + 'post_id' => $post_id,
233 399 ));
234 400 }
235 401
402 + public function save_post_js()
403 + {
404 + // Vérifier que les données POST existent
405 + if (!isset($_POST['nonce']) || !isset($_POST['post_id']) || !isset($_POST['js_content'])) {
406 + wp_send_json_error('Missing required data');
407 + }
408 +
409 + // Déséchapper et assainir les données
410 + $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
411 + $raw_post_id = wp_unslash($_POST['post_id']);
412 + $js_content = wp_unslash($_POST['js_content']);
413 + $js_type = isset($_POST['js_type']) ? sanitize_text_field(wp_unslash($_POST['js_type'])) : 'page';
414 + $replace = isset($_POST['replace']) ? filter_var(wp_unslash($_POST['replace']), FILTER_VALIDATE_BOOLEAN) : false;
415 +
416 + // Paramètres optionnels pour le Site Editor (templates / template parts)
417 + $template_id = isset($_POST['template_id']) ? sanitize_text_field(wp_unslash($_POST['template_id'])) : '';
418 + $template_type = isset($_POST['template_type']) ? sanitize_text_field(wp_unslash($_POST['template_type'])) : '';
419 +
420 + // Vérifier le nonce
421 + if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
422 + wp_die('Security check failed');
423 + }
424 +
425 + $post_id = $this->resolve_target_post_id($raw_post_id, $template_id, $template_type);
426 + if ($post_id <= 0) {
427 + wp_send_json_error('Invalid target (save the template once in the Site Editor first)');
428 + }
429 +
430 + // Vérifier que l'utilisateur peut éditer cette cible
431 + if (!$this->current_user_can_edit_target($post_id)) {
432 + wp_send_json_error('Insufficient permissions');
433 + }
434 +
435 + // Le JS enregistré ici est ré-émis tel quel dans un <script> sur le front
436 + // (pages, blocs, templates du Site Editor). Seuls les utilisateurs disposant
437 + // de la capacité unfiltered_html peuvent stocker du script exécuté chez
438 + // d'autres personnes : même barrière que le bloc HTML personnalisé de WordPress.
439 + // Placé avant toutes les branches (page / block / site editor) pour qu'elles
440 + // en héritent.
441 + if (!current_user_can('unfiltered_html')) {
442 + wp_send_json_error('Insufficient permissions: saving custom JavaScript requires the unfiltered_html capability');
443 + }
444 +
445 + if ($js_type === 'page') {
446 + // Pour les pages, remplacer complètement le JS de page
447 + update_post_meta($post_id, 'ai_builder_page_js_content', $js_content);
448 +
449 + // Récupérer le JS de blocs existant
450 + $block_js = get_post_meta($post_id, 'ai_builder_block_js_content', true);
451 +
452 + // Combiner page JS + block JS pour le JS final
453 + $final_js = $js_content;
454 + if (!empty($block_js)) {
455 + $final_js .= "\n" . $block_js;
456 + }
457 + update_post_meta($post_id, 'ai_builder_js_content', $final_js);
458 +
459 + } else if ($js_type === 'block') {
460 + $page_js = get_post_meta($post_id, 'ai_builder_page_js_content', true);
461 + $block_js = get_post_meta($post_id, 'ai_builder_block_js_content', true);
462 +
463 + if (empty($page_js)) {
464 + $page_js = '';
465 + }
466 + if (empty($block_js)) {
467 + $block_js = '';
468 + }
469 +
470 + if ($replace) {
471 + // Si c'est une édition manuelle, remplacer complètement le JS de blocs
472 + $block_js = $js_content;
473 + } else {
474 + // Si c'est une génération IA, ajouter au JS existant
475 + $block_js .= "\n/* Block JS - " . date('Y-m-d H:i:s') . " */\n" . $js_content . "\n";
476 + }
477 +
478 + // Sauvegarder le JS de bloc
479 + update_post_meta($post_id, 'ai_builder_block_js_content', $block_js);
480 +
481 + // Combiner page JS + block JS pour le JS final
482 + $final_js = $page_js;
483 + if (!empty($block_js)) {
484 + $final_js .= "\n" . $block_js;
485 + }
486 + update_post_meta($post_id, 'ai_builder_js_content', $final_js);
487 +
488 + }
489 +
490 + wp_send_json_success(array(
491 + 'message' => 'JS saved successfully',
492 + 'post_id' => $post_id,
493 + ));
494 + }
495 +
496 + public function get_post_js()
497 + {
498 + // Vérifier que les données POST existent
499 + if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
500 + wp_send_json_error('Missing required data');
501 + }
502 +
503 + // Déséchapper et assainir les données
504 + $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
505 + $raw_post_id = wp_unslash($_POST['post_id']);
506 +
507 + // Paramètres optionnels pour le Site Editor (templates / template parts)
508 + $template_id = isset($_POST['template_id']) ? sanitize_text_field(wp_unslash($_POST['template_id'])) : '';
509 + $template_type = isset($_POST['template_type']) ? sanitize_text_field(wp_unslash($_POST['template_type'])) : '';
510 +
511 + // Vérifier le nonce
512 + if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
513 + wp_die('Security check failed');
514 + }
515 +
516 + $post_id = $this->resolve_target_post_id($raw_post_id, $template_id, $template_type);
517 + if ($post_id <= 0) {
518 + wp_send_json_success(array(
519 + 'pageJS' => '',
520 + 'blockJS' => '',
521 + 'combinedJS' => '',
522 + 'post_id' => 0,
523 + ));
524 + }
525 +
526 + // Vérifier que l'utilisateur peut lire cette cible
527 + if (!$this->current_user_can_read_target($post_id)) {
528 + wp_send_json_error('Insufficient permissions');
529 + }
530 +
531 + // Récupérer les JS depuis les meta du post
532 + $page_js = get_post_meta($post_id, 'ai_builder_page_js_content', true);
533 + $block_js = get_post_meta($post_id, 'ai_builder_block_js_content', true);
534 + $combined_js = get_post_meta($post_id, 'ai_builder_js_content', true);
535 +
536 + wp_send_json_success(array(
537 + 'pageJS' => $page_js ?: '',
538 + 'blockJS' => $block_js ?: '',
539 + 'combinedJS' => $combined_js ?: '',
540 + 'post_id' => $post_id
541 + ));
542 + }
543 +
236 544 public function save_meta_description()
237 545 {
238 546 if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
239 547 wp_send_json_error('Missing required data');
@@ -269,11 +577,9 @@
269 577 {
270 578 $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
271 579 $key = 'aibui_cf_mailerr_' . md5($ip);
272 580 set_transient($key, $wp_error instanceof WP_Error ? $wp_error->get_error_message() : 'Unknown mail error', 120);
273 - if (defined('WP_DEBUG') && WP_DEBUG) {
274 - 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)));
275 - }
581 +
276 582 }
277 583
278 584 public function submit_contact_form()
279 585 {
@@ -444,32 +750,8 @@
444 750 },
445 751 $content
446 752 );
447 753
448 - // Créer un fichier de log dans le plugin
449 - $log_file = plugin_dir_path(__FILE__) . '../debug-unescape.log';
450 - $log_content = "=== DEBUG UNESCAPE - " . date('Y-m-d H:i:s') . " ===\n";
451 - $log_content .= "Total corrections appliquées: " . $replacement_count . "\n\n";
452 - $log_content .= "Contenu original (premiers 500 chars):\n" . substr($original_content, 0, 500) . "\n\n";
453 - $log_content .= "Contenu corrigé (premiers 500 chars):\n" . substr($content, 0, 500) . "\n\n";
454 - if (strlen($content) > 500) {
455 - $log_content .= "Contenu corrigé (derniers 500 chars):\n" . substr($content, -500) . "\n\n";
456 - }
457 - $log_content .= "\n=== Détails par bloc ===\n";
458 - foreach ($debug_log as $log) {
459 - $log_content .= "\nBloc: " . $log['block'] . "\n";
460 - $log_content .= "Original JSON: " . $log['original_json'] . "\n";
461 - $log_content .= "Fixed JSON: " . $log['fixed_json'] . "\n";
462 - if (isset($log['error'])) {
463 - $log_content .= "ERREUR: " . $log['error'] . "\n";
464 - $log_content .= "JSON complet: " . $log['original_json_full'] . "\n";
465 - } else {
466 - $log_content .= "SUCCÈS\n";
467 - }
468 - }
469 - $log_content .= "\n=== FIN DEBUG ===\n\n";
470 - file_put_contents($log_file, $log_content, FILE_APPEND);
471 -
472 754 // Vérifier que le contenu est au format HTML sérialisé WordPress
473 755 // Le contenu doit commencer par un commentaire de bloc WordPress
474 756 if (empty($content) || strpos(trim($content), '<!-- wp:') !== 0) {
475 757 wp_send_json_error('Invalid content format: Expected WordPress serialized block HTML');
@@ -716,6 +998,32 @@
716 998 // Marquer la page comme créée via IA
717 999 update_post_meta($post_id, '_aibui_created_by_ai', '1');
718 1000
719 1001 wp_send_json_success('Page marked as AI-created');
1002 + }
1003 +
1004 + /**
1005 + * Get whether a post was created via AI Builder.
1006 + */
1007 + public function get_ai_created_status()
1008 + {
1009 + if (!isset($_POST['nonce']) || !isset($_POST['post_id'])) {
1010 + wp_send_json_error('Missing required data');
1011 + }
1012 +
1013 + $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
1014 + $post_id = intval($_POST['post_id']);
1015 +
1016 + if (!wp_verify_nonce($nonce, 'aibui_nonce')) {
1017 + wp_die('Security check failed');
1018 + }
1019 +
1020 + if (!current_user_can('read_post', $post_id)) {
1021 + wp_send_json_error('Insufficient permissions');
1022 + }
1023 +
1024 + $flag = get_post_meta($post_id, '_aibui_created_by_ai', true);
1025 + wp_send_json_success(array(
1026 + 'isAICreated' => ($flag === '1' || $flag === 1 || $flag === true),
1027 + ));
720 1028 }
721 1029 }