PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.12.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.12.1
5.12.1 5.12.0 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 week ago Commands 2 years ago Db 3 weeks ago Ecommerce 1 week ago Report 1 week ago Site 1 week ago TrackingCode 1 week ago Updater 4 years ago User 1 week ago Workarounds 2 years ago WpStatistics 1 week ago views 1 week ago AIBotTracking.php 1 week ago API.php 3 weeks ago Access.php 1 week ago AjaxTracker.php 5 months ago Annotations.php 1 week ago Bootstrap.php 11 months ago Capabilities.php 1 week ago Compatibility.php 1 week ago Email.php 1 week ago ErrorNotice.php 1 week ago Feature.php 3 months ago Installer.php 1 week ago Logger.php 1 year ago MinimumRequirementsNotice.php 1 month ago OptOut.php 2 months ago Paths.php 1 week ago PluginActionLinks.php 3 months ago PluginAdminOverrides.php 1 week ago PluginInit.php 1 week ago PrivacyBadge.php 4 years ago RedirectOnActivation.php 3 months ago Referral.php 3 months ago Roles.php 3 months ago ScheduledTasks.php 1 week ago Settings.php 1 week ago Site.php 3 years ago TrackingCode.php 1 week ago Uninstaller.php 6 months ago Updater.php 1 week ago User.php 3 weeks 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.MisspelledInText
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