` directive that * loads the misc/track_ai_bot.php script. This technique will only work * for CDNs that support ESI. * * The misc/track_ai_bot.php script uses this class without all of WordPress loaded. * It can also be loaded outside of WordPress. Because of this, it is important * that the script and this class use as few total dependencies as possible. Otherwise, * AI bot tracking reduce the performance of requests to cached content. */ class AIBotTracking { private static $ai_bot_tracked = false; private static $extensions_to_track = array( '', // no extension 'htm', 'html', 'php', ); /** * @var Settings */ private $settings; /** * @var Logger */ private $logger; /** * @var AjaxTracker|null */ private $tracker = null; /** * @param Settings $settings * @param Logger $logger */ public function __construct( Settings $settings, Logger $logger ) { $this->settings = $settings; $this->logger = $logger; } public function register_hooks() { add_action( 'litespeed_init', array( $this, 'litespeed_init' ) ); add_action( 'wp_footer', array( $this, 'do_ai_bot_tracking' ), 999999 ); } public function litespeed_init() { if ( class_exists( ESI::class ) && $this->settings->is_ai_bot_tracking_enabled_via_esi_includes() ) { ESI::set_has_esi(); } } /** * @param string|null $url * @return void */ public function do_ai_bot_tracking( $url = null ) { // track AI bots only once per request if ( self::$ai_bot_tracked ) { return; } self::$ai_bot_tracked = true; // if using ESI to track, and not within the track_ai_bot.php script, output the appropriate ESI tag $is_using_esi_to_track = $this->settings->is_ai_bot_tracking_enabled_via_esi_includes(); if ( $is_using_esi_to_track && empty( $GLOBALS['WP_PIWIK_IN_ESI'] ) ) { $track_script_url = plugins_url( '/misc/track_ai_bot.php', WP_PIWIK_FILE ) . '?mtm_esi=1&mtm_url=' . rawurlencode( AjaxTracker::getCurrentUrl() ); echo ''; return; } if ( ! $this->is_doing_ai_bot_tracking_this_request() ) { return; } $response_code = http_response_code(); $request_elapsed_ms = null; // cannot track request time if executed via an esi:include if ( empty( $GLOBALS['WP_PIWIK_IN_ESI'] ) && array_key_exists( 'REQUEST_TIME_FLOAT', $_SERVER ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash $request_elapsed_ms = (int) ( ( microtime( true ) - floatval( $_SERVER['REQUEST_TIME_FLOAT'] ) ) * 1000 ); } if ( empty( $response_code ) ) { $response_code = 200; } // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled $source = 'WordPress'; if ( empty( $url ) ) { $url = AjaxTracker::getCurrentUrl(); } $tracker = $this->get_tracker(); $tracker->setUrl( $url ); // cannot count bytes echo'd so no response size tracked $tracker->doTrackPageViewIfAIBot( $response_code, null, $request_elapsed_ms, $source ); } public function should_track_current_page() { if ( is_admin() ) { return false; } if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return false; } if ( wp_doing_ajax() ) { return false; } if ( empty( $_SERVER['REQUEST_URI'] ) ) { return false; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $request_path = (string) wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH ); if ( preg_match( '/matomo\.php$/', $request_path ) ) { return false; } if ( $this->is_request_for_file( $request_path ) ) { return false; } return true; } private function is_request_for_file( $request_path ) { if ( ! empty( $_SERVER['DOCUMENT_ROOT'] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized && is_dir( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) . $request_path ) ) { return false; } $extension = pathinfo( $request_path, PATHINFO_EXTENSION ); return ! in_array( $extension, self::$extensions_to_track, true ); } public function is_js_execution_detected() { return ! empty( $_COOKIE['matomo_has_js'] ) && '1' === $_COOKIE['matomo_has_js']; } private function is_doing_ai_bot_tracking_this_request() { if ( ! empty( $GLOBALS['WP_PIWIK_IN_ESI'] ) ) { return true; } if ( ! $this->should_track_current_page() ) { return false; } if ( $this->is_js_execution_detected() ) { return false; } $tracker = $this->get_tracker(); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase if ( ! AjaxTracker::isUserAgentAIBot( $tracker->userAgent ) ) { return false; } if ( ! $this->settings->is_ai_bot_tracking_enabled() || ! $this->settings->is_tracking_enabled() ) { return false; } return true; } private function get_tracker() { if ( empty( $this->tracker ) ) { $this->tracker = new AjaxTracker( $this->settings, $this->logger ); $this->tracker->setRequestTimeout( 1 ); } return $this->tracker; } }