PluginProbe ʕ •ᴥ•ʔ
Rank Math SEO – AI SEO Tools to Dominate SEO Rankings / 1.0.271.1
Rank Math SEO – AI SEO Tools to Dominate SEO Rankings v1.0.271.1
1.0.271 1.0.271.1 1.0.270 1.0.269 trunk 1.0.216 1.0.217 1.0.218 1.0.219 1.0.220 1.0.221 1.0.222 1.0.223 1.0.224 1.0.225 1.0.226 1.0.227 1.0.227.1 1.0.228 1.0.229 1.0.230 1.0.231 1.0.232 1.0.233 1.0.234 1.0.234.1 1.0.235 1.0.236 1.0.237 1.0.238 1.0.239 1.0.240 1.0.241 1.0.242 1.0.243 1.0.244 1.0.245 1.0.246 1.0.247 1.0.248 1.0.249 1.0.250 1.0.251 1.0.251.1 1.0.252 1.0.252.1 1.0.253 1.0.254 1.0.255 1.0.256 1.0.257 1.0.258 1.0.259 1.0.259.1 1.0.260 1.0.261 1.0.262 1.0.263 1.0.264 1.0.264.1 1.0.265 1.0.266 1.0.266.1 1.0.267 1.0.268
seo-by-rank-math / includes / class-tracking.php
seo-by-rank-math / includes Last commit date
3rdparty 6 days ago abilities 6 days ago admin 6 days ago cli 5 years ago frontend 2 weeks ago helpers 2 weeks ago metaboxes 2 years ago module 2 weeks ago modules 6 days ago opengraph 2 weeks ago replace-variables 2 weeks ago rest 6 days ago settings 2 weeks ago traits 2 weeks ago updates 2 weeks ago class-auto-updater.php 5 years ago class-cmb2.php 2 weeks ago class-common.php 5 months ago class-compatibility.php 1 year ago class-data-encryption.php 5 months ago class-defaults.php 6 years ago class-frontend-seo-score.php 2 weeks ago class-helper.php 10 months ago class-installer.php 2 weeks ago class-json-manager.php 1 year ago class-kb.php 7 months ago class-metadata.php 2 months ago class-post.php 1 year ago class-rewrite.php 5 months ago class-settings.php 1 year ago class-term.php 1 year ago class-thumbnail-overlay.php 1 year ago class-tracking.php 6 days ago class-update-email.php 2 weeks ago class-updates.php 3 months ago class-user.php 8 months ago index.php 7 years ago interface-runner.php 7 years ago template-tags.php 1 year ago
class-tracking.php
440 lines
1 <?php
2 /**
3 * Code related to the Mixpanel Tracking.
4 *
5 * @since 1.0.x
6 * @package RankMath
7 * @subpackage RankMath\Core
8 * @author Rank Math <support@rankmath.com>
9 */
10
11 declare(strict_types=1);
12
13 namespace RankMath;
14
15 use RankMath\Traits\Hooker;
16 use RankMath\Helper;
17 use RankMath\Helpers\Param;
18 use RankMath\Helpers\Str;
19 use RankMath\Helpers\Editor;
20 use RankMath\Admin\Admin_Helper;
21 use WPMedia\Mixpanel\Optin;
22 use WPMedia\Mixpanel\TrackingPlugin;
23
24 /**
25 * Tracking class.
26 */
27 class Tracking {
28 use Hooker;
29
30 /**
31 * Opt-in instance.
32 *
33 * @var Optin
34 */
35 private $optin;
36
37 /**
38 * Mixpanel instance.
39 *
40 * @var TrackingPlugin
41 */
42 private $mixpanel;
43
44 /**
45 * User email for identification.
46 *
47 * @var string
48 */
49 private $user_email = '';
50
51 /**
52 * User language.
53 *
54 * @var string
55 */
56 private $user_language = '';
57
58 /**
59 * Plugin name.
60 *
61 * @var string
62 */
63 private $plugin = '';
64
65 /**
66 * Constructor.
67 */
68 public function __construct() {
69 $this->plugin = defined( 'RANK_MATH_PRO_VERSION' ) ? 'Rank Math Pro ' . RANK_MATH_PRO_VERSION : 'Rank Math Free ' . rank_math()->version;
70 $this->optin = new Optin( 'rank_math', 'manage_options' );
71 $this->mixpanel = new TrackingPlugin( '517e881edc2636e99a2ecf013d8134d3', $this->plugin, 'RankMath', 'RankMath' );
72
73 $this->action( 'init', 'hooks' );
74 }
75
76 /**
77 * Initialize the tracking class hooks.
78 */
79 public function hooks(): void {
80 $this->init_user_data();
81 $this->action( 'rank_math_mixpanel_optin_changed', 'track_optin_change' );
82 $this->action( 'rank_math/module_changed', 'track_module_option_change', 10, 2 );
83 $this->action( 'rank_math/setup_wizard/enable_tracking', 'enable_usage_tracking' );
84 $this->action( 'rank_math/setup_wizard/step_viewed', 'track_setup_wizard_step_view' );
85 $this->action( 'rank_math/admin/enqueue_scripts', 'enqueue_mixpanel' );
86 $this->action( 'admin_init', 'track_admin_page_view' );
87 $this->action( 'rank_math/admin/options/general_data', 'set_usage_tracking_option', 99 );
88 $this->action( 'rank_math/settings/before_save', 'update_mixpanel_optin', 10, 2 );
89 $this->filter( 'rank_math/settings/saved_data', 'add_mixpanel_data', 10, 2 );
90 $this->action( 'cmb2_save_options-page_fields_rank-math-options-general_options', 'update_mixpanel_optin_cmb2' );
91 }
92
93 /**
94 * Track the opt-in status change.
95 *
96 * @param bool $status The new opt-in status.
97 */
98 public function track_optin_change( $status ): void {
99 $this->identify_user();
100 $this->mixpanel->track_optin( $status );
101 }
102
103 /**
104 * Track module option changes.
105 *
106 * @param string $module_id The module ID.
107 * @param string $state The new state (on/off).
108 */
109 public function track_module_option_change( $module_id, $state ): void {
110 if ( ! $this->is_opted_in() ) {
111 return;
112 }
113
114 $enable_module = $state === 'on';
115 $properties = [
116 'context' => 'wp_plugin',
117 'option_name' => 'module ' . $module_id,
118 'previous_value' => ! $enable_module ? 1 : 0,
119 'new_value' => $enable_module ? 1 : 0,
120 'language' => $this->user_language,
121 ];
122
123 $this->track_event( 'Option Changed', $properties );
124 }
125
126 /**
127 * Enable or disable usage tracking.
128 *
129 * @param bool $enable True to enable, false to disable.
130 */
131 public function enable_usage_tracking( $enable ): void {
132 if ( $enable ) {
133 $this->optin->enable();
134 return;
135 }
136
137 $this->optin->disable();
138 }
139
140 /**
141 * Track setup wizard step views.
142 */
143 public function track_setup_wizard_step_view() {
144 if ( ! $this->is_opted_in() ) {
145 return;
146 }
147
148 // Get the actual admin page URL from the request referer.
149 $referer = wp_get_referer();
150 $current_url = $referer ? $referer : Helper::get_current_page_url();
151 $path = $this->get_current_path_with_query();
152
153 // Parse the referer URL to get the current step being viewed.
154 $url_parts = wp_parse_url( $referer );
155 parse_str( $url_parts['query'] ?? '', $query_params );
156 $current_step_being_viewed = $query_params['step'] ?? 'compatibility';
157
158 // Use static variable to prevent duplicate tracking in the same request.
159 static $tracked_steps = [];
160 if ( in_array( $current_step_being_viewed, $tracked_steps, true ) ) {
161 return;
162 }
163 $tracked_steps[] = $current_step_being_viewed;
164
165 $properties = [
166 'current_url' => $current_url,
167 'path' => $path,
168 'context' => 'wp_plugin',
169 'language' => $this->user_language,
170 ];
171
172 $this->track_event( 'Page Viewed', $properties );
173 }
174
175 /**
176 * Enqueue Mixpanel script on Block Editor pages.
177 */
178 public function enqueue_mixpanel(): void {
179 if ( ! $this->optin->can_track() ) {
180 return;
181 }
182
183 if ( ! Helper::is_block_editor() || ! Editor::can_add_editor() ) {
184 return;
185 }
186
187 $this->mixpanel->add_script();
188 Helper::add_json(
189 'tracking',
190 [
191 'plugin' => $this->plugin,
192 'path' => isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '',
193 'email' => $this->user_email,
194 'language' => $this->user_language,
195 ],
196 );
197 }
198
199 /**
200 * Track admin page views.
201 */
202 public function track_admin_page_view() {
203 if ( ! $this->optin->can_track() ) {
204 return;
205 }
206
207 // Only track Rank Math admin/configuration pages.
208 if ( ! Str::starts_with( 'rank-math', Param::get( 'page' ) ) ) {
209 return;
210 }
211
212 // Check if we've already tracked this user in the last 7 days.
213 $transient_key = 'rank_math_admin_tracking_' . md5( $this->user_email );
214 if ( get_transient( $transient_key ) ) {
215 return;
216 }
217
218 // Set transient for 7 days to prevent duplicate tracking.
219 set_transient( $transient_key, true, WEEK_IN_SECONDS );
220
221 // Get the current admin page URL.
222 $current_url = Helper::get_current_page_url();
223 $path = $this->get_current_path_with_query();
224
225 $properties = [
226 'current_url' => $current_url,
227 'path' => $path,
228 'context' => 'wp_plugin',
229 'language' => $this->user_language,
230 ];
231
232 // Determine capability based on current Rank Math page and pass it to Mixpanel.
233 $page = (string) Param::get( 'page' );
234 $event_capability = $this->get_event_capability_for_page( $page );
235 $this->track_event( 'Page Viewed', $properties, $event_capability );
236 }
237
238 /**
239 * Add usage_tracking option to the general settings.
240 *
241 * @param array $json Localized data.
242 */
243 public function set_usage_tracking_option( $json ) {
244 // Early bail if the current page is not general settings.
245 if ( ! isset( $json['optionPage'] ) || $json['optionPage'] !== 'general' || ! isset( $json['data'] ) ) {
246 return $json;
247 }
248
249 $json['canAddUsageTracking'] = current_user_can( 'manage_options' );
250 $json['data']['usage_tracking'] = $this->optin->can_track();
251
252 return $json;
253 }
254
255 /**
256 * Update opt-in value.
257 *
258 * @param string $type Settings type.
259 * @param array $settings Settings data.
260 */
261 public function update_mixpanel_optin( $type, $settings ) {
262 if ( $type !== 'general' || ! isset( $settings['usage_tracking'] ) ) {
263 return;
264 }
265
266 if ( ! empty( $settings['usage_tracking'] ) ) {
267 $this->optin->enable();
268 return;
269 }
270
271 $this->optin->disable();
272 }
273
274 /**
275 * Add usage tracking data to the saved settings.
276 *
277 * @param array $data Settings data.
278 * @param string $type Settings type.
279 * @return array
280 */
281 public function add_mixpanel_data( $data, $type ) {
282 if ( $type !== 'general' ) {
283 return $data;
284 }
285
286 $data['usage_tracking'] = $this->optin->can_track();
287 return $data;
288 }
289
290 /**
291 * Update Mixpanel optin option when general settings are saved. Used
292 */
293 public function update_mixpanel_optin_cmb2() {
294 // Get the value from the form submission.
295 $usage_tracking = isset( $_POST['usage_tracking'] ) ? sanitize_text_field( wp_unslash( $_POST['usage_tracking'] ) ) : 'off';
296 if ( $usage_tracking === 'on' ) {
297 $this->optin->enable();
298 return;
299 }
300
301 $this->optin->disable();
302 }
303
304 /**
305 * Check if usage tracking is enabled (opt-in).
306 *
307 * @return bool
308 */
309 public function is_opted_in(): bool {
310 return $this->optin->is_enabled();
311 }
312
313 /**
314 * Track a WordPress Abilities API execution event.
315 *
316 * MCP and direct calls share the same event name; the context property
317 * differentiates them ('wp_plugin' vs 'wp_plugin_mcp').
318 *
319 * @param string $event_name Mixpanel event name (e.g. 'SEO Audit Run').
320 * @param array $properties Additional event properties (e.g. score, test_id).
321 * @param string $event_capability WordPress capability required to execute the ability.
322 */
323 public function track_ability_executed( string $event_name, array $properties = [], string $event_capability = '' ): void {
324 if ( ! $this->is_opted_in() ) {
325 return;
326 }
327
328 $context = ! empty( $_SERVER['HTTP_MCP_SESSION_ID'] ) ? 'wp_plugin_mcp' : 'wp_plugin'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
329
330 $this->track_event(
331 $event_name,
332 array_merge( [ 'context' => $context ], $properties ),
333 $event_capability
334 );
335 }
336
337 /**
338 * Track a custom event for Rank Math.
339 *
340 * @param string $event Event name.
341 * @param array $properties Additional properties to merge.
342 * @param string $event_capability The capability required to track the event.
343 */
344 public function track_event( string $event, array $properties = [], string $event_capability = '' ): void {
345 $defaults = [
346 'context' => 'wp_plugin',
347 'language' => $this->user_language,
348 ];
349
350 $this->identify_user();
351 $this->mixpanel->track( $event, array_merge( $defaults, $properties ), $event_capability );
352 }
353
354 /**
355 * Get the current request path with query string, suitable for tracking.
356 * Handles AJAX and REST requests by using referer when available.
357 *
358 * @return string
359 */
360 public function get_current_path_with_query(): string {
361 // For AJAX/REST requests, use referer to get the originating page.
362 $referer = wp_get_referer();
363 $current_url = $referer ? $referer : Helper::get_current_page_url();
364 $path = wp_parse_url( $current_url, PHP_URL_PATH ) . '?' . wp_parse_url( $current_url, PHP_URL_QUERY );
365
366 return $path;
367 }
368
369 /**
370 * Identify the current user in Mixpanel.
371 * Safe to call multiple times; no-ops when opt-out.
372 */
373 public function identify_user(): void {
374 $this->mixpanel->identify( $this->user_email );
375 }
376
377 /**
378 * Get the current plugin label (with version) used in tracking payloads.
379 *
380 * @return string
381 */
382 public function get_plugin_label(): string {
383 return $this->plugin;
384 }
385
386 /**
387 * Initialize user data.
388 */
389 private function init_user_data() {
390 if ( ! $this->user_email ) {
391 $this->user_email = $this->get_user_email();
392 }
393
394 if ( ! $this->user_language ) {
395 $this->user_language = get_user_locale();
396 }
397 }
398
399 /**
400 * Get user email for identification.
401 *
402 * @return string
403 */
404 private function get_user_email(): string {
405 $account = Admin_Helper::get_registration_data();
406 if ( ! empty( $account['email'] ) ) {
407 return $account['email'];
408 }
409
410 $user = wp_get_current_user();
411 return isset( $user->user_email ) ? (string) $user->user_email : '';
412 }
413
414 /**
415 * Get the capability required for tracking based on the current Rank Math page.
416 *
417 * @param string $page The `page` query arg, e.g., 'rank-math-options-general'.
418 *
419 * @return string Capability name or empty for default behavior.
420 */
421 private function get_event_capability_for_page( string $page ): string {
422 // Map known Rank Math admin pages to their capabilities.
423 $map = [
424 'rank-math-options-general' => 'rank_math_general',
425 'rank-math-options-titles' => 'rank_math_titles',
426 'rank-math-options-sitemap' => 'rank_math_sitemap',
427 'rank-math-options-instant-indexing' => 'rank_math_general',
428 'rank-math-404-monitor' => 'rank_math_404_monitor',
429 'rank-math-redirections' => 'rank_math_redirections',
430 'rank-math-role-manager' => 'rank_math_role_manager',
431 'rank-math-analytics' => 'rank_math_analytics',
432 'rank-math-seo-analysis' => 'rank_math_site_analysis',
433 'rank-math-content-ai-page' => 'rank_math_content_ai',
434 'rank-math-links-page' => 'rank_math_general',
435 ];
436
437 return isset( $map[ $page ] ) ? $map[ $page ] : '';
438 }
439 }
440