\(\)])\s*/', '$1', $css); // Final trims and unnecessary semicolons $css = str_replace(';}', '}', $css); return trim($css); } // Build or fetch a combined CSS file for plugin assets/css/*.css /** * Feuilles de style qui composent le bundle frontend. * * Source unique : le combineur et le repli doivent charger exactement le même * ensemble, sinon un site dont le cache n'est pas inscriptible n'obtient pas le * même rendu qu'un site normal. * * @return string[] Chemins absolus, triés. */ function aibui_get_frontend_css_paths() { $css_dir = plugin_dir_path(__FILE__) . 'assets/css/'; if (!is_dir($css_dir)) { return array(); } $files = glob($css_dir . '*.css'); if (!$files) { return array(); } // Ordre stable : le CSS en cascade dépend de l'ordre de concaténation. sort($files, SORT_STRING); // Feuille réservée à l'admin, jamais servie au front. return array_values(array_filter($files, function ($path) { return basename($path) !== 'style-admin.css'; })); } /** * Scripts qui composent le bundle frontend. * * @return string[] Chemins relatifs à la racine du plugin. */ function aibui_get_frontend_js_rel_paths() { return array( 'assets/js/carousel-frontend.js', 'assets/js/map-frontend.js', 'assets/js/tabs-frontend.js', 'assets/js/table-frontend.js', 'assets/js/stats-tooltips.js', 'assets/js/snackbar-frontend.js', ); } function aibui_get_combined_css_url() { $css_url_base = plugin_dir_url(__FILE__) . 'assets/css/'; $files = aibui_get_frontend_css_paths(); if (!$files) return ''; // Compute a hash based on file mtimes and paths to invalidate cache when any source changes $sig_parts = []; foreach ($files as $path) { $sig_parts[] = basename($path) . ':' . filemtime($path); } $signature = md5(implode('|', $sig_parts)); // Store in uploads to keep plugin dir clean and writable $uploads = wp_upload_dir(); if (!empty($uploads['error'])) { return ''; } $cache_dir = rtrim($uploads['basedir'], '/').'/ai-builder-cache'; $cache_url = rtrim($uploads['baseurl'], '/').'/ai-builder-cache'; if (!is_dir($cache_dir)) { wp_mkdir_p($cache_dir); } $combined_filename = 'combined-'.$signature.'.css'; $combined_path = $cache_dir.'/'.$combined_filename; $combined_url = $cache_url.'/'.$combined_filename; if (!file_exists($combined_path)) { $buffer = ''; // Ordre et filtrage assurés par aibui_get_frontend_css_paths(). foreach ($files as $path) { $content = file_get_contents($path); if ($content === false) continue; $buffer .= "\n/* ".basename($path)." */\n".$content; } $minified = aibui_minify_css($buffer); // Graceful write if (is_writable($cache_dir)) { file_put_contents($combined_path, $minified); } else { return ''; } } return $combined_url; } // Simple JS minifier (very conservative) function aibui_minify_js($js) { if (!is_string($js) || $js === '') return ''; // Remove block comments but keep /*! license comments */ $js = preg_replace('#/(?!\!)(\*[^*]*\*+(?:[^/*][^*]*\*+)*/)#', '', $js); // Remove line comments $js = preg_replace('#(^|\s)//.*$#m', '$1', $js); // Collapse whitespace $js = preg_replace('/\s+/', ' ', $js); return trim($js); } // Build a combined frontend JS bundle from selected plugin scripts function aibui_get_combined_js_url() { $js_list = aibui_get_frontend_js_rel_paths(); $sig_parts = array(); $contents = ''; foreach ($js_list as $rel) { $path = plugin_dir_path(__FILE__) . $rel; if (!file_exists($path)) continue; $sig_parts[] = $rel . ':' . filemtime($path); } if (empty($sig_parts)) return ''; $signature = md5(implode('|', $sig_parts)); $uploads = wp_upload_dir(); if (!empty($uploads['error'])) { return ''; } $cache_dir = rtrim($uploads['basedir'], '/').'/ai-builder-cache'; $cache_url = rtrim($uploads['baseurl'], '/').'/ai-builder-cache'; if (!is_dir($cache_dir)) { wp_mkdir_p($cache_dir); } $combined_filename = 'frontend-'.$signature.'.js'; $combined_path = $cache_dir.'/'.$combined_filename; $combined_url = $cache_url.'/'.$combined_filename; if (!file_exists($combined_path)) { $buffer = ''; foreach ($js_list as $rel) { $path = plugin_dir_path(__FILE__) . $rel; if (!file_exists($path)) continue; $content = file_get_contents($path); if ($content === false) continue; $buffer .= "\n/* ".$rel." */\n".$content."\n"; } $minified = aibui_minify_js($buffer); if (is_writable($cache_dir)) { file_put_contents($combined_path, $minified); } else { return ''; } } return $combined_url; } /** * Check if WooCommerce is installed and active. * * @return bool True if WooCommerce is active, false otherwise. */ function aibui_is_woocommerce_installed() { // Check if WooCommerce class exists (plugin loaded) if (class_exists('WooCommerce')) { return true; } // Alternative check: see if the plugin is active if (function_exists('is_plugin_active')) { return is_plugin_active('woocommerce/woocommerce.php'); } // Fallback: check active plugins option $active_plugins = get_option('active_plugins', array()); return in_array('woocommerce/woocommerce.php', $active_plugins, true); } /** * Get the name of the currently active WordPress theme. * * @return string Theme name or empty string if not available. */ function aibui_get_active_theme_name() { if (function_exists('wp_get_theme')) { $theme = wp_get_theme(); return $theme->get('Name'); } return ''; } /** * Get up to 15 active plugin names for AI context. * * @return array */ function aibui_get_active_plugins_context() { // Ensure WordPress plugin helpers are available. if (!function_exists('get_plugins')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $installed_plugins = function_exists('get_plugins') ? get_plugins() : array(); $active_plugins = (array) get_option('active_plugins', array()); $active_plugins = array_slice($active_plugins, 0, 15); $plugin_names = array(); foreach ($active_plugins as $plugin_file) { $plugin_file = (string) $plugin_file; if (isset($installed_plugins[$plugin_file]['Name']) && $installed_plugins[$plugin_file]['Name'] !== '') { $plugin_names[] = sanitize_text_field($installed_plugins[$plugin_file]['Name']); } else { $plugin_names[] = sanitize_text_field($plugin_file); } } return array_values(array_slice($plugin_names, 0, 15)); } /** * Get current WordPress version. * * @return string */ function aibui_get_wordpress_version() { return (string) get_bloginfo('version'); } /** * Check if global AI personalization settings are still empty. * * We query the remote settings API (same as the Settings page) and consider the * configuration "empty" when all settings (primaryColor, secondaryColor, siteName, * siteDescription, designStyle, blockShapes, copywritingTone) are missing/empty. */ function aibui_are_personalization_settings_empty() { // Require an authenticated session with the cloud API. $jwt_token = get_option('aibui_jwt_token', ''); if (empty($jwt_token)) { return false; } $api_url = 'https://api.wordpress-ai-builder.com/api/settings'; $response = wp_remote_get($api_url, array( 'timeout' => 15, 'headers' => array( 'Authorization' => 'Bearer ' . $jwt_token, 'Content-Type' => 'application/json', ), )); if (is_wp_error($response)) { return false; } $code = wp_remote_retrieve_response_code($response); if ($code !== 200) { return false; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (!is_array($data)) { return false; } $primaryColor = isset($data['primaryColor']) ? trim((string) $data['primaryColor']) : ''; $secondaryColor = isset($data['secondaryColor']) ? trim((string) $data['secondaryColor']) : ''; $siteName = isset($data['siteName']) ? trim((string) $data['siteName']) : ''; $siteDescription = isset($data['siteDescription']) ? trim((string) $data['siteDescription']) : ''; $designStyle = isset($data['designStyle']) ? trim((string) $data['designStyle']) : ''; $blockShapes = isset($data['blockShapes']) ? trim((string) $data['blockShapes']) : ''; $copywritingTone = isset($data['copywritingTone']) ? trim((string) $data['copywritingTone']) : ''; return ($primaryColor === '' && $secondaryColor === '' && $siteName === '' && $siteDescription === '' && $designStyle === '' && $blockShapes === '' && $copywritingTone === ''); } /** * Render onboarding content (as in-page blocks, not admin notices). * * This is intentionally NOT hooked into admin_notices because we only want it * to appear inside specific AI Builder pages (Account, Credits, Tutorial). */ function aibui_render_onboarding_blocks() { if (!is_admin()) { return; } $show_personalization = current_user_can('manage_options') && aibui_are_personalization_settings_empty(); $show_first_gen = current_user_can('edit_posts') && aibui_user_has_not_generated_content(); if (!$show_personalization && !$show_first_gen) { return; } $settings_url = admin_url('admin.php?page=aibui-settings'); $new_page_url = admin_url('post-new.php?post_type=page'); ?>
AI Builder Settings for better, more personalized results.', 'ai-builder'), esc_url($settings_url) )); ?>
10, 'headers' => array( 'Authorization' => 'Bearer ' . $jwt_token, 'Content-Type' => 'application/json', ), )); if (is_wp_error($response)) { return false; } $code = wp_remote_retrieve_response_code($response); if ($code !== 200) { return false; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (!is_array($data)) { return false; } $user = isset($data['user']) ? $data['user'] : null; if ($user === null) { return false; } // Get hasGeneratedAIContent from user object (not from root data) $hasGeneratedAIContent = isset($user['hasGeneratedAIContent']) ? (bool) $user['hasGeneratedAIContent'] : true; // Cache the result for 10 seconds set_transient($cache_key, $hasGeneratedAIContent ? 'yes' : 'no', 10); return !$hasGeneratedAIContent; } // NOTE: the first-generation onboarding is now rendered via aibui_render_onboarding_blocks() // inside Account/Credits/Tutorial page templates (not as a global admin notice). // Charger les menus admin require_once plugin_dir_path(__FILE__) . 'admin/menu.php'; // Charger le gestionnaire AJAX require_once plugin_dir_path(__FILE__) . 'includes/class-ajax-handler.php'; // Charger le gestionnaire CSS require_once plugin_dir_path(__FILE__) . 'includes/class-css-handler.php'; // Charger le gestionnaire JS require_once plugin_dir_path(__FILE__) . 'includes/class-js-handler.php'; // Charger l'injection CSS/JS pour les templates et template parts (FSE). // Réutilise les post meta existantes (ai_builder_css_content / ai_builder_js_content) // stockées sur les posts wp_template et wp_template_part. require_once plugin_dir_path(__FILE__) . 'includes/class-template-assets-renderer.php'; // Charger le gestionnaire de traduction require_once plugin_dir_path(__FILE__) . 'includes/class-translation-handler.php'; require_once plugin_dir_path(__FILE__) . 'includes/class-translation-settings.php'; require_once plugin_dir_path(__FILE__) . 'includes/class-translation-manager.php'; require_once plugin_dir_path(__FILE__) . 'includes/class-translation-switcher.php'; // Charger les services de l'Agent Chat require_once plugin_dir_path(__FILE__) . 'includes/class-agent-discovery-service.php'; require_once plugin_dir_path(__FILE__) . 'includes/class-agent-security-service.php'; require_once plugin_dir_path(__FILE__) . 'includes/class-agent-execution-service.php'; require_once plugin_dir_path(__FILE__) . 'includes/class-agent-chat-handler.php'; // Initialiser le gestionnaire de traduction new AIBUI_Translation_Handler(); AIBUI_Translation_Settings::init(); $translation_manager = new AIBUI_Translation_Manager(); $translation_switcher = new AIBUI_Translation_Switcher($translation_manager); // Initialiser le gestionnaire de l'Agent Chat new AIBUI_Agent_Chat_Handler(); add_action('admin_enqueue_scripts', function ($hook) { // Charger le CSS admin sur toutes les pages d'administration wp_enqueue_style( 'ai-builder-admin-style', plugin_dir_url(__FILE__) . 'assets/css/style-admin.css', [], AIBUI_VERSION ); // Charger sur l'éditeur de page/article et l'éditeur de modèles (site editor) if ($hook === 'post.php' || $hook === 'post-new.php' || $hook === 'site-editor.php') { wp_enqueue_style( 'chat-widget-style', plugin_dir_url(__FILE__) . 'assets/css/chat-widget.css', [], AIBUI_VERSION ); wp_enqueue_script( 'ai-builder-config', plugin_dir_url(__FILE__) . 'config.js', [], AIBUI_VERSION, true ); wp_enqueue_script( 'chat-widget', plugin_dir_url(__FILE__) . 'assets/js/chat-widget.js', ['ai-builder-config'], AIBUI_VERSION, true ); // Styles tabs pour s'assurer du chargement dans l'éditeur wp_enqueue_style( 'ai-builder-tabs-css-admin-editor', plugin_dir_url(__FILE__) . 'assets/css/tabs.css', [], AIBUI_VERSION ); // Injection des variables JS pour AJAX et le nonce wp_localize_script( 'chat-widget', 'aiBuilderVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aibui_nonce'), 'adminBaseUrl' => admin_url(), // Base URL for plugin assets (e.g. chat style preview images) 'pluginUrl' => plugin_dir_url(__FILE__), // Flag to hint we are on the Site Editor (patterns/template parts) 'isPatternEditor' => ($hook === 'site-editor.php'), // WooCommerce detection 'wooCommerceInstalled' => aibui_is_woocommerce_installed(), // Active theme name 'activeThemeName' => aibui_get_active_theme_name(), // Active plugins context (max 15) 'sitePlugins' => aibui_get_active_plugins_context(), // Current WordPress version 'wordpressVersion' => aibui_get_wordpress_version(), // Shown in the diagnostics line under chat error messages 'pluginVersion' => AIBUI_VERSION, ) ); // Enqueue Multi-Page apply script to support applying generations via URL param wp_enqueue_script( 'ai-builder-multi-page-apply', plugin_dir_url(__FILE__) . 'assets/js/multi-page-apply.js', ['ai-builder-config', 'chat-widget', 'wp-blocks', 'wp-data', 'wp-editor', 'wp-block-editor', 'wp-element'], AIBUI_VERSION, true ); wp_localize_script( 'ai-builder-multi-page-apply', 'aiBuilderVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aibui_nonce'), // Keep editor context keys to avoid overriding data needed by chat-widget: // this localisation is printed last and replaces the object above. 'adminBaseUrl' => admin_url(), 'pluginUrl' => plugin_dir_url(__FILE__), 'pluginVersion' => AIBUI_VERSION, 'isPatternEditor' => ($hook === 'site-editor.php'), 'wooCommerceInstalled' => aibui_is_woocommerce_installed(), // Active theme name 'activeThemeName' => aibui_get_active_theme_name(), // Active plugins context (max 15) 'sitePlugins' => aibui_get_active_plugins_context(), // Current WordPress version 'wordpressVersion' => aibui_get_wordpress_version(), ) ); } $current_screen = get_current_screen(); // Charger les styles et scripts pour la page account du plugin AI Builder if ($current_screen && strpos($current_screen->id, 'aibui-assistant') !== false) { wp_enqueue_style( 'ai-builder-account-style', plugin_dir_url(__FILE__) . 'assets/css/account.css', [], filemtime(plugin_dir_path(__FILE__) . 'assets/css/account.css') ); wp_enqueue_script( 'ai-builder-config', plugin_dir_url(__FILE__) . 'config.js', [], AIBUI_VERSION, true ); wp_enqueue_script( 'ai-builder-account', plugin_dir_url(__FILE__) . 'assets/js/account.js', ['ai-builder-config'], AIBUI_VERSION, true ); // Injection des variables JS pour AJAX et le nonce wp_localize_script( 'ai-builder-account', 'aiBuilderVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aibui_nonce'), 'accountUrl' => admin_url('admin.php?page=aibui-account'), 'siteDomain' => parse_url(home_url(), PHP_URL_HOST), 'installationId' => aibui_get_installation_id(), ) ); } else if ($current_screen && strpos($current_screen->id, 'aibui-credits') !== false) { wp_enqueue_script( 'ai-builder-credits', plugin_dir_url(__FILE__) . 'assets/js/credits.js', ['ai-builder-config'], AIBUI_VERSION, true ); // Injection des variables JS pour AJAX et le nonce wp_localize_script( 'ai-builder-credits', 'aiBuilderVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aibui_nonce'), ) ); wp_enqueue_style( 'ai-builder-credits-additional-style', plugin_dir_url(__FILE__) . 'assets/css/credits-additional.css', [], AIBUI_VERSION ); wp_enqueue_style( 'ai-builder-credits-style', plugin_dir_url(__FILE__) . 'assets/css/credits.css', [], AIBUI_VERSION ); wp_enqueue_script( 'ai-builder-config', plugin_dir_url(__FILE__) . 'config.js', [], AIBUI_VERSION, true ); } else if ($current_screen && strpos($current_screen->id, 'aibui-settings') !== false) { wp_enqueue_style( 'ai-builder-settings-style', plugin_dir_url(__FILE__) . 'assets/css/settings.css', [], AIBUI_VERSION ); wp_enqueue_script( 'ai-builder-config', plugin_dir_url(__FILE__) . 'config.js', [], AIBUI_VERSION, true ); wp_enqueue_script( 'ai-builder-settings', plugin_dir_url(__FILE__) . 'assets/js/settings.js', ['ai-builder-config'], AIBUI_VERSION, true ); // Injection des variables JS pour AJAX et le nonce wp_localize_script( 'ai-builder-settings', 'aiBuilderVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aibui_nonce'), ) ); } else if ($current_screen && strpos($current_screen->id, 'aibui-reset-password') !== false) { wp_enqueue_style( 'ai-builder-reset-password-style', plugin_dir_url(__FILE__) . 'assets/css/reset-password.css', [], AIBUI_VERSION ); wp_enqueue_script( 'ai-builder-config', plugin_dir_url(__FILE__) . 'config.js', [], AIBUI_VERSION, true ); wp_enqueue_script( 'ai-builder-reset-password', plugin_dir_url(__FILE__) . 'assets/js/reset-password.js', ['ai-builder-config'], AIBUI_VERSION, true ); // Injection des variables JS pour AJAX et le nonce wp_localize_script( 'ai-builder-reset-password', 'aiBuilderVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aibui_nonce'), 'accountUrl' => admin_url('admin.php?page=aibui-account'), ) ); } else if ($current_screen && strpos($current_screen->id, 'aibui-tuto') !== false) { wp_enqueue_style( 'ai-builder-tutorial-style', plugin_dir_url(__FILE__) . 'assets/css/tutorial.css', [], AIBUI_VERSION ); wp_enqueue_script( 'ai-builder-config', plugin_dir_url(__FILE__) . 'config.js', [], AIBUI_VERSION, true ); } else if ($current_screen && strpos($current_screen->id, 'aibui-multi-page') !== false) { wp_enqueue_style( 'ai-builder-multi-page-style', plugin_dir_url(__FILE__) . 'assets/css/multi-page.css', [], AIBUI_VERSION ); wp_enqueue_script( 'ai-builder-config', plugin_dir_url(__FILE__) . 'config.js', [], AIBUI_VERSION, true ); wp_enqueue_script( 'ai-builder-multi-page', plugin_dir_url(__FILE__) . 'assets/js/multi-page.js', ['ai-builder-config'], AIBUI_VERSION, true ); // Injection des variables JS pour AJAX et le nonce wp_localize_script( 'ai-builder-multi-page', 'aiBuilderVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aibui_nonce'), 'adminBaseUrl' => admin_url(), 'wooCommerceInstalled' => aibui_is_woocommerce_installed(), // Active theme name 'activeThemeName' => aibui_get_active_theme_name(), // Active plugins context (max 15) 'sitePlugins' => aibui_get_active_plugins_context(), // Current WordPress version 'wordpressVersion' => aibui_get_wordpress_version(), ) ); } else if ($current_screen && strpos($current_screen->id, 'aibui-agent-chat') !== false) { // Agent Chat page scripts and styles wp_enqueue_script( 'ai-builder-config', plugin_dir_url(__FILE__) . 'config.js', [], AIBUI_VERSION, true ); wp_enqueue_script( 'ai-builder-agent-chat', plugin_dir_url(__FILE__) . 'assets/js/agent-chat.js', ['ai-builder-config'], AIBUI_VERSION, true ); // Localize with nonce - IMPORTANT: use a different nonce for agent actions wp_localize_script( 'ai-builder-agent-chat', 'aibuiAgentVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aibui_agent_nonce'), 'restBase' => esc_url_raw(rest_url()), 'wpRestDocsBase' => 'https://developer.wordpress.org/rest-api/reference/', ) ); // Also expose standard AJAX nonce for shared endpoints like aibui_get_token wp_localize_script( 'ai-builder-agent-chat', 'aiBuilderVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aibui_nonce'), ) ); } // Bandeau de review : uniquement sur les pages admin du plugin if ($current_screen) { $screen_id = $current_screen->id; $is_plugin_screen = strpos($screen_id, 'aibui-assistant') !== false || strpos($screen_id, 'aibui-credits') !== false || strpos($screen_id, 'aibui-tuto') !== false || strpos($screen_id, 'aibui-multi-page') !== false || strpos($screen_id, 'aibui-agent-chat') !== false || strpos($screen_id, 'aibui-translation-settings') !== false || strpos($screen_id, 'aibui-headers-footers') !== false || strpos($screen_id, 'aibui-settings') !== false; if ($is_plugin_screen) { // S'assurer que config.js est chargé wp_enqueue_script( 'ai-builder-config', plugin_dir_url(__FILE__) . 'config.js', [], AIBUI_VERSION, true ); wp_enqueue_script( 'ai-builder-review-banner', plugin_dir_url(__FILE__) . 'assets/js/review-banner.js', ['ai-builder-config'], AIBUI_VERSION, true ); wp_localize_script( 'ai-builder-review-banner', 'aiBuilderReviewVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aibui_nonce'), 'reviewUrl' => 'https://wordpress.org/support/plugin/ai-builder/reviews/#new-post', ) ); } } }); add_action('wp_enqueue_scripts', function () { // Single combined frontend CSS $combined_css = aibui_get_combined_css_url(); if ($combined_css) { wp_enqueue_style( 'ai-builder-combined', $combined_css, [], null ); } else { // La combinaison a échoué : uploads/ non inscriptible (système de // fichiers en lecture seule, droits, ou déport des médias vers S3 qui // fait pointer wp_upload_dir() ailleurs). On ne se contentait alors que // de force-align-full.css, et tous les blocs perdaient leur style. // On sert désormais les mêmes feuilles, individuellement : plus de // requêtes, mais le rendu reste correct. $css_url_base = plugin_dir_url(__FILE__) . 'assets/css/'; foreach (aibui_get_frontend_css_paths() as $path) { $basename = basename($path); wp_enqueue_style( 'aibui-css-' . sanitize_key(pathinfo($basename, PATHINFO_FILENAME)), $css_url_base . $basename, [], AIBUI_VERSION ); } } // Charger les styles CSS du bloc AI Image côté frontend wp_enqueue_style( 'ai-builder-ai-image-frontend', plugin_dir_url(__FILE__) . 'assets/js/build/index.css', [], filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.css') ); // Frontend JS single bundle $combined_js = aibui_get_combined_js_url(); if ($combined_js) { wp_enqueue_script( 'ai-builder-frontend-bundle', $combined_js, [], null, true ); } else { // Même situation que pour le CSS : on ne chargeait que le carrousel, et // onglets, tableaux, cartes, snackbars et infobulles de stats cessaient // de fonctionner. On sert désormais tous les scripts du bundle. foreach (aibui_get_frontend_js_rel_paths() as $rel) { if (!file_exists(plugin_dir_path(__FILE__) . $rel)) { continue; } wp_enqueue_script( 'aibui-js-' . sanitize_key(pathinfo($rel, PATHINFO_FILENAME)), plugin_dir_url(__FILE__) . $rel, [], AIBUI_VERSION, true ); } } // Load fixed background group block CSS for frontend wp_enqueue_style( 'ai-builder-fixed-bg-group-frontend', plugin_dir_url(__FILE__) . 'assets/js/src/editor-blocks/fixed-bg-group/style.css', [], AIBUI_VERSION ); // CSS above is covered by the combined bundle // Other JS are included in the combined bundle above // CSS above is covered by the combined bundle // Included in combined bundle // CSS above is covered by the combined bundle // Included in combined bundle }); // Enqueue contact form script only when the block is present add_action('wp_enqueue_scripts', function () { global $post; $should_load = false; // Check if current post has the block if (is_a($post, 'WP_Post') && has_block('ai-builder/aibui-contact-form', $post)) { $should_load = true; } // Fallback: check if we're on a page that might have the block if (!$should_load && (is_page() || is_single() || is_home() || is_front_page())) { $should_load = true; } if ($should_load) { wp_enqueue_script( 'ai-builder-contact-form', plugin_dir_url(__FILE__) . 'assets/js/contact-form.js', [], AIBUI_VERSION, true ); } }); add_action('enqueue_block_editor_assets', function () { // Single combined CSS for editor $combined_css = aibui_get_combined_css_url(); if ($combined_css) { wp_enqueue_style( 'ai-builder-combined-editor', $combined_css, [], null ); } else { wp_enqueue_style( 'aibui-force-alignfull-editor', plugin_dir_url(__FILE__) . 'assets/css/force-align-full.css', [], AIBUI_VERSION ); } // Charger les styles build des blocs dans l'éditeur wp_enqueue_style( 'ai-builder-blocks-editor-build', plugin_dir_url(__FILE__) . 'assets/js/build/index.css', [], filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.css') ); // Styles spécifiques d'aperçu du formulaire de contact wp_enqueue_style( 'ai-builder-contact-form-editor', plugin_dir_url(__FILE__) . 'assets/css/contact-form-editor.css', [], AIBUI_VERSION ); // Covered by combined bundle // Stats tooltips in editor preview wp_enqueue_script( 'ai-builder-stats-tooltips-editor', plugin_dir_url(__FILE__) . 'assets/js/stats-tooltips.js', [], AIBUI_VERSION, true ); }); add_action('enqueue_block_editor_assets', function () { wp_enqueue_script( 'ai-builder-blocks', plugin_dir_url(__FILE__) . 'assets/js/build/index.js', [ 'wp-blocks', 'wp-block-editor', 'wp-element', 'wp-components', 'wp-i18n', 'wp-api-fetch', 'wp-hooks' ], filemtime(plugin_dir_path(__FILE__) . 'assets/js/build/index.js'), true ); // Injection des variables JS pour AJAX et le nonce (utilisées par les blocs éditeur). // wp_localize_script réécrit l'objet entier : la dernière localisation imprimée // l'emporte. adminBaseUrl et pluginUrl doivent donc figurer ici aussi, sans quoi // le JS retomberait sur des chemins codés en dur, faux en sous-répertoire. wp_localize_script( 'ai-builder-blocks', 'aiBuilderVars', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aibui_nonce'), 'adminBaseUrl' => admin_url(), 'pluginUrl' => plugin_dir_url(__FILE__), ) ); wp_enqueue_script( 'ai-builder-language-switcher-block', plugin_dir_url(__FILE__) . 'assets/js/language-switcher-block.js', array('wp-blocks', 'wp-block-editor', 'wp-element', 'wp-components', 'wp-i18n'), filemtime(plugin_dir_path(__FILE__) . 'assets/js/language-switcher-block.js'), true ); // Unregister legacy/unused AI Builder blocks from the inserter wp_enqueue_script( 'ai-builder-unregister-ai-blocks', plugin_dir_url(__FILE__) . 'assets/js/unregister-ai-blocks.js', array('ai-builder-blocks', 'wp-blocks'), AIBUI_VERSION, true ); // CSS Class Inspector: button in block sidebar to jump to CSS wp_enqueue_script( 'ai-builder-css-class-inspector', plugin_dir_url(__FILE__) . 'assets/js/css-class-inspector.js', array('wp-hooks', 'wp-compose', 'wp-block-editor', 'wp-components', 'wp-element', 'wp-i18n'), AIBUI_VERSION, true ); $translation_settings = AIBUI_Translation_Settings::get_settings(); $supported_languages = AIBUI_Translation_Handler::get_supported_languages(); $available_langs = isset($translation_settings['available_langs']) && is_array($translation_settings['available_langs']) ? array_values(array_unique($translation_settings['available_langs'])) : array(); $default_lang = isset($translation_settings['default_lang']) ? $translation_settings['default_lang'] : 'en'; if ($default_lang && !in_array($default_lang, $available_langs, true)) { array_unshift($available_langs, $default_lang); } $switcher_defaults = array( 'backgroundColor' => !empty($translation_settings['switcher_bg']) ? $translation_settings['switcher_bg'] : '', 'textColor' => !empty($translation_settings['switcher_text']) ? $translation_settings['switcher_text'] : '', 'minWidth' => !empty($translation_settings['switcher_min_width']) ? (int) $translation_settings['switcher_min_width'] : 90, 'minHeight' => !empty($translation_settings['switcher_min_height']) ? (int) $translation_settings['switcher_min_height'] : 30, 'borderRadius' => 22, ); wp_localize_script( 'ai-builder-language-switcher-block', 'aiBuilderLangSwitch', array( 'availableLangs' => $available_langs, 'defaultLang' => $default_lang, 'labels' => $supported_languages, 'switcherEnabled' => !empty($translation_settings['enable_switcher']), 'defaults' => $switcher_defaults, ) ); }); // Enregistrer le bloc côté PHP add_action('init', function () use ($translation_switcher) { wp_register_style( 'ai-builder-language-switcher-style', plugin_dir_url(__FILE__) . 'assets/css/language-switcher.css', array(), AIBUI_VERSION ); // Bloc AI Block register_block_type('ai-builder/ai-block', [ 'editor_script' => 'ai-builder-blocks', 'editor_style' => 'ai-builder-blocks-style', 'style' => 'ai-builder-blocks-style', 'render_callback' => function ($attributes) { // Le bloc AI Block ne s'affiche pas côté frontend car il est remplacé par le contenu généré return ''; } ]); // Bloc stat-bar (statique, pas de render_callback) register_block_type('ai-builder/stat-bar', [ 'editor_script' => 'ai-builder-blocks', 'editor_style' => 'ai-builder-blocks-style', 'style' => 'ai-builder-blocks-style', ]); // Bloc aibui-stats (rendu simple côté éditeur, frontend statique) register_block_type('ai-builder/aibui-stats', [ 'editor_script' => 'ai-builder-blocks', 'editor_style' => 'ai-builder-blocks-style', 'style' => 'ai-builder-blocks-style', ]); // Bloc aibui-carousel register_block_type('ai-builder/aibui-carousel', [ 'editor_script' => 'ai-builder-blocks', 'editor_style' => 'ai-builder-blocks-style', 'style' => 'ai-builder-blocks-style', ]); // Bloc aibui-yt-video register_block_type('ai-builder/aibui-yt-video', [ 'editor_script' => 'ai-builder-blocks', 'editor_style' => 'ai-builder-blocks-style', 'style' => 'ai-builder-blocks-style', ]); // Bloc aibui-map register_block_type('ai-builder/aibui-map', [ 'editor_script' => 'ai-builder-blocks', 'editor_style' => 'ai-builder-blocks-style', 'style' => 'ai-builder-blocks-style', ]); // Bloc aibui-tabs // Bloc aibui-table register_block_type('ai-builder/aibui-table', [ 'editor_script' => 'ai-builder-blocks', 'editor_style' => 'ai-builder-blocks-style', 'style' => 'ai-builder-blocks-style', ]); register_block_type('ai-builder/aibui-tabs', [ 'editor_script' => 'ai-builder-blocks', 'editor_style' => 'ai-builder-blocks-style', 'style' => 'ai-builder-blocks-style', ]); // Bloc aibui-contact-form (rendu via callback PHP pour sécurité) register_block_type('ai-builder/aibui-contact-form', [ 'editor_script' => 'ai-builder-blocks', 'editor_style' => 'ai-builder-blocks-style', 'style' => 'ai-builder-blocks-style', 'render_callback' => function ($attributes, $content, $block) { $recipient = isset($attributes['recipientEmail']) ? sanitize_email($attributes['recipientEmail']) : ''; if (empty($recipient) || !is_email($recipient)) { $recipient = sanitize_email(get_option('admin_email')); } if (empty($recipient) || !is_email($recipient)) { // Pas de destinataire valide disponible, n'arrête pas l'affichage, mais désactive l'envoi $recipient = ''; } $fields = isset($attributes['fields']) && is_array($attributes['fields']) ? $attributes['fields'] : []; $fields = array_slice($fields, 0, 5); $form_title = isset($attributes['formTitle']) ? wp_kses_post($attributes['formTitle']) : ''; $description = isset($attributes['description']) ? wp_kses_post($attributes['description']) : ''; $button_label = isset($attributes['buttonLabel']) ? sanitize_text_field($attributes['buttonLabel']) : __('Send', 'ai-builder'); $success_message = isset($attributes['successMessage']) ? sanitize_text_field($attributes['successMessage']) : __('Thank you, your message has been sent.', 'ai-builder'); $from_email_attr = isset($attributes['fromEmail']) ? sanitize_email($attributes['fromEmail']) : ''; $nonce = wp_create_nonce('aibui_contact_form'); $action = esc_url(admin_url('admin-ajax.php')); ob_start(); ?>

$field): $label = isset($field['label']) ? sanitize_text_field($field['label']) : ''; $type = isset($field['type']) ? sanitize_text_field($field['type']) : 'text'; $required = !empty($field['required']); $name = 'field_' . $idx; ?>
'ai-builder-language-switcher-block', 'editor_style' => 'ai-builder-language-switcher-style', 'style' => 'ai-builder-language-switcher-style', 'render_callback' => array($translation_switcher, 'render_block'), 'attributes' => [ 'backgroundColor' => ['type' => 'string', 'default' => '#5686c9'], 'textColor' => ['type' => 'string', 'default' => '#ffffff'], 'minWidth' => ['type' => 'number', 'default' => 90], 'minHeight' => ['type' => 'number', 'default' => 30], 'borderRadius' => ['type' => 'number', 'default' => 22], ], 'supports' => [ 'align' => ['left', 'center', 'right', 'wide', 'full'], ], ]); }); add_action('after_setup_theme', function () { add_theme_support('align-wide'); }); function enqueue_ai_builder_scripts() { // Charger config.js en premier (dépendance pour les autres scripts) wp_enqueue_script( 'ai-builder-config', plugins_url('config.js', __FILE__), array(), AIBUI_VERSION, false // Charger dans le pour être disponible partout ); // Autres scripts qui utilisent config.js wp_enqueue_script( 'ai-builder-image-ai-controls', plugins_url('assets/js/src/editor-blocks/image-ai-blocks/image-ai-controls.js', __FILE__), array('ai-builder-config'), AIBUI_VERSION, true ); // Autres scripts qui utilisent config.js wp_enqueue_script( 'ai-builder-text-ai-controls', plugins_url('assets/js/src/editor-blocks/text-ai-blocks/text-ai-controls.js', __FILE__), array('ai-builder-config'), AIBUI_VERSION, true ); // Autres scripts qui utilisent config.js wp_enqueue_script( 'ai-builder-text-ai-controls', plugins_url('assets/js/src/editor-blocks/text-ai-blocks/text-ai-controls.js', __FILE__), array('ai-builder-config'), AIBUI_VERSION, true ); // Autres scripts qui utilisent config.js wp_enqueue_script( 'ai-builder-ai-block', plugins_url('assets/js/src/editor-blocks/ai-block/ai-block.js', __FILE__), array('ai-builder-config'), AIBUI_VERSION, true ); // Localize WooCommerce detection for block editor scripts wp_localize_script( 'ai-builder-config', 'aiBuilderEditorVars', array( 'wooCommerceInstalled' => aibui_is_woocommerce_installed(), // Active theme name 'activeThemeName' => aibui_get_active_theme_name(), ) ); } add_action('enqueue_block_editor_assets', 'enqueue_ai_builder_scripts'); add_action('wp_enqueue_scripts', function () { // Combined CSS already enqueued above; only enqueue any extra block scss-generated styles if needed // Table, snackbar, cards editor styles are not required on frontend separately when bundled // Load snackbar frontend JavaScript wp_enqueue_script( 'ai-builder-snackbar-frontend', plugin_dir_url(__FILE__) . 'assets/js/snackbar-frontend.js', [], AIBUI_VERSION, true ); }); add_action('enqueue_block_editor_assets', function () { // Combined CSS already enqueued above; keep editor-specific assets below // Load fixed background group block CSS specifically (already inside combined, but keep safe fallback if combined failed) $fixed_bg_css_url = plugin_dir_url(__FILE__) . 'assets/js/src/editor-blocks/fixed-bg-group/style.css'; wp_enqueue_style('ai-builder-fixed-bg-group-editor', $fixed_bg_css_url, [], AIBUI_VERSION); // Load WordPress media scripts for image selection wp_enqueue_media(); }); // ------------------------------- // Meta description per page/post // ------------------------------- add_action('add_meta_boxes', function () { add_meta_box( 'aibui_meta_description', __('Meta description', 'ai-builder'), function ($post) { $value = get_post_meta($post->ID, 'aibui_meta_description', true); wp_nonce_field('aibui_meta_description_save', 'aibui_meta_description_nonce'); echo '

' . esc_html__('Add a meta description for this page/post.', 'ai-builder') . '

'; echo ''; echo '

' . esc_html__('Tip: 140–160 characters for best SEO results.', 'ai-builder') . '

'; }, array('post', 'page'), 'normal', 'default' ); }); add_action('save_post', function ($post_id) { if (!isset($_POST['aibui_meta_description_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['aibui_meta_description_nonce'])), 'aibui_meta_description_save')) { return; } if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return; if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) return; if (!current_user_can('edit_post', $post_id)) return; $raw = isset($_POST['aibui_meta_description']) ? wp_unslash($_POST['aibui_meta_description']) : ''; $san = trim(wp_strip_all_tags($raw)); if (strlen($san) > 320) { $san = mb_substr($san, 0, 320); } if ($san === '') { delete_post_meta($post_id, 'aibui_meta_description'); } else { update_post_meta($post_id, 'aibui_meta_description', $san); } }); add_action('wp_head', function () { if (is_admin() || !is_singular()) { return; } $post_id = get_queried_object_id(); if (!$post_id) { return; } $possible_keys = array( 'aibui_meta_description', '_ai_builder_seo_desc', '_yoast_wpseo_metadesc', '_ai_translation_meta_desc' ); $desc = ''; foreach ($possible_keys as $meta_key) { $value = get_post_meta($post_id, $meta_key, true); if (!empty($value)) { $desc = $value; break; } } if (!$desc) { $excerpt = get_post_field('post_excerpt', $post_id); if (!empty($excerpt)) { $desc = $excerpt; } } $desc = trim(wp_strip_all_tags((string) $desc)); if ($desc !== '') { echo "\n\n"; } }, 1); // Initialiser le gestionnaire AJAX new AIBUI_Ajax_Handler(); // AJAX: create a header or footer template part for the active block theme add_action('wp_ajax_aibui_create_template_part', function () { check_ajax_referer('aibui_nonce', 'nonce'); if (!current_user_can('edit_theme_options')) { wp_send_json_error('Permission denied'); } $area = sanitize_text_field($_POST['area'] ?? ''); if (!in_array($area, array('header', 'footer'), true)) { wp_send_json_error('Invalid area'); } $theme_slug = get_stylesheet(); $title = $area === 'header' ? 'Header' : 'Footer'; $post_id = wp_insert_post(array( 'post_title' => $title, 'post_name' => $area, 'post_content' => '', 'post_status' => 'publish', 'post_type' => 'wp_template_part', )); if (is_wp_error($post_id)) { wp_send_json_error($post_id->get_error_message()); } wp_set_object_terms($post_id, $area, 'wp_template_part_area'); wp_set_object_terms($post_id, $theme_slug, 'wp_theme'); $edit_url = admin_url( 'site-editor.php?postType=wp_template_part&postId=' . urlencode($theme_slug . '//' . $area) . '&canvas=edit' ); wp_send_json_success(array('edit_url' => $edit_url)); }); // ------------------------------- // Multi-Page Generator: Cleanup cron and migration // ------------------------------- function aibui_cleanup_old_generations() { require_once plugin_dir_path(__FILE__) . 'includes/class-generations-storage.php'; $storage = new AIBUI_Generations_Storage(); $deleted_count = $storage->cleanup_old(30); // Delete applied generations older than 30 days } add_action('aibui_daily_cleanup', 'aibui_cleanup_old_generations'); // Schedule daily cleanup if not already scheduled if (!wp_next_scheduled('aibui_daily_cleanup')) { wp_schedule_event(time(), 'daily', 'aibui_daily_cleanup'); } // Migrate old wp_options data to files (one-time migration on activation/update) function aibui_migrate_generations_to_files() { // Check if migration already done if (get_option('aibui_generations_migrated_to_files', false)) { return; } require_once plugin_dir_path(__FILE__) . 'includes/class-generations-storage.php'; $storage = new AIBUI_Generations_Storage(); $migrated_count = $storage->migrate_from_options(); if ($migrated_count > 0) { // Mark migration as done update_option('aibui_generations_migrated_to_files', true, false); } } // Run migration on admin init (only once) add_action('admin_init', function() { static $migration_done = false; if (!$migration_done && current_user_can('manage_options')) { aibui_migrate_generations_to_files(); $migration_done = true; } }, 5);