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

1,088 lines 39.7 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.0
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.0');
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 global AI personalization settings are still empty.
165 *
166 * We query the remote settings API (same as the Settings page) and consider the
167 * configuration "empty" when all settings (primaryColor, secondaryColor, siteName,
168 * siteDescription, designStyle, blockShapes, copywritingTone) are missing/empty.
169 */
170 function aibui_are_personalization_settings_empty()
171 {
172 // Require an authenticated session with the cloud API.
173 $jwt_token = get_option('aibui_jwt_token', '');
174 if (empty($jwt_token)) {
175 return false;
176 }
177
178 $api_url = 'https://api.wordpress-ai-builder.com/api/settings';
179 $response = wp_remote_get($api_url, array(
180 'timeout' => 15,
181 'headers' => array(
182 'Authorization' => 'Bearer ' . $jwt_token,
183 'Content-Type' => 'application/json',
184 ),
185 ));
186
187 if (is_wp_error($response)) {
188 return false;
189 }
190
191 $code = wp_remote_retrieve_response_code($response);
192 if ($code !== 200) {
193 return false;
194 }
195
196 $body = wp_remote_retrieve_body($response);
197 $data = json_decode($body, true);
198 if (!is_array($data)) {
199 return false;
200 }
201
202 $primaryColor = isset($data['primaryColor']) ? trim((string) $data['primaryColor']) : '';
203 $secondaryColor = isset($data['secondaryColor']) ? trim((string) $data['secondaryColor']) : '';
204 $siteName = isset($data['siteName']) ? trim((string) $data['siteName']) : '';
205 $siteDescription = isset($data['siteDescription']) ? trim((string) $data['siteDescription']) : '';
206 $designStyle = isset($data['designStyle']) ? trim((string) $data['designStyle']) : '';
207 $blockShapes = isset($data['blockShapes']) ? trim((string) $data['blockShapes']) : '';
208 $copywritingTone = isset($data['copywritingTone']) ? trim((string) $data['copywritingTone']) : '';
209
210 return ($primaryColor === '' && $secondaryColor === '' && $siteName === '' && $siteDescription === '' && $designStyle === '' && $blockShapes === '' && $copywritingTone === '');
211 }
212
213 /**
214 * Display a subtle onboarding banner on key AI Builder pages when
215 * personalization settings are still empty.
216 */
217 function aibui_personalization_notice()
218 {
219 if (!is_admin() || !current_user_can('manage_options')) {
220 return;
221 }
222
223 $page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
224 $target_pages = array(
225 'aibui-assistant', // Account (main dashboard)
226 'aibui-credits', // Credits
227 'aibui-tuto', // Tutorial
228 'aibui-multi-page', // Multi Page Generator
229 'aibui-translation-settings', // Translations
230 );
231
232 if (!in_array($page, $target_pages, true)) {
233 return;
234 }
235
236 if (!aibui_are_personalization_settings_empty()) {
237 return;
238 }
239
240 $settings_url = admin_url('admin.php?page=aibui-settings');
241 ?>
242 <div class="notice" style="border-left:4px solid #2563eb;padding:12px 16px;margin:12px 0;background:#eff6ff;color:#111827;">
243 <p style="margin:0;font-size:13px;line-height:1.5;">
244 <strong>Make AI Builder more personal for your site.</strong>
245 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.
246 </p>
247 </div>
248 <?php
249 }
250 add_action('admin_notices', 'aibui_personalization_notice');
251
252 // Charger les menus admin
253 require_once plugin_dir_path(__FILE__) . 'admin/menu.php';
254
255 // Charger le gestionnaire AJAX
256 require_once plugin_dir_path(__FILE__) . 'includes/class-ajax-handler.php';
257
258 // Charger le gestionnaire CSS
259 require_once plugin_dir_path(__FILE__) . 'includes/class-css-handler.php';
260
261 // Charger le gestionnaire de traduction
262 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-handler.php';
263 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-settings.php';
264 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-manager.php';
265 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-switcher.php';
266
267 // Charger les services de l'Agent Chat
268 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-discovery-service.php';
269 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-security-service.php';
270 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-execution-service.php';
271 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-chat-handler.php';
272
273 // Initialiser le gestionnaire de traduction
274 new AIBUI_Translation_Handler();
275 AIBUI_Translation_Settings::init();
276 $translation_manager = new AIBUI_Translation_Manager();
277 $translation_switcher = new AIBUI_Translation_Switcher($translation_manager);
278
279 // Initialiser le gestionnaire de l'Agent Chat
280 new AIBUI_Agent_Chat_Handler();
281
282 add_action('admin_enqueue_scripts', function ($hook) {
283 // Charger le CSS admin sur toutes les pages d'administration
284 wp_enqueue_style(
285 'ai-builder-admin-style',
286 plugin_dir_url(__FILE__) . 'assets/css/style-admin.css',
287 [],
288 AIBUI_VERSION
289 );
290
291 // Charger sur l'éditeur de page/article et l'éditeur de modèles (site editor)
292 if ($hook === 'post.php' || $hook === 'post-new.php' || $hook === 'site-editor.php') {
293 wp_enqueue_style(
294 'chat-widget-style',
295 plugin_dir_url(__FILE__) . 'assets/css/chat-widget.css',
296 [],
297 AIBUI_VERSION
298 );
299 wp_enqueue_script(
300 'ai-builder-config',
301 plugin_dir_url(__FILE__) . 'config.js',
302 [],
303 AIBUI_VERSION,
304 true
305 );
306 wp_enqueue_script(
307 'chat-widget',
308 plugin_dir_url(__FILE__) . 'assets/js/chat-widget.js',
309 ['ai-builder-config'],
310 AIBUI_VERSION,
311 true
312 );
313 // Styles tabs pour s'assurer du chargement dans l'éditeur
314 wp_enqueue_style(
315 'ai-builder-tabs-css-admin-editor',
316 plugin_dir_url(__FILE__) . 'assets/css/tabs.css',
317 [],
318 AIBUI_VERSION
319 );
320 // Injection des variables JS pour AJAX et le nonce
321 wp_localize_script(
322 'chat-widget',
323 'aiBuilderVars',
324 array(
325 'ajaxurl' => admin_url('admin-ajax.php'),
326 'nonce' => wp_create_nonce('aibui_nonce'),
327 // Flag to hint we are on the Site Editor (patterns/template parts)
328 'isPatternEditor' => ($hook === 'site-editor.php'),
329 )
330 );
331
332 // Enqueue Multi-Page apply script to support applying generations via URL param
333 wp_enqueue_script(
334 'ai-builder-multi-page-apply',
335 plugin_dir_url(__FILE__) . 'assets/js/multi-page-apply.js',
336 ['ai-builder-config', 'chat-widget', 'wp-blocks', 'wp-data', 'wp-editor', 'wp-block-editor', 'wp-element'],
337 AIBUI_VERSION,
338 true
339 );
340 wp_localize_script(
341 'ai-builder-multi-page-apply',
342 'aiBuilderVars',
343 array(
344 'ajaxurl' => admin_url('admin-ajax.php'),
345 'nonce' => wp_create_nonce('aibui_nonce'),
346 )
347 );
348 }
349
350 // Charger les styles et scripts pour la page account du plugin AI Builder
351 $current_screen = get_current_screen();
352 if ($current_screen && strpos($current_screen->id, 'aibui-assistant') !== false) {
353 wp_enqueue_style(
354 'ai-builder-account-style',
355 plugin_dir_url(__FILE__) . 'assets/css/account.css',
356 [],
357 filemtime(plugin_dir_path(__FILE__) . 'assets/css/account.css')
358 );
359 wp_enqueue_script(
360 'ai-builder-config',
361 plugin_dir_url(__FILE__) . 'config.js',
362 [],
363 AIBUI_VERSION,
364 true
365 );
366 wp_enqueue_script(
367 'ai-builder-account',
368 plugin_dir_url(__FILE__) . 'assets/js/account.js',
369 ['ai-builder-config'],
370 AIBUI_VERSION,
371 true
372 );
373 // Injection des variables JS pour AJAX et le nonce
374 wp_localize_script(
375 'ai-builder-account',
376 'aiBuilderVars',
377 array(
378 'ajaxurl' => admin_url('admin-ajax.php'),
379 'nonce' => wp_create_nonce('aibui_nonce'),
380 'accountUrl' => admin_url('admin.php?page=aibui-account'),
381 'siteDomain' => parse_url(home_url(), PHP_URL_HOST),
382 )
383 );
384 } else if ($current_screen && strpos($current_screen->id, 'aibui-credits') !== false) {
385 wp_enqueue_script(
386 'ai-builder-credits',
387 plugin_dir_url(__FILE__) . 'assets/js/credits.js',
388 ['ai-builder-config'],
389 AIBUI_VERSION,
390 true
391 );
392 // Injection des variables JS pour AJAX et le nonce
393 wp_localize_script(
394 'ai-builder-credits',
395 'aiBuilderVars',
396 array(
397 'ajaxurl' => admin_url('admin-ajax.php'),
398 'nonce' => wp_create_nonce('aibui_nonce'),
399 )
400 );
401 wp_enqueue_style(
402 'ai-builder-credits-additional-style',
403 plugin_dir_url(__FILE__) . 'assets/css/credits-additional.css',
404 [],
405 AIBUI_VERSION
406 );
407 wp_enqueue_style(
408 'ai-builder-credits-style',
409 plugin_dir_url(__FILE__) . 'assets/css/credits.css',
410 [],
411 AIBUI_VERSION
412 );
413 wp_enqueue_script(
414 'ai-builder-config',
415 plugin_dir_url(__FILE__) . 'config.js',
416 [],
417 AIBUI_VERSION,
418 true
419 );
420 } else if ($current_screen && strpos($current_screen->id, 'aibui-settings') !== false) {
421 wp_enqueue_style(
422 'ai-builder-settings-style',
423 plugin_dir_url(__FILE__) . 'assets/css/settings.css',
424 [],
425 AIBUI_VERSION
426 );
427 wp_enqueue_script(
428 'ai-builder-config',
429 plugin_dir_url(__FILE__) . 'config.js',
430 [],
431 AIBUI_VERSION,
432 true
433 );
434 wp_enqueue_script(
435 'ai-builder-settings',
436 plugin_dir_url(__FILE__) . 'assets/js/settings.js',
437 ['ai-builder-config'],
438 AIBUI_VERSION,
439 true
440 );
441 // Injection des variables JS pour AJAX et le nonce
442 wp_localize_script(
443 'ai-builder-settings',
444 'aiBuilderVars',
445 array(
446 'ajaxurl' => admin_url('admin-ajax.php'),
447 'nonce' => wp_create_nonce('aibui_nonce'),
448 )
449 );
450 } else if ($current_screen && strpos($current_screen->id, 'aibui-reset-password') !== false) {
451 wp_enqueue_style(
452 'ai-builder-reset-password-style',
453 plugin_dir_url(__FILE__) . 'assets/css/reset-password.css',
454 [],
455 AIBUI_VERSION
456 );
457 wp_enqueue_script(
458 'ai-builder-config',
459 plugin_dir_url(__FILE__) . 'config.js',
460 [],
461 AIBUI_VERSION,
462 true
463 );
464 wp_enqueue_script(
465 'ai-builder-reset-password',
466 plugin_dir_url(__FILE__) . 'assets/js/reset-password.js',
467 ['ai-builder-config'],
468 AIBUI_VERSION,
469 true
470 );
471 // Injection des variables JS pour AJAX et le nonce
472 wp_localize_script(
473 'ai-builder-reset-password',
474 'aiBuilderVars',
475 array(
476 'ajaxurl' => admin_url('admin-ajax.php'),
477 'nonce' => wp_create_nonce('aibui_nonce'),
478 'accountUrl' => admin_url('admin.php?page=aibui-account'),
479 )
480 );
481 } else if ($current_screen && strpos($current_screen->id, 'aibui-tuto') !== false) {
482 wp_enqueue_style(
483 'ai-builder-tutorial-style',
484 plugin_dir_url(__FILE__) . 'assets/css/tutorial.css',
485 [],
486 AIBUI_VERSION
487 );
488 wp_enqueue_script(
489 'ai-builder-config',
490 plugin_dir_url(__FILE__) . 'config.js',
491 [],
492 AIBUI_VERSION,
493 true
494 );
495 } else if ($current_screen && strpos($current_screen->id, 'aibui-multi-page') !== false) {
496 wp_enqueue_style(
497 'ai-builder-multi-page-style',
498 plugin_dir_url(__FILE__) . 'assets/css/multi-page.css',
499 [],
500 AIBUI_VERSION
501 );
502 wp_enqueue_script(
503 'ai-builder-config',
504 plugin_dir_url(__FILE__) . 'config.js',
505 [],
506 AIBUI_VERSION,
507 true
508 );
509 wp_enqueue_script(
510 'ai-builder-multi-page',
511 plugin_dir_url(__FILE__) . 'assets/js/multi-page.js',
512 ['ai-builder-config'],
513 AIBUI_VERSION,
514 true
515 );
516 // Injection des variables JS pour AJAX et le nonce
517 wp_localize_script(
518 'ai-builder-multi-page',
519 'aiBuilderVars',
520 array(
521 'ajaxurl' => admin_url('admin-ajax.php'),
522 'nonce' => wp_create_nonce('aibui_nonce'),
523 'adminBaseUrl' => admin_url(),
524 )
525 );
526 } else if ($current_screen && strpos($current_screen->id, 'aibui-agent-chat') !== false) {
527 // Agent Chat page scripts and styles
528 wp_enqueue_script(
529 'ai-builder-config',
530 plugin_dir_url(__FILE__) . 'config.js',
531 [],
532 AIBUI_VERSION,
533 true
534 );
535 wp_enqueue_script(
536 'ai-builder-agent-chat',
537 plugin_dir_url(__FILE__) . 'assets/js/agent-chat.js',
538 ['ai-builder-config'],
539 AIBUI_VERSION,
540 true
541 );
542 // Localize with nonce - IMPORTANT: use a different nonce for agent actions
543 wp_localize_script(
544 'ai-builder-agent-chat',
545 'aibuiAgentVars',
546 array(
547 'ajaxurl' => admin_url('admin-ajax.php'),
548 'nonce' => wp_create_nonce('aibui_agent_nonce'),
549 'restBase' => esc_url_raw(rest_url()),
550 'wpRestDocsBase' => 'https://developer.wordpress.org/rest-api/reference/',
551 )
552 );
553 // Also expose standard AJAX nonce for shared endpoints like aibui_get_token
554 wp_localize_script(
555 'ai-builder-agent-chat',
556 'aiBuilderVars',
557 array(
558 'ajaxurl' => admin_url('admin-ajax.php'),
559 'nonce' => wp_create_nonce('aibui_nonce'),
560 )
561 );
562 }
563
564 });
565
566
567 add_action('wp_enqueue_scripts', function () {
568 // Single combined frontend CSS
569 $combined_css = aibui_get_combined_css_url();
570 if ($combined_css) {
571 wp_enqueue_style(
572 'ai-builder-combined',
573 $combined_css,
574 [],
575 null
576 );
577 } else {
578 // Fallback: enqueue at least the essential stylesheet
579 wp_enqueue_style(
580 'aibui-force-alignfull',
581 plugin_dir_url(__FILE__) . 'assets/css/force-align-full.css',
582 [],
583 AIBUI_VERSION
584 );
585 }
586
587 // Charger les styles CSS du bloc AI Image côté frontend
588 wp_enqueue_style(
589 'ai-builder-ai-image-frontend',
590 plugin_dir_url(__FILE__) . 'assets/js/build/index.css',
591 [],
592 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.css')
593 );
594 // Frontend JS single bundle
595 $combined_js = aibui_get_combined_js_url();
596 if ($combined_js) {
597 wp_enqueue_script(
598 'ai-builder-frontend-bundle',
599 $combined_js,
600 [],
601 null,
602 true
603 );
604 } else {
605 // Fallback: at least enqueue carousel script if bundling failed
606 wp_enqueue_script(
607 'ai-builder-carousel-frontend',
608 plugin_dir_url(__FILE__) . 'assets/js/carousel-frontend.js',
609 [],
610 AIBUI_VERSION,
611 true
612 );
613 }
614
615 // Load fixed background group block CSS for frontend
616 wp_enqueue_style(
617 'ai-builder-fixed-bg-group-frontend',
618 plugin_dir_url(__FILE__) . 'assets/js/src/editor-blocks/fixed-bg-group/style.css',
619 [],
620 AIBUI_VERSION
621 );
622
623 // CSS above is covered by the combined bundle
624
625 // Other JS are included in the combined bundle above
626 // CSS above is covered by the combined bundle
627 // Included in combined bundle
628 // CSS above is covered by the combined bundle
629 // Included in combined bundle
630 });
631
632 // Enqueue contact form script only when the block is present
633 add_action('wp_enqueue_scripts', function () {
634 global $post;
635 $should_load = false;
636
637 // Check if current post has the block
638 if (is_a($post, 'WP_Post') && has_block('ai-builder/aibui-contact-form', $post)) {
639 $should_load = true;
640 }
641
642 // Fallback: check if we're on a page that might have the block
643 if (!$should_load && (is_page() || is_single() || is_home() || is_front_page())) {
644 $should_load = true;
645 }
646
647 if ($should_load) {
648 wp_enqueue_script(
649 'ai-builder-contact-form',
650 plugin_dir_url(__FILE__) . 'assets/js/contact-form.js',
651 [],
652 AIBUI_VERSION,
653 true
654 );
655 }
656 });
657
658 add_action('enqueue_block_editor_assets', function () {
659 // Single combined CSS for editor
660 $combined_css = aibui_get_combined_css_url();
661 if ($combined_css) {
662 wp_enqueue_style(
663 'ai-builder-combined-editor',
664 $combined_css,
665 [],
666 null
667 );
668 } else {
669 wp_enqueue_style(
670 'aibui-force-alignfull-editor',
671 plugin_dir_url(__FILE__) . 'assets/css/force-align-full.css',
672 [],
673 AIBUI_VERSION
674 );
675 }
676 // Charger les styles build des blocs dans l'éditeur
677 wp_enqueue_style(
678 'ai-builder-blocks-editor-build',
679 plugin_dir_url(__FILE__) . 'assets/js/build/index.css',
680 [],
681 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.css')
682 );
683 // Styles spécifiques d'aperçu du formulaire de contact
684 wp_enqueue_style(
685 'ai-builder-contact-form-editor',
686 plugin_dir_url(__FILE__) . 'assets/css/contact-form-editor.css',
687 [],
688 AIBUI_VERSION
689 );
690 // Covered by combined bundle
691 // Stats tooltips in editor preview
692 wp_enqueue_script(
693 'ai-builder-stats-tooltips-editor',
694 plugin_dir_url(__FILE__) . 'assets/js/stats-tooltips.js',
695 [],
696 AIBUI_VERSION,
697 true
698 );
699 });
700
701 add_action('enqueue_block_editor_assets', function () {
702 wp_enqueue_script(
703 'ai-builder-blocks',
704 plugin_dir_url(__FILE__) . 'assets/js/build/index.js',
705 [
706 'wp-blocks',
707 'wp-block-editor',
708 'wp-element',
709 'wp-components',
710 'wp-i18n',
711 'wp-api-fetch',
712 'wp-hooks'
713 ],
714 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.js'),
715 true
716 );
717
718 // Injection des variables JS pour AJAX et le nonce (utilisées par les blocs éditeur)
719 wp_localize_script(
720 'ai-builder-blocks',
721 'aiBuilderVars',
722 array(
723 'ajaxurl' => admin_url('admin-ajax.php'),
724 'nonce' => wp_create_nonce('aibui_nonce'),
725 )
726 );
727
728 wp_enqueue_script(
729 'ai-builder-language-switcher-block',
730 plugin_dir_url(__FILE__) . 'assets/js/language-switcher-block.js',
731 array('wp-blocks', 'wp-block-editor', 'wp-element', 'wp-components', 'wp-i18n'),
732 filemtime(plugin_dir_path(__FILE__) . 'assets/js/language-switcher-block.js'),
733 true
734 );
735
736 $translation_settings = AIBUI_Translation_Settings::get_settings();
737 $supported_languages = AIBUI_Translation_Handler::get_supported_languages();
738 $available_langs = isset($translation_settings['available_langs']) && is_array($translation_settings['available_langs'])
739 ? array_values(array_unique($translation_settings['available_langs']))
740 : array();
741 $default_lang = isset($translation_settings['default_lang']) ? $translation_settings['default_lang'] : 'en';
742 if ($default_lang && !in_array($default_lang, $available_langs, true)) {
743 array_unshift($available_langs, $default_lang);
744 }
745 $switcher_defaults = array(
746 'backgroundColor' => !empty($translation_settings['switcher_bg']) ? $translation_settings['switcher_bg'] : '',
747 'textColor' => !empty($translation_settings['switcher_text']) ? $translation_settings['switcher_text'] : '',
748 'minWidth' => !empty($translation_settings['switcher_min_width']) ? (int) $translation_settings['switcher_min_width'] : 90,
749 'minHeight' => !empty($translation_settings['switcher_min_height']) ? (int) $translation_settings['switcher_min_height'] : 30,
750 'borderRadius' => 22,
751 );
752
753 wp_localize_script(
754 'ai-builder-language-switcher-block',
755 'aiBuilderLangSwitch',
756 array(
757 'availableLangs' => $available_langs,
758 'defaultLang' => $default_lang,
759 'labels' => $supported_languages,
760 'switcherEnabled' => !empty($translation_settings['enable_switcher']),
761 'defaults' => $switcher_defaults,
762 )
763 );
764 });
765
766 // Enregistrer le bloc côté PHP
767 add_action('init', function () use ($translation_switcher) {
768 wp_register_style(
769 'ai-builder-language-switcher-style',
770 plugin_dir_url(__FILE__) . 'assets/css/language-switcher.css',
771 array(),
772 AIBUI_VERSION
773 );
774 // Bloc AI Block
775 register_block_type('ai-builder/ai-block', [
776 'editor_script' => 'ai-builder-blocks',
777 'editor_style' => 'ai-builder-blocks-style',
778 'style' => 'ai-builder-blocks-style',
779 'render_callback' => function ($attributes) {
780 // Le bloc AI Block ne s'affiche pas côté frontend car il est remplacé par le contenu généré
781 return '';
782 }
783 ]);
784
785 // Bloc stat-bar (statique, pas de render_callback)
786 register_block_type('ai-builder/stat-bar', [
787 'editor_script' => 'ai-builder-blocks',
788 'editor_style' => 'ai-builder-blocks-style',
789 'style' => 'ai-builder-blocks-style',
790 ]);
791
792 // Bloc aibui-stats (rendu simple côté éditeur, frontend statique)
793 register_block_type('ai-builder/aibui-stats', [
794 'editor_script' => 'ai-builder-blocks',
795 'editor_style' => 'ai-builder-blocks-style',
796 'style' => 'ai-builder-blocks-style',
797 ]);
798
799 // Bloc aibui-carousel
800 register_block_type('ai-builder/aibui-carousel', [
801 'editor_script' => 'ai-builder-blocks',
802 'editor_style' => 'ai-builder-blocks-style',
803 'style' => 'ai-builder-blocks-style',
804 ]);
805
806 // Bloc aibui-yt-video
807 register_block_type('ai-builder/aibui-yt-video', [
808 'editor_script' => 'ai-builder-blocks',
809 'editor_style' => 'ai-builder-blocks-style',
810 'style' => 'ai-builder-blocks-style',
811 ]);
812
813 // Bloc aibui-map
814 register_block_type('ai-builder/aibui-map', [
815 'editor_script' => 'ai-builder-blocks',
816 'editor_style' => 'ai-builder-blocks-style',
817 'style' => 'ai-builder-blocks-style',
818 ]);
819
820 // Bloc aibui-tabs
821 // Bloc aibui-table
822 register_block_type('ai-builder/aibui-table', [
823 'editor_script' => 'ai-builder-blocks',
824 'editor_style' => 'ai-builder-blocks-style',
825 'style' => 'ai-builder-blocks-style',
826 ]);
827 register_block_type('ai-builder/aibui-tabs', [
828 'editor_script' => 'ai-builder-blocks',
829 'editor_style' => 'ai-builder-blocks-style',
830 'style' => 'ai-builder-blocks-style',
831 ]);
832
833 // Bloc aibui-contact-form (rendu via callback PHP pour sécurité)
834 register_block_type('ai-builder/aibui-contact-form', [
835 'editor_script' => 'ai-builder-blocks',
836 'editor_style' => 'ai-builder-blocks-style',
837 'style' => 'ai-builder-blocks-style',
838 'render_callback' => function ($attributes, $content, $block) {
839 $recipient = isset($attributes['recipientEmail']) ? sanitize_email($attributes['recipientEmail']) : '';
840 if (empty($recipient) || !is_email($recipient)) {
841 $recipient = sanitize_email(get_option('admin_email'));
842 }
843 if (empty($recipient) || !is_email($recipient)) {
844 // Pas de destinataire valide disponible, n'arrête pas l'affichage, mais désactive l'envoi
845 $recipient = '';
846 }
847
848 $fields = isset($attributes['fields']) && is_array($attributes['fields']) ? $attributes['fields'] : [];
849 $fields = array_slice($fields, 0, 5);
850
851 $form_title = isset($attributes['formTitle']) ? wp_kses_post($attributes['formTitle']) : '';
852 $description = isset($attributes['description']) ? wp_kses_post($attributes['description']) : '';
853 $button_label = isset($attributes['buttonLabel']) ? sanitize_text_field($attributes['buttonLabel']) : __('Send', 'ai-builder');
854 $success_message = isset($attributes['successMessage']) ? sanitize_text_field($attributes['successMessage']) : __('Thank you, your message has been sent.', 'ai-builder');
855 $from_email_attr = isset($attributes['fromEmail']) ? sanitize_email($attributes['fromEmail']) : '';
856
857 $nonce = wp_create_nonce('aibui_contact_form');
858 $action = esc_url(admin_url('admin-ajax.php'));
859
860 ob_start();
861 ?>
862 <form class="aibui-contact-form aibui-contact-form--frontend" method="post" action="<?php echo $action; ?>"
863 data-endpoint="<?php echo $action; ?>">
864 <?php if ($form_title): ?>
865 <h3 class="aibui-form-title"><?php echo $form_title; ?></h3>
866 <?php endif; ?>
867 <?php if ($description): ?>
868 <p class="aibui-form-description"><?php echo $description; ?></p>
869 <?php endif; ?>
870 <input type="hidden" name="action" value="aibui_submit_contact_form" />
871 <input type="hidden" name="nonce" value="<?php echo esc_attr($nonce); ?>" />
872 <input type="hidden" name="recipient" value="<?php echo esc_attr($recipient); ?>" />
873 <?php if ($from_email_attr): ?>
874 <input type="hidden" name="from_email" value="<?php echo esc_attr($from_email_attr); ?>" />
875 <?php endif; ?>
876 <?php foreach ($fields as $idx => $field):
877 $label = isset($field['label']) ? sanitize_text_field($field['label']) : '';
878 $type = isset($field['type']) ? sanitize_text_field($field['type']) : 'text';
879 $required = !empty($field['required']);
880 $name = 'field_' . $idx;
881 ?>
882 <div class="aibui-field">
883 <label>
884 <?php echo esc_html($label); ?> <?php echo $required ? ' *' : ''; ?>
885 <?php if ($type === 'textarea'): ?>
886 <textarea name="<?php echo esc_attr($name); ?>" <?php echo $required ? 'required' : ''; ?>
887 aria-label="<?php echo esc_attr($label); ?>"></textarea>
888 <?php else: ?>
889 <input
890 type="<?php echo $type === 'email' ? 'email' : ($type === 'number' ? 'number' : ($type === 'date' ? 'date' : 'text')); ?>"
891 name="<?php echo esc_attr($name); ?>" <?php echo $required ? 'required' : ''; ?>
892 aria-label="<?php echo esc_attr($label); ?>" />
893 <?php endif; ?>
894 <input type="hidden" name="label_<?php echo esc_attr($name); ?>" value="<?php echo esc_attr($label); ?>" />
895 <input type="hidden" name="type_<?php echo esc_attr($name); ?>" value="<?php echo esc_attr($type); ?>" />
896 <input type="hidden" name="required_<?php echo esc_attr($name); ?>"
897 value="<?php echo $required ? '1' : '0'; ?>" />
898 </label>
899 </div>
900 <?php endforeach; ?>
901 <div style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;">
902 <label>Leave this field empty<input type="text" name="website" tabindex="-1" autocomplete="off" /></label>
903 </div>
904 <div class="aibui-form-message" role="status" aria-live="polite"></div>
905 <input type="hidden" name="success_message" value="<?php echo esc_attr($success_message); ?>" />
906 <button type="submit" class="aibui-button aibui-button--primary"><?php echo esc_html($button_label); ?></button>
907 </form>
908 <?php
909 return ob_get_clean();
910 }
911 ]);
912
913 register_block_type('ai-builder/language-switcher', [
914 'editor_script' => 'ai-builder-language-switcher-block',
915 'editor_style' => 'ai-builder-language-switcher-style',
916 'style' => 'ai-builder-language-switcher-style',
917 'render_callback' => array($translation_switcher, 'render_block'),
918 'attributes' => [
919 'backgroundColor' => ['type' => 'string', 'default' => '#5686c9'],
920 'textColor' => ['type' => 'string', 'default' => '#ffffff'],
921 'minWidth' => ['type' => 'number', 'default' => 90],
922 'minHeight' => ['type' => 'number', 'default' => 30],
923 'borderRadius' => ['type' => 'number', 'default' => 22],
924 ],
925 'supports' => [
926 'align' => ['left', 'center', 'right', 'wide', 'full'],
927 ],
928 ]);
929 });
930
931 add_action('after_setup_theme', function () {
932 add_theme_support('align-wide');
933 });
934
935 function enqueue_ai_builder_scripts() {
936 // Charger config.js en premier (dépendance pour les autres scripts)
937 wp_enqueue_script(
938 'ai-builder-config',
939 plugins_url('config.js', __FILE__),
940 array(),
941 AIBUI_VERSION,
942 false // Charger dans le <head> pour être disponible partout
943 );
944
945 // Autres scripts qui utilisent config.js
946 wp_enqueue_script(
947 'ai-builder-image-ai-controls',
948 plugins_url('assets/js/src/editor-blocks/image-ai-blocks/image-ai-controls.js', __FILE__),
949 array('ai-builder-config'),
950 AIBUI_VERSION,
951 true
952 );
953
954 // Autres scripts qui utilisent config.js
955 wp_enqueue_script(
956 'ai-builder-text-ai-controls',
957 plugins_url('assets/js/src/editor-blocks/text-ai-blocks/text-ai-controls.js', __FILE__),
958 array('ai-builder-config'),
959 AIBUI_VERSION,
960 true
961 );
962
963 // Autres scripts qui utilisent config.js
964 wp_enqueue_script(
965 'ai-builder-text-ai-controls',
966 plugins_url('assets/js/src/editor-blocks/text-ai-blocks/text-ai-controls.js', __FILE__),
967 array('ai-builder-config'),
968 AIBUI_VERSION,
969 true
970 );
971
972 // Autres scripts qui utilisent config.js
973 wp_enqueue_script(
974 'ai-builder-ai-block',
975 plugins_url('assets/js/src/editor-blocks/ai-block/ai-block.js', __FILE__),
976 array('ai-builder-config'),
977 AIBUI_VERSION,
978 true
979 );
980 }
981 add_action('enqueue_block_editor_assets', 'enqueue_ai_builder_scripts');
982
983 add_action('wp_enqueue_scripts', function () {
984 // Combined CSS already enqueued above; only enqueue any extra block scss-generated styles if needed
985 // Table, snackbar, cards editor styles are not required on frontend separately when bundled
986
987 // Load snackbar frontend JavaScript
988 wp_enqueue_script(
989 'ai-builder-snackbar-frontend',
990 plugin_dir_url(__FILE__) . 'assets/js/snackbar-frontend.js',
991 [],
992 AIBUI_VERSION,
993 true
994 );
995 });
996
997 add_action('enqueue_block_editor_assets', function () {
998 // Combined CSS already enqueued above; keep editor-specific assets below
999 // Load fixed background group block CSS specifically (already inside combined, but keep safe fallback if combined failed)
1000 $fixed_bg_css_url = plugin_dir_url(__FILE__) . 'assets/js/src/editor-blocks/fixed-bg-group/style.css';
1001 wp_enqueue_style('ai-builder-fixed-bg-group-editor', $fixed_bg_css_url, [], AIBUI_VERSION);
1002
1003 // Load WordPress media scripts for image selection
1004 wp_enqueue_media();
1005 });
1006
1007 // -------------------------------
1008 // Meta description per page/post
1009 // -------------------------------
1010 add_action('add_meta_boxes', function () {
1011 add_meta_box(
1012 'aibui_meta_description',
1013 __('Meta description', 'ai-builder'),
1014 function ($post) {
1015 $value = get_post_meta($post->ID, 'aibui_meta_description', true);
1016 wp_nonce_field('aibui_meta_description_save', 'aibui_meta_description_nonce');
1017 echo '<p>' . esc_html__('Add a meta description for this page/post.', 'ai-builder') . '</p>';
1018 echo '<textarea style="width:100%;min-height:90px;" id="aibui_meta_description_field" name="aibui_meta_description" maxlength="320">' . esc_textarea($value) . '</textarea>';
1019 echo '<p style="margin-top:6px;color:#555;">' . esc_html__('Tip: 140–160 characters for best SEO results.', 'ai-builder') . '</p>';
1020 },
1021 array('post', 'page'),
1022 'normal',
1023 'default'
1024 );
1025 });
1026
1027 add_action('save_post', function ($post_id) {
1028 if (!isset($_POST['aibui_meta_description_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['aibui_meta_description_nonce'])), 'aibui_meta_description_save')) {
1029 return;
1030 }
1031 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
1032 if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) return;
1033 if (!current_user_can('edit_post', $post_id)) return;
1034
1035 $raw = isset($_POST['aibui_meta_description']) ? wp_unslash($_POST['aibui_meta_description']) : '';
1036 $san = trim(wp_strip_all_tags($raw));
1037 if (strlen($san) > 320) {
1038 $san = mb_substr($san, 0, 320);
1039 }
1040 if ($san === '') {
1041 delete_post_meta($post_id, 'aibui_meta_description');
1042 } else {
1043 update_post_meta($post_id, 'aibui_meta_description', $san);
1044 }
1045 });
1046
1047 add_action('wp_head', function () {
1048 if (is_admin() || !is_singular()) {
1049 return;
1050 }
1051
1052 $post_id = get_queried_object_id();
1053 if (!$post_id) {
1054 return;
1055 }
1056
1057 $possible_keys = array(
1058 'aibui_meta_description',
1059 '_ai_builder_seo_desc',
1060 '_yoast_wpseo_metadesc',
1061 '_ai_translation_meta_desc'
1062 );
1063
1064 $desc = '';
1065 foreach ($possible_keys as $meta_key) {
1066 $value = get_post_meta($post_id, $meta_key, true);
1067 if (!empty($value)) {
1068 $desc = $value;
1069 break;
1070 }
1071 }
1072
1073 if (!$desc) {
1074 $excerpt = get_post_field('post_excerpt', $post_id);
1075 if (!empty($excerpt)) {
1076 $desc = $excerpt;
1077 }
1078 }
1079
1080 $desc = trim(wp_strip_all_tags((string) $desc));
1081 if ($desc !== '') {
1082 echo "\n<meta name=\"description\" content=\"" . esc_attr($desc) . "\" />\n";
1083 }
1084 }, 1);
1085
1086 // Initialiser le gestionnaire AJAX
1087 new AIBUI_Ajax_Handler();
1088