PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.3.11
AI Builder – Generate pages, blocks, images & translate with AI v2.3.11
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 / aibui-builder.php

aibui-builder.php in AI Builder – Generate pages, blocks, images & translate with AI 2.3.11, at aibui-builder.php

1,170 lines 42.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: AI Builder - Generate pages, blocks, text and images with AI
4 * Plugin URI: https://website-ai-builder.com/
5 * Description: This plugin is used to build your website with AI.
6 * Version: 2.3.11
7 * Author: enkic
8 * Author URI: https://enkicorbin.fr/
9 * License: GPLv2 or later
10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
11 * Text Domain: ai-builder
12 * Requires at least: 5.0
13 * Requires PHP: 7.4
14 */
15
16 if (!defined('ABSPATH'))
17 exit;
18
19 // Définir la version du plugin
20 define('AIBUI_VERSION', '2.3.11');
21
22 // Simple CSS minifier (safe whitespace/comment removal)
23 function aibui_minify_css($css)
24 {
25 if (!is_string($css) || $css === '') return '';
26 // Remove comments
27 $css = preg_replace('#/\*.*?\*/#s', '', $css);
28 // Collapse whitespace
29 $css = preg_replace('/\s+/', ' ', $css);
30 // Remove spaces around symbols
31 $css = preg_replace('/\s*([{};:,>\(\)])\s*/', '$1', $css);
32 // Final trims and unnecessary semicolons
33 $css = str_replace(';}', '}', $css);
34 return trim($css);
35 }
36
37 // Build or fetch a combined CSS file for plugin assets/css/*.css
38 function aibui_get_combined_css_url()
39 {
40 $css_dir = plugin_dir_path(__FILE__) . 'assets/css/';
41 $css_url_base = plugin_dir_url(__FILE__) . 'assets/css/';
42
43 // If directory missing, bail to original behavior
44 if (!is_dir($css_dir)) return '';
45
46 $files = glob($css_dir . '*.css');
47 if (!$files) return '';
48
49 // Compute a hash based on file mtimes and paths to invalidate cache when any source changes
50 $sig_parts = [];
51 foreach ($files as $path) {
52 $sig_parts[] = basename($path) . ':' . filemtime($path);
53 }
54 $signature = md5(implode('|', $sig_parts));
55
56 // Store in uploads to keep plugin dir clean and writable
57 $uploads = wp_upload_dir();
58 if (!empty($uploads['error'])) {
59 return '';
60 }
61 $cache_dir = rtrim($uploads['basedir'], '/').'/ai-builder-cache';
62 $cache_url = rtrim($uploads['baseurl'], '/').'/ai-builder-cache';
63 if (!is_dir($cache_dir)) {
64 wp_mkdir_p($cache_dir);
65 }
66
67 $combined_filename = 'combined-'.$signature.'.css';
68 $combined_path = $cache_dir.'/'.$combined_filename;
69 $combined_url = $cache_url.'/'.$combined_filename;
70
71 if (!file_exists($combined_path)) {
72 $buffer = '';
73 // Keep a stable order: alphabetical by filename
74 sort($files, SORT_STRING);
75 foreach ($files as $path) {
76 // Skip admin-only stylesheet to avoid leaking to frontend bundle
77 if (basename($path) === 'style-admin.css') continue;
78 $content = file_get_contents($path);
79 if ($content === false) continue;
80 $buffer .= "\n/* ".basename($path)." */\n".$content;
81 }
82 $minified = aibui_minify_css($buffer);
83 // Graceful write
84 if (is_writable($cache_dir)) {
85 file_put_contents($combined_path, $minified);
86 } else {
87 return '';
88 }
89 }
90
91 return $combined_url;
92 }
93
94 // Simple JS minifier (very conservative)
95 function aibui_minify_js($js)
96 {
97 if (!is_string($js) || $js === '') return '';
98 // Remove block comments but keep /*! license comments */
99 $js = preg_replace('#/(?!\!)(\*[^*]*\*+(?:[^/*][^*]*\*+)*/)#', '', $js);
100 // Remove line comments
101 $js = preg_replace('#(^|\s)//.*$#m', '$1', $js);
102 // Collapse whitespace
103 $js = preg_replace('/\s+/', ' ', $js);
104 return trim($js);
105 }
106
107 // Build a combined frontend JS bundle from selected plugin scripts
108 function aibui_get_combined_js_url()
109 {
110 $js_list = array(
111 'assets/js/carousel-frontend.js',
112 'assets/js/map-frontend.js',
113 'assets/js/tabs-frontend.js',
114 'assets/js/table-frontend.js',
115 'assets/js/stats-tooltips.js',
116 'assets/js/snackbar-frontend.js',
117 );
118
119 $sig_parts = array();
120 $contents = '';
121 foreach ($js_list as $rel) {
122 $path = plugin_dir_path(__FILE__) . $rel;
123 if (!file_exists($path)) continue;
124 $sig_parts[] = $rel . ':' . filemtime($path);
125 }
126 if (empty($sig_parts)) return '';
127 $signature = md5(implode('|', $sig_parts));
128
129 $uploads = wp_upload_dir();
130 if (!empty($uploads['error'])) {
131 return '';
132 }
133 $cache_dir = rtrim($uploads['basedir'], '/').'/ai-builder-cache';
134 $cache_url = rtrim($uploads['baseurl'], '/').'/ai-builder-cache';
135 if (!is_dir($cache_dir)) {
136 wp_mkdir_p($cache_dir);
137 }
138
139 $combined_filename = 'frontend-'.$signature.'.js';
140 $combined_path = $cache_dir.'/'.$combined_filename;
141 $combined_url = $cache_url.'/'.$combined_filename;
142
143 if (!file_exists($combined_path)) {
144 $buffer = '';
145 foreach ($js_list as $rel) {
146 $path = plugin_dir_path(__FILE__) . $rel;
147 if (!file_exists($path)) continue;
148 $content = file_get_contents($path);
149 if ($content === false) continue;
150 $buffer .= "\n/* ".$rel." */\n".$content."\n";
151 }
152 $minified = aibui_minify_js($buffer);
153 if (is_writable($cache_dir)) {
154 file_put_contents($combined_path, $minified);
155 } else {
156 return '';
157 }
158 }
159
160 return $combined_url;
161 }
162
163 /**
164 * Check if WooCommerce is installed and active.
165 *
166 * @return bool True if WooCommerce is active, false otherwise.
167 */
168 function aibui_is_woocommerce_installed()
169 {
170 // Check if WooCommerce class exists (plugin loaded)
171 if (class_exists('WooCommerce')) {
172 return true;
173 }
174
175 // Alternative check: see if the plugin is active
176 if (function_exists('is_plugin_active')) {
177 return is_plugin_active('woocommerce/woocommerce.php');
178 }
179
180 // Fallback: check active plugins option
181 $active_plugins = get_option('active_plugins', array());
182 return in_array('woocommerce/woocommerce.php', $active_plugins, true);
183 }
184
185
186 /**
187 * Check if global AI personalization settings are still empty.
188 *
189 * We query the remote settings API (same as the Settings page) and consider the
190 * configuration "empty" when all settings (primaryColor, secondaryColor, siteName,
191 * siteDescription, designStyle, blockShapes, copywritingTone) are missing/empty.
192 */
193 function aibui_are_personalization_settings_empty()
194 {
195 // Require an authenticated session with the cloud API.
196 $jwt_token = get_option('aibui_jwt_token', '');
197 if (empty($jwt_token)) {
198 return false;
199 }
200
201 $api_url = 'https://api.wordpress-ai-builder.com/api/settings';
202 $response = wp_remote_get($api_url, array(
203 'timeout' => 15,
204 'headers' => array(
205 'Authorization' => 'Bearer ' . $jwt_token,
206 'Content-Type' => 'application/json',
207 ),
208 ));
209
210 if (is_wp_error($response)) {
211 return false;
212 }
213
214 $code = wp_remote_retrieve_response_code($response);
215 if ($code !== 200) {
216 return false;
217 }
218
219 $body = wp_remote_retrieve_body($response);
220 $data = json_decode($body, true);
221 if (!is_array($data)) {
222 return false;
223 }
224
225 $primaryColor = isset($data['primaryColor']) ? trim((string) $data['primaryColor']) : '';
226 $secondaryColor = isset($data['secondaryColor']) ? trim((string) $data['secondaryColor']) : '';
227 $siteName = isset($data['siteName']) ? trim((string) $data['siteName']) : '';
228 $siteDescription = isset($data['siteDescription']) ? trim((string) $data['siteDescription']) : '';
229 $designStyle = isset($data['designStyle']) ? trim((string) $data['designStyle']) : '';
230 $blockShapes = isset($data['blockShapes']) ? trim((string) $data['blockShapes']) : '';
231 $copywritingTone = isset($data['copywritingTone']) ? trim((string) $data['copywritingTone']) : '';
232
233 return ($primaryColor === '' && $secondaryColor === '' && $siteName === '' && $siteDescription === '' && $designStyle === '' && $blockShapes === '' && $copywritingTone === '');
234 }
235
236 /**
237 * Display a subtle onboarding banner on key AI Builder pages when
238 * personalization settings are still empty.
239 */
240 function aibui_personalization_notice()
241 {
242 if (!is_admin() || !current_user_can('manage_options')) {
243 return;
244 }
245
246 $page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
247 $target_pages = array(
248 'aibui-assistant', // Account (main dashboard)
249 'aibui-credits', // Credits
250 'aibui-tuto', // Tutorial
251 'aibui-multi-page', // Multi Page Generator
252 'aibui-translation-settings', // Translations
253 );
254
255 if (!in_array($page, $target_pages, true)) {
256 return;
257 }
258
259 if (!aibui_are_personalization_settings_empty()) {
260 return;
261 }
262
263 $settings_url = admin_url('admin.php?page=aibui-settings');
264 ?>
265 <div class="notice" style="border-left:4px solid #2563eb;padding:12px 16px;margin:12px 0;background:#eff6ff;color:#111827;">
266 <p style="margin:0;font-size:13px;line-height:1.5;">
267 <strong>Make AI Builder more personal for your site.</strong>
268 Configure your site preferences in <a href="<?php echo esc_url($settings_url); ?>" style="font-weight:500;color:#1d4ed8;text-decoration:underline;">AI Builder Settings</a> for better, more personalized results.
269 </p>
270 </div>
271 <?php
272 }
273 add_action('admin_notices', 'aibui_personalization_notice');
274
275 // Charger les menus admin
276 require_once plugin_dir_path(__FILE__) . 'admin/menu.php';
277
278 // Charger le gestionnaire AJAX
279 require_once plugin_dir_path(__FILE__) . 'includes/class-ajax-handler.php';
280
281 // Charger le gestionnaire CSS
282 require_once plugin_dir_path(__FILE__) . 'includes/class-css-handler.php';
283
284 // Charger le gestionnaire de traduction
285 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-handler.php';
286 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-settings.php';
287 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-manager.php';
288 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-switcher.php';
289
290 // Charger les services de l'Agent Chat
291 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-discovery-service.php';
292 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-security-service.php';
293 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-execution-service.php';
294 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-chat-handler.php';
295
296 // Initialiser le gestionnaire de traduction
297 new AIBUI_Translation_Handler();
298 AIBUI_Translation_Settings::init();
299 $translation_manager = new AIBUI_Translation_Manager();
300 $translation_switcher = new AIBUI_Translation_Switcher($translation_manager);
301
302 // Initialiser le gestionnaire de l'Agent Chat
303 new AIBUI_Agent_Chat_Handler();
304
305 add_action('admin_enqueue_scripts', function ($hook) {
306 // Charger le CSS admin sur toutes les pages d'administration
307 wp_enqueue_style(
308 'ai-builder-admin-style',
309 plugin_dir_url(__FILE__) . 'assets/css/style-admin.css',
310 [],
311 AIBUI_VERSION
312 );
313
314 // Charger sur l'éditeur de page/article et l'éditeur de modèles (site editor)
315 if ($hook === 'post.php' || $hook === 'post-new.php' || $hook === 'site-editor.php') {
316 wp_enqueue_style(
317 'chat-widget-style',
318 plugin_dir_url(__FILE__) . 'assets/css/chat-widget.css',
319 [],
320 AIBUI_VERSION
321 );
322 wp_enqueue_script(
323 'ai-builder-config',
324 plugin_dir_url(__FILE__) . 'config.js',
325 [],
326 AIBUI_VERSION,
327 true
328 );
329 wp_enqueue_script(
330 'chat-widget',
331 plugin_dir_url(__FILE__) . 'assets/js/chat-widget.js',
332 ['ai-builder-config'],
333 AIBUI_VERSION,
334 true
335 );
336 // Styles tabs pour s'assurer du chargement dans l'éditeur
337 wp_enqueue_style(
338 'ai-builder-tabs-css-admin-editor',
339 plugin_dir_url(__FILE__) . 'assets/css/tabs.css',
340 [],
341 AIBUI_VERSION
342 );
343 // Injection des variables JS pour AJAX et le nonce
344 wp_localize_script(
345 'chat-widget',
346 'aiBuilderVars',
347 array(
348 'ajaxurl' => admin_url('admin-ajax.php'),
349 'nonce' => wp_create_nonce('aibui_nonce'),
350 // Flag to hint we are on the Site Editor (patterns/template parts)
351 'isPatternEditor' => ($hook === 'site-editor.php'),
352 // WooCommerce detection
353 'wooCommerceInstalled' => aibui_is_woocommerce_installed(),
354 )
355 );
356
357 // Enqueue Multi-Page apply script to support applying generations via URL param
358 wp_enqueue_script(
359 'ai-builder-multi-page-apply',
360 plugin_dir_url(__FILE__) . 'assets/js/multi-page-apply.js',
361 ['ai-builder-config', 'chat-widget', 'wp-blocks', 'wp-data', 'wp-editor', 'wp-block-editor', 'wp-element'],
362 AIBUI_VERSION,
363 true
364 );
365 wp_localize_script(
366 'ai-builder-multi-page-apply',
367 'aiBuilderVars',
368 array(
369 'ajaxurl' => admin_url('admin-ajax.php'),
370 'nonce' => wp_create_nonce('aibui_nonce'),
371 )
372 );
373 }
374
375 // Charger les styles et scripts pour la page account du plugin AI Builder
376 $current_screen = get_current_screen();
377 if ($current_screen && strpos($current_screen->id, 'aibui-assistant') !== false) {
378 wp_enqueue_style(
379 'ai-builder-account-style',
380 plugin_dir_url(__FILE__) . 'assets/css/account.css',
381 [],
382 filemtime(plugin_dir_path(__FILE__) . 'assets/css/account.css')
383 );
384 wp_enqueue_script(
385 'ai-builder-config',
386 plugin_dir_url(__FILE__) . 'config.js',
387 [],
388 AIBUI_VERSION,
389 true
390 );
391 wp_enqueue_script(
392 'ai-builder-account',
393 plugin_dir_url(__FILE__) . 'assets/js/account.js',
394 ['ai-builder-config'],
395 AIBUI_VERSION,
396 true
397 );
398 // Injection des variables JS pour AJAX et le nonce
399 wp_localize_script(
400 'ai-builder-account',
401 'aiBuilderVars',
402 array(
403 'ajaxurl' => admin_url('admin-ajax.php'),
404 'nonce' => wp_create_nonce('aibui_nonce'),
405 'accountUrl' => admin_url('admin.php?page=aibui-account'),
406 'siteDomain' => parse_url(home_url(), PHP_URL_HOST),
407 )
408 );
409 } else if ($current_screen && strpos($current_screen->id, 'aibui-credits') !== false) {
410 wp_enqueue_script(
411 'ai-builder-credits',
412 plugin_dir_url(__FILE__) . 'assets/js/credits.js',
413 ['ai-builder-config'],
414 AIBUI_VERSION,
415 true
416 );
417 // Injection des variables JS pour AJAX et le nonce
418 wp_localize_script(
419 'ai-builder-credits',
420 'aiBuilderVars',
421 array(
422 'ajaxurl' => admin_url('admin-ajax.php'),
423 'nonce' => wp_create_nonce('aibui_nonce'),
424 )
425 );
426 wp_enqueue_style(
427 'ai-builder-credits-additional-style',
428 plugin_dir_url(__FILE__) . 'assets/css/credits-additional.css',
429 [],
430 AIBUI_VERSION
431 );
432 wp_enqueue_style(
433 'ai-builder-credits-style',
434 plugin_dir_url(__FILE__) . 'assets/css/credits.css',
435 [],
436 AIBUI_VERSION
437 );
438 wp_enqueue_script(
439 'ai-builder-config',
440 plugin_dir_url(__FILE__) . 'config.js',
441 [],
442 AIBUI_VERSION,
443 true
444 );
445 } else if ($current_screen && strpos($current_screen->id, 'aibui-settings') !== false) {
446 wp_enqueue_style(
447 'ai-builder-settings-style',
448 plugin_dir_url(__FILE__) . 'assets/css/settings.css',
449 [],
450 AIBUI_VERSION
451 );
452 wp_enqueue_script(
453 'ai-builder-config',
454 plugin_dir_url(__FILE__) . 'config.js',
455 [],
456 AIBUI_VERSION,
457 true
458 );
459 wp_enqueue_script(
460 'ai-builder-settings',
461 plugin_dir_url(__FILE__) . 'assets/js/settings.js',
462 ['ai-builder-config'],
463 AIBUI_VERSION,
464 true
465 );
466 // Injection des variables JS pour AJAX et le nonce
467 wp_localize_script(
468 'ai-builder-settings',
469 'aiBuilderVars',
470 array(
471 'ajaxurl' => admin_url('admin-ajax.php'),
472 'nonce' => wp_create_nonce('aibui_nonce'),
473 )
474 );
475 } else if ($current_screen && strpos($current_screen->id, 'aibui-reset-password') !== false) {
476 wp_enqueue_style(
477 'ai-builder-reset-password-style',
478 plugin_dir_url(__FILE__) . 'assets/css/reset-password.css',
479 [],
480 AIBUI_VERSION
481 );
482 wp_enqueue_script(
483 'ai-builder-config',
484 plugin_dir_url(__FILE__) . 'config.js',
485 [],
486 AIBUI_VERSION,
487 true
488 );
489 wp_enqueue_script(
490 'ai-builder-reset-password',
491 plugin_dir_url(__FILE__) . 'assets/js/reset-password.js',
492 ['ai-builder-config'],
493 AIBUI_VERSION,
494 true
495 );
496 // Injection des variables JS pour AJAX et le nonce
497 wp_localize_script(
498 'ai-builder-reset-password',
499 'aiBuilderVars',
500 array(
501 'ajaxurl' => admin_url('admin-ajax.php'),
502 'nonce' => wp_create_nonce('aibui_nonce'),
503 'accountUrl' => admin_url('admin.php?page=aibui-account'),
504 )
505 );
506 } else if ($current_screen && strpos($current_screen->id, 'aibui-tuto') !== false) {
507 wp_enqueue_style(
508 'ai-builder-tutorial-style',
509 plugin_dir_url(__FILE__) . 'assets/css/tutorial.css',
510 [],
511 AIBUI_VERSION
512 );
513 wp_enqueue_script(
514 'ai-builder-config',
515 plugin_dir_url(__FILE__) . 'config.js',
516 [],
517 AIBUI_VERSION,
518 true
519 );
520 } else if ($current_screen && strpos($current_screen->id, 'aibui-multi-page') !== false) {
521 wp_enqueue_style(
522 'ai-builder-multi-page-style',
523 plugin_dir_url(__FILE__) . 'assets/css/multi-page.css',
524 [],
525 AIBUI_VERSION
526 );
527 wp_enqueue_script(
528 'ai-builder-config',
529 plugin_dir_url(__FILE__) . 'config.js',
530 [],
531 AIBUI_VERSION,
532 true
533 );
534 wp_enqueue_script(
535 'ai-builder-multi-page',
536 plugin_dir_url(__FILE__) . 'assets/js/multi-page.js',
537 ['ai-builder-config'],
538 AIBUI_VERSION,
539 true
540 );
541 // Injection des variables JS pour AJAX et le nonce
542 wp_localize_script(
543 'ai-builder-multi-page',
544 'aiBuilderVars',
545 array(
546 'ajaxurl' => admin_url('admin-ajax.php'),
547 'nonce' => wp_create_nonce('aibui_nonce'),
548 'adminBaseUrl' => admin_url(),
549 'wooCommerceInstalled' => aibui_is_woocommerce_installed(),
550 )
551 );
552 } else if ($current_screen && strpos($current_screen->id, 'aibui-agent-chat') !== false) {
553 // Agent Chat page scripts and styles
554 wp_enqueue_script(
555 'ai-builder-config',
556 plugin_dir_url(__FILE__) . 'config.js',
557 [],
558 AIBUI_VERSION,
559 true
560 );
561 wp_enqueue_script(
562 'ai-builder-agent-chat',
563 plugin_dir_url(__FILE__) . 'assets/js/agent-chat.js',
564 ['ai-builder-config'],
565 AIBUI_VERSION,
566 true
567 );
568 // Localize with nonce - IMPORTANT: use a different nonce for agent actions
569 wp_localize_script(
570 'ai-builder-agent-chat',
571 'aibuiAgentVars',
572 array(
573 'ajaxurl' => admin_url('admin-ajax.php'),
574 'nonce' => wp_create_nonce('aibui_agent_nonce'),
575 'restBase' => esc_url_raw(rest_url()),
576 'wpRestDocsBase' => 'https://developer.wordpress.org/rest-api/reference/',
577 )
578 );
579 // Also expose standard AJAX nonce for shared endpoints like aibui_get_token
580 wp_localize_script(
581 'ai-builder-agent-chat',
582 'aiBuilderVars',
583 array(
584 'ajaxurl' => admin_url('admin-ajax.php'),
585 'nonce' => wp_create_nonce('aibui_nonce'),
586 )
587 );
588 }
589
590 });
591
592 add_action('wp_enqueue_scripts', function () {
593 // Single combined frontend CSS
594 $combined_css = aibui_get_combined_css_url();
595 if ($combined_css) {
596 wp_enqueue_style(
597 'ai-builder-combined',
598 $combined_css,
599 [],
600 null
601 );
602 } else {
603 // Fallback: enqueue at least the essential stylesheet
604 wp_enqueue_style(
605 'aibui-force-alignfull',
606 plugin_dir_url(__FILE__) . 'assets/css/force-align-full.css',
607 [],
608 AIBUI_VERSION
609 );
610 }
611
612 // Charger les styles CSS du bloc AI Image côté frontend
613 wp_enqueue_style(
614 'ai-builder-ai-image-frontend',
615 plugin_dir_url(__FILE__) . 'assets/js/build/index.css',
616 [],
617 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.css')
618 );
619 // Frontend JS single bundle
620 $combined_js = aibui_get_combined_js_url();
621 if ($combined_js) {
622 wp_enqueue_script(
623 'ai-builder-frontend-bundle',
624 $combined_js,
625 [],
626 null,
627 true
628 );
629 } else {
630 // Fallback: at least enqueue carousel script if bundling failed
631 wp_enqueue_script(
632 'ai-builder-carousel-frontend',
633 plugin_dir_url(__FILE__) . 'assets/js/carousel-frontend.js',
634 [],
635 AIBUI_VERSION,
636 true
637 );
638 }
639
640 // Load fixed background group block CSS for frontend
641 wp_enqueue_style(
642 'ai-builder-fixed-bg-group-frontend',
643 plugin_dir_url(__FILE__) . 'assets/js/src/editor-blocks/fixed-bg-group/style.css',
644 [],
645 AIBUI_VERSION
646 );
647
648 // CSS above is covered by the combined bundle
649
650 // Other JS are included in the combined bundle above
651 // CSS above is covered by the combined bundle
652 // Included in combined bundle
653 // CSS above is covered by the combined bundle
654 // Included in combined bundle
655 });
656
657 // Enqueue contact form script only when the block is present
658 add_action('wp_enqueue_scripts', function () {
659 global $post;
660 $should_load = false;
661
662 // Check if current post has the block
663 if (is_a($post, 'WP_Post') && has_block('ai-builder/aibui-contact-form', $post)) {
664 $should_load = true;
665 }
666
667 // Fallback: check if we're on a page that might have the block
668 if (!$should_load && (is_page() || is_single() || is_home() || is_front_page())) {
669 $should_load = true;
670 }
671
672 if ($should_load) {
673 wp_enqueue_script(
674 'ai-builder-contact-form',
675 plugin_dir_url(__FILE__) . 'assets/js/contact-form.js',
676 [],
677 AIBUI_VERSION,
678 true
679 );
680 }
681 });
682
683 add_action('enqueue_block_editor_assets', function () {
684 // Single combined CSS for editor
685 $combined_css = aibui_get_combined_css_url();
686 if ($combined_css) {
687 wp_enqueue_style(
688 'ai-builder-combined-editor',
689 $combined_css,
690 [],
691 null
692 );
693 } else {
694 wp_enqueue_style(
695 'aibui-force-alignfull-editor',
696 plugin_dir_url(__FILE__) . 'assets/css/force-align-full.css',
697 [],
698 AIBUI_VERSION
699 );
700 }
701 // Charger les styles build des blocs dans l'éditeur
702 wp_enqueue_style(
703 'ai-builder-blocks-editor-build',
704 plugin_dir_url(__FILE__) . 'assets/js/build/index.css',
705 [],
706 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.css')
707 );
708 // Styles spécifiques d'aperçu du formulaire de contact
709 wp_enqueue_style(
710 'ai-builder-contact-form-editor',
711 plugin_dir_url(__FILE__) . 'assets/css/contact-form-editor.css',
712 [],
713 AIBUI_VERSION
714 );
715 // Covered by combined bundle
716 // Stats tooltips in editor preview
717 wp_enqueue_script(
718 'ai-builder-stats-tooltips-editor',
719 plugin_dir_url(__FILE__) . 'assets/js/stats-tooltips.js',
720 [],
721 AIBUI_VERSION,
722 true
723 );
724 });
725
726 add_action('enqueue_block_editor_assets', function () {
727 wp_enqueue_script(
728 'ai-builder-blocks',
729 plugin_dir_url(__FILE__) . 'assets/js/build/index.js',
730 [
731 'wp-blocks',
732 'wp-block-editor',
733 'wp-element',
734 'wp-components',
735 'wp-i18n',
736 'wp-api-fetch',
737 'wp-hooks'
738 ],
739 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.js'),
740 true
741 );
742
743 // Injection des variables JS pour AJAX et le nonce (utilisées par les blocs éditeur)
744 wp_localize_script(
745 'ai-builder-blocks',
746 'aiBuilderVars',
747 array(
748 'ajaxurl' => admin_url('admin-ajax.php'),
749 'nonce' => wp_create_nonce('aibui_nonce'),
750 )
751 );
752
753 wp_enqueue_script(
754 'ai-builder-language-switcher-block',
755 plugin_dir_url(__FILE__) . 'assets/js/language-switcher-block.js',
756 array('wp-blocks', 'wp-block-editor', 'wp-element', 'wp-components', 'wp-i18n'),
757 filemtime(plugin_dir_path(__FILE__) . 'assets/js/language-switcher-block.js'),
758 true
759 );
760
761 $translation_settings = AIBUI_Translation_Settings::get_settings();
762 $supported_languages = AIBUI_Translation_Handler::get_supported_languages();
763 $available_langs = isset($translation_settings['available_langs']) && is_array($translation_settings['available_langs'])
764 ? array_values(array_unique($translation_settings['available_langs']))
765 : array();
766 $default_lang = isset($translation_settings['default_lang']) ? $translation_settings['default_lang'] : 'en';
767 if ($default_lang && !in_array($default_lang, $available_langs, true)) {
768 array_unshift($available_langs, $default_lang);
769 }
770 $switcher_defaults = array(
771 'backgroundColor' => !empty($translation_settings['switcher_bg']) ? $translation_settings['switcher_bg'] : '',
772 'textColor' => !empty($translation_settings['switcher_text']) ? $translation_settings['switcher_text'] : '',
773 'minWidth' => !empty($translation_settings['switcher_min_width']) ? (int) $translation_settings['switcher_min_width'] : 90,
774 'minHeight' => !empty($translation_settings['switcher_min_height']) ? (int) $translation_settings['switcher_min_height'] : 30,
775 'borderRadius' => 22,
776 );
777
778 wp_localize_script(
779 'ai-builder-language-switcher-block',
780 'aiBuilderLangSwitch',
781 array(
782 'availableLangs' => $available_langs,
783 'defaultLang' => $default_lang,
784 'labels' => $supported_languages,
785 'switcherEnabled' => !empty($translation_settings['enable_switcher']),
786 'defaults' => $switcher_defaults,
787 )
788 );
789 });
790
791 // Enregistrer le bloc côté PHP
792 add_action('init', function () use ($translation_switcher) {
793 wp_register_style(
794 'ai-builder-language-switcher-style',
795 plugin_dir_url(__FILE__) . 'assets/css/language-switcher.css',
796 array(),
797 AIBUI_VERSION
798 );
799 // Bloc AI Block
800 register_block_type('ai-builder/ai-block', [
801 'editor_script' => 'ai-builder-blocks',
802 'editor_style' => 'ai-builder-blocks-style',
803 'style' => 'ai-builder-blocks-style',
804 'render_callback' => function ($attributes) {
805 // Le bloc AI Block ne s'affiche pas côté frontend car il est remplacé par le contenu généré
806 return '';
807 }
808 ]);
809
810 // Bloc stat-bar (statique, pas de render_callback)
811 register_block_type('ai-builder/stat-bar', [
812 'editor_script' => 'ai-builder-blocks',
813 'editor_style' => 'ai-builder-blocks-style',
814 'style' => 'ai-builder-blocks-style',
815 ]);
816
817 // Bloc aibui-stats (rendu simple côté éditeur, frontend statique)
818 register_block_type('ai-builder/aibui-stats', [
819 'editor_script' => 'ai-builder-blocks',
820 'editor_style' => 'ai-builder-blocks-style',
821 'style' => 'ai-builder-blocks-style',
822 ]);
823
824 // Bloc aibui-carousel
825 register_block_type('ai-builder/aibui-carousel', [
826 'editor_script' => 'ai-builder-blocks',
827 'editor_style' => 'ai-builder-blocks-style',
828 'style' => 'ai-builder-blocks-style',
829 ]);
830
831 // Bloc aibui-yt-video
832 register_block_type('ai-builder/aibui-yt-video', [
833 'editor_script' => 'ai-builder-blocks',
834 'editor_style' => 'ai-builder-blocks-style',
835 'style' => 'ai-builder-blocks-style',
836 ]);
837
838 // Bloc aibui-map
839 register_block_type('ai-builder/aibui-map', [
840 'editor_script' => 'ai-builder-blocks',
841 'editor_style' => 'ai-builder-blocks-style',
842 'style' => 'ai-builder-blocks-style',
843 ]);
844
845 // Bloc aibui-tabs
846 // Bloc aibui-table
847 register_block_type('ai-builder/aibui-table', [
848 'editor_script' => 'ai-builder-blocks',
849 'editor_style' => 'ai-builder-blocks-style',
850 'style' => 'ai-builder-blocks-style',
851 ]);
852 register_block_type('ai-builder/aibui-tabs', [
853 'editor_script' => 'ai-builder-blocks',
854 'editor_style' => 'ai-builder-blocks-style',
855 'style' => 'ai-builder-blocks-style',
856 ]);
857
858 // Bloc aibui-contact-form (rendu via callback PHP pour sécurité)
859 register_block_type('ai-builder/aibui-contact-form', [
860 'editor_script' => 'ai-builder-blocks',
861 'editor_style' => 'ai-builder-blocks-style',
862 'style' => 'ai-builder-blocks-style',
863 'render_callback' => function ($attributes, $content, $block) {
864 $recipient = isset($attributes['recipientEmail']) ? sanitize_email($attributes['recipientEmail']) : '';
865 if (empty($recipient) || !is_email($recipient)) {
866 $recipient = sanitize_email(get_option('admin_email'));
867 }
868 if (empty($recipient) || !is_email($recipient)) {
869 // Pas de destinataire valide disponible, n'arrête pas l'affichage, mais désactive l'envoi
870 $recipient = '';
871 }
872
873 $fields = isset($attributes['fields']) && is_array($attributes['fields']) ? $attributes['fields'] : [];
874 $fields = array_slice($fields, 0, 5);
875
876 $form_title = isset($attributes['formTitle']) ? wp_kses_post($attributes['formTitle']) : '';
877 $description = isset($attributes['description']) ? wp_kses_post($attributes['description']) : '';
878 $button_label = isset($attributes['buttonLabel']) ? sanitize_text_field($attributes['buttonLabel']) : __('Send', 'ai-builder');
879 $success_message = isset($attributes['successMessage']) ? sanitize_text_field($attributes['successMessage']) : __('Thank you, your message has been sent.', 'ai-builder');
880 $from_email_attr = isset($attributes['fromEmail']) ? sanitize_email($attributes['fromEmail']) : '';
881
882 $nonce = wp_create_nonce('aibui_contact_form');
883 $action = esc_url(admin_url('admin-ajax.php'));
884
885 ob_start();
886 ?>
887 <form class="aibui-contact-form aibui-contact-form--frontend" method="post" action="<?php echo $action; ?>"
888 data-endpoint="<?php echo $action; ?>">
889 <?php if ($form_title): ?>
890 <h3 class="aibui-form-title"><?php echo $form_title; ?></h3>
891 <?php endif; ?>
892 <?php if ($description): ?>
893 <p class="aibui-form-description"><?php echo $description; ?></p>
894 <?php endif; ?>
895 <input type="hidden" name="action" value="aibui_submit_contact_form" />
896 <input type="hidden" name="nonce" value="<?php echo esc_attr($nonce); ?>" />
897 <input type="hidden" name="recipient" value="<?php echo esc_attr($recipient); ?>" />
898 <?php if ($from_email_attr): ?>
899 <input type="hidden" name="from_email" value="<?php echo esc_attr($from_email_attr); ?>" />
900 <?php endif; ?>
901 <?php foreach ($fields as $idx => $field):
902 $label = isset($field['label']) ? sanitize_text_field($field['label']) : '';
903 $type = isset($field['type']) ? sanitize_text_field($field['type']) : 'text';
904 $required = !empty($field['required']);
905 $name = 'field_' . $idx;
906 ?>
907 <div class="aibui-field">
908 <label>
909 <?php echo esc_html($label); ?> <?php echo $required ? ' *' : ''; ?>
910 <?php if ($type === 'textarea'): ?>
911 <textarea name="<?php echo esc_attr($name); ?>" <?php echo $required ? 'required' : ''; ?>
912 aria-label="<?php echo esc_attr($label); ?>"></textarea>
913 <?php else: ?>
914 <input
915 type="<?php echo $type === 'email' ? 'email' : ($type === 'number' ? 'number' : ($type === 'date' ? 'date' : 'text')); ?>"
916 name="<?php echo esc_attr($name); ?>" <?php echo $required ? 'required' : ''; ?>
917 aria-label="<?php echo esc_attr($label); ?>" />
918 <?php endif; ?>
919 <input type="hidden" name="label_<?php echo esc_attr($name); ?>" value="<?php echo esc_attr($label); ?>" />
920 <input type="hidden" name="type_<?php echo esc_attr($name); ?>" value="<?php echo esc_attr($type); ?>" />
921 <input type="hidden" name="required_<?php echo esc_attr($name); ?>"
922 value="<?php echo $required ? '1' : '0'; ?>" />
923 </label>
924 </div>
925 <?php endforeach; ?>
926 <div style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;">
927 <label>Leave this field empty<input type="text" name="website" tabindex="-1" autocomplete="off" /></label>
928 </div>
929 <div class="aibui-form-message" role="status" aria-live="polite"></div>
930 <input type="hidden" name="success_message" value="<?php echo esc_attr($success_message); ?>" />
931 <button type="submit" class="aibui-button aibui-button--primary"><?php echo esc_html($button_label); ?></button>
932 </form>
933 <?php
934 return ob_get_clean();
935 }
936 ]);
937
938 register_block_type('ai-builder/language-switcher', [
939 'editor_script' => 'ai-builder-language-switcher-block',
940 'editor_style' => 'ai-builder-language-switcher-style',
941 'style' => 'ai-builder-language-switcher-style',
942 'render_callback' => array($translation_switcher, 'render_block'),
943 'attributes' => [
944 'backgroundColor' => ['type' => 'string', 'default' => '#5686c9'],
945 'textColor' => ['type' => 'string', 'default' => '#ffffff'],
946 'minWidth' => ['type' => 'number', 'default' => 90],
947 'minHeight' => ['type' => 'number', 'default' => 30],
948 'borderRadius' => ['type' => 'number', 'default' => 22],
949 ],
950 'supports' => [
951 'align' => ['left', 'center', 'right', 'wide', 'full'],
952 ],
953 ]);
954 });
955
956 add_action('after_setup_theme', function () {
957 add_theme_support('align-wide');
958 });
959
960 function enqueue_ai_builder_scripts() {
961 // Charger config.js en premier (dépendance pour les autres scripts)
962 wp_enqueue_script(
963 'ai-builder-config',
964 plugins_url('config.js', __FILE__),
965 array(),
966 AIBUI_VERSION,
967 false // Charger dans le <head> pour être disponible partout
968 );
969
970 // Autres scripts qui utilisent config.js
971 wp_enqueue_script(
972 'ai-builder-image-ai-controls',
973 plugins_url('assets/js/src/editor-blocks/image-ai-blocks/image-ai-controls.js', __FILE__),
974 array('ai-builder-config'),
975 AIBUI_VERSION,
976 true
977 );
978
979 // Autres scripts qui utilisent config.js
980 wp_enqueue_script(
981 'ai-builder-text-ai-controls',
982 plugins_url('assets/js/src/editor-blocks/text-ai-blocks/text-ai-controls.js', __FILE__),
983 array('ai-builder-config'),
984 AIBUI_VERSION,
985 true
986 );
987
988 // Autres scripts qui utilisent config.js
989 wp_enqueue_script(
990 'ai-builder-text-ai-controls',
991 plugins_url('assets/js/src/editor-blocks/text-ai-blocks/text-ai-controls.js', __FILE__),
992 array('ai-builder-config'),
993 AIBUI_VERSION,
994 true
995 );
996
997 // Autres scripts qui utilisent config.js
998 wp_enqueue_script(
999 'ai-builder-ai-block',
1000 plugins_url('assets/js/src/editor-blocks/ai-block/ai-block.js', __FILE__),
1001 array('ai-builder-config'),
1002 AIBUI_VERSION,
1003 true
1004 );
1005
1006 // Localize WooCommerce detection for block editor scripts
1007 wp_localize_script(
1008 'ai-builder-config',
1009 'aiBuilderEditorVars',
1010 array(
1011 'wooCommerceInstalled' => aibui_is_woocommerce_installed(),
1012 )
1013 );
1014 }
1015 add_action('enqueue_block_editor_assets', 'enqueue_ai_builder_scripts');
1016
1017 add_action('wp_enqueue_scripts', function () {
1018 // Combined CSS already enqueued above; only enqueue any extra block scss-generated styles if needed
1019 // Table, snackbar, cards editor styles are not required on frontend separately when bundled
1020
1021 // Load snackbar frontend JavaScript
1022 wp_enqueue_script(
1023 'ai-builder-snackbar-frontend',
1024 plugin_dir_url(__FILE__) . 'assets/js/snackbar-frontend.js',
1025 [],
1026 AIBUI_VERSION,
1027 true
1028 );
1029 });
1030
1031 add_action('enqueue_block_editor_assets', function () {
1032 // Combined CSS already enqueued above; keep editor-specific assets below
1033 // Load fixed background group block CSS specifically (already inside combined, but keep safe fallback if combined failed)
1034 $fixed_bg_css_url = plugin_dir_url(__FILE__) . 'assets/js/src/editor-blocks/fixed-bg-group/style.css';
1035 wp_enqueue_style('ai-builder-fixed-bg-group-editor', $fixed_bg_css_url, [], AIBUI_VERSION);
1036
1037 // Load WordPress media scripts for image selection
1038 wp_enqueue_media();
1039 });
1040
1041 // -------------------------------
1042 // Meta description per page/post
1043 // -------------------------------
1044 add_action('add_meta_boxes', function () {
1045 add_meta_box(
1046 'aibui_meta_description',
1047 __('Meta description', 'ai-builder'),
1048 function ($post) {
1049 $value = get_post_meta($post->ID, 'aibui_meta_description', true);
1050 wp_nonce_field('aibui_meta_description_save', 'aibui_meta_description_nonce');
1051 echo '<p>' . esc_html__('Add a meta description for this page/post.', 'ai-builder') . '</p>';
1052 echo '<textarea style="width:100%;min-height:90px;" id="aibui_meta_description_field" name="aibui_meta_description" maxlength="320">' . esc_textarea($value) . '</textarea>';
1053 echo '<p style="margin-top:6px;color:#555;">' . esc_html__('Tip: 140–160 characters for best SEO results.', 'ai-builder') . '</p>';
1054 },
1055 array('post', 'page'),
1056 'normal',
1057 'default'
1058 );
1059 });
1060
1061 add_action('save_post', function ($post_id) {
1062 if (!isset($_POST['aibui_meta_description_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['aibui_meta_description_nonce'])), 'aibui_meta_description_save')) {
1063 return;
1064 }
1065 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
1066 if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) return;
1067 if (!current_user_can('edit_post', $post_id)) return;
1068
1069 $raw = isset($_POST['aibui_meta_description']) ? wp_unslash($_POST['aibui_meta_description']) : '';
1070 $san = trim(wp_strip_all_tags($raw));
1071 if (strlen($san) > 320) {
1072 $san = mb_substr($san, 0, 320);
1073 }
1074 if ($san === '') {
1075 delete_post_meta($post_id, 'aibui_meta_description');
1076 } else {
1077 update_post_meta($post_id, 'aibui_meta_description', $san);
1078 }
1079 });
1080
1081 add_action('wp_head', function () {
1082 if (is_admin() || !is_singular()) {
1083 return;
1084 }
1085
1086 $post_id = get_queried_object_id();
1087 if (!$post_id) {
1088 return;
1089 }
1090
1091 $possible_keys = array(
1092 'aibui_meta_description',
1093 '_ai_builder_seo_desc',
1094 '_yoast_wpseo_metadesc',
1095 '_ai_translation_meta_desc'
1096 );
1097
1098 $desc = '';
1099 foreach ($possible_keys as $meta_key) {
1100 $value = get_post_meta($post_id, $meta_key, true);
1101 if (!empty($value)) {
1102 $desc = $value;
1103 break;
1104 }
1105 }
1106
1107 if (!$desc) {
1108 $excerpt = get_post_field('post_excerpt', $post_id);
1109 if (!empty($excerpt)) {
1110 $desc = $excerpt;
1111 }
1112 }
1113
1114 $desc = trim(wp_strip_all_tags((string) $desc));
1115 if ($desc !== '') {
1116 echo "\n<meta name=\"description\" content=\"" . esc_attr($desc) . "\" />\n";
1117 }
1118 }, 1);
1119
1120 // Initialiser le gestionnaire AJAX
1121 new AIBUI_Ajax_Handler();
1122
1123 // -------------------------------
1124 // Multi-Page Generator: Cleanup cron and migration
1125 // -------------------------------
1126 function aibui_cleanup_old_generations() {
1127 require_once plugin_dir_path(__FILE__) . 'includes/class-generations-storage.php';
1128 $storage = new AIBUI_Generations_Storage();
1129 $deleted_count = $storage->cleanup_old(30); // Delete applied generations older than 30 days
1130
1131 if (defined('WP_DEBUG') && WP_DEBUG) {
1132 error_log("[AI Builder] Cleaned up {$deleted_count} old generation files");
1133 }
1134 }
1135 add_action('aibui_daily_cleanup', 'aibui_cleanup_old_generations');
1136
1137 // Schedule daily cleanup if not already scheduled
1138 if (!wp_next_scheduled('aibui_daily_cleanup')) {
1139 wp_schedule_event(time(), 'daily', 'aibui_daily_cleanup');
1140 }
1141
1142 // Migrate old wp_options data to files (one-time migration on activation/update)
1143 function aibui_migrate_generations_to_files() {
1144 // Check if migration already done
1145 if (get_option('aibui_generations_migrated_to_files', false)) {
1146 return;
1147 }
1148
1149 require_once plugin_dir_path(__FILE__) . 'includes/class-generations-storage.php';
1150 $storage = new AIBUI_Generations_Storage();
1151 $migrated_count = $storage->migrate_from_options();
1152
1153 if ($migrated_count > 0) {
1154 // Mark migration as done
1155 update_option('aibui_generations_migrated_to_files', true, false);
1156
1157 if (defined('WP_DEBUG') && WP_DEBUG) {
1158 error_log("[AI Builder] Migrated {$migrated_count} generations from wp_options to files");
1159 }
1160 }
1161 }
1162 // Run migration on admin init (only once)
1163 add_action('admin_init', function() {
1164 static $migration_done = false;
1165 if (!$migration_done && current_user_can('manage_options')) {
1166 aibui_migrate_generations_to_files();
1167 $migration_done = true;
1168 }
1169 }, 5);
1170