0 ? $assigned_agent_id : $default_agent_id; $agent = $agent_id ? get_post( $agent_id ) : null; // No agent resolved — don't show the chatbot. if ( ! $agent_id ) { return; } // ── Resolve agent config ────────────────────────────────────────── // Defaults cover the case where $agent_id resolves but the post is // missing or unpublished (e.g. a trashed default agent). $welcome_msg = __( 'Hi! How can I help you today?', 'salyut-agents' ); $service_options = []; $logo_type = 'salyut-agents'; if ( $agent && $agent->post_status === 'publish' ) { $welcome_msg = get_post_meta( $agent_id, '_salyut_agents_agent_welcome', true ); if ( empty( $welcome_msg ) ) { $welcome_msg = __( 'Hi! How can I help you today?', 'salyut-agents' ); } $buttons_raw = get_post_meta( $agent_id, '_salyut_agents_agent_buttons', true ); $decoded = json_decode( $buttons_raw ?: '[]', true ); $service_options = array_values( array_filter( array_map( function ( $b ) { if ( is_string( $b ) && $b !== '' ) { return [ 'label' => $b, 'message' => $b ]; } return ( isset( $b['label'] ) && $b['label'] !== '' ) ? $b : null; }, (array) $decoded ) ) ); $logo_type = 'avatar'; } // Still needed for the standalone 'assetsUrl' config key below even // though avatar resolution itself moved into salyut_agents_get_agent_ // display_info() (ea442c4) — that commit correctly dropped this // variable's OWN avatar-resolution use but missed that the same // variable backs this separate output key too, leaving it undefined. $assets_url = plugins_url( 'assets/images/', SALYUT_AGENTS_PLUGIN_FILE ); // Same resolution includes/rest.php's own reply payload uses to report // "who actually answered" — one shared source of truth for name/avatar/ // department (defined in includes/rest.php, loaded well before this // wp_enqueue_scripts callback ever runs), never two copies that could // quietly drift apart. $agent_display = salyut_agents_get_agent_display_info( $agent_id ); $agent_name = $agent_display['name']; $agent_dept = $agent_display['department']; $avatar_url = $agent_display['avatar']; // ── Resolve handoff config ─────────────────────────────────────── $handoff_raw = get_post_meta( $agent_id, '_salyut_agents_agent_handoff', true ); $handoff_statement = ! empty( $handoff_raw ) ? $handoff_raw : sprintf( __( "Would you like to talk to an expert in this department?", 'salyut-agents' ), $agent_name ); $silent_handoff = get_post_meta( $agent_id, '_salyut_agents_agent_silent_handoff', true ) === '1'; // ── Resolve widget behaviour config ────────────────────────────── // openDelay is stored in seconds but passed to JS in milliseconds, // consistent with how inactivityLimit is handled on the JS side. $open_delay_seconds = (int) get_option( 'salyut_agents_open_delay', 0 ); // ── Scripts — native ES modules, no build step ──────────────────── // // api.js/ui.js/session.js are registered (not enqueued) and declared as // static deps of the entry module. This gets them into WP's dependency // walk for translation discovery (api.js/ui.js carry __()/sprintf() // calls) and the printed import map — matching the bare specifiers // ('salyut-agents/api' etc.) used in salyut-agents-frontend.js, so the // browser resolves them through the import map at their cache-busted // ?ver= URL rather than as plain unversioned relative fetches. wp_register_script_module( 'salyut-agents/session', plugins_url( 'resources/scripts/session.js', __DIR__ ), [], salyut_agents_asset_ver( $scripts_dir . 'session.js' ), [ 'in_footer' => true ] ); wp_register_script_module( 'salyut-agents/api', plugins_url( 'resources/scripts/api.js', __DIR__ ), [], salyut_agents_asset_ver( $scripts_dir . 'api.js' ), [ 'in_footer' => true ] ); wp_set_script_module_translations( 'salyut-agents/api', 'salyut-agents' ); wp_register_script_module( 'salyut-agents/ui', plugins_url( 'resources/scripts/ui.js', __DIR__ ), [], salyut_agents_asset_ver( $scripts_dir . 'ui.js' ), [ 'in_footer' => true ] ); wp_set_script_module_translations( 'salyut-agents/ui', 'salyut-agents' ); // Generic card engine — registry + safe renderer + plain-text fallback. // Carries no booking (or any addon-specific) knowledge; see // resources/scripts/cards.js's own header. wp_register_script_module( 'salyut-agents/cards', plugins_url( 'resources/scripts/cards.js', __DIR__ ), [], salyut_agents_asset_ver( $scripts_dir . 'cards.js' ), [ 'in_footer' => true ] ); wp_set_script_module_translations( 'salyut-agents/cards', 'salyut-agents' ); $frontend_deps = [ 'salyut-agents/api', 'salyut-agents/ui', 'salyut-agents/session', 'salyut-agents/cards' ]; /** * Filters the script module dependencies of 'salyut-agents/frontend' * (this file's own entry point, whose domReady() callback does the * history replay on every page load). * * An addon that registers its own card renderer(s) via cards.js's * registerCardRenderer() — called at that module's own top level, the * instant it executes — has NO dependency relationship with this * entry point by default: Script Modules are independent, parallel- * loading entry points unless one explicitly depends on the other. * On a page load where this file's own (small) dependency chain * happens to finish executing before the addon's own (larger) one * has, cards.js's renderCard() finds no renderer registered yet for * that type and falls back to showing the row's plain text — exactly * the behavior meant for a genuinely unknown card type, wrongly * triggered here by a loading-order race instead. * * An addon hooks this (at plain file-load time, NOT inside its own * wp_enqueue_scripts callback, so the hook is already registered no * matter which plugin's wp_enqueue_scripts callback happens to run * first) to add its own card-renderer module name(s) here, so the * Script Modules API's own dependency graph — not loading-order luck * — guarantees they have fully executed, registerCardRenderer() calls * included, before this entry point's own replay logic ever runs. * * @since 1.1.0 * * @param string[] $deps Module names, e.g. 'salyut-agents/cards'. */ $frontend_deps = apply_filters( 'salyut_agents_frontend_script_module_deps', $frontend_deps ); wp_enqueue_script_module( 'salyut-agents/frontend', plugins_url( 'resources/scripts/salyut-agents-frontend.js', __DIR__ ), $frontend_deps, salyut_agents_asset_ver( $scripts_dir . 'salyut-agents-frontend.js' ), [ 'in_footer' => true ] ); wp_set_script_module_translations( 'salyut-agents/frontend', 'salyut-agents' ); // @wordpress/i18n is not an importable script module on WP core — our // modules read window.wp.i18n directly (see resources/scripts/*.js), so // the classic wp-i18n script must be present. Classic scripts always run // before deferred `type="module"` scripts, so a plain enqueue here // (no special ordering) guarantees it's ready in time. wp_enqueue_script( 'wp-i18n' ); // salyutAgentsObj config global. wp_localize_script() only attaches data // to classic script handles, not script modules, so this is printed as // its own classic inline script — same JSON_HEX_TAG | JSON_UNESCAPED_SLASHES // escaping wp_localize_script() itself uses — piggybacked on wp-i18n // purely as a place to hang it; it has no dependency on wp-i18n's own // code and, like that script, always executes before the deferred module // that reads window.salyutAgentsObj. $salyut_agents_frontend_cfg = [ 'messageUrl' => esc_url_raw( rest_url( 'salyut-agents/v1/message' ) ), 'sessionUrl' => esc_url_raw( rest_url( 'salyut-agents/v1/session' ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'welcomeMessage' => $welcome_msg, 'serviceOptions' => $service_options, 'showServiceOptions' => ! empty( $service_options ), 'inactivityLimit' => SALYUT_AGENTS_INACTIVITY_LIMIT, 'maxMessageLength' => SALYUT_AGENTS_MAX_MESSAGE_LENGTH, 'assetsUrl' => $assets_url, 'avatarUrl' => $avatar_url, 'agentName' => $agent_name, 'agentDept' => $agent_dept, 'agentId' => $agent_id, 'supportEmail' => get_option( 'salyut_agents_support_email', '' ), 'contactUrl' => get_option( 'salyut_agents_contact_url', '' ), 'debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, 'postId' => $page_id, 'pageTitle' => get_the_title(), 'handoffStatement' => $handoff_statement, 'silentHandoff' => $silent_handoff, // Widget visibility / behaviour 'startMinimized' => (bool) get_option( 'salyut_agents_start_minimized', false ), 'openDelay' => $open_delay_seconds * 1000, // JS expects milliseconds 'scrollThreshold' => (int) get_option( 'salyut_agents_scroll_threshold', 0 ), // Cloudflare Turnstile — public site key + enabled flag only. The SECRET // key is intentionally never localized to JS; it is read server-side only. 'turnstileEnabled' => (bool) get_option( 'salyut_agents_turnstile_enabled', false ), 'turnstileSiteKey' => get_option( 'salyut_agents_turnstile_site_key', '' ), /** * Filters the card types the client-side registry should expect. * * Default empty — no addon has registered a card type yet, so the * widget never looks for a renderer that will never arrive and * every card falls through to the plain-text content column * instead (see resources/scripts/cards.js). * * @since 1.1.0 * * @param array $types Keyed by card type slug: ['schema_version' => int, 'label' => string]. */ 'cardTypes' => apply_filters( 'salyut_agents_card_types', [] ), ]; /** * Filters the whole frontend config object before it is sent to the * browser. Default: $cfg unchanged — every key above is already set by * the time this runs, so a no-op listener changes nothing. * * @since 1.1.0 * * @param array $cfg The config object about to be JSON-encoded. * @param int $agent_id Resolved agent ID for this page load. * @param int $page_id Current page/post ID. * @param string $surface 'widget' here; a future drop-in/block surface * passes its own value through its own enqueue. */ $salyut_agents_frontend_cfg = apply_filters( 'salyut_agents_frontend_config', $salyut_agents_frontend_cfg, $agent_id, $page_id, 'widget' ); wp_add_inline_script( 'wp-i18n', 'window.salyutAgentsObj = ' . wp_json_encode( $salyut_agents_frontend_cfg, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';' ); // ── Styles ────────────────────────────────────────────────────────── wp_enqueue_style( 'salyut-agents-frontend-css', plugins_url( 'resources/styles/salyut-agents-frontend.css', __DIR__ ), [], salyut_agents_asset_ver( $styles_dir . 'salyut-agents-frontend.css' ) ); // User-configured widget colors — generated in PHP, delivered as an // inline addition to the frontend stylesheet rather than a raw