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

1,519 lines 54.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.7.1
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.7.1');
21
22 /**
23 * Redirect to AI Builder dashboard after plugin activation.
24 */
25 function aibui_activation_redirect() {
26 // Set a transient to trigger redirect on next admin page load
27 set_transient('aibui_activation_redirect', true, 30);
28 aibui_get_installation_id();
29 }
30 register_activation_hook(__FILE__, 'aibui_activation_redirect');
31
32 /**
33 * Perform the redirect after plugin activation.
34 */
35 function aibui_redirect_after_activation() {
36 // Check if we should redirect
37 if (!get_transient('aibui_activation_redirect')) {
38 return;
39 }
40
41 // Delete the transient so we don't redirect again
42 delete_transient('aibui_activation_redirect');
43
44 // Only redirect if user can manage options and it's not a bulk activation
45 if (!current_user_can('manage_options') || isset($_GET['activate-multi'])) {
46 return;
47 }
48
49 // Redirect to AI Builder dashboard
50 wp_safe_redirect(admin_url('admin.php?page=aibui-assistant'));
51 exit;
52 }
53 add_action('admin_init', 'aibui_redirect_after_activation');
54
55 /**
56 * Option key: one UUID per WordPress installation (distinct from hostname; avoids localhost collisions).
57 */
58 define('AIBUI_INSTALLATION_ID_OPTION', 'aibui_installation_id');
59
60 /**
61 * UUID v4; uses core helper on WP 6.3+, else random_bytes fallback.
62 *
63 * @return string
64 */
65 function aibui_generate_uuid4()
66 {
67 if (function_exists('wp_generate_uuid4')) {
68 return wp_generate_uuid4();
69 }
70 try {
71 $data = random_bytes(16);
72 } catch (Exception $e) {
73 return sprintf(
74 '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
75 wp_rand(0, 0xffff),
76 wp_rand(0, 0xffff),
77 wp_rand(0, 0xffff),
78 wp_rand(0, 0x0fff) | 0x4000,
79 wp_rand(0, 0x3fff) | 0x8000,
80 wp_rand(0, 0xffff),
81 wp_rand(0, 0xffff),
82 wp_rand(0, 0xffff)
83 );
84 }
85 $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
86 $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
87 return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
88 }
89
90 /**
91 * Persistent installation id for this DB; created on first read.
92 *
93 * @return string
94 */
95 function aibui_get_installation_id()
96 {
97 $id = get_option(AIBUI_INSTALLATION_ID_OPTION, '');
98 if (!is_string($id) || $id === '') {
99 $id = aibui_generate_uuid4();
100 update_option(AIBUI_INSTALLATION_ID_OPTION, $id, false);
101 }
102 return $id;
103 }
104
105 // Simple CSS minifier (safe whitespace/comment removal)
106 function aibui_minify_css($css)
107 {
108 if (!is_string($css) || $css === '') return '';
109 // Remove comments
110 $css = preg_replace('#/\*.*?\*/#s', '', $css);
111 // Collapse whitespace
112 $css = preg_replace('/\s+/', ' ', $css);
113 // Remove spaces around symbols
114 $css = preg_replace('/\s*([{};:,>\(\)])\s*/', '$1', $css);
115 // Final trims and unnecessary semicolons
116 $css = str_replace(';}', '}', $css);
117 return trim($css);
118 }
119
120 // Build or fetch a combined CSS file for plugin assets/css/*.css
121 function aibui_get_combined_css_url()
122 {
123 $css_dir = plugin_dir_path(__FILE__) . 'assets/css/';
124 $css_url_base = plugin_dir_url(__FILE__) . 'assets/css/';
125
126 // If directory missing, bail to original behavior
127 if (!is_dir($css_dir)) return '';
128
129 $files = glob($css_dir . '*.css');
130 if (!$files) return '';
131
132 // Compute a hash based on file mtimes and paths to invalidate cache when any source changes
133 $sig_parts = [];
134 foreach ($files as $path) {
135 $sig_parts[] = basename($path) . ':' . filemtime($path);
136 }
137 $signature = md5(implode('|', $sig_parts));
138
139 // Store in uploads to keep plugin dir clean and writable
140 $uploads = wp_upload_dir();
141 if (!empty($uploads['error'])) {
142 return '';
143 }
144 $cache_dir = rtrim($uploads['basedir'], '/').'/ai-builder-cache';
145 $cache_url = rtrim($uploads['baseurl'], '/').'/ai-builder-cache';
146 if (!is_dir($cache_dir)) {
147 wp_mkdir_p($cache_dir);
148 }
149
150 $combined_filename = 'combined-'.$signature.'.css';
151 $combined_path = $cache_dir.'/'.$combined_filename;
152 $combined_url = $cache_url.'/'.$combined_filename;
153
154 if (!file_exists($combined_path)) {
155 $buffer = '';
156 // Keep a stable order: alphabetical by filename
157 sort($files, SORT_STRING);
158 foreach ($files as $path) {
159 // Skip admin-only stylesheet to avoid leaking to frontend bundle
160 if (basename($path) === 'style-admin.css') continue;
161 $content = file_get_contents($path);
162 if ($content === false) continue;
163 $buffer .= "\n/* ".basename($path)." */\n".$content;
164 }
165 $minified = aibui_minify_css($buffer);
166 // Graceful write
167 if (is_writable($cache_dir)) {
168 file_put_contents($combined_path, $minified);
169 } else {
170 return '';
171 }
172 }
173
174 return $combined_url;
175 }
176
177 // Simple JS minifier (very conservative)
178 function aibui_minify_js($js)
179 {
180 if (!is_string($js) || $js === '') return '';
181 // Remove block comments but keep /*! license comments */
182 $js = preg_replace('#/(?!\!)(\*[^*]*\*+(?:[^/*][^*]*\*+)*/)#', '', $js);
183 // Remove line comments
184 $js = preg_replace('#(^|\s)//.*$#m', '$1', $js);
185 // Collapse whitespace
186 $js = preg_replace('/\s+/', ' ', $js);
187 return trim($js);
188 }
189
190 // Build a combined frontend JS bundle from selected plugin scripts
191 function aibui_get_combined_js_url()
192 {
193 $js_list = array(
194 'assets/js/carousel-frontend.js',
195 'assets/js/map-frontend.js',
196 'assets/js/tabs-frontend.js',
197 'assets/js/table-frontend.js',
198 'assets/js/stats-tooltips.js',
199 'assets/js/snackbar-frontend.js',
200 );
201
202 $sig_parts = array();
203 $contents = '';
204 foreach ($js_list as $rel) {
205 $path = plugin_dir_path(__FILE__) . $rel;
206 if (!file_exists($path)) continue;
207 $sig_parts[] = $rel . ':' . filemtime($path);
208 }
209 if (empty($sig_parts)) return '';
210 $signature = md5(implode('|', $sig_parts));
211
212 $uploads = wp_upload_dir();
213 if (!empty($uploads['error'])) {
214 return '';
215 }
216 $cache_dir = rtrim($uploads['basedir'], '/').'/ai-builder-cache';
217 $cache_url = rtrim($uploads['baseurl'], '/').'/ai-builder-cache';
218 if (!is_dir($cache_dir)) {
219 wp_mkdir_p($cache_dir);
220 }
221
222 $combined_filename = 'frontend-'.$signature.'.js';
223 $combined_path = $cache_dir.'/'.$combined_filename;
224 $combined_url = $cache_url.'/'.$combined_filename;
225
226 if (!file_exists($combined_path)) {
227 $buffer = '';
228 foreach ($js_list as $rel) {
229 $path = plugin_dir_path(__FILE__) . $rel;
230 if (!file_exists($path)) continue;
231 $content = file_get_contents($path);
232 if ($content === false) continue;
233 $buffer .= "\n/* ".$rel." */\n".$content."\n";
234 }
235 $minified = aibui_minify_js($buffer);
236 if (is_writable($cache_dir)) {
237 file_put_contents($combined_path, $minified);
238 } else {
239 return '';
240 }
241 }
242
243 return $combined_url;
244 }
245
246 /**
247 * Check if WooCommerce is installed and active.
248 *
249 * @return bool True if WooCommerce is active, false otherwise.
250 */
251 function aibui_is_woocommerce_installed()
252 {
253 // Check if WooCommerce class exists (plugin loaded)
254 if (class_exists('WooCommerce')) {
255 return true;
256 }
257
258 // Alternative check: see if the plugin is active
259 if (function_exists('is_plugin_active')) {
260 return is_plugin_active('woocommerce/woocommerce.php');
261 }
262
263 // Fallback: check active plugins option
264 $active_plugins = get_option('active_plugins', array());
265 return in_array('woocommerce/woocommerce.php', $active_plugins, true);
266 }
267
268 /**
269 * Get the name of the currently active WordPress theme.
270 *
271 * @return string Theme name or empty string if not available.
272 */
273 function aibui_get_active_theme_name()
274 {
275 if (function_exists('wp_get_theme')) {
276 $theme = wp_get_theme();
277 return $theme->get('Name');
278 }
279 return '';
280 }
281
282 /**
283 * Get up to 15 active plugin names for AI context.
284 *
285 * @return array<int, string>
286 */
287 function aibui_get_active_plugins_context()
288 {
289 // Ensure WordPress plugin helpers are available.
290 if (!function_exists('get_plugins')) {
291 require_once ABSPATH . 'wp-admin/includes/plugin.php';
292 }
293
294 $installed_plugins = function_exists('get_plugins') ? get_plugins() : array();
295 $active_plugins = (array) get_option('active_plugins', array());
296 $active_plugins = array_slice($active_plugins, 0, 15);
297
298 $plugin_names = array();
299 foreach ($active_plugins as $plugin_file) {
300 $plugin_file = (string) $plugin_file;
301 if (isset($installed_plugins[$plugin_file]['Name']) && $installed_plugins[$plugin_file]['Name'] !== '') {
302 $plugin_names[] = sanitize_text_field($installed_plugins[$plugin_file]['Name']);
303 } else {
304 $plugin_names[] = sanitize_text_field($plugin_file);
305 }
306 }
307
308 return array_values(array_slice($plugin_names, 0, 15));
309 }
310
311 /**
312 * Get current WordPress version.
313 *
314 * @return string
315 */
316 function aibui_get_wordpress_version()
317 {
318 return (string) get_bloginfo('version');
319 }
320
321
322 /**
323 * Check if global AI personalization settings are still empty.
324 *
325 * We query the remote settings API (same as the Settings page) and consider the
326 * configuration "empty" when all settings (primaryColor, secondaryColor, siteName,
327 * siteDescription, designStyle, blockShapes, copywritingTone) are missing/empty.
328 */
329 function aibui_are_personalization_settings_empty()
330 {
331 // Require an authenticated session with the cloud API.
332 $jwt_token = get_option('aibui_jwt_token', '');
333 if (empty($jwt_token)) {
334 return false;
335 }
336
337 $api_url = 'https://api.wordpress-ai-builder.com/api/settings';
338 $response = wp_remote_get($api_url, array(
339 'timeout' => 15,
340 'headers' => array(
341 'Authorization' => 'Bearer ' . $jwt_token,
342 'Content-Type' => 'application/json',
343 ),
344 ));
345
346 if (is_wp_error($response)) {
347 return false;
348 }
349
350 $code = wp_remote_retrieve_response_code($response);
351 if ($code !== 200) {
352 return false;
353 }
354
355 $body = wp_remote_retrieve_body($response);
356 $data = json_decode($body, true);
357 if (!is_array($data)) {
358 return false;
359 }
360
361 $primaryColor = isset($data['primaryColor']) ? trim((string) $data['primaryColor']) : '';
362 $secondaryColor = isset($data['secondaryColor']) ? trim((string) $data['secondaryColor']) : '';
363 $siteName = isset($data['siteName']) ? trim((string) $data['siteName']) : '';
364 $siteDescription = isset($data['siteDescription']) ? trim((string) $data['siteDescription']) : '';
365 $designStyle = isset($data['designStyle']) ? trim((string) $data['designStyle']) : '';
366 $blockShapes = isset($data['blockShapes']) ? trim((string) $data['blockShapes']) : '';
367 $copywritingTone = isset($data['copywritingTone']) ? trim((string) $data['copywritingTone']) : '';
368
369 return ($primaryColor === '' && $secondaryColor === '' && $siteName === '' && $siteDescription === '' && $designStyle === '' && $blockShapes === '' && $copywritingTone === '');
370 }
371
372 /**
373 * Render onboarding content (as in-page blocks, not admin notices).
374 *
375 * This is intentionally NOT hooked into admin_notices because we only want it
376 * to appear inside specific AI Builder pages (Account, Credits, Tutorial).
377 */
378 function aibui_render_onboarding_blocks()
379 {
380 if (!is_admin()) {
381 return;
382 }
383
384 $show_personalization = current_user_can('manage_options') && aibui_are_personalization_settings_empty();
385 $show_first_gen = current_user_can('edit_posts') && aibui_user_has_not_generated_content();
386
387 if (!$show_personalization && !$show_first_gen) {
388 return;
389 }
390
391 $settings_url = admin_url('admin.php?page=aibui-settings');
392 $new_page_url = admin_url('post-new.php?post_type=page');
393 ?>
394 <div class="aibui-onboarding-blocks" role="region" aria-label="<?php echo esc_attr__('AI Builder onboarding', 'ai-builder'); ?>">
395 <?php if ($show_personalization): ?>
396 <div class="aibui-onboarding-card aibui-onboarding-card--info">
397 <div class="aibui-onboarding-card__body">
398 <div class="aibui-onboarding-card__title">
399 <?php echo esc_html__('Make AI Builder more personal for your site.', 'ai-builder'); ?>
400 </div>
401 <div class="aibui-onboarding-card__text">
402 <?php echo wp_kses_post(sprintf(
403 /* translators: %s: AI Builder settings URL */
404 __('Configure your site preferences in <a href="%s">AI Builder Settings</a> for better, more personalized results.', 'ai-builder'),
405 esc_url($settings_url)
406 )); ?>
407 </div>
408 </div>
409 </div>
410 <?php endif; ?>
411
412 <?php if ($show_first_gen): ?>
413 <div class="aibui-onboarding-card aibui-onboarding-card--success">
414 <div class="aibui-onboarding-card__body">
415 <div class="aibui-onboarding-card__title">
416 <?php echo esc_html__('Ready to create your first AI-powered content?', 'ai-builder'); ?>
417 </div>
418 <div class="aibui-onboarding-card__text">
419 <?php echo wp_kses_post(__('To generate your first AI content, go to any page or post and use the AI chat widget in the bottom-left corner of the editor. Simply describe what you want, and AI Builder will create it for you!', 'ai-builder')); ?>
420 </div>
421 <div class="aibui-onboarding-card__actions">
422 <a class="aibui-onboarding-card__cta" href="<?php echo esc_url($new_page_url); ?>">
423 <span aria-hidden="true"></span>
424 <?php echo esc_html__('Create your first Gutenberg page with AI', 'ai-builder'); ?>
425 </a>
426 </div>
427 </div>
428 </div>
429 <?php endif; ?>
430 </div>
431 <?php
432 }
433
434 /**
435 * Check if the user has never generated AI content.
436 *
437 * We query the remote user profile API and check if hasGeneratedAIContent is false.
438 */
439 function aibui_user_has_not_generated_content()
440 {
441 // Require an authenticated session with the cloud API.
442 $jwt_token = get_option('aibui_jwt_token', '');
443 if (empty($jwt_token)) {
444 return false;
445 }
446
447 // Use transient to cache the result to avoid excessive API calls
448 $cache_key = 'aibui_has_generated_content_' . md5($jwt_token);
449 $cached = get_transient($cache_key);
450 if ($cached !== false) {
451 return $cached === 'no';
452 }
453
454 $api_url = 'https://api.wordpress-ai-builder.com/api/user/profile';
455 $response = wp_remote_get($api_url, array(
456 'timeout' => 10,
457 'headers' => array(
458 'Authorization' => 'Bearer ' . $jwt_token,
459 'Content-Type' => 'application/json',
460 ),
461 ));
462
463 if (is_wp_error($response)) {
464 return false;
465 }
466
467 $code = wp_remote_retrieve_response_code($response);
468 if ($code !== 200) {
469 return false;
470 }
471
472 $body = wp_remote_retrieve_body($response);
473 $data = json_decode($body, true);
474
475 if (!is_array($data)) {
476 return false;
477 }
478
479 $user = isset($data['user']) ? $data['user'] : null;
480
481 if ($user === null) {
482 return false;
483 }
484
485 // Get hasGeneratedAIContent from user object (not from root data)
486 $hasGeneratedAIContent = isset($user['hasGeneratedAIContent']) ? (bool) $user['hasGeneratedAIContent'] : true;
487
488
489 // Cache the result for 10 seconds
490 set_transient($cache_key, $hasGeneratedAIContent ? 'yes' : 'no', 10);
491
492 return !$hasGeneratedAIContent;
493 }
494
495 // NOTE: the first-generation onboarding is now rendered via aibui_render_onboarding_blocks()
496 // inside Account/Credits/Tutorial page templates (not as a global admin notice).
497
498 // Charger les menus admin
499 require_once plugin_dir_path(__FILE__) . 'admin/menu.php';
500
501 // Charger le gestionnaire AJAX
502 require_once plugin_dir_path(__FILE__) . 'includes/class-ajax-handler.php';
503
504 // Charger le gestionnaire CSS
505 require_once plugin_dir_path(__FILE__) . 'includes/class-css-handler.php';
506
507 // Charger le gestionnaire JS
508 require_once plugin_dir_path(__FILE__) . 'includes/class-js-handler.php';
509
510 // Charger le gestionnaire de traduction
511 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-handler.php';
512 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-settings.php';
513 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-manager.php';
514 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-switcher.php';
515
516 // Charger les services de l'Agent Chat
517 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-discovery-service.php';
518 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-security-service.php';
519 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-execution-service.php';
520 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-chat-handler.php';
521
522 // Initialiser le gestionnaire de traduction
523 new AIBUI_Translation_Handler();
524 AIBUI_Translation_Settings::init();
525 $translation_manager = new AIBUI_Translation_Manager();
526 $translation_switcher = new AIBUI_Translation_Switcher($translation_manager);
527
528 // Initialiser le gestionnaire de l'Agent Chat
529 new AIBUI_Agent_Chat_Handler();
530
531 add_action('admin_enqueue_scripts', function ($hook) {
532 // Charger le CSS admin sur toutes les pages d'administration
533 wp_enqueue_style(
534 'ai-builder-admin-style',
535 plugin_dir_url(__FILE__) . 'assets/css/style-admin.css',
536 [],
537 AIBUI_VERSION
538 );
539
540 // Charger sur l'éditeur de page/article et l'éditeur de modèles (site editor)
541 if ($hook === 'post.php' || $hook === 'post-new.php' || $hook === 'site-editor.php') {
542 wp_enqueue_style(
543 'chat-widget-style',
544 plugin_dir_url(__FILE__) . 'assets/css/chat-widget.css',
545 [],
546 AIBUI_VERSION
547 );
548 wp_enqueue_script(
549 'ai-builder-config',
550 plugin_dir_url(__FILE__) . 'config.js',
551 [],
552 AIBUI_VERSION,
553 true
554 );
555 wp_enqueue_script(
556 'chat-widget',
557 plugin_dir_url(__FILE__) . 'assets/js/chat-widget.js',
558 ['ai-builder-config'],
559 AIBUI_VERSION,
560 true
561 );
562 // Styles tabs pour s'assurer du chargement dans l'éditeur
563 wp_enqueue_style(
564 'ai-builder-tabs-css-admin-editor',
565 plugin_dir_url(__FILE__) . 'assets/css/tabs.css',
566 [],
567 AIBUI_VERSION
568 );
569 // Injection des variables JS pour AJAX et le nonce
570 wp_localize_script(
571 'chat-widget',
572 'aiBuilderVars',
573 array(
574 'ajaxurl' => admin_url('admin-ajax.php'),
575 'nonce' => wp_create_nonce('aibui_nonce'),
576 'adminBaseUrl' => admin_url(),
577 // Base URL for plugin assets (e.g. chat style preview images)
578 'pluginUrl' => plugin_dir_url(__FILE__),
579 // Flag to hint we are on the Site Editor (patterns/template parts)
580 'isPatternEditor' => ($hook === 'site-editor.php'),
581 // WooCommerce detection
582 'wooCommerceInstalled' => aibui_is_woocommerce_installed(),
583 // Active theme name
584 'activeThemeName' => aibui_get_active_theme_name(),
585 // Active plugins context (max 15)
586 'sitePlugins' => aibui_get_active_plugins_context(),
587 // Current WordPress version
588 'wordpressVersion' => aibui_get_wordpress_version(),
589 )
590 );
591
592 // Enqueue Multi-Page apply script to support applying generations via URL param
593 wp_enqueue_script(
594 'ai-builder-multi-page-apply',
595 plugin_dir_url(__FILE__) . 'assets/js/multi-page-apply.js',
596 ['ai-builder-config', 'chat-widget', 'wp-blocks', 'wp-data', 'wp-editor', 'wp-block-editor', 'wp-element'],
597 AIBUI_VERSION,
598 true
599 );
600 wp_localize_script(
601 'ai-builder-multi-page-apply',
602 'aiBuilderVars',
603 array(
604 'ajaxurl' => admin_url('admin-ajax.php'),
605 'nonce' => wp_create_nonce('aibui_nonce'),
606 // Keep editor context keys to avoid overriding data needed by chat-widget.
607 'isPatternEditor' => ($hook === 'site-editor.php'),
608 'wooCommerceInstalled' => aibui_is_woocommerce_installed(),
609 // Active theme name
610 'activeThemeName' => aibui_get_active_theme_name(),
611 // Active plugins context (max 15)
612 'sitePlugins' => aibui_get_active_plugins_context(),
613 // Current WordPress version
614 'wordpressVersion' => aibui_get_wordpress_version(),
615 )
616 );
617 }
618
619 $current_screen = get_current_screen();
620 // Charger les styles et scripts pour la page account du plugin AI Builder
621 if ($current_screen && strpos($current_screen->id, 'aibui-assistant') !== false) {
622 wp_enqueue_style(
623 'ai-builder-account-style',
624 plugin_dir_url(__FILE__) . 'assets/css/account.css',
625 [],
626 filemtime(plugin_dir_path(__FILE__) . 'assets/css/account.css')
627 );
628 wp_enqueue_script(
629 'ai-builder-config',
630 plugin_dir_url(__FILE__) . 'config.js',
631 [],
632 AIBUI_VERSION,
633 true
634 );
635 wp_enqueue_script(
636 'ai-builder-account',
637 plugin_dir_url(__FILE__) . 'assets/js/account.js',
638 ['ai-builder-config'],
639 AIBUI_VERSION,
640 true
641 );
642 // Injection des variables JS pour AJAX et le nonce
643 wp_localize_script(
644 'ai-builder-account',
645 'aiBuilderVars',
646 array(
647 'ajaxurl' => admin_url('admin-ajax.php'),
648 'nonce' => wp_create_nonce('aibui_nonce'),
649 'accountUrl' => admin_url('admin.php?page=aibui-account'),
650 'siteDomain' => parse_url(home_url(), PHP_URL_HOST),
651 'installationId' => aibui_get_installation_id(),
652 )
653 );
654 } else if ($current_screen && strpos($current_screen->id, 'aibui-credits') !== false) {
655 wp_enqueue_script(
656 'ai-builder-credits',
657 plugin_dir_url(__FILE__) . 'assets/js/credits.js',
658 ['ai-builder-config'],
659 AIBUI_VERSION,
660 true
661 );
662 // Injection des variables JS pour AJAX et le nonce
663 wp_localize_script(
664 'ai-builder-credits',
665 'aiBuilderVars',
666 array(
667 'ajaxurl' => admin_url('admin-ajax.php'),
668 'nonce' => wp_create_nonce('aibui_nonce'),
669 )
670 );
671 wp_enqueue_style(
672 'ai-builder-credits-additional-style',
673 plugin_dir_url(__FILE__) . 'assets/css/credits-additional.css',
674 [],
675 AIBUI_VERSION
676 );
677 wp_enqueue_style(
678 'ai-builder-credits-style',
679 plugin_dir_url(__FILE__) . 'assets/css/credits.css',
680 [],
681 AIBUI_VERSION
682 );
683 wp_enqueue_script(
684 'ai-builder-config',
685 plugin_dir_url(__FILE__) . 'config.js',
686 [],
687 AIBUI_VERSION,
688 true
689 );
690 } else if ($current_screen && strpos($current_screen->id, 'aibui-settings') !== false) {
691 wp_enqueue_style(
692 'ai-builder-settings-style',
693 plugin_dir_url(__FILE__) . 'assets/css/settings.css',
694 [],
695 AIBUI_VERSION
696 );
697 wp_enqueue_script(
698 'ai-builder-config',
699 plugin_dir_url(__FILE__) . 'config.js',
700 [],
701 AIBUI_VERSION,
702 true
703 );
704 wp_enqueue_script(
705 'ai-builder-settings',
706 plugin_dir_url(__FILE__) . 'assets/js/settings.js',
707 ['ai-builder-config'],
708 AIBUI_VERSION,
709 true
710 );
711 // Injection des variables JS pour AJAX et le nonce
712 wp_localize_script(
713 'ai-builder-settings',
714 'aiBuilderVars',
715 array(
716 'ajaxurl' => admin_url('admin-ajax.php'),
717 'nonce' => wp_create_nonce('aibui_nonce'),
718 )
719 );
720 } else if ($current_screen && strpos($current_screen->id, 'aibui-reset-password') !== false) {
721 wp_enqueue_style(
722 'ai-builder-reset-password-style',
723 plugin_dir_url(__FILE__) . 'assets/css/reset-password.css',
724 [],
725 AIBUI_VERSION
726 );
727 wp_enqueue_script(
728 'ai-builder-config',
729 plugin_dir_url(__FILE__) . 'config.js',
730 [],
731 AIBUI_VERSION,
732 true
733 );
734 wp_enqueue_script(
735 'ai-builder-reset-password',
736 plugin_dir_url(__FILE__) . 'assets/js/reset-password.js',
737 ['ai-builder-config'],
738 AIBUI_VERSION,
739 true
740 );
741 // Injection des variables JS pour AJAX et le nonce
742 wp_localize_script(
743 'ai-builder-reset-password',
744 'aiBuilderVars',
745 array(
746 'ajaxurl' => admin_url('admin-ajax.php'),
747 'nonce' => wp_create_nonce('aibui_nonce'),
748 'accountUrl' => admin_url('admin.php?page=aibui-account'),
749 )
750 );
751 } else if ($current_screen && strpos($current_screen->id, 'aibui-tuto') !== false) {
752 wp_enqueue_style(
753 'ai-builder-tutorial-style',
754 plugin_dir_url(__FILE__) . 'assets/css/tutorial.css',
755 [],
756 AIBUI_VERSION
757 );
758 wp_enqueue_script(
759 'ai-builder-config',
760 plugin_dir_url(__FILE__) . 'config.js',
761 [],
762 AIBUI_VERSION,
763 true
764 );
765 } else if ($current_screen && strpos($current_screen->id, 'aibui-multi-page') !== false) {
766 wp_enqueue_style(
767 'ai-builder-multi-page-style',
768 plugin_dir_url(__FILE__) . 'assets/css/multi-page.css',
769 [],
770 AIBUI_VERSION
771 );
772 wp_enqueue_script(
773 'ai-builder-config',
774 plugin_dir_url(__FILE__) . 'config.js',
775 [],
776 AIBUI_VERSION,
777 true
778 );
779 wp_enqueue_script(
780 'ai-builder-multi-page',
781 plugin_dir_url(__FILE__) . 'assets/js/multi-page.js',
782 ['ai-builder-config'],
783 AIBUI_VERSION,
784 true
785 );
786 // Injection des variables JS pour AJAX et le nonce
787 wp_localize_script(
788 'ai-builder-multi-page',
789 'aiBuilderVars',
790 array(
791 'ajaxurl' => admin_url('admin-ajax.php'),
792 'nonce' => wp_create_nonce('aibui_nonce'),
793 'adminBaseUrl' => admin_url(),
794 'wooCommerceInstalled' => aibui_is_woocommerce_installed(),
795 // Active theme name
796 'activeThemeName' => aibui_get_active_theme_name(),
797 // Active plugins context (max 15)
798 'sitePlugins' => aibui_get_active_plugins_context(),
799 // Current WordPress version
800 'wordpressVersion' => aibui_get_wordpress_version(),
801 )
802 );
803 } else if ($current_screen && strpos($current_screen->id, 'aibui-agent-chat') !== false) {
804 // Agent Chat page scripts and styles
805 wp_enqueue_script(
806 'ai-builder-config',
807 plugin_dir_url(__FILE__) . 'config.js',
808 [],
809 AIBUI_VERSION,
810 true
811 );
812 wp_enqueue_script(
813 'ai-builder-agent-chat',
814 plugin_dir_url(__FILE__) . 'assets/js/agent-chat.js',
815 ['ai-builder-config'],
816 AIBUI_VERSION,
817 true
818 );
819 // Localize with nonce - IMPORTANT: use a different nonce for agent actions
820 wp_localize_script(
821 'ai-builder-agent-chat',
822 'aibuiAgentVars',
823 array(
824 'ajaxurl' => admin_url('admin-ajax.php'),
825 'nonce' => wp_create_nonce('aibui_agent_nonce'),
826 'restBase' => esc_url_raw(rest_url()),
827 'wpRestDocsBase' => 'https://developer.wordpress.org/rest-api/reference/',
828 )
829 );
830 // Also expose standard AJAX nonce for shared endpoints like aibui_get_token
831 wp_localize_script(
832 'ai-builder-agent-chat',
833 'aiBuilderVars',
834 array(
835 'ajaxurl' => admin_url('admin-ajax.php'),
836 'nonce' => wp_create_nonce('aibui_nonce'),
837 )
838 );
839 }
840
841 // Bandeau de review : uniquement sur les pages admin du plugin
842 if ($current_screen) {
843 $screen_id = $current_screen->id;
844 $is_plugin_screen =
845 strpos($screen_id, 'aibui-assistant') !== false ||
846 strpos($screen_id, 'aibui-credits') !== false ||
847 strpos($screen_id, 'aibui-tuto') !== false ||
848 strpos($screen_id, 'aibui-multi-page') !== false ||
849 strpos($screen_id, 'aibui-agent-chat') !== false ||
850 strpos($screen_id, 'aibui-translation-settings') !== false ||
851 strpos($screen_id, 'aibui-headers-footers') !== false ||
852 strpos($screen_id, 'aibui-settings') !== false;
853
854 if ($is_plugin_screen) {
855 // S'assurer que config.js est chargé
856 wp_enqueue_script(
857 'ai-builder-config',
858 plugin_dir_url(__FILE__) . 'config.js',
859 [],
860 AIBUI_VERSION,
861 true
862 );
863 wp_enqueue_script(
864 'ai-builder-review-banner',
865 plugin_dir_url(__FILE__) . 'assets/js/review-banner.js',
866 ['ai-builder-config'],
867 AIBUI_VERSION,
868 true
869 );
870 wp_localize_script(
871 'ai-builder-review-banner',
872 'aiBuilderReviewVars',
873 array(
874 'ajaxurl' => admin_url('admin-ajax.php'),
875 'nonce' => wp_create_nonce('aibui_nonce'),
876 'reviewUrl' => 'https://wordpress.org/support/plugin/ai-builder/reviews/#new-post',
877 )
878 );
879 }
880 }
881
882 });
883
884 add_action('wp_enqueue_scripts', function () {
885 // Single combined frontend CSS
886 $combined_css = aibui_get_combined_css_url();
887 if ($combined_css) {
888 wp_enqueue_style(
889 'ai-builder-combined',
890 $combined_css,
891 [],
892 null
893 );
894 } else {
895 // Fallback: enqueue at least the essential stylesheet
896 wp_enqueue_style(
897 'aibui-force-alignfull',
898 plugin_dir_url(__FILE__) . 'assets/css/force-align-full.css',
899 [],
900 AIBUI_VERSION
901 );
902 }
903
904 // Charger les styles CSS du bloc AI Image côté frontend
905 wp_enqueue_style(
906 'ai-builder-ai-image-frontend',
907 plugin_dir_url(__FILE__) . 'assets/js/build/index.css',
908 [],
909 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.css')
910 );
911 // Frontend JS single bundle
912 $combined_js = aibui_get_combined_js_url();
913 if ($combined_js) {
914 wp_enqueue_script(
915 'ai-builder-frontend-bundle',
916 $combined_js,
917 [],
918 null,
919 true
920 );
921 } else {
922 // Fallback: at least enqueue carousel script if bundling failed
923 wp_enqueue_script(
924 'ai-builder-carousel-frontend',
925 plugin_dir_url(__FILE__) . 'assets/js/carousel-frontend.js',
926 [],
927 AIBUI_VERSION,
928 true
929 );
930 }
931
932 // Load fixed background group block CSS for frontend
933 wp_enqueue_style(
934 'ai-builder-fixed-bg-group-frontend',
935 plugin_dir_url(__FILE__) . 'assets/js/src/editor-blocks/fixed-bg-group/style.css',
936 [],
937 AIBUI_VERSION
938 );
939
940 // CSS above is covered by the combined bundle
941
942 // Other JS are included in the combined bundle above
943 // CSS above is covered by the combined bundle
944 // Included in combined bundle
945 // CSS above is covered by the combined bundle
946 // Included in combined bundle
947 });
948
949 // Enqueue contact form script only when the block is present
950 add_action('wp_enqueue_scripts', function () {
951 global $post;
952 $should_load = false;
953
954 // Check if current post has the block
955 if (is_a($post, 'WP_Post') && has_block('ai-builder/aibui-contact-form', $post)) {
956 $should_load = true;
957 }
958
959 // Fallback: check if we're on a page that might have the block
960 if (!$should_load && (is_page() || is_single() || is_home() || is_front_page())) {
961 $should_load = true;
962 }
963
964 if ($should_load) {
965 wp_enqueue_script(
966 'ai-builder-contact-form',
967 plugin_dir_url(__FILE__) . 'assets/js/contact-form.js',
968 [],
969 AIBUI_VERSION,
970 true
971 );
972 }
973 });
974
975 add_action('enqueue_block_editor_assets', function () {
976 // Single combined CSS for editor
977 $combined_css = aibui_get_combined_css_url();
978 if ($combined_css) {
979 wp_enqueue_style(
980 'ai-builder-combined-editor',
981 $combined_css,
982 [],
983 null
984 );
985 } else {
986 wp_enqueue_style(
987 'aibui-force-alignfull-editor',
988 plugin_dir_url(__FILE__) . 'assets/css/force-align-full.css',
989 [],
990 AIBUI_VERSION
991 );
992 }
993 // Charger les styles build des blocs dans l'éditeur
994 wp_enqueue_style(
995 'ai-builder-blocks-editor-build',
996 plugin_dir_url(__FILE__) . 'assets/js/build/index.css',
997 [],
998 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.css')
999 );
1000 // Styles spécifiques d'aperçu du formulaire de contact
1001 wp_enqueue_style(
1002 'ai-builder-contact-form-editor',
1003 plugin_dir_url(__FILE__) . 'assets/css/contact-form-editor.css',
1004 [],
1005 AIBUI_VERSION
1006 );
1007 // Covered by combined bundle
1008 // Stats tooltips in editor preview
1009 wp_enqueue_script(
1010 'ai-builder-stats-tooltips-editor',
1011 plugin_dir_url(__FILE__) . 'assets/js/stats-tooltips.js',
1012 [],
1013 AIBUI_VERSION,
1014 true
1015 );
1016 });
1017
1018 add_action('enqueue_block_editor_assets', function () {
1019 wp_enqueue_script(
1020 'ai-builder-blocks',
1021 plugin_dir_url(__FILE__) . 'assets/js/build/index.js',
1022 [
1023 'wp-blocks',
1024 'wp-block-editor',
1025 'wp-element',
1026 'wp-components',
1027 'wp-i18n',
1028 'wp-api-fetch',
1029 'wp-hooks'
1030 ],
1031 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.js'),
1032 true
1033 );
1034
1035 // Injection des variables JS pour AJAX et le nonce (utilisées par les blocs éditeur)
1036 wp_localize_script(
1037 'ai-builder-blocks',
1038 'aiBuilderVars',
1039 array(
1040 'ajaxurl' => admin_url('admin-ajax.php'),
1041 'nonce' => wp_create_nonce('aibui_nonce'),
1042 )
1043 );
1044
1045 wp_enqueue_script(
1046 'ai-builder-language-switcher-block',
1047 plugin_dir_url(__FILE__) . 'assets/js/language-switcher-block.js',
1048 array('wp-blocks', 'wp-block-editor', 'wp-element', 'wp-components', 'wp-i18n'),
1049 filemtime(plugin_dir_path(__FILE__) . 'assets/js/language-switcher-block.js'),
1050 true
1051 );
1052
1053 // Unregister legacy/unused AI Builder blocks from the inserter
1054 wp_enqueue_script(
1055 'ai-builder-unregister-ai-blocks',
1056 plugin_dir_url(__FILE__) . 'assets/js/unregister-ai-blocks.js',
1057 array('ai-builder-blocks', 'wp-blocks'),
1058 AIBUI_VERSION,
1059 true
1060 );
1061
1062 // CSS Class Inspector: button in block sidebar to jump to CSS
1063 wp_enqueue_script(
1064 'ai-builder-css-class-inspector',
1065 plugin_dir_url(__FILE__) . 'assets/js/css-class-inspector.js',
1066 array('wp-hooks', 'wp-compose', 'wp-block-editor', 'wp-components', 'wp-element', 'wp-i18n'),
1067 AIBUI_VERSION,
1068 true
1069 );
1070
1071 $translation_settings = AIBUI_Translation_Settings::get_settings();
1072 $supported_languages = AIBUI_Translation_Handler::get_supported_languages();
1073 $available_langs = isset($translation_settings['available_langs']) && is_array($translation_settings['available_langs'])
1074 ? array_values(array_unique($translation_settings['available_langs']))
1075 : array();
1076 $default_lang = isset($translation_settings['default_lang']) ? $translation_settings['default_lang'] : 'en';
1077 if ($default_lang && !in_array($default_lang, $available_langs, true)) {
1078 array_unshift($available_langs, $default_lang);
1079 }
1080 $switcher_defaults = array(
1081 'backgroundColor' => !empty($translation_settings['switcher_bg']) ? $translation_settings['switcher_bg'] : '',
1082 'textColor' => !empty($translation_settings['switcher_text']) ? $translation_settings['switcher_text'] : '',
1083 'minWidth' => !empty($translation_settings['switcher_min_width']) ? (int) $translation_settings['switcher_min_width'] : 90,
1084 'minHeight' => !empty($translation_settings['switcher_min_height']) ? (int) $translation_settings['switcher_min_height'] : 30,
1085 'borderRadius' => 22,
1086 );
1087
1088 wp_localize_script(
1089 'ai-builder-language-switcher-block',
1090 'aiBuilderLangSwitch',
1091 array(
1092 'availableLangs' => $available_langs,
1093 'defaultLang' => $default_lang,
1094 'labels' => $supported_languages,
1095 'switcherEnabled' => !empty($translation_settings['enable_switcher']),
1096 'defaults' => $switcher_defaults,
1097 )
1098 );
1099 });
1100
1101 // Enregistrer le bloc côté PHP
1102 add_action('init', function () use ($translation_switcher) {
1103 wp_register_style(
1104 'ai-builder-language-switcher-style',
1105 plugin_dir_url(__FILE__) . 'assets/css/language-switcher.css',
1106 array(),
1107 AIBUI_VERSION
1108 );
1109 // Bloc AI Block
1110 register_block_type('ai-builder/ai-block', [
1111 'editor_script' => 'ai-builder-blocks',
1112 'editor_style' => 'ai-builder-blocks-style',
1113 'style' => 'ai-builder-blocks-style',
1114 'render_callback' => function ($attributes) {
1115 // Le bloc AI Block ne s'affiche pas côté frontend car il est remplacé par le contenu généré
1116 return '';
1117 }
1118 ]);
1119
1120 // Bloc stat-bar (statique, pas de render_callback)
1121 register_block_type('ai-builder/stat-bar', [
1122 'editor_script' => 'ai-builder-blocks',
1123 'editor_style' => 'ai-builder-blocks-style',
1124 'style' => 'ai-builder-blocks-style',
1125 ]);
1126
1127 // Bloc aibui-stats (rendu simple côté éditeur, frontend statique)
1128 register_block_type('ai-builder/aibui-stats', [
1129 'editor_script' => 'ai-builder-blocks',
1130 'editor_style' => 'ai-builder-blocks-style',
1131 'style' => 'ai-builder-blocks-style',
1132 ]);
1133
1134 // Bloc aibui-carousel
1135 register_block_type('ai-builder/aibui-carousel', [
1136 'editor_script' => 'ai-builder-blocks',
1137 'editor_style' => 'ai-builder-blocks-style',
1138 'style' => 'ai-builder-blocks-style',
1139 ]);
1140
1141 // Bloc aibui-yt-video
1142 register_block_type('ai-builder/aibui-yt-video', [
1143 'editor_script' => 'ai-builder-blocks',
1144 'editor_style' => 'ai-builder-blocks-style',
1145 'style' => 'ai-builder-blocks-style',
1146 ]);
1147
1148 // Bloc aibui-map
1149 register_block_type('ai-builder/aibui-map', [
1150 'editor_script' => 'ai-builder-blocks',
1151 'editor_style' => 'ai-builder-blocks-style',
1152 'style' => 'ai-builder-blocks-style',
1153 ]);
1154
1155 // Bloc aibui-tabs
1156 // Bloc aibui-table
1157 register_block_type('ai-builder/aibui-table', [
1158 'editor_script' => 'ai-builder-blocks',
1159 'editor_style' => 'ai-builder-blocks-style',
1160 'style' => 'ai-builder-blocks-style',
1161 ]);
1162 register_block_type('ai-builder/aibui-tabs', [
1163 'editor_script' => 'ai-builder-blocks',
1164 'editor_style' => 'ai-builder-blocks-style',
1165 'style' => 'ai-builder-blocks-style',
1166 ]);
1167
1168 // Bloc aibui-contact-form (rendu via callback PHP pour sécurité)
1169 register_block_type('ai-builder/aibui-contact-form', [
1170 'editor_script' => 'ai-builder-blocks',
1171 'editor_style' => 'ai-builder-blocks-style',
1172 'style' => 'ai-builder-blocks-style',
1173 'render_callback' => function ($attributes, $content, $block) {
1174 $recipient = isset($attributes['recipientEmail']) ? sanitize_email($attributes['recipientEmail']) : '';
1175 if (empty($recipient) || !is_email($recipient)) {
1176 $recipient = sanitize_email(get_option('admin_email'));
1177 }
1178 if (empty($recipient) || !is_email($recipient)) {
1179 // Pas de destinataire valide disponible, n'arrête pas l'affichage, mais désactive l'envoi
1180 $recipient = '';
1181 }
1182
1183 $fields = isset($attributes['fields']) && is_array($attributes['fields']) ? $attributes['fields'] : [];
1184 $fields = array_slice($fields, 0, 5);
1185
1186 $form_title = isset($attributes['formTitle']) ? wp_kses_post($attributes['formTitle']) : '';
1187 $description = isset($attributes['description']) ? wp_kses_post($attributes['description']) : '';
1188 $button_label = isset($attributes['buttonLabel']) ? sanitize_text_field($attributes['buttonLabel']) : __('Send', 'ai-builder');
1189 $success_message = isset($attributes['successMessage']) ? sanitize_text_field($attributes['successMessage']) : __('Thank you, your message has been sent.', 'ai-builder');
1190 $from_email_attr = isset($attributes['fromEmail']) ? sanitize_email($attributes['fromEmail']) : '';
1191
1192 $nonce = wp_create_nonce('aibui_contact_form');
1193 $action = esc_url(admin_url('admin-ajax.php'));
1194
1195 ob_start();
1196 ?>
1197 <form class="aibui-contact-form aibui-contact-form--frontend" method="post" action="<?php echo $action; ?>"
1198 data-endpoint="<?php echo $action; ?>">
1199 <?php if ($form_title): ?>
1200 <h3 class="aibui-form-title"><?php echo $form_title; ?></h3>
1201 <?php endif; ?>
1202 <?php if ($description): ?>
1203 <p class="aibui-form-description"><?php echo $description; ?></p>
1204 <?php endif; ?>
1205 <input type="hidden" name="action" value="aibui_submit_contact_form" />
1206 <input type="hidden" name="nonce" value="<?php echo esc_attr($nonce); ?>" />
1207 <input type="hidden" name="recipient" value="<?php echo esc_attr($recipient); ?>" />
1208 <?php if ($from_email_attr): ?>
1209 <input type="hidden" name="from_email" value="<?php echo esc_attr($from_email_attr); ?>" />
1210 <?php endif; ?>
1211 <?php foreach ($fields as $idx => $field):
1212 $label = isset($field['label']) ? sanitize_text_field($field['label']) : '';
1213 $type = isset($field['type']) ? sanitize_text_field($field['type']) : 'text';
1214 $required = !empty($field['required']);
1215 $name = 'field_' . $idx;
1216 ?>
1217 <div class="aibui-field">
1218 <label>
1219 <?php echo esc_html($label); ?> <?php echo $required ? ' *' : ''; ?>
1220 <?php if ($type === 'textarea'): ?>
1221 <textarea name="<?php echo esc_attr($name); ?>" <?php echo $required ? 'required' : ''; ?>
1222 aria-label="<?php echo esc_attr($label); ?>"></textarea>
1223 <?php else: ?>
1224 <input
1225 type="<?php echo $type === 'email' ? 'email' : ($type === 'number' ? 'number' : ($type === 'date' ? 'date' : 'text')); ?>"
1226 name="<?php echo esc_attr($name); ?>" <?php echo $required ? 'required' : ''; ?>
1227 aria-label="<?php echo esc_attr($label); ?>" />
1228 <?php endif; ?>
1229 <input type="hidden" name="label_<?php echo esc_attr($name); ?>" value="<?php echo esc_attr($label); ?>" />
1230 <input type="hidden" name="type_<?php echo esc_attr($name); ?>" value="<?php echo esc_attr($type); ?>" />
1231 <input type="hidden" name="required_<?php echo esc_attr($name); ?>"
1232 value="<?php echo $required ? '1' : '0'; ?>" />
1233 </label>
1234 </div>
1235 <?php endforeach; ?>
1236 <div style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;">
1237 <label>Leave this field empty<input type="text" name="website" tabindex="-1" autocomplete="off" /></label>
1238 </div>
1239 <div class="aibui-form-message" role="status" aria-live="polite"></div>
1240 <input type="hidden" name="success_message" value="<?php echo esc_attr($success_message); ?>" />
1241 <button type="submit" class="aibui-button aibui-button--primary"><?php echo esc_html($button_label); ?></button>
1242 </form>
1243 <?php
1244 return ob_get_clean();
1245 }
1246 ]);
1247
1248 register_block_type('ai-builder/language-switcher', [
1249 'editor_script' => 'ai-builder-language-switcher-block',
1250 'editor_style' => 'ai-builder-language-switcher-style',
1251 'style' => 'ai-builder-language-switcher-style',
1252 'render_callback' => array($translation_switcher, 'render_block'),
1253 'attributes' => [
1254 'backgroundColor' => ['type' => 'string', 'default' => '#5686c9'],
1255 'textColor' => ['type' => 'string', 'default' => '#ffffff'],
1256 'minWidth' => ['type' => 'number', 'default' => 90],
1257 'minHeight' => ['type' => 'number', 'default' => 30],
1258 'borderRadius' => ['type' => 'number', 'default' => 22],
1259 ],
1260 'supports' => [
1261 'align' => ['left', 'center', 'right', 'wide', 'full'],
1262 ],
1263 ]);
1264 });
1265
1266 add_action('after_setup_theme', function () {
1267 add_theme_support('align-wide');
1268 });
1269
1270 function enqueue_ai_builder_scripts() {
1271 // Charger config.js en premier (dépendance pour les autres scripts)
1272 wp_enqueue_script(
1273 'ai-builder-config',
1274 plugins_url('config.js', __FILE__),
1275 array(),
1276 AIBUI_VERSION,
1277 false // Charger dans le <head> pour être disponible partout
1278 );
1279
1280 // Autres scripts qui utilisent config.js
1281 wp_enqueue_script(
1282 'ai-builder-image-ai-controls',
1283 plugins_url('assets/js/src/editor-blocks/image-ai-blocks/image-ai-controls.js', __FILE__),
1284 array('ai-builder-config'),
1285 AIBUI_VERSION,
1286 true
1287 );
1288
1289 // Autres scripts qui utilisent config.js
1290 wp_enqueue_script(
1291 'ai-builder-text-ai-controls',
1292 plugins_url('assets/js/src/editor-blocks/text-ai-blocks/text-ai-controls.js', __FILE__),
1293 array('ai-builder-config'),
1294 AIBUI_VERSION,
1295 true
1296 );
1297
1298 // Autres scripts qui utilisent config.js
1299 wp_enqueue_script(
1300 'ai-builder-text-ai-controls',
1301 plugins_url('assets/js/src/editor-blocks/text-ai-blocks/text-ai-controls.js', __FILE__),
1302 array('ai-builder-config'),
1303 AIBUI_VERSION,
1304 true
1305 );
1306
1307 // Autres scripts qui utilisent config.js
1308 wp_enqueue_script(
1309 'ai-builder-ai-block',
1310 plugins_url('assets/js/src/editor-blocks/ai-block/ai-block.js', __FILE__),
1311 array('ai-builder-config'),
1312 AIBUI_VERSION,
1313 true
1314 );
1315
1316 // Localize WooCommerce detection for block editor scripts
1317 wp_localize_script(
1318 'ai-builder-config',
1319 'aiBuilderEditorVars',
1320 array(
1321 'wooCommerceInstalled' => aibui_is_woocommerce_installed(),
1322 // Active theme name
1323 'activeThemeName' => aibui_get_active_theme_name(),
1324 )
1325 );
1326 }
1327 add_action('enqueue_block_editor_assets', 'enqueue_ai_builder_scripts');
1328
1329 add_action('wp_enqueue_scripts', function () {
1330 // Combined CSS already enqueued above; only enqueue any extra block scss-generated styles if needed
1331 // Table, snackbar, cards editor styles are not required on frontend separately when bundled
1332
1333 // Load snackbar frontend JavaScript
1334 wp_enqueue_script(
1335 'ai-builder-snackbar-frontend',
1336 plugin_dir_url(__FILE__) . 'assets/js/snackbar-frontend.js',
1337 [],
1338 AIBUI_VERSION,
1339 true
1340 );
1341 });
1342
1343 add_action('enqueue_block_editor_assets', function () {
1344 // Combined CSS already enqueued above; keep editor-specific assets below
1345 // Load fixed background group block CSS specifically (already inside combined, but keep safe fallback if combined failed)
1346 $fixed_bg_css_url = plugin_dir_url(__FILE__) . 'assets/js/src/editor-blocks/fixed-bg-group/style.css';
1347 wp_enqueue_style('ai-builder-fixed-bg-group-editor', $fixed_bg_css_url, [], AIBUI_VERSION);
1348
1349 // Load WordPress media scripts for image selection
1350 wp_enqueue_media();
1351 });
1352
1353 // -------------------------------
1354 // Meta description per page/post
1355 // -------------------------------
1356 add_action('add_meta_boxes', function () {
1357 add_meta_box(
1358 'aibui_meta_description',
1359 __('Meta description', 'ai-builder'),
1360 function ($post) {
1361 $value = get_post_meta($post->ID, 'aibui_meta_description', true);
1362 wp_nonce_field('aibui_meta_description_save', 'aibui_meta_description_nonce');
1363 echo '<p>' . esc_html__('Add a meta description for this page/post.', 'ai-builder') . '</p>';
1364 echo '<textarea style="width:100%;min-height:90px;" id="aibui_meta_description_field" name="aibui_meta_description" maxlength="320">' . esc_textarea($value) . '</textarea>';
1365 echo '<p style="margin-top:6px;color:#555;">' . esc_html__('Tip: 140–160 characters for best SEO results.', 'ai-builder') . '</p>';
1366 },
1367 array('post', 'page'),
1368 'normal',
1369 'default'
1370 );
1371 });
1372
1373 add_action('save_post', function ($post_id) {
1374 if (!isset($_POST['aibui_meta_description_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['aibui_meta_description_nonce'])), 'aibui_meta_description_save')) {
1375 return;
1376 }
1377 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
1378 if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) return;
1379 if (!current_user_can('edit_post', $post_id)) return;
1380
1381 $raw = isset($_POST['aibui_meta_description']) ? wp_unslash($_POST['aibui_meta_description']) : '';
1382 $san = trim(wp_strip_all_tags($raw));
1383 if (strlen($san) > 320) {
1384 $san = mb_substr($san, 0, 320);
1385 }
1386 if ($san === '') {
1387 delete_post_meta($post_id, 'aibui_meta_description');
1388 } else {
1389 update_post_meta($post_id, 'aibui_meta_description', $san);
1390 }
1391 });
1392
1393 add_action('wp_head', function () {
1394 if (is_admin() || !is_singular()) {
1395 return;
1396 }
1397
1398 $post_id = get_queried_object_id();
1399 if (!$post_id) {
1400 return;
1401 }
1402
1403 $possible_keys = array(
1404 'aibui_meta_description',
1405 '_ai_builder_seo_desc',
1406 '_yoast_wpseo_metadesc',
1407 '_ai_translation_meta_desc'
1408 );
1409
1410 $desc = '';
1411 foreach ($possible_keys as $meta_key) {
1412 $value = get_post_meta($post_id, $meta_key, true);
1413 if (!empty($value)) {
1414 $desc = $value;
1415 break;
1416 }
1417 }
1418
1419 if (!$desc) {
1420 $excerpt = get_post_field('post_excerpt', $post_id);
1421 if (!empty($excerpt)) {
1422 $desc = $excerpt;
1423 }
1424 }
1425
1426 $desc = trim(wp_strip_all_tags((string) $desc));
1427 if ($desc !== '') {
1428 echo "\n<meta name=\"description\" content=\"" . esc_attr($desc) . "\" />\n";
1429 }
1430 }, 1);
1431
1432 // Initialiser le gestionnaire AJAX
1433 new AIBUI_Ajax_Handler();
1434
1435 // AJAX: create a header or footer template part for the active block theme
1436 add_action('wp_ajax_aibui_create_template_part', function () {
1437 check_ajax_referer('aibui_nonce', 'nonce');
1438
1439 if (!current_user_can('edit_theme_options')) {
1440 wp_send_json_error('Permission denied');
1441 }
1442
1443 $area = sanitize_text_field($_POST['area'] ?? '');
1444 if (!in_array($area, array('header', 'footer'), true)) {
1445 wp_send_json_error('Invalid area');
1446 }
1447
1448 $theme_slug = get_stylesheet();
1449 $title = $area === 'header' ? 'Header' : 'Footer';
1450
1451 $post_id = wp_insert_post(array(
1452 'post_title' => $title,
1453 'post_name' => $area,
1454 'post_content' => '',
1455 'post_status' => 'publish',
1456 'post_type' => 'wp_template_part',
1457 ));
1458
1459 if (is_wp_error($post_id)) {
1460 wp_send_json_error($post_id->get_error_message());
1461 }
1462
1463 wp_set_object_terms($post_id, $area, 'wp_template_part_area');
1464 wp_set_object_terms($post_id, $theme_slug, 'wp_theme');
1465
1466 $edit_url = admin_url(
1467 'site-editor.php?postType=wp_template_part&postId='
1468 . urlencode($theme_slug . '//' . $area)
1469 . '&canvas=edit'
1470 );
1471
1472 wp_send_json_success(array('edit_url' => $edit_url));
1473 });
1474
1475
1476 // -------------------------------
1477 // Multi-Page Generator: Cleanup cron and migration
1478 // -------------------------------
1479 function aibui_cleanup_old_generations() {
1480 require_once plugin_dir_path(__FILE__) . 'includes/class-generations-storage.php';
1481 $storage = new AIBUI_Generations_Storage();
1482 $deleted_count = $storage->cleanup_old(30); // Delete applied generations older than 30 days
1483
1484
1485 }
1486 add_action('aibui_daily_cleanup', 'aibui_cleanup_old_generations');
1487
1488 // Schedule daily cleanup if not already scheduled
1489 if (!wp_next_scheduled('aibui_daily_cleanup')) {
1490 wp_schedule_event(time(), 'daily', 'aibui_daily_cleanup');
1491 }
1492
1493 // Migrate old wp_options data to files (one-time migration on activation/update)
1494 function aibui_migrate_generations_to_files() {
1495 // Check if migration already done
1496 if (get_option('aibui_generations_migrated_to_files', false)) {
1497 return;
1498 }
1499
1500 require_once plugin_dir_path(__FILE__) . 'includes/class-generations-storage.php';
1501 $storage = new AIBUI_Generations_Storage();
1502 $migrated_count = $storage->migrate_from_options();
1503
1504 if ($migrated_count > 0) {
1505 // Mark migration as done
1506 update_option('aibui_generations_migrated_to_files', true, false);
1507
1508
1509 }
1510 }
1511 // Run migration on admin init (only once)
1512 add_action('admin_init', function() {
1513 static $migration_done = false;
1514 if (!$migration_done && current_user_can('manage_options')) {
1515 aibui_migrate_generations_to_files();
1516 $migration_done = true;
1517 }
1518 }, 5);
1519