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

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