woocommerce_active() ) { add_action( 'admin_notices', array( $this, 'woocommerce_missing_notice' ) ); return; } Xpay_REST::instance(); Xpay_Robots::instance(); Xpay_Schema::instance(); Xpay_Cart::instance(); Xpay_Webhooks::instance(); Xpay_Settings::instance(); Xpay_Emitter_Probe::register_cron(); Xpay_Deflection::register(); Xpay_Agent_Analytics::register(); Xpay_Admin_REST::instance(); Xpay_Widget::instance(); Xpay_Storefront_Widget::instance(); Xpay_Shop_Assist_Page::instance(); if ( is_admin() ) { Xpay_Consent::instance(); } add_action( 'admin_init', array( $this, 'maybe_redirect_after_activation' ) ); $this->maybe_handle_version_bump(); } private function maybe_handle_version_bump() { $stored = get_option( 'xpay_wc_installed_version' ); if ( $stored === XPAY_WC_VERSION ) { return; } // Version changed — re-flush rewrite rules so any added or removed // discovery routes take effect without requiring a deactivate/reactivate. update_option( 'xpay_wc_flush_rewrites', 1 ); update_option( 'xpay_wc_installed_version', XPAY_WC_VERSION ); } public function maybe_redirect_after_activation() { if ( ! get_transient( 'xpay_wc_post_activation_redirect' ) ) { return; } delete_transient( 'xpay_wc_post_activation_redirect' ); // Don't redirect on bulk-activate (multiple plugins activated at once). if ( isset( $_GET['activate-multi'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } if ( wp_safe_redirect( admin_url( 'options-general.php?page=agentic-commerce-for-woocommerce' ) ) ) { exit; } } public function woocommerce_active() { if ( class_exists( 'WooCommerce' ) ) { return true; } $active = (array) get_option( 'active_plugins', array() ); if ( is_multisite() ) { $active = array_merge( $active, array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ) ); } return in_array( 'woocommerce/woocommerce.php', $active, true ); } public function woocommerce_missing_notice() { echo '

'; esc_html_e( 'Agentic Commerce for WooCommerce requires WooCommerce to be installed and active.', 'agentic-commerce-for-woocommerce' ); echo '

'; } public static function on_activate() { if ( ! get_option( 'xpay_wc_site_token' ) ) { update_option( 'xpay_wc_site_token', wp_generate_password( 32, false ) ); } // Force a rewrite-rule flush after we register routes on next init. update_option( 'xpay_wc_flush_rewrites', 1 ); $first_time = ! (bool) get_option( 'xpay_wc_first_activated_at' ); if ( $first_time ) { update_option( 'xpay_wc_first_activated_at', time() ); } // Redirect to Settings → xpay on the very next admin page load if the // merchant hasn't connected yet. Covers both fresh installs and upgrades // where the merchant never got around to connecting. if ( ! self::is_connected() ) { set_transient( 'xpay_wc_post_activation_redirect', 1, 60 ); } // Prime the emitter-probe cache shortly after activation so the // first /llms.txt or /.well-known/* request sees a populated // detection result. Skips silently if Xpay_Emitter_Probe isn't // loaded yet (defensive — the require_once chain in this file // loads it before this method runs, but staying paranoid). if ( class_exists( 'Xpay_Emitter_Probe' ) ) { Xpay_Emitter_Probe::prime_on_activation(); } // Belt-and-suspenders: Xpay_Telemetry::track() already short-circuits // when telemetry is not opted in, but checking is_enabled() at the // call site makes the activation path trivially auditable as // network-silent on fresh installs without reading the Telemetry // class body. if ( class_exists( 'Xpay_Telemetry' ) && Xpay_Telemetry::is_enabled() ) { Xpay_Telemetry::track( 'plugin_activated', array( 'first_time' => $first_time, ) ); } } public static function on_deactivate() { flush_rewrite_rules(); if ( class_exists( 'Xpay_Shop_Assist_Page' ) ) { Xpay_Shop_Assist_Page::clear_cron(); } if ( class_exists( 'Xpay_Emitter_Probe' ) ) { Xpay_Emitter_Probe::unregister_cron(); } if ( class_exists( 'Xpay_Deflection' ) ) { Xpay_Deflection::unregister_cron(); } if ( class_exists( 'Xpay_Agent_Analytics' ) ) { Xpay_Agent_Analytics::unregister_cron(); } // Belt-and-suspenders is_enabled() guard — see on_activate() above. if ( class_exists( 'Xpay_Telemetry' ) && Xpay_Telemetry::is_enabled() ) { Xpay_Telemetry::track( 'plugin_deactivated', array( 'was_connected' => self::is_connected(), ) ); } } public static function is_connected() { return (bool) get_option( 'xpay_wc_merchant_slug' ); } public static function merchant_slug() { return (string) get_option( 'xpay_wc_merchant_slug', '' ); } public static function api_key() { return (string) get_option( 'xpay_wc_api_key', '' ); } /** * Is this merchant entitled to the "AI Storefront Assistant" add-on? * * Resolution order: * 1. XPAY_WC_STOREFRONT_WIDGET wp-config constant (staging/dev override). * 2. Cached backend entitlement (reflects the add-on purchase incl. free * design-partner grants), TTL 6h. * 3. On backend failure, the local admin toggle xpay_wc_storefront_widget_enabled. */ public static function is_storefront_widget_entitled() { if ( defined( 'XPAY_WC_STOREFRONT_WIDGET' ) ) { return (bool) XPAY_WC_STOREFRONT_WIDGET; } $slug = self::merchant_slug(); if ( '' === $slug ) { return false; } $cached = get_transient( 'xpay_wc_storefront_entitlement' ); if ( false !== $cached ) { return (bool) $cached; } $entitled = self::fetch_storefront_entitlement( $slug ); set_transient( 'xpay_wc_storefront_entitlement', $entitled ? 1 : 0, 6 * HOUR_IN_SECONDS ); return $entitled; } private static function fetch_storefront_entitlement( $slug ) { $url = trailingslashit( XPAY_WC_AGENT_COMMERCE_BASE ) . 'widget/entitlement?slug=' . rawurlencode( $slug ); $res = wp_remote_get( $url, array( 'timeout' => 4 ) ); if ( is_wp_error( $res ) || 200 !== (int) wp_remote_retrieve_response_code( $res ) ) { // Endpoint not reachable yet → fall back to the local admin toggle so // the widget stays controllable. return (bool) get_option( 'xpay_wc_storefront_widget_enabled', 0 ); } $body = json_decode( (string) wp_remote_retrieve_body( $res ), true ); return ! empty( $body['storefront_widget'] ) || ! empty( $body['entitled'] ); } /** * Drop the cached entitlement so a just-purchased add-on lights up without * waiting for the 6h TTL. Call on settings save / add-on purchase webhook. */ public static function clear_storefront_entitlement_cache() { delete_transient( 'xpay_wc_storefront_entitlement' ); } }