init_hooks(); } /** * Prevent cloning. */ private function __clone() {} /** * Prevent unserializing. */ public function __wakeup() { wc_doing_it_wrong( __FUNCTION__, __( 'Unserializing is forbidden.', 'agentshop' ), '1.0.0' ); } /** * Initialize hooks. */ private function init_hooks() { add_action( 'plugins_loaded', array( $this, 'on_plugins_loaded' ), 10 ); add_action( 'init', array( $this, 'init' ) ); add_action( 'woocommerce_init', array( $this, 'init_woocommerce' ), 0 ); // Plugin lifecycle. register_activation_hook( AGENTSHOP_PLUGIN_FILE, array( $this, 'activate' ) ); register_deactivation_hook( AGENTSHOP_PLUGIN_FILE, array( $this, 'deactivate' ) ); // Plugin links. add_filter( 'plugin_action_links_' . AGENTSHOP_PLUGIN_BASENAME, array( $this, 'add_settings_link' ) ); // REST API. add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) ); // Cron. add_action( 'agentshop_daily_sync', array( $this, 'daily_sync' ) ); // GDPR. add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_data_exporter' ) ); add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_data_eraser' ) ); // Privacy policy. add_action( 'admin_init', array( $this, 'add_privacy_policy_content' ) ); // WP Consent API - register cookies. add_filter( 'wp_consent_api_registered_' . AGENTSHOP_PLUGIN_BASENAME, '__return_true' ); add_action( 'plugins_loaded', array( $this, 'register_cookies_with_consent_api' ), 20 ); } /** * Check dependencies on plugins_loaded. */ public function on_plugins_loaded() { // Note: load_plugin_textdomain() is not needed for plugins hosted on WordPress.org // since WordPress 4.6+. Translations are loaded automatically from translate.wordpress.org. // Check WooCommerce. if ( ! class_exists( 'WooCommerce' ) ) { add_action( 'admin_notices', array( $this, 'woocommerce_missing_notice' ) ); return; } // Check WooCommerce version. if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '7.0', '<' ) ) { add_action( 'admin_notices', array( $this, 'woocommerce_version_notice' ) ); return; } } /** * Initialize on init hook. */ public function init() { $this->api = new AgentShop\Api\Client(); $this->admin = new AgentShop\Admin\Settings(); // Initialize LLMs.txt handler. new AgentShop\LlmsTxt\Handler(); // Initialize AEO (AI Engine Optimization) handler. new AgentShop\Aeo\Handler(); } /** * Initialize WooCommerce components. */ public function init_woocommerce() { if ( ! class_exists( 'WooCommerce' ) ) { return; } $this->webhooks = new AgentShop\Webhooks\Manager(); $this->pixel = new AgentShop\Pixel\Tracker(); } /** * Plugin activation. */ public function activate() { // Check WooCommerce. if ( ! class_exists( 'WooCommerce' ) ) { wp_die( esc_html__( 'AgentShop requires WooCommerce to be installed and active.', 'agentshop' ), esc_html__( 'Plugin Activation Error', 'agentshop' ), array( 'back_link' => true ) ); } // Set default options. $defaults = array( 'api_endpoint' => AGENTSHOP_API_URL, 'tracking_enabled' => true, 'installed_at' => current_time( 'mysql' ), 'version' => AGENTSHOP_VERSION, ); $existing = get_option( 'agentshop_settings', array() ); update_option( 'agentshop_settings', array_merge( $defaults, $existing ) ); // Schedule cron. if ( ! wp_next_scheduled( 'agentshop_daily_sync' ) ) { wp_schedule_event( time(), 'daily', 'agentshop_daily_sync' ); } // Flush rewrite rules. flush_rewrite_rules(); // Set activation redirect. set_transient( 'agentshop_activated', true, 30 ); } /** * Plugin deactivation. */ public function deactivate() { wp_clear_scheduled_hook( 'agentshop_daily_sync' ); flush_rewrite_rules(); } /** * Add settings link to plugins page. * * @param array $links Plugin links. * @return array */ public function add_settings_link( $links ) { $settings_link = sprintf( '%s', admin_url( 'admin.php?page=agentshop' ), __( 'Settings', 'agentshop' ) ); array_unshift( $links, $settings_link ); return $links; } /** * Register REST API routes. */ public function register_rest_routes() { register_rest_route( 'agentshop/v1', '/verify', array( 'methods' => 'GET', 'callback' => array( $this, 'rest_verify' ), 'permission_callback' => array( $this, 'verify_rest_permission' ), 'schema' => array( $this, 'get_verify_schema' ), ) ); } /** * Permission callback for verify endpoint. * Always returns true but sets a flag for authenticated vs unauthenticated access. * * @param WP_REST_Request $request Request object. * @return bool */ public function verify_rest_permission( $request ) { // Always allow access, but the callback will return limited info for unauthenticated requests. return true; } /** * Check if request is authenticated with valid secret. * * @param WP_REST_Request $request Request object. * @return bool */ private function is_authenticated_request( $request ) { $options = $this->get_options(); $secret = isset( $options['webhook_secret'] ) ? $options['webhook_secret'] : ''; // If no secret configured, return minimal data for security. if ( empty( $secret ) ) { return false; } // Check for secret in header or query param. $provided_secret = $request->get_header( 'X-AgentShop-Secret' ); if ( empty( $provided_secret ) ) { $provided_secret = $request->get_param( 'secret' ); } if ( empty( $provided_secret ) ) { return false; } return hash_equals( $secret, $provided_secret ); } /** * REST verify endpoint callback. * * @param WP_REST_Request $request Request object. * @return WP_REST_Response */ public function rest_verify( $request ) { nocache_headers(); // Return minimal info for unauthenticated requests. if ( ! $this->is_authenticated_request( $request ) ) { return new WP_REST_Response( array( 'verified' => true, 'plugin' => 'agentshop', ), 200 ); } // Return full info for authenticated requests. return new WP_REST_Response( array( 'verified' => true, 'plugin' => 'agentshop', 'version' => AGENTSHOP_VERSION, 'site_url' => get_site_url(), 'wp_version' => get_bloginfo( 'version' ), 'wc_version' => defined( 'WC_VERSION' ) ? WC_VERSION : null, 'connected' => $this->is_connected(), ), 200 ); } /** * Get verify endpoint schema. * * @return array */ public function get_verify_schema() { return array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'verify', 'type' => 'object', 'properties' => array( 'verified' => array( 'type' => 'boolean', 'description' => __( 'Whether the plugin is verified.', 'agentshop' ), ), 'plugin' => array( 'type' => 'string', 'description' => __( 'Plugin identifier.', 'agentshop' ), ), 'version' => array( 'type' => 'string', 'description' => __( 'Plugin version.', 'agentshop' ), ), 'site_url' => array( 'type' => 'string', 'description' => __( 'Site URL.', 'agentshop' ), ), 'wp_version' => array( 'type' => 'string', 'description' => __( 'WordPress version.', 'agentshop' ), ), 'wc_version' => array( 'type' => array( 'string', 'null' ), 'description' => __( 'WooCommerce version.', 'agentshop' ), ), 'connected' => array( 'type' => 'boolean', 'description' => __( 'Whether the store is connected.', 'agentshop' ), ), ), ); } /** * Daily sync cron job. */ public function daily_sync() { if ( $this->is_connected() && $this->webhooks ) { $this->webhooks->verify_webhooks(); } } /** * WooCommerce missing notice. */ public function woocommerce_missing_notice() { printf( '

%s %s

', esc_html__( 'AgentShop:', 'agentshop' ), esc_html__( 'WooCommerce must be installed and active.', 'agentshop' ) ); } /** * WooCommerce version notice. */ public function woocommerce_version_notice() { printf( '

%s %s

', esc_html__( 'AgentShop:', 'agentshop' ), esc_html__( 'WooCommerce 7.0 or higher is required.', 'agentshop' ) ); } /** * Register cookies with WP Consent API. * * @see https://github.com/WordPress/wp-consent-level-api */ public function register_cookies_with_consent_api() { if ( ! function_exists( 'wp_add_cookie_info' ) ) { return; } // Customer ID cookie - anonymous identifier for order attribution. wp_add_cookie_info( 'agentshop_cid', // Cookie name. 'AgentShop', // Plugin/service. 'statistics', // Consent category. __( '2 years', 'agentshop' ), // Expiration. __( 'Stores a unique customer identifier for tracking order attribution.', 'agentshop' ), // Function/description. __( 'Anonymous identifier (UUID)', 'agentshop' ) // Collected personal data. ); // Session ID cookie - anonymous session tracking. wp_add_cookie_info( 'agentshop_sid', 'AgentShop', 'statistics', __( 'Session', 'agentshop' ), __( 'Stores a session identifier for tracking visitor sessions.', 'agentshop' ), __( 'Anonymous identifier (UUID)', 'agentshop' ) ); // LLM source cookie - stores detected AI referrer. wp_add_cookie_info( 'agentshop_llm', 'AgentShop', 'statistics', __( '30 days', 'agentshop' ), __( 'Stores the LLM/AI referrer source when detected.', 'agentshop' ), __( 'Referrer source name', 'agentshop' ) ); } /** * Get plugin options. * * @param string|null $key Option key. * @param mixed $default Default value. * @return mixed */ public function get_options( $key = null, $default = null ) { $options = get_option( 'agentshop_settings', array() ); if ( null === $key ) { return $options; } return isset( $options[ $key ] ) ? $options[ $key ] : $default; } /** * Update plugin options. * * @param array $new_options Options to update. * @return bool */ public function update_options( $new_options ) { $current = $this->get_options(); return update_option( 'agentshop_settings', array_merge( $current, $new_options ) ); } /** * Check if connected to AgentShop. * * @return bool */ public function is_connected() { $options = $this->get_options(); return ! empty( $options['pixel_token'] ) && ! empty( $options['store_id'] ); } /** * Check if tracking is enabled. * * @return bool */ public function is_tracking_enabled() { return $this->is_connected() && $this->get_options( 'tracking_enabled', true ); } /** * Get store URL (normalized). * * @return string */ public function get_store_url() { $url = get_site_url(); return rtrim( preg_replace( '#^https?://#', '', $url ), '/' ); } /** * Get API endpoint. * * @return string */ public function get_api_endpoint() { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { return AGENTSHOP_API_URL; } return $this->get_options( 'api_endpoint', AGENTSHOP_API_URL ); } /** * Log message using WooCommerce logger. * * @param string $message Message. * @param string $level Level (debug, info, notice, warning, error, critical, alert, emergency). * @param array $context Context. */ public function log( $message, $level = 'info', $context = array() ) { if ( ! function_exists( 'wc_get_logger' ) ) { return; } $logger = wc_get_logger(); if ( ! empty( $context ) ) { $message .= ' ' . wp_json_encode( $context ); } $logger->log( $level, $message, array( 'source' => 'agentshop' ) ); } /** * Detect LLM source from referrer. * * @param string $referrer Referrer URL. * @return string|null */ public function detect_llm_source( $referrer ) { if ( empty( $referrer ) ) { return null; } $referrer = strtolower( $referrer ); $sources = array( 'chatgpt' => array( 'chat.openai.com', 'chatgpt.com' ), 'gemini' => array( 'gemini.google.com', 'bard.google.com' ), 'perplexity' => array( 'perplexity.ai' ), 'grok' => array( 'grok.x.ai', 'x.com/i/grok' ), 'claude' => array( 'claude.ai' ), ); foreach ( $sources as $source => $domains ) { foreach ( $domains as $domain ) { if ( strpos( $referrer, $domain ) !== false ) { return $source; } } } return null; } /** * Check if we have consent to set tracking cookies. * * Uses WP Consent API if available, otherwise defaults to true. * * @return bool */ private function has_cookie_consent() { // If WP Consent API is available, check for statistics consent. if ( function_exists( 'wp_has_consent' ) ) { return wp_has_consent( 'statistics' ); } // Default to true if consent API not installed. return true; } /** * Get or create customer ID. * * @return string */ public function get_customer_id() { $cookie_name = 'agentshop_cid'; if ( isset( $_COOKIE[ $cookie_name ] ) ) { return sanitize_text_field( wp_unslash( $_COOKIE[ $cookie_name ] ) ); } $customer_id = wp_generate_uuid4(); // Only set cookie if we have consent. if ( ! headers_sent() && $this->has_cookie_consent() ) { setcookie( $cookie_name, $customer_id, array( 'expires' => time() + ( 2 * YEAR_IN_SECONDS ), 'path' => COOKIEPATH, 'domain' => COOKIE_DOMAIN, 'secure' => is_ssl(), 'httponly' => false, 'samesite' => 'Lax', ) ); } return $customer_id; } /** * Get or create session ID. * * @return string */ public function get_session_id() { $cookie_name = 'agentshop_sid'; if ( isset( $_COOKIE[ $cookie_name ] ) ) { return sanitize_text_field( wp_unslash( $_COOKIE[ $cookie_name ] ) ); } $session_id = wp_generate_uuid4(); // Only set cookie if we have consent. if ( ! headers_sent() && $this->has_cookie_consent() ) { setcookie( $cookie_name, $session_id, array( 'expires' => 0, 'path' => COOKIEPATH, 'domain' => COOKIE_DOMAIN, 'secure' => is_ssl(), 'httponly' => false, 'samesite' => 'Lax', ) ); } return $session_id; } /** * Encrypt sensitive data using AES-256-CBC. * * @param string $data Data to encrypt. * @return string|false Encrypted data or false on failure. */ public function encrypt_data( $data ) { if ( empty( $data ) ) { return $data; } if ( ! extension_loaded( 'openssl' ) ) { $this->log( 'OpenSSL extension not available for encryption', 'error' ); return false; } $key = $this->get_encryption_key(); $iv = openssl_random_pseudo_bytes( 16 ); $cipher = openssl_encrypt( $data, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv ); if ( false === $cipher ) { return false; } return base64_encode( $iv . $cipher ); } /** * Decrypt sensitive data using AES-256-CBC. * * @param string $encrypted Encrypted data. * @return string|false Decrypted data or false on failure. */ public function decrypt_data( $encrypted ) { if ( empty( $encrypted ) ) { return $encrypted; } if ( ! extension_loaded( 'openssl' ) ) { $this->log( 'OpenSSL extension not available for decryption', 'error' ); return false; } $key = $this->get_encryption_key(); $data = base64_decode( $encrypted ); if ( false === $data || strlen( $data ) < 17 ) { return false; } $iv = substr( $data, 0, 16 ); $cipher = substr( $data, 16 ); return openssl_decrypt( $cipher, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv ); } /** * Get encryption key derived from WordPress auth key. * * @return string */ private function get_encryption_key() { $auth_key = defined( 'AUTH_KEY' ) && AUTH_KEY ? AUTH_KEY : 'agentshop-default-key'; return hash( 'sha256', $auth_key . 'agentshop_encryption', true ); } /** * Register GDPR data exporter. * * @param array $exporters Exporters. * @return array */ public function register_data_exporter( $exporters ) { $exporters['agentshop'] = array( 'exporter_friendly_name' => __( 'AgentShop Data', 'agentshop' ), 'callback' => array( 'AgentShop\Privacy\Handler', 'export_personal_data' ), ); return $exporters; } /** * Register GDPR data eraser. * * @param array $erasers Erasers. * @return array */ public function register_data_eraser( $erasers ) { $erasers['agentshop'] = array( 'eraser_friendly_name' => __( 'AgentShop Data', 'agentshop' ), 'callback' => array( 'AgentShop\Privacy\Handler', 'erase_personal_data' ), ); return $erasers; } /** * Add privacy policy content. */ public function add_privacy_policy_content() { if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) { return; } $content = sprintf( '

%s

%s

%s

%s

%s

%s

%s

%s

', __( 'AgentShop Analytics', 'agentshop' ), __( 'This site uses AgentShop to track and analyze visitor behavior, particularly traffic from AI assistants and large language models (LLMs).', 'agentshop' ), __( 'What data we collect', 'agentshop' ), __( 'When you visit our store, AgentShop may collect:', 'agentshop' ), __( 'Page views and product interactions', 'agentshop' ), __( 'Referral source (including AI assistant origin)', 'agentshop' ), __( 'Order and conversion data (anonymized)', 'agentshop' ), __( 'Cookies', 'agentshop' ), __( 'AgentShop uses the following cookies:', 'agentshop' ), __( 'agentshop_cid - Customer identifier (expires in 2 years)', 'agentshop' ), __( 'agentshop_sid - Session identifier (expires when browser closes)', 'agentshop' ), __( 'agentshop_llm - LLM source tracking (expires in 30 days)', 'agentshop' ), __( 'Data sharing', 'agentshop' ), __( 'Analytics data is transmitted to AgentShop servers (useagentshop.com) for processing. For more information, visit our privacy policy at https://useagentshop.com/privacy', 'agentshop' ) ); wp_add_privacy_policy_content( 'AgentShop', $content ); } } /** * Global accessor function. * * @return AgentShop */ function agentshop() { return AgentShop::instance(); } // Initialize. agentshop();