PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.10.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.10.1
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / classes / WpMatomo / AIBotTracking.php
matomo / classes / WpMatomo Last commit date
Admin 1 month ago Commands 2 years ago Db 1 year ago Ecommerce 1 month ago Report 2 months ago Site 2 months ago TrackingCode 4 months ago Updater 4 years ago User 2 months ago Workarounds 2 years ago WpStatistics 5 months ago views 1 month ago AIBotTracking.php 4 months ago API.php 2 months ago Access.php 4 years ago AjaxTracker.php 4 months ago Annotations.php 2 months ago Bootstrap.php 10 months ago Capabilities.php 2 months ago Compatibility.php 2 months ago Email.php 2 years ago ErrorNotice.php 1 month ago Feature.php 2 months ago Installer.php 1 month ago Logger.php 1 year ago OptOut.php 1 month ago Paths.php 5 months ago PluginActionLinks.php 2 months ago PluginAdminOverrides.php 2 months ago PluginInit.php 2 months ago PrivacyBadge.php 4 years ago RedirectOnActivation.php 2 months ago Referral.php 2 months ago Roles.php 2 months ago ScheduledTasks.php 2 months ago Settings.php 4 months ago Site.php 3 years ago TrackingCode.php 2 months ago Uninstaller.php 5 months ago Updater.php 10 months ago User.php 4 years ago
AIBotTracking.php
232 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 * @package matomo
8 */
9
10 namespace WpMatomo;
11
12 use LiteSpeed\ESI;
13
14 /**
15 * Performs server side tracking for AI bots.
16 *
17 * Normal server side tracking: when no caching plugins or CDNs are being
18 * used, this class will send tracking requests in the wp_footer hook.
19 *
20 * When advanced-cache.php is used: when a caching plugin creates an
21 * advanced-cache.php file that WordPress uses, tracking must be done
22 * through the standalone script in misc/track_ai_bot.php. This script
23 * must be manually added to a user's wp-config.php file, right after
24 * ABSPATH is defined.
25 *
26 * When .htaccess is used: when a caching plugin modifies the .htaccess
27 * to serve cached files directly, AI bot tracking is not possible.
28 *
29 * When a CDN is used: when a CDN is used to serve cached content, AI
30 * bot tracking can be accomplished through the use of ESI (Edge Side Includes).
31 * In this case, this class outputs an `<esi:include>` directive that
32 * loads the misc/track_ai_bot.php script. This technique will only work
33 * for CDNs that support ESI.
34 *
35 * The misc/track_ai_bot.php script uses this class without all of WordPress loaded.
36 * It can also be loaded outside of WordPress. Because of this, it is important
37 * that the script and this class use as few total dependencies as possible. Otherwise,
38 * AI bot tracking reduce the performance of requests to cached content.
39 */
40 class AIBotTracking {
41
42 private static $ai_bot_tracked = false;
43
44 private static $extensions_to_track = [
45 '', // no extension
46
47 'htm',
48 'html',
49 'php',
50 ];
51
52 /**
53 * @var Settings
54 */
55 private $settings;
56
57 /**
58 * @var AjaxTracker
59 */
60 private $tracker;
61
62 /**
63 * @param Settings $settings
64 * @param ?AIBotTracking $tracker
65 */
66 public function __construct( $settings, $tracker = null ) {
67 $this->settings = $settings;
68 $this->tracker = $tracker;
69 }
70
71 public function register_hooks() {
72 add_action( 'litespeed_init', [ $this, 'litespeed_init' ] );
73 add_action( 'wp_footer', [ $this, 'do_ai_bot_tracking' ], 999999 );
74 }
75
76 public function litespeed_init() {
77 if ( class_exists( ESI::class )
78 && $this->settings->is_tracking_ai_bots_via_esi_includes()
79 ) {
80 ESI::set_has_esi();
81 }
82 }
83
84 public function do_ai_bot_tracking( $url = null ) {
85 // track AI bots only once per request
86 if ( self::$ai_bot_tracked ) {
87 return;
88 }
89
90 self::$ai_bot_tracked = true;
91
92 // if using ESI to track, and not within the track_ai_bot.php script, output the appropriate ESI tag
93 $is_using_esi_to_track = $this->settings->is_tracking_ai_bots_via_esi_includes();
94 if (
95 $is_using_esi_to_track
96 && empty( $GLOBALS['MATOMO_IN_AI_ESI'] )
97 ) {
98 $track_script_url = plugins_url( '/misc/track_ai_bot.php', MATOMO_ANALYTICS_FILE ) . '?mtm_esi=1&mtm_url=' . rawurlencode( AjaxTracker::getCurrentUrl() );
99 echo '<esi:include src="' . esc_attr( $track_script_url ) . '" cache-control="no-cache" />';
100 return;
101 }
102
103 if ( ! $this->is_doing_ai_bot_tracking_this_request() ) {
104 return;
105 }
106
107 $response_code = http_response_code();
108 $request_elapsed_ms = null;
109
110 if (
111 empty( $GLOBALS['MATOMO_IN_AI_ESI'] )
112 && array_key_exists( 'REQUEST_TIME_FLOAT', $_SERVER )
113 ) {
114 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
115 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
116 $request_elapsed_ms = (int) ( ( microtime( true ) - $_SERVER['REQUEST_TIME_FLOAT'] ) * 1000 );
117 }
118
119 if ( empty( $response_code ) ) {
120 $response_code = 200;
121 }
122
123 // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled
124 $source = 'wordpress';
125
126 if ( empty( $url ) ) {
127 $url = AjaxTracker::getCurrentUrl();
128 }
129
130 $tracker = $this->get_tracker();
131 $tracker->setUrl( $url );
132
133 // cannot count bytes echo'd so no response size tracked
134 $tracker->doTrackPageViewIfAIBot( $response_code, null, $request_elapsed_ms, $source );
135 }
136
137 public function should_track_current_page() {
138 if ( is_admin() ) {
139 return false;
140 }
141
142 if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
143 return false;
144 }
145
146 if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
147 return false;
148 }
149
150 if (
151 ! array_key_exists( 'REQUEST_URI', $_SERVER )
152 || null === $_SERVER['REQUEST_URI']
153 ) {
154 return false;
155 }
156
157 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
158 $request_path = (string) wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH );
159
160 if ( preg_match( '/matomo\.php$/', $request_path ) ) {
161 return false;
162 }
163
164 if ( $this->is_request_for_file( $request_path ) ) {
165 return false;
166 }
167
168 return true;
169 }
170
171 private function is_request_for_file( $request_path ) {
172 if (
173 ! empty( $_SERVER['DOCUMENT_ROOT'] )
174 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
175 && is_dir( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) . $request_path )
176 ) {
177 return false;
178 }
179
180 $extension = pathinfo( $request_path, PATHINFO_EXTENSION );
181 return ! in_array( $extension, self::$extensions_to_track, true );
182 }
183
184 public static function set_is_ai_bot_tracked( $is_tracked ) {
185 self::$ai_bot_tracked = $is_tracked;
186 }
187
188 public function is_js_execution_detected() {
189 return ! empty( $_COOKIE['matomo_has_js'] )
190 && '1' === $_COOKIE['matomo_has_js'];
191 }
192
193 private function is_doing_ai_bot_tracking_this_request() {
194 if ( ! empty( $GLOBALS['MATOMO_IN_AI_ESI'] ) ) {
195 return true;
196 }
197
198 if ( ! $this->should_track_current_page() ) {
199 return false;
200 }
201
202 if ( $this->is_js_execution_detected() ) {
203 return false;
204 }
205
206 $tracker = $this->get_tracker();
207
208 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
209 if ( ! AjaxTracker::isUserAgentAIBot( $tracker->userAgent ) ) {
210 return false;
211 }
212
213 if (
214 ! $this->settings->is_ai_bot_tracking_enabled()
215 || ! $this->settings->is_tracking_enabled()
216 ) {
217 return false;
218 }
219
220 return true;
221 }
222
223 private function get_tracker() {
224 if ( empty( $this->tracker ) ) {
225 $this->tracker = new AjaxTracker( $this->settings );
226 $this->tracker->setRequestTimeout( 1 );
227 }
228
229 return $this->tracker;
230 }
231 }
232