PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.7.2
AI Builder – Generate pages, blocks, images & translate with AI v2.7.2
2.7.9 2.7.8 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.3.10 2.3.11 All 121 releases
ai-builder / aibui-builder.php

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

1,553 lines 56.0 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.2
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.2');
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 // DEBUG heartbeats (à retirer) pour détecter un éventuel fatal silencieux
502 // lors de l'inclusion du service des templates.
503 @file_put_contents(
504 plugin_dir_path(__FILE__) . 'aibui-debug.log',
505 '[' . date('Y-m-d H:i:s') . '] aibui-builder.php REACHED line ' . __LINE__ . "\n",
506 FILE_APPEND
507 );
508
509 add_action('wp_head', function () {
510 echo "\n<!-- ai-builder heartbeat :: BEFORE_TEMPLATE_ASSETS_REQUIRE -->\n";
511 }, 1);
512
513 // Charger le service de gestion du CSS/JS des templates et template parts.
514 // IMPORTANT : doit être chargé AVANT class-ajax-handler.php car celui-ci y fait
515 // appel pour router les sauvegardes provenant du Site Editor.
516 $aibui_template_assets_file = plugin_dir_path(__FILE__) . 'includes/class-template-assets.php';
517 if (function_exists('opcache_invalidate')) {
518 @opcache_invalidate($aibui_template_assets_file, true);
519 }
520 @clearstatcache(true, $aibui_template_assets_file);
521 require_once $aibui_template_assets_file;
522
523 @file_put_contents(
524 plugin_dir_path(__FILE__) . 'aibui-debug.log',
525 '[' . date('Y-m-d H:i:s') . '] after require class_exists=' . (class_exists('AIBUI_Template_Assets') ? '1' : '0') . "\n",
526 FILE_APPEND
527 );
528
529 add_action('wp_head', function () {
530 $class_exists = class_exists('AIBUI_Template_Assets') ? 1 : 0;
531 $file_exists = file_exists(plugin_dir_path(__FILE__) . 'includes/class-template-assets.php') ? 1 : 0;
532 echo "\n<!-- ai-builder heartbeat :: AFTER_TEMPLATE_ASSETS_REQUIRE class_exists=" . $class_exists . " file_exists=" . $file_exists . " -->\n";
533 }, 1);
534
535 // Charger le gestionnaire AJAX
536 require_once plugin_dir_path(__FILE__) . 'includes/class-ajax-handler.php';
537
538 // Charger le gestionnaire CSS
539 require_once plugin_dir_path(__FILE__) . 'includes/class-css-handler.php';
540
541 // Charger le gestionnaire JS
542 require_once plugin_dir_path(__FILE__) . 'includes/class-js-handler.php';
543
544 // Charger le gestionnaire de traduction
545 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-handler.php';
546 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-settings.php';
547 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-manager.php';
548 require_once plugin_dir_path(__FILE__) . 'includes/class-translation-switcher.php';
549
550 // Charger les services de l'Agent Chat
551 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-discovery-service.php';
552 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-security-service.php';
553 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-execution-service.php';
554 require_once plugin_dir_path(__FILE__) . 'includes/class-agent-chat-handler.php';
555
556 // Initialiser le gestionnaire de traduction
557 new AIBUI_Translation_Handler();
558 AIBUI_Translation_Settings::init();
559 $translation_manager = new AIBUI_Translation_Manager();
560 $translation_switcher = new AIBUI_Translation_Switcher($translation_manager);
561
562 // Initialiser le gestionnaire de l'Agent Chat
563 new AIBUI_Agent_Chat_Handler();
564
565 add_action('admin_enqueue_scripts', function ($hook) {
566 // Charger le CSS admin sur toutes les pages d'administration
567 wp_enqueue_style(
568 'ai-builder-admin-style',
569 plugin_dir_url(__FILE__) . 'assets/css/style-admin.css',
570 [],
571 AIBUI_VERSION
572 );
573
574 // Charger sur l'éditeur de page/article et l'éditeur de modèles (site editor)
575 if ($hook === 'post.php' || $hook === 'post-new.php' || $hook === 'site-editor.php') {
576 wp_enqueue_style(
577 'chat-widget-style',
578 plugin_dir_url(__FILE__) . 'assets/css/chat-widget.css',
579 [],
580 AIBUI_VERSION
581 );
582 wp_enqueue_script(
583 'ai-builder-config',
584 plugin_dir_url(__FILE__) . 'config.js',
585 [],
586 AIBUI_VERSION,
587 true
588 );
589 wp_enqueue_script(
590 'chat-widget',
591 plugin_dir_url(__FILE__) . 'assets/js/chat-widget.js',
592 ['ai-builder-config'],
593 AIBUI_VERSION,
594 true
595 );
596 // Styles tabs pour s'assurer du chargement dans l'éditeur
597 wp_enqueue_style(
598 'ai-builder-tabs-css-admin-editor',
599 plugin_dir_url(__FILE__) . 'assets/css/tabs.css',
600 [],
601 AIBUI_VERSION
602 );
603 // Injection des variables JS pour AJAX et le nonce
604 wp_localize_script(
605 'chat-widget',
606 'aiBuilderVars',
607 array(
608 'ajaxurl' => admin_url('admin-ajax.php'),
609 'nonce' => wp_create_nonce('aibui_nonce'),
610 'adminBaseUrl' => admin_url(),
611 // Base URL for plugin assets (e.g. chat style preview images)
612 'pluginUrl' => plugin_dir_url(__FILE__),
613 // Flag to hint we are on the Site Editor (patterns/template parts)
614 'isPatternEditor' => ($hook === 'site-editor.php'),
615 // WooCommerce detection
616 'wooCommerceInstalled' => aibui_is_woocommerce_installed(),
617 // Active theme name
618 'activeThemeName' => aibui_get_active_theme_name(),
619 // Active plugins context (max 15)
620 'sitePlugins' => aibui_get_active_plugins_context(),
621 // Current WordPress version
622 'wordpressVersion' => aibui_get_wordpress_version(),
623 )
624 );
625
626 // Enqueue Multi-Page apply script to support applying generations via URL param
627 wp_enqueue_script(
628 'ai-builder-multi-page-apply',
629 plugin_dir_url(__FILE__) . 'assets/js/multi-page-apply.js',
630 ['ai-builder-config', 'chat-widget', 'wp-blocks', 'wp-data', 'wp-editor', 'wp-block-editor', 'wp-element'],
631 AIBUI_VERSION,
632 true
633 );
634 wp_localize_script(
635 'ai-builder-multi-page-apply',
636 'aiBuilderVars',
637 array(
638 'ajaxurl' => admin_url('admin-ajax.php'),
639 'nonce' => wp_create_nonce('aibui_nonce'),
640 // Keep editor context keys to avoid overriding data needed by chat-widget.
641 'isPatternEditor' => ($hook === 'site-editor.php'),
642 'wooCommerceInstalled' => aibui_is_woocommerce_installed(),
643 // Active theme name
644 'activeThemeName' => aibui_get_active_theme_name(),
645 // Active plugins context (max 15)
646 'sitePlugins' => aibui_get_active_plugins_context(),
647 // Current WordPress version
648 'wordpressVersion' => aibui_get_wordpress_version(),
649 )
650 );
651 }
652
653 $current_screen = get_current_screen();
654 // Charger les styles et scripts pour la page account du plugin AI Builder
655 if ($current_screen && strpos($current_screen->id, 'aibui-assistant') !== false) {
656 wp_enqueue_style(
657 'ai-builder-account-style',
658 plugin_dir_url(__FILE__) . 'assets/css/account.css',
659 [],
660 filemtime(plugin_dir_path(__FILE__) . 'assets/css/account.css')
661 );
662 wp_enqueue_script(
663 'ai-builder-config',
664 plugin_dir_url(__FILE__) . 'config.js',
665 [],
666 AIBUI_VERSION,
667 true
668 );
669 wp_enqueue_script(
670 'ai-builder-account',
671 plugin_dir_url(__FILE__) . 'assets/js/account.js',
672 ['ai-builder-config'],
673 AIBUI_VERSION,
674 true
675 );
676 // Injection des variables JS pour AJAX et le nonce
677 wp_localize_script(
678 'ai-builder-account',
679 'aiBuilderVars',
680 array(
681 'ajaxurl' => admin_url('admin-ajax.php'),
682 'nonce' => wp_create_nonce('aibui_nonce'),
683 'accountUrl' => admin_url('admin.php?page=aibui-account'),
684 'siteDomain' => parse_url(home_url(), PHP_URL_HOST),
685 'installationId' => aibui_get_installation_id(),
686 )
687 );
688 } else if ($current_screen && strpos($current_screen->id, 'aibui-credits') !== false) {
689 wp_enqueue_script(
690 'ai-builder-credits',
691 plugin_dir_url(__FILE__) . 'assets/js/credits.js',
692 ['ai-builder-config'],
693 AIBUI_VERSION,
694 true
695 );
696 // Injection des variables JS pour AJAX et le nonce
697 wp_localize_script(
698 'ai-builder-credits',
699 'aiBuilderVars',
700 array(
701 'ajaxurl' => admin_url('admin-ajax.php'),
702 'nonce' => wp_create_nonce('aibui_nonce'),
703 )
704 );
705 wp_enqueue_style(
706 'ai-builder-credits-additional-style',
707 plugin_dir_url(__FILE__) . 'assets/css/credits-additional.css',
708 [],
709 AIBUI_VERSION
710 );
711 wp_enqueue_style(
712 'ai-builder-credits-style',
713 plugin_dir_url(__FILE__) . 'assets/css/credits.css',
714 [],
715 AIBUI_VERSION
716 );
717 wp_enqueue_script(
718 'ai-builder-config',
719 plugin_dir_url(__FILE__) . 'config.js',
720 [],
721 AIBUI_VERSION,
722 true
723 );
724 } else if ($current_screen && strpos($current_screen->id, 'aibui-settings') !== false) {
725 wp_enqueue_style(
726 'ai-builder-settings-style',
727 plugin_dir_url(__FILE__) . 'assets/css/settings.css',
728 [],
729 AIBUI_VERSION
730 );
731 wp_enqueue_script(
732 'ai-builder-config',
733 plugin_dir_url(__FILE__) . 'config.js',
734 [],
735 AIBUI_VERSION,
736 true
737 );
738 wp_enqueue_script(
739 'ai-builder-settings',
740 plugin_dir_url(__FILE__) . 'assets/js/settings.js',
741 ['ai-builder-config'],
742 AIBUI_VERSION,
743 true
744 );
745 // Injection des variables JS pour AJAX et le nonce
746 wp_localize_script(
747 'ai-builder-settings',
748 'aiBuilderVars',
749 array(
750 'ajaxurl' => admin_url('admin-ajax.php'),
751 'nonce' => wp_create_nonce('aibui_nonce'),
752 )
753 );
754 } else if ($current_screen && strpos($current_screen->id, 'aibui-reset-password') !== false) {
755 wp_enqueue_style(
756 'ai-builder-reset-password-style',
757 plugin_dir_url(__FILE__) . 'assets/css/reset-password.css',
758 [],
759 AIBUI_VERSION
760 );
761 wp_enqueue_script(
762 'ai-builder-config',
763 plugin_dir_url(__FILE__) . 'config.js',
764 [],
765 AIBUI_VERSION,
766 true
767 );
768 wp_enqueue_script(
769 'ai-builder-reset-password',
770 plugin_dir_url(__FILE__) . 'assets/js/reset-password.js',
771 ['ai-builder-config'],
772 AIBUI_VERSION,
773 true
774 );
775 // Injection des variables JS pour AJAX et le nonce
776 wp_localize_script(
777 'ai-builder-reset-password',
778 'aiBuilderVars',
779 array(
780 'ajaxurl' => admin_url('admin-ajax.php'),
781 'nonce' => wp_create_nonce('aibui_nonce'),
782 'accountUrl' => admin_url('admin.php?page=aibui-account'),
783 )
784 );
785 } else if ($current_screen && strpos($current_screen->id, 'aibui-tuto') !== false) {
786 wp_enqueue_style(
787 'ai-builder-tutorial-style',
788 plugin_dir_url(__FILE__) . 'assets/css/tutorial.css',
789 [],
790 AIBUI_VERSION
791 );
792 wp_enqueue_script(
793 'ai-builder-config',
794 plugin_dir_url(__FILE__) . 'config.js',
795 [],
796 AIBUI_VERSION,
797 true
798 );
799 } else if ($current_screen && strpos($current_screen->id, 'aibui-multi-page') !== false) {
800 wp_enqueue_style(
801 'ai-builder-multi-page-style',
802 plugin_dir_url(__FILE__) . 'assets/css/multi-page.css',
803 [],
804 AIBUI_VERSION
805 );
806 wp_enqueue_script(
807 'ai-builder-config',
808 plugin_dir_url(__FILE__) . 'config.js',
809 [],
810 AIBUI_VERSION,
811 true
812 );
813 wp_enqueue_script(
814 'ai-builder-multi-page',
815 plugin_dir_url(__FILE__) . 'assets/js/multi-page.js',
816 ['ai-builder-config'],
817 AIBUI_VERSION,
818 true
819 );
820 // Injection des variables JS pour AJAX et le nonce
821 wp_localize_script(
822 'ai-builder-multi-page',
823 'aiBuilderVars',
824 array(
825 'ajaxurl' => admin_url('admin-ajax.php'),
826 'nonce' => wp_create_nonce('aibui_nonce'),
827 'adminBaseUrl' => admin_url(),
828 'wooCommerceInstalled' => aibui_is_woocommerce_installed(),
829 // Active theme name
830 'activeThemeName' => aibui_get_active_theme_name(),
831 // Active plugins context (max 15)
832 'sitePlugins' => aibui_get_active_plugins_context(),
833 // Current WordPress version
834 'wordpressVersion' => aibui_get_wordpress_version(),
835 )
836 );
837 } else if ($current_screen && strpos($current_screen->id, 'aibui-agent-chat') !== false) {
838 // Agent Chat page scripts and styles
839 wp_enqueue_script(
840 'ai-builder-config',
841 plugin_dir_url(__FILE__) . 'config.js',
842 [],
843 AIBUI_VERSION,
844 true
845 );
846 wp_enqueue_script(
847 'ai-builder-agent-chat',
848 plugin_dir_url(__FILE__) . 'assets/js/agent-chat.js',
849 ['ai-builder-config'],
850 AIBUI_VERSION,
851 true
852 );
853 // Localize with nonce - IMPORTANT: use a different nonce for agent actions
854 wp_localize_script(
855 'ai-builder-agent-chat',
856 'aibuiAgentVars',
857 array(
858 'ajaxurl' => admin_url('admin-ajax.php'),
859 'nonce' => wp_create_nonce('aibui_agent_nonce'),
860 'restBase' => esc_url_raw(rest_url()),
861 'wpRestDocsBase' => 'https://developer.wordpress.org/rest-api/reference/',
862 )
863 );
864 // Also expose standard AJAX nonce for shared endpoints like aibui_get_token
865 wp_localize_script(
866 'ai-builder-agent-chat',
867 'aiBuilderVars',
868 array(
869 'ajaxurl' => admin_url('admin-ajax.php'),
870 'nonce' => wp_create_nonce('aibui_nonce'),
871 )
872 );
873 }
874
875 // Bandeau de review : uniquement sur les pages admin du plugin
876 if ($current_screen) {
877 $screen_id = $current_screen->id;
878 $is_plugin_screen =
879 strpos($screen_id, 'aibui-assistant') !== false ||
880 strpos($screen_id, 'aibui-credits') !== false ||
881 strpos($screen_id, 'aibui-tuto') !== false ||
882 strpos($screen_id, 'aibui-multi-page') !== false ||
883 strpos($screen_id, 'aibui-agent-chat') !== false ||
884 strpos($screen_id, 'aibui-translation-settings') !== false ||
885 strpos($screen_id, 'aibui-headers-footers') !== false ||
886 strpos($screen_id, 'aibui-settings') !== false;
887
888 if ($is_plugin_screen) {
889 // S'assurer que config.js est chargé
890 wp_enqueue_script(
891 'ai-builder-config',
892 plugin_dir_url(__FILE__) . 'config.js',
893 [],
894 AIBUI_VERSION,
895 true
896 );
897 wp_enqueue_script(
898 'ai-builder-review-banner',
899 plugin_dir_url(__FILE__) . 'assets/js/review-banner.js',
900 ['ai-builder-config'],
901 AIBUI_VERSION,
902 true
903 );
904 wp_localize_script(
905 'ai-builder-review-banner',
906 'aiBuilderReviewVars',
907 array(
908 'ajaxurl' => admin_url('admin-ajax.php'),
909 'nonce' => wp_create_nonce('aibui_nonce'),
910 'reviewUrl' => 'https://wordpress.org/support/plugin/ai-builder/reviews/#new-post',
911 )
912 );
913 }
914 }
915
916 });
917
918 add_action('wp_enqueue_scripts', function () {
919 // Single combined frontend CSS
920 $combined_css = aibui_get_combined_css_url();
921 if ($combined_css) {
922 wp_enqueue_style(
923 'ai-builder-combined',
924 $combined_css,
925 [],
926 null
927 );
928 } else {
929 // Fallback: enqueue at least the essential stylesheet
930 wp_enqueue_style(
931 'aibui-force-alignfull',
932 plugin_dir_url(__FILE__) . 'assets/css/force-align-full.css',
933 [],
934 AIBUI_VERSION
935 );
936 }
937
938 // Charger les styles CSS du bloc AI Image côté frontend
939 wp_enqueue_style(
940 'ai-builder-ai-image-frontend',
941 plugin_dir_url(__FILE__) . 'assets/js/build/index.css',
942 [],
943 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.css')
944 );
945 // Frontend JS single bundle
946 $combined_js = aibui_get_combined_js_url();
947 if ($combined_js) {
948 wp_enqueue_script(
949 'ai-builder-frontend-bundle',
950 $combined_js,
951 [],
952 null,
953 true
954 );
955 } else {
956 // Fallback: at least enqueue carousel script if bundling failed
957 wp_enqueue_script(
958 'ai-builder-carousel-frontend',
959 plugin_dir_url(__FILE__) . 'assets/js/carousel-frontend.js',
960 [],
961 AIBUI_VERSION,
962 true
963 );
964 }
965
966 // Load fixed background group block CSS for frontend
967 wp_enqueue_style(
968 'ai-builder-fixed-bg-group-frontend',
969 plugin_dir_url(__FILE__) . 'assets/js/src/editor-blocks/fixed-bg-group/style.css',
970 [],
971 AIBUI_VERSION
972 );
973
974 // CSS above is covered by the combined bundle
975
976 // Other JS are included in the combined bundle above
977 // CSS above is covered by the combined bundle
978 // Included in combined bundle
979 // CSS above is covered by the combined bundle
980 // Included in combined bundle
981 });
982
983 // Enqueue contact form script only when the block is present
984 add_action('wp_enqueue_scripts', function () {
985 global $post;
986 $should_load = false;
987
988 // Check if current post has the block
989 if (is_a($post, 'WP_Post') && has_block('ai-builder/aibui-contact-form', $post)) {
990 $should_load = true;
991 }
992
993 // Fallback: check if we're on a page that might have the block
994 if (!$should_load && (is_page() || is_single() || is_home() || is_front_page())) {
995 $should_load = true;
996 }
997
998 if ($should_load) {
999 wp_enqueue_script(
1000 'ai-builder-contact-form',
1001 plugin_dir_url(__FILE__) . 'assets/js/contact-form.js',
1002 [],
1003 AIBUI_VERSION,
1004 true
1005 );
1006 }
1007 });
1008
1009 add_action('enqueue_block_editor_assets', function () {
1010 // Single combined CSS for editor
1011 $combined_css = aibui_get_combined_css_url();
1012 if ($combined_css) {
1013 wp_enqueue_style(
1014 'ai-builder-combined-editor',
1015 $combined_css,
1016 [],
1017 null
1018 );
1019 } else {
1020 wp_enqueue_style(
1021 'aibui-force-alignfull-editor',
1022 plugin_dir_url(__FILE__) . 'assets/css/force-align-full.css',
1023 [],
1024 AIBUI_VERSION
1025 );
1026 }
1027 // Charger les styles build des blocs dans l'éditeur
1028 wp_enqueue_style(
1029 'ai-builder-blocks-editor-build',
1030 plugin_dir_url(__FILE__) . 'assets/js/build/index.css',
1031 [],
1032 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.css')
1033 );
1034 // Styles spécifiques d'aperçu du formulaire de contact
1035 wp_enqueue_style(
1036 'ai-builder-contact-form-editor',
1037 plugin_dir_url(__FILE__) . 'assets/css/contact-form-editor.css',
1038 [],
1039 AIBUI_VERSION
1040 );
1041 // Covered by combined bundle
1042 // Stats tooltips in editor preview
1043 wp_enqueue_script(
1044 'ai-builder-stats-tooltips-editor',
1045 plugin_dir_url(__FILE__) . 'assets/js/stats-tooltips.js',
1046 [],
1047 AIBUI_VERSION,
1048 true
1049 );
1050 });
1051
1052 add_action('enqueue_block_editor_assets', function () {
1053 wp_enqueue_script(
1054 'ai-builder-blocks',
1055 plugin_dir_url(__FILE__) . 'assets/js/build/index.js',
1056 [
1057 'wp-blocks',
1058 'wp-block-editor',
1059 'wp-element',
1060 'wp-components',
1061 'wp-i18n',
1062 'wp-api-fetch',
1063 'wp-hooks'
1064 ],
1065 filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.js'),
1066 true
1067 );
1068
1069 // Injection des variables JS pour AJAX et le nonce (utilisées par les blocs éditeur)
1070 wp_localize_script(
1071 'ai-builder-blocks',
1072 'aiBuilderVars',
1073 array(
1074 'ajaxurl' => admin_url('admin-ajax.php'),
1075 'nonce' => wp_create_nonce('aibui_nonce'),
1076 )
1077 );
1078
1079 wp_enqueue_script(
1080 'ai-builder-language-switcher-block',
1081 plugin_dir_url(__FILE__) . 'assets/js/language-switcher-block.js',
1082 array('wp-blocks', 'wp-block-editor', 'wp-element', 'wp-components', 'wp-i18n'),
1083 filemtime(plugin_dir_path(__FILE__) . 'assets/js/language-switcher-block.js'),
1084 true
1085 );
1086
1087 // Unregister legacy/unused AI Builder blocks from the inserter
1088 wp_enqueue_script(
1089 'ai-builder-unregister-ai-blocks',
1090 plugin_dir_url(__FILE__) . 'assets/js/unregister-ai-blocks.js',
1091 array('ai-builder-blocks', 'wp-blocks'),
1092 AIBUI_VERSION,
1093 true
1094 );
1095
1096 // CSS Class Inspector: button in block sidebar to jump to CSS
1097 wp_enqueue_script(
1098 'ai-builder-css-class-inspector',
1099 plugin_dir_url(__FILE__) . 'assets/js/css-class-inspector.js',
1100 array('wp-hooks', 'wp-compose', 'wp-block-editor', 'wp-components', 'wp-element', 'wp-i18n'),
1101 AIBUI_VERSION,
1102 true
1103 );
1104
1105 $translation_settings = AIBUI_Translation_Settings::get_settings();
1106 $supported_languages = AIBUI_Translation_Handler::get_supported_languages();
1107 $available_langs = isset($translation_settings['available_langs']) && is_array($translation_settings['available_langs'])
1108 ? array_values(array_unique($translation_settings['available_langs']))
1109 : array();
1110 $default_lang = isset($translation_settings['default_lang']) ? $translation_settings['default_lang'] : 'en';
1111 if ($default_lang && !in_array($default_lang, $available_langs, true)) {
1112 array_unshift($available_langs, $default_lang);
1113 }
1114 $switcher_defaults = array(
1115 'backgroundColor' => !empty($translation_settings['switcher_bg']) ? $translation_settings['switcher_bg'] : '',
1116 'textColor' => !empty($translation_settings['switcher_text']) ? $translation_settings['switcher_text'] : '',
1117 'minWidth' => !empty($translation_settings['switcher_min_width']) ? (int) $translation_settings['switcher_min_width'] : 90,
1118 'minHeight' => !empty($translation_settings['switcher_min_height']) ? (int) $translation_settings['switcher_min_height'] : 30,
1119 'borderRadius' => 22,
1120 );
1121
1122 wp_localize_script(
1123 'ai-builder-language-switcher-block',
1124 'aiBuilderLangSwitch',
1125 array(
1126 'availableLangs' => $available_langs,
1127 'defaultLang' => $default_lang,
1128 'labels' => $supported_languages,
1129 'switcherEnabled' => !empty($translation_settings['enable_switcher']),
1130 'defaults' => $switcher_defaults,
1131 )
1132 );
1133 });
1134
1135 // Enregistrer le bloc côté PHP
1136 add_action('init', function () use ($translation_switcher) {
1137 wp_register_style(
1138 'ai-builder-language-switcher-style',
1139 plugin_dir_url(__FILE__) . 'assets/css/language-switcher.css',
1140 array(),
1141 AIBUI_VERSION
1142 );
1143 // Bloc AI Block
1144 register_block_type('ai-builder/ai-block', [
1145 'editor_script' => 'ai-builder-blocks',
1146 'editor_style' => 'ai-builder-blocks-style',
1147 'style' => 'ai-builder-blocks-style',
1148 'render_callback' => function ($attributes) {
1149 // Le bloc AI Block ne s'affiche pas côté frontend car il est remplacé par le contenu généré
1150 return '';
1151 }
1152 ]);
1153
1154 // Bloc stat-bar (statique, pas de render_callback)
1155 register_block_type('ai-builder/stat-bar', [
1156 'editor_script' => 'ai-builder-blocks',
1157 'editor_style' => 'ai-builder-blocks-style',
1158 'style' => 'ai-builder-blocks-style',
1159 ]);
1160
1161 // Bloc aibui-stats (rendu simple côté éditeur, frontend statique)
1162 register_block_type('ai-builder/aibui-stats', [
1163 'editor_script' => 'ai-builder-blocks',
1164 'editor_style' => 'ai-builder-blocks-style',
1165 'style' => 'ai-builder-blocks-style',
1166 ]);
1167
1168 // Bloc aibui-carousel
1169 register_block_type('ai-builder/aibui-carousel', [
1170 'editor_script' => 'ai-builder-blocks',
1171 'editor_style' => 'ai-builder-blocks-style',
1172 'style' => 'ai-builder-blocks-style',
1173 ]);
1174
1175 // Bloc aibui-yt-video
1176 register_block_type('ai-builder/aibui-yt-video', [
1177 'editor_script' => 'ai-builder-blocks',
1178 'editor_style' => 'ai-builder-blocks-style',
1179 'style' => 'ai-builder-blocks-style',
1180 ]);
1181
1182 // Bloc aibui-map
1183 register_block_type('ai-builder/aibui-map', [
1184 'editor_script' => 'ai-builder-blocks',
1185 'editor_style' => 'ai-builder-blocks-style',
1186 'style' => 'ai-builder-blocks-style',
1187 ]);
1188
1189 // Bloc aibui-tabs
1190 // Bloc aibui-table
1191 register_block_type('ai-builder/aibui-table', [
1192 'editor_script' => 'ai-builder-blocks',
1193 'editor_style' => 'ai-builder-blocks-style',
1194 'style' => 'ai-builder-blocks-style',
1195 ]);
1196 register_block_type('ai-builder/aibui-tabs', [
1197 'editor_script' => 'ai-builder-blocks',
1198 'editor_style' => 'ai-builder-blocks-style',
1199 'style' => 'ai-builder-blocks-style',
1200 ]);
1201
1202 // Bloc aibui-contact-form (rendu via callback PHP pour sécurité)
1203 register_block_type('ai-builder/aibui-contact-form', [
1204 'editor_script' => 'ai-builder-blocks',
1205 'editor_style' => 'ai-builder-blocks-style',
1206 'style' => 'ai-builder-blocks-style',
1207 'render_callback' => function ($attributes, $content, $block) {
1208 $recipient = isset($attributes['recipientEmail']) ? sanitize_email($attributes['recipientEmail']) : '';
1209 if (empty($recipient) || !is_email($recipient)) {
1210 $recipient = sanitize_email(get_option('admin_email'));
1211 }
1212 if (empty($recipient) || !is_email($recipient)) {
1213 // Pas de destinataire valide disponible, n'arrête pas l'affichage, mais désactive l'envoi
1214 $recipient = '';
1215 }
1216
1217 $fields = isset($attributes['fields']) && is_array($attributes['fields']) ? $attributes['fields'] : [];
1218 $fields = array_slice($fields, 0, 5);
1219
1220 $form_title = isset($attributes['formTitle']) ? wp_kses_post($attributes['formTitle']) : '';
1221 $description = isset($attributes['description']) ? wp_kses_post($attributes['description']) : '';
1222 $button_label = isset($attributes['buttonLabel']) ? sanitize_text_field($attributes['buttonLabel']) : __('Send', 'ai-builder');
1223 $success_message = isset($attributes['successMessage']) ? sanitize_text_field($attributes['successMessage']) : __('Thank you, your message has been sent.', 'ai-builder');
1224 $from_email_attr = isset($attributes['fromEmail']) ? sanitize_email($attributes['fromEmail']) : '';
1225
1226 $nonce = wp_create_nonce('aibui_contact_form');
1227 $action = esc_url(admin_url('admin-ajax.php'));
1228
1229 ob_start();
1230 ?>
1231 <form class="aibui-contact-form aibui-contact-form--frontend" method="post" action="<?php echo $action; ?>"
1232 data-endpoint="<?php echo $action; ?>">
1233 <?php if ($form_title): ?>
1234 <h3 class="aibui-form-title"><?php echo $form_title; ?></h3>
1235 <?php endif; ?>
1236 <?php if ($description): ?>
1237 <p class="aibui-form-description"><?php echo $description; ?></p>
1238 <?php endif; ?>
1239 <input type="hidden" name="action" value="aibui_submit_contact_form" />
1240 <input type="hidden" name="nonce" value="<?php echo esc_attr($nonce); ?>" />
1241 <input type="hidden" name="recipient" value="<?php echo esc_attr($recipient); ?>" />
1242 <?php if ($from_email_attr): ?>
1243 <input type="hidden" name="from_email" value="<?php echo esc_attr($from_email_attr); ?>" />
1244 <?php endif; ?>
1245 <?php foreach ($fields as $idx => $field):
1246 $label = isset($field['label']) ? sanitize_text_field($field['label']) : '';
1247 $type = isset($field['type']) ? sanitize_text_field($field['type']) : 'text';
1248 $required = !empty($field['required']);
1249 $name = 'field_' . $idx;
1250 ?>
1251 <div class="aibui-field">
1252 <label>
1253 <?php echo esc_html($label); ?> <?php echo $required ? ' *' : ''; ?>
1254 <?php if ($type === 'textarea'): ?>
1255 <textarea name="<?php echo esc_attr($name); ?>" <?php echo $required ? 'required' : ''; ?>
1256 aria-label="<?php echo esc_attr($label); ?>"></textarea>
1257 <?php else: ?>
1258 <input
1259 type="<?php echo $type === 'email' ? 'email' : ($type === 'number' ? 'number' : ($type === 'date' ? 'date' : 'text')); ?>"
1260 name="<?php echo esc_attr($name); ?>" <?php echo $required ? 'required' : ''; ?>
1261 aria-label="<?php echo esc_attr($label); ?>" />
1262 <?php endif; ?>
1263 <input type="hidden" name="label_<?php echo esc_attr($name); ?>" value="<?php echo esc_attr($label); ?>" />
1264 <input type="hidden" name="type_<?php echo esc_attr($name); ?>" value="<?php echo esc_attr($type); ?>" />
1265 <input type="hidden" name="required_<?php echo esc_attr($name); ?>"
1266 value="<?php echo $required ? '1' : '0'; ?>" />
1267 </label>
1268 </div>
1269 <?php endforeach; ?>
1270 <div style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;">
1271 <label>Leave this field empty<input type="text" name="website" tabindex="-1" autocomplete="off" /></label>
1272 </div>
1273 <div class="aibui-form-message" role="status" aria-live="polite"></div>
1274 <input type="hidden" name="success_message" value="<?php echo esc_attr($success_message); ?>" />
1275 <button type="submit" class="aibui-button aibui-button--primary"><?php echo esc_html($button_label); ?></button>
1276 </form>
1277 <?php
1278 return ob_get_clean();
1279 }
1280 ]);
1281
1282 register_block_type('ai-builder/language-switcher', [
1283 'editor_script' => 'ai-builder-language-switcher-block',
1284 'editor_style' => 'ai-builder-language-switcher-style',
1285 'style' => 'ai-builder-language-switcher-style',
1286 'render_callback' => array($translation_switcher, 'render_block'),
1287 'attributes' => [
1288 'backgroundColor' => ['type' => 'string', 'default' => '#5686c9'],
1289 'textColor' => ['type' => 'string', 'default' => '#ffffff'],
1290 'minWidth' => ['type' => 'number', 'default' => 90],
1291 'minHeight' => ['type' => 'number', 'default' => 30],
1292 'borderRadius' => ['type' => 'number', 'default' => 22],
1293 ],
1294 'supports' => [
1295 'align' => ['left', 'center', 'right', 'wide', 'full'],
1296 ],
1297 ]);
1298 });
1299
1300 add_action('after_setup_theme', function () {
1301 add_theme_support('align-wide');
1302 });
1303
1304 function enqueue_ai_builder_scripts() {
1305 // Charger config.js en premier (dépendance pour les autres scripts)
1306 wp_enqueue_script(
1307 'ai-builder-config',
1308 plugins_url('config.js', __FILE__),
1309 array(),
1310 AIBUI_VERSION,
1311 false // Charger dans le <head> pour être disponible partout
1312 );
1313
1314 // Autres scripts qui utilisent config.js
1315 wp_enqueue_script(
1316 'ai-builder-image-ai-controls',
1317 plugins_url('assets/js/src/editor-blocks/image-ai-blocks/image-ai-controls.js', __FILE__),
1318 array('ai-builder-config'),
1319 AIBUI_VERSION,
1320 true
1321 );
1322
1323 // Autres scripts qui utilisent config.js
1324 wp_enqueue_script(
1325 'ai-builder-text-ai-controls',
1326 plugins_url('assets/js/src/editor-blocks/text-ai-blocks/text-ai-controls.js', __FILE__),
1327 array('ai-builder-config'),
1328 AIBUI_VERSION,
1329 true
1330 );
1331
1332 // Autres scripts qui utilisent config.js
1333 wp_enqueue_script(
1334 'ai-builder-text-ai-controls',
1335 plugins_url('assets/js/src/editor-blocks/text-ai-blocks/text-ai-controls.js', __FILE__),
1336 array('ai-builder-config'),
1337 AIBUI_VERSION,
1338 true
1339 );
1340
1341 // Autres scripts qui utilisent config.js
1342 wp_enqueue_script(
1343 'ai-builder-ai-block',
1344 plugins_url('assets/js/src/editor-blocks/ai-block/ai-block.js', __FILE__),
1345 array('ai-builder-config'),
1346 AIBUI_VERSION,
1347 true
1348 );
1349
1350 // Localize WooCommerce detection for block editor scripts
1351 wp_localize_script(
1352 'ai-builder-config',
1353 'aiBuilderEditorVars',
1354 array(
1355 'wooCommerceInstalled' => aibui_is_woocommerce_installed(),
1356 // Active theme name
1357 'activeThemeName' => aibui_get_active_theme_name(),
1358 )
1359 );
1360 }
1361 add_action('enqueue_block_editor_assets', 'enqueue_ai_builder_scripts');
1362
1363 add_action('wp_enqueue_scripts', function () {
1364 // Combined CSS already enqueued above; only enqueue any extra block scss-generated styles if needed
1365 // Table, snackbar, cards editor styles are not required on frontend separately when bundled
1366
1367 // Load snackbar frontend JavaScript
1368 wp_enqueue_script(
1369 'ai-builder-snackbar-frontend',
1370 plugin_dir_url(__FILE__) . 'assets/js/snackbar-frontend.js',
1371 [],
1372 AIBUI_VERSION,
1373 true
1374 );
1375 });
1376
1377 add_action('enqueue_block_editor_assets', function () {
1378 // Combined CSS already enqueued above; keep editor-specific assets below
1379 // Load fixed background group block CSS specifically (already inside combined, but keep safe fallback if combined failed)
1380 $fixed_bg_css_url = plugin_dir_url(__FILE__) . 'assets/js/src/editor-blocks/fixed-bg-group/style.css';
1381 wp_enqueue_style('ai-builder-fixed-bg-group-editor', $fixed_bg_css_url, [], AIBUI_VERSION);
1382
1383 // Load WordPress media scripts for image selection
1384 wp_enqueue_media();
1385 });
1386
1387 // -------------------------------
1388 // Meta description per page/post
1389 // -------------------------------
1390 add_action('add_meta_boxes', function () {
1391 add_meta_box(
1392 'aibui_meta_description',
1393 __('Meta description', 'ai-builder'),
1394 function ($post) {
1395 $value = get_post_meta($post->ID, 'aibui_meta_description', true);
1396 wp_nonce_field('aibui_meta_description_save', 'aibui_meta_description_nonce');
1397 echo '<p>' . esc_html__('Add a meta description for this page/post.', 'ai-builder') . '</p>';
1398 echo '<textarea style="width:100%;min-height:90px;" id="aibui_meta_description_field" name="aibui_meta_description" maxlength="320">' . esc_textarea($value) . '</textarea>';
1399 echo '<p style="margin-top:6px;color:#555;">' . esc_html__('Tip: 140–160 characters for best SEO results.', 'ai-builder') . '</p>';
1400 },
1401 array('post', 'page'),
1402 'normal',
1403 'default'
1404 );
1405 });
1406
1407 add_action('save_post', function ($post_id) {
1408 if (!isset($_POST['aibui_meta_description_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['aibui_meta_description_nonce'])), 'aibui_meta_description_save')) {
1409 return;
1410 }
1411 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
1412 if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) return;
1413 if (!current_user_can('edit_post', $post_id)) return;
1414
1415 $raw = isset($_POST['aibui_meta_description']) ? wp_unslash($_POST['aibui_meta_description']) : '';
1416 $san = trim(wp_strip_all_tags($raw));
1417 if (strlen($san) > 320) {
1418 $san = mb_substr($san, 0, 320);
1419 }
1420 if ($san === '') {
1421 delete_post_meta($post_id, 'aibui_meta_description');
1422 } else {
1423 update_post_meta($post_id, 'aibui_meta_description', $san);
1424 }
1425 });
1426
1427 add_action('wp_head', function () {
1428 if (is_admin() || !is_singular()) {
1429 return;
1430 }
1431
1432 $post_id = get_queried_object_id();
1433 if (!$post_id) {
1434 return;
1435 }
1436
1437 $possible_keys = array(
1438 'aibui_meta_description',
1439 '_ai_builder_seo_desc',
1440 '_yoast_wpseo_metadesc',
1441 '_ai_translation_meta_desc'
1442 );
1443
1444 $desc = '';
1445 foreach ($possible_keys as $meta_key) {
1446 $value = get_post_meta($post_id, $meta_key, true);
1447 if (!empty($value)) {
1448 $desc = $value;
1449 break;
1450 }
1451 }
1452
1453 if (!$desc) {
1454 $excerpt = get_post_field('post_excerpt', $post_id);
1455 if (!empty($excerpt)) {
1456 $desc = $excerpt;
1457 }
1458 }
1459
1460 $desc = trim(wp_strip_all_tags((string) $desc));
1461 if ($desc !== '') {
1462 echo "\n<meta name=\"description\" content=\"" . esc_attr($desc) . "\" />\n";
1463 }
1464 }, 1);
1465
1466 // Initialiser le gestionnaire AJAX
1467 new AIBUI_Ajax_Handler();
1468
1469 // AJAX: create a header or footer template part for the active block theme
1470 add_action('wp_ajax_aibui_create_template_part', function () {
1471 check_ajax_referer('aibui_nonce', 'nonce');
1472
1473 if (!current_user_can('edit_theme_options')) {
1474 wp_send_json_error('Permission denied');
1475 }
1476
1477 $area = sanitize_text_field($_POST['area'] ?? '');
1478 if (!in_array($area, array('header', 'footer'), true)) {
1479 wp_send_json_error('Invalid area');
1480 }
1481
1482 $theme_slug = get_stylesheet();
1483 $title = $area === 'header' ? 'Header' : 'Footer';
1484
1485 $post_id = wp_insert_post(array(
1486 'post_title' => $title,
1487 'post_name' => $area,
1488 'post_content' => '',
1489 'post_status' => 'publish',
1490 'post_type' => 'wp_template_part',
1491 ));
1492
1493 if (is_wp_error($post_id)) {
1494 wp_send_json_error($post_id->get_error_message());
1495 }
1496
1497 wp_set_object_terms($post_id, $area, 'wp_template_part_area');
1498 wp_set_object_terms($post_id, $theme_slug, 'wp_theme');
1499
1500 $edit_url = admin_url(
1501 'site-editor.php?postType=wp_template_part&postId='
1502 . urlencode($theme_slug . '//' . $area)
1503 . '&canvas=edit'
1504 );
1505
1506 wp_send_json_success(array('edit_url' => $edit_url));
1507 });
1508
1509
1510 // -------------------------------
1511 // Multi-Page Generator: Cleanup cron and migration
1512 // -------------------------------
1513 function aibui_cleanup_old_generations() {
1514 require_once plugin_dir_path(__FILE__) . 'includes/class-generations-storage.php';
1515 $storage = new AIBUI_Generations_Storage();
1516 $deleted_count = $storage->cleanup_old(30); // Delete applied generations older than 30 days
1517
1518
1519 }
1520 add_action('aibui_daily_cleanup', 'aibui_cleanup_old_generations');
1521
1522 // Schedule daily cleanup if not already scheduled
1523 if (!wp_next_scheduled('aibui_daily_cleanup')) {
1524 wp_schedule_event(time(), 'daily', 'aibui_daily_cleanup');
1525 }
1526
1527 // Migrate old wp_options data to files (one-time migration on activation/update)
1528 function aibui_migrate_generations_to_files() {
1529 // Check if migration already done
1530 if (get_option('aibui_generations_migrated_to_files', false)) {
1531 return;
1532 }
1533
1534 require_once plugin_dir_path(__FILE__) . 'includes/class-generations-storage.php';
1535 $storage = new AIBUI_Generations_Storage();
1536 $migrated_count = $storage->migrate_from_options();
1537
1538 if ($migrated_count > 0) {
1539 // Mark migration as done
1540 update_option('aibui_generations_migrated_to_files', true, false);
1541
1542
1543 }
1544 }
1545 // Run migration on admin init (only once)
1546 add_action('admin_init', function() {
1547 static $migration_done = false;
1548 if (!$migration_done && current_user_can('manage_options')) {
1549 aibui_migrate_generations_to_files();
1550 $migration_done = true;
1551 }
1552 }, 5);
1553