should_ignore_request()) { return; } if (! is_singular()) { return; } $post_id = get_queried_object_id(); if (! $post_id || 'publish' !== get_post_status($post_id)) { return; } if (is_user_logged_in()) { $settings = A360_Insights_Settings::get(); if (! empty($settings['exclude_logged_in_users'])) { return; } if (! empty($settings['exclude_administrators']) && current_user_can('manage_options')) { return; } } if (! A360_Insights_Settings::should_track_post($post_id)) { return; } $source_data = $this->source_data(); A360_Insights_DB::insert_view($post_id, $source_data); $tracked = true; } /** * Store temporary referrer diagnostics for every WordPress request. * * @return void */ public function maybe_debug_referrer() { static $debugged = false; if ($debugged) { return; } $post_id = is_singular() ? get_queried_object_id() : 0; $data = $this->debug_source_data(); A360_Insights_DB::insert_referrer_debug($post_id, $data); $debugged = true; } /** * Enqueue browser-side referrer repair script. * * @return void */ public function enqueue_referrer_script() { if (is_admin() || is_preview() || is_feed() || is_robots()) { return; } if (! is_singular()) { return; } $post_id = get_queried_object_id(); if (! $post_id || 'publish' !== get_post_status($post_id)) { return; } if (is_user_logged_in()) { $settings = A360_Insights_Settings::get(); if (! empty($settings['exclude_logged_in_users'])) { return; } if (! empty($settings['exclude_administrators']) && current_user_can('manage_options')) { return; } } if (! A360_Insights_Settings::should_track_post($post_id)) { return; } wp_enqueue_script( 'a360-insights-referrer', A360_INSIGHTS_URL . 'assets/referrer.js', array(), A360_INSIGHTS_VERSION, true ); wp_localize_script( 'a360-insights-referrer', 'a360InsightsReferrer', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('a360_js_referrer'), 'post_id' => absint($post_id), ) ); } /** * Update the last direct view with document.referrer detected in the browser. * * @return void */ public function save_js_referrer() { check_ajax_referer('a360_js_referrer', 'nonce'); $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; $referrer_url = isset($_POST['referrer']) ? esc_url_raw(wp_unslash($_POST['referrer'])) : ''; $current_url = isset($_POST['url']) ? esc_url_raw(wp_unslash($_POST['url'])) : ''; if (! $post_id || ! $referrer_url || 'publish' !== get_post_status($post_id)) { wp_send_json_error(); } if (! A360_Insights_Settings::should_track_post($post_id)) { wp_send_json_error(); } $referrer_host = wp_parse_url($referrer_url, PHP_URL_HOST); $referrer_host = is_string($referrer_host) ? strtolower(sanitize_text_field($referrer_host)) : ''; if (! $referrer_host) { wp_send_json_error(); } $site_host = wp_parse_url(home_url(), PHP_URL_HOST); $site_host = is_string($site_host) ? strtolower(preg_replace('/^www\./', '', $site_host)) : ''; $normalized_referrer_host = strtolower(preg_replace('/^www\./', '', $referrer_host)); if ($site_host && $normalized_referrer_host === $site_host) { wp_send_json_success(); } $current_path = $current_url ? wp_parse_url($current_url, PHP_URL_PATH) : ''; $current_path = is_string($current_path) ? $current_path : ''; if ($this->is_google_business_profile_visit($referrer_host, $referrer_url, $current_path)) { $source = 'google_business_profile'; } else { $source = $this->classify_referrer($referrer_host, $referrer_url); } if ('internal' === $source) { wp_send_json_success(); } A360_Insights_DB::update_recent_direct_view( $post_id, array( 'source' => $source, 'referrer_host' => $referrer_host, 'referrer_url' => $referrer_url, ) ); wp_send_json_success(); } /** * Determine whether the current request should be ignored before tracking. * * @return bool */ private function should_ignore_request() { $settings = A360_Insights_Settings::get(); if (! empty($settings['exclude_cron']) && wp_doing_cron()) { return true; } if (! empty($settings['exclude_cli']) && defined('WP_CLI') && WP_CLI) { return true; } if (! empty($settings['ignore_prefetch']) && $this->is_prefetch_request()) { return true; } if (! empty($settings['exclude_bots']) && $this->is_bot_request()) { return true; } return false; } /** * Detect common browser prefetch, prerender and preview requests. * * @return bool */ private function is_prefetch_request() { $headers = array( 'HTTP_X_MOZ', 'HTTP_PURPOSE', 'HTTP_SEC_PURPOSE', 'HTTP_X_PURPOSE', ); foreach ($headers as $header) { if (empty($_SERVER[$header])) { continue; } $value = strtolower(sanitize_text_field(wp_unslash($_SERVER[$header]))); if (false !== strpos($value, 'prefetch') || false !== strpos($value, 'prerender') || false !== strpos($value, 'preview')) { return true; } } return false; } /** * Detect common bots, crawlers, link previewers and uptime monitors. * * @return bool */ private function is_bot_request() { if (empty($_SERVER['HTTP_USER_AGENT'])) { return false; } $user_agent = strtolower(sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT']))); if ('' === $user_agent) { return false; } $patterns = array( 'bot', 'crawler', 'spider', 'crawl', 'slurp', 'googlebot', 'bingbot', 'duckduckbot', 'baiduspider', 'yandex', 'sogou', 'exabot', 'facebot', 'facebookexternalhit', 'linkedinbot', 'twitterbot', 'pinterest', 'whatsapp', 'telegrambot', 'discordbot', 'skypeuripreview', 'vkshare', 'ahrefs', 'semrush', 'mj12bot', 'dotbot', 'petalbot', 'bytespider', 'uptimerobot', 'statuscake', 'pingdom', 'gtmetrix', 'pagespeed', 'lighthouse', 'headlesschrome', 'phantomjs', 'python-requests', 'curl/', 'wget/', 'httpclient', ); foreach ($patterns as $pattern) { if (false !== strpos($user_agent, $pattern)) { return true; } } return false; } /** * Raw source data for the temporary request debug table. * * @return array */ private function debug_source_data() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Temporary request diagnostics. $utm_source = isset($_GET['utm_source']) ? (string) wp_unslash($_GET['utm_source']) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Temporary request diagnostics. $utm_medium = isset($_GET['utm_medium']) ? (string) wp_unslash($_GET['utm_medium']) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Temporary request diagnostics. $utm_campaign = isset($_GET['utm_campaign']) ? (string) wp_unslash($_GET['utm_campaign']) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Temporary request diagnostics. $source_hint = isset($_GET['a360_source']) ? (string) wp_unslash($_GET['a360_source']) : ''; $full_referrer = isset($_SERVER['HTTP_REFERER']) ? (string) wp_unslash($_SERVER['HTTP_REFERER']) : ''; $referrer_host = $full_referrer ? wp_parse_url($full_referrer, PHP_URL_HOST) : ''; $referrer_host = is_string($referrer_host) ? $referrer_host : ''; $request_path = isset($_SERVER['REQUEST_URI']) ? (string) wp_unslash($_SERVER['REQUEST_URI']) : ''; $classify_host = strtolower($referrer_host); if ($this->is_google_business_profile_visit($classify_host, $full_referrer, $request_path, $utm_source, $utm_medium, $utm_campaign, $source_hint)) { $source = 'google_business_profile'; } elseif ($classify_host) { $source = $this->classify_referrer($classify_host, $full_referrer); } elseif ($utm_source) { $source = $this->classify_source_value($utm_source); } else { $source = 'direct'; } return array( 'source' => $source, 'referrer_host' => $referrer_host, 'referrer_url' => $full_referrer, 'full_referrer' => $full_referrer, 'request_path' => $request_path, 'utm_source' => $utm_source, 'utm_medium' => $utm_medium, 'utm_campaign' => $utm_campaign, ); } /** * Source data. * * @return array */ private function source_data() { $settings = A360_Insights_Settings::get(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public read-only UTM tracking parameter. $utm_source = isset($_GET['utm_source']) && ! empty($settings['store_utm_parameters']) ? sanitize_text_field(wp_unslash($_GET['utm_source'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public read-only UTM tracking parameter. $utm_medium = isset($_GET['utm_medium']) && ! empty($settings['store_utm_parameters']) ? sanitize_text_field(wp_unslash($_GET['utm_medium'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public read-only UTM tracking parameter. $utm_campaign = isset($_GET['utm_campaign']) && ! empty($settings['store_utm_parameters']) ? sanitize_text_field(wp_unslash($_GET['utm_campaign'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public read-only source hint for Google Business Profile links. $source_hint = isset($_GET['a360_source']) ? sanitize_key(wp_unslash($_GET['a360_source'])) : ''; $full_referrer = isset($_SERVER['HTTP_REFERER']) ? esc_url_raw(wp_unslash($_SERVER['HTTP_REFERER'])) : ''; $referrer_url = ! empty($settings['store_referrer_details']) ? $full_referrer : ''; $referrer_host = $full_referrer ? wp_parse_url($full_referrer, PHP_URL_HOST) : ''; $referrer_host = is_string($referrer_host) ? strtolower(sanitize_text_field($referrer_host)) : ''; $request_path = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : ''; if ($this->is_google_business_profile_visit($referrer_host, $full_referrer, $request_path, $utm_source, $utm_medium, $utm_campaign, $source_hint)) { $source = 'google_business_profile'; } elseif ($referrer_host) { $source = $this->classify_referrer($referrer_host, $referrer_url); } elseif ($utm_source) { $source = $this->classify_source_value($utm_source); } else { $source = 'direct'; } if ('internal' === $source) { $source = 'direct'; $referrer_host = ''; $referrer_url = ''; } return array( 'source' => $source, 'referrer_host' => $referrer_host, 'referrer_url' => $referrer_url, 'full_referrer' => $full_referrer, 'request_path' => $request_path, 'utm_source' => $utm_source, 'utm_medium' => $utm_medium, 'utm_campaign' => $utm_campaign, ); } /** * Detect likely Google Business Profile / Maps / local pack visits. * * @param string $host Referrer host. * @param string $url Full referrer URL. * @param string $request_path Current request path. * @param string $utm_source UTM source. * @param string $utm_medium UTM medium. * @param string $utm_campaign UTM campaign. * @param string $source_hint Explicit source hint. * @return bool */ private function is_google_business_profile_visit($host, $url, $request_path = '', $utm_source = '', $utm_medium = '', $utm_campaign = '', $source_hint = '') { $haystack = strtolower( implode( ' ', array( (string) $host, (string) $url, (string) $utm_source, (string) $utm_medium, (string) $utm_campaign, (string) $source_hint, ) ) ); if ( false !== strpos($haystack, 'google_business_profile') || false !== strpos($haystack, 'google-business-profile') || false !== strpos($haystack, 'business_profile') || false !== strpos($haystack, 'business-profile') || false !== strpos($haystack, 'google-my-business') || false !== strpos($haystack, 'google_my_business') || false !== strpos($haystack, 'googleprofile') || false !== strpos($haystack, 'gmb') || false !== strpos($haystack, 'gbp') || 'gbp' === $source_hint || 'gmb' === $source_hint ) { return true; } $normalized_host = strtolower(preg_replace('/^www\./', '', (string) $host)); if (! preg_match('/(^|\.)google\./', $normalized_host) && ! in_array($normalized_host, array('maps.app.goo.gl', 'posts.gle'), true)) { return false; } $path = $url ? wp_parse_url($url, PHP_URL_PATH) : ''; $query = $url ? wp_parse_url($url, PHP_URL_QUERY) : ''; $path = is_string($path) ? strtolower($path) : ''; $query = is_string($query) ? strtolower($query) : ''; if ('maps.google.com' === $normalized_host || in_array($normalized_host, array('maps.app.goo.gl', 'posts.gle'), true)) { return true; } $local_path_markers = array( '/maps', '/local', '/place', '/business', '/localservices', ); foreach ($local_path_markers as $marker) { if (false !== strpos($path, $marker)) { return true; } } $local_query_markers = array( 'tbm=lcl', 'ludocid=', 'kgmid=', 'lrd=', 'rlst=', 'stick=', ); foreach ($local_query_markers as $marker) { if (false !== strpos($query, $marker)) { return true; } } return false; } private function classify_source_value($value) { $value = strtolower(sanitize_text_field($value)); if (false !== strpos($value, 'codex')) { return 'codex'; } if (false !== strpos($value, 'chatgpt') || false !== strpos($value, 'openai')) { return 'chatgpt'; } if (false !== strpos($value, 'gemini') || false !== strpos($value, 'bard')) { return 'gemini'; } if (false !== strpos($value, 'copilot')) { return 'copilot'; } if (false !== strpos($value, 'perplexity')) { return 'perplexity'; } if (false !== strpos($value, 'claude') || false !== strpos($value, 'anthropic')) { return 'claude'; } if (false !== strpos($value, 'meta.ai') || false !== strpos($value, 'meta_ai')) { return 'meta_ai'; } if (false !== strpos($value, 'mistral') || false !== strpos($value, 'lechat')) { return 'mistral'; } if (false !== strpos($value, 'you.com') || 'you' === $value) { return 'you'; } if (false !== strpos($value, 'poe')) { return 'poe'; } if (false !== strpos($value, 'phind')) { return 'phind'; } if (false !== strpos($value, 'deepseek')) { return 'deepseek'; } if (false !== strpos($value, 'grok') || false !== strpos($value, 'x.ai')) { return 'grok'; } if (false !== strpos($value, 'google')) { return 'google'; } if (false !== strpos($value, 'bing')) { return 'bing'; } return sanitize_key($value); } /** * Classify referrer host. * * @param string $host Host. * @param string $url Full referrer URL. * @return string */ private function classify_referrer($host, $url = '') { $site_host = wp_parse_url(home_url(), PHP_URL_HOST); $site_host = is_string($site_host) ? strtolower(preg_replace('/^www\./', '', $site_host)) : ''; $host = strtolower(preg_replace('/^www\./', '', $host)); $path = $url ? wp_parse_url($url, PHP_URL_PATH) : ''; $path = is_string($path) ? strtolower($path) : ''; if ( $site_host && ( $host === $site_host || substr($host, -1 * (strlen($site_host) + 1)) === '.' . $site_host ) ) { return 'internal'; } if ( preg_match('/^codex\.openai\.com$/', $host) || ( preg_match('/(^|\.)chatgpt\.com$|(^|\.)openai\.com$/', $host) && false !== strpos($path, 'codex') ) ) { return 'codex'; } if (preg_match('/(^|\.)chatgpt\.com$|(^|\.)openai\.com$/', $host)) { return 'chatgpt'; } if (preg_match('/^gemini\.google\.com$|^bard\.google\.com$/', $host)) { return 'gemini'; } if (preg_match('/^copilot\.microsoft\.com$/', $host)) { return 'copilot'; } if (preg_match('/(^|\.)perplexity\.ai$/', $host)) { return 'perplexity'; } if (preg_match('/(^|\.)claude\.ai$|(^|\.)anthropic\.com$/', $host)) { return 'claude'; } if (preg_match('/(^|\.)meta\.ai$/', $host)) { return 'meta_ai'; } if (preg_match('/(^|\.)mistral\.ai$|(^|\.)chat\.mistral\.ai$/', $host)) { return 'mistral'; } if (preg_match('/(^|\.)you\.com$/', $host)) { return 'you'; } if (preg_match('/(^|\.)poe\.com$/', $host)) { return 'poe'; } if (preg_match('/(^|\.)phind\.com$/', $host)) { return 'phind'; } if (preg_match('/(^|\.)deepseek\.com$/', $host)) { return 'deepseek'; } if (preg_match('/(^|\.)grok\.com$|(^|\.)x\.ai$/', $host)) { return 'grok'; } if (preg_match('/(^|\.)google\./', $host)) { return 'google'; } if (preg_match('/(^|\.)facebook\.com$|(^|\.)fb\.com$|(^|\.)l\.facebook\.com$|(^|\.)m\.facebook\.com$/', $host)) { return 'facebook'; } if (preg_match('/(^|\.)youtube\.com$|(^|\.)youtu\.be$/', $host)) { return 'youtube'; } if (preg_match('/instagram\.com|x\.com|twitter\.com|linkedin\.com|tiktok\.com|pinterest\./', $host)) { return 'social'; } return 'referral'; } }