AIVASTARK_WIDGET_BAKED_KEY, 'personalize_users' => false, ), $opts ); } /** * Build the logged-in visitor's profile for the widget, or null. * * This is what makes the widget "user aware" with no code on the site owner's * part: WordPress already knows who is logged in at render time, so the profile * is emitted straight into the page. Returns null unless the owner opted in AND * somebody is actually logged in — anonymous visitors are untouched. * * `id` is the WordPress user ID: stable per site, so Aivastark recognizes the * same person across devices and browsers. * * TRUST: this is Level 1 (UNVERIFIED) identity. It is rendered into the page and * travels from the browser, so it is for greeting/personalization only — never * for gating anything. See docs/widget/user-context-identity.md. * * Extensible: filter `aivastark_widget_user_context` receives the array and the * WP_User, so a theme or plugin can add plan / company / traits (WooCommerce * membership tier, subscription plan, lifetime value, ...). WordPress core has * no concept of a "plan", so this function never invents one. * * @return array|null */ function aivastark_widget_user_context() { $opts = aivastark_widget_get_options(); if ( empty( $opts['personalize_users'] ) ) { return null; } if ( ! is_user_logged_in() ) { return null; } $user = wp_get_current_user(); if ( ! $user || 0 === (int) $user->ID ) { return null; } $ctx = array( 'id' => (string) $user->ID ); $name = trim( (string) $user->display_name ); if ( '' !== $name ) { $ctx['name'] = $name; } $email = trim( (string) $user->user_email ); if ( '' !== $email && is_email( $email ) ) { $ctx['email'] = $email; } // The primary role is the closest useful signal WordPress has. It goes in // traits rather than masquerading as a subscription "plan". $roles = array_values( (array) $user->roles ); if ( ! empty( $roles[0] ) ) { $ctx['traits'] = array( 'role' => (string) $roles[0] ); } // Sign the id with the project's signing secret, if we hold one. This is // what upgrades WordPress from "display-only identity" to an id Aivastark's // backend can actually rely on — computed here, on the server, so the secret // never reaches a browser. The site owner writes no code for this: the // secret arrives automatically when they click Connect. // // Only the id is signed, matching the server-side verifier // (_shared/agent/host_identity.ts). Everything else stays self-reported. $secret = Aivastark_Widget_Options::get_signing_secret(); if ( '' !== $secret ) { $ctx['signature'] = hash_hmac( 'sha256', $ctx['id'], $secret ); } /** * Filter the user context sent to Aivastark. * * @param array $ctx { id, name?, email?, traits? } — `id` is required. * @param WP_User $user The current user. */ $ctx = apply_filters( 'aivastark_widget_user_context', $ctx, $user ); // A filter that strips the id (or returns junk) leaves no identity to send. if ( ! is_array( $ctx ) || empty( $ctx['id'] ) ) { return null; } return $ctx; } /** * Enqueue the widget for the given key. Idempotent — inline data is only * attached the first time, so multiple shortcodes on one page are safe. */ function aivastark_widget_enqueue( $key ) { static $attached = false; $key = trim( (string) $key ); if ( '' === $key ) { return; } wp_enqueue_script( 'aivastark-widget' ); if ( $attached ) { return; } $attached = true; $config = array( 'key' => $key ); $user_ctx = aivastark_widget_user_context(); if ( null !== $user_ctx ) { $config['user'] = $user_ctx; // This page now contains one specific person's name/email, so it must // never be served to anyone else from a full-page cache. WP Rocket, // LiteSpeed Cache, W3 Total Cache and WP Super Cache all honour this // constant. Defined ONLY on personalized responses — defining it on // anonymous page views would disable the site's page cache wholesale. if ( ! defined( 'DONOTCACHEPAGE' ) ) { define( 'DONOTCACHEPAGE', true ); } } wp_add_inline_script( 'aivastark-widget', 'window.AivastarkConfig=' . wp_json_encode( $config, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';', 'before' ); } /** * Origin of the Aivastark dashboard SPA (OAuth approval + embed iframe). * Filterable so staging / self-hosted dashboards can be targeted. */ function aivastark_widget_dashboard_url() { $url = apply_filters( 'aivastark_widget_dashboard_url', AIVASTARK_WIDGET_DASHBOARD_URL ); return untrailingslashit( esc_url_raw( $url ) ); } /** * Base URL of the Aivastark Supabase Edge Functions (pairing + SSO). Filterable. */ function aivastark_widget_functions_url() { $url = apply_filters( 'aivastark_widget_functions_url', AIVASTARK_WIDGET_FUNCTIONS_URL ); return untrailingslashit( esc_url_raw( $url ) ); } /** * Public (publishable) anon key sent as the `apikey` header to the Edge * Functions gateway. Public value; filterable for staging. */ function aivastark_widget_anon_key() { return (string) apply_filters( 'aivastark_widget_anon_key', AIVASTARK_WIDGET_ANON_KEY ); } /** * Mask a Widget Key for display (show only the last 4 characters). */ function aivastark_widget_mask_key( $key ) { $key = trim( (string) $key ); if ( strlen( $key ) <= 4 ) { return $key; } return str_repeat( '•', max( 0, strlen( $key ) - 4 ) ) . substr( $key, -4 ); }