PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 8.5.74
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v8.5.74
9.1.2 9.1.1 9.1.0 9.0.3 9.0.2 9.0.1 9.0.0 8.5.79 8.5.78 8.5.77 8.5.76 8.5.75 8.5.74 8.5.73 8.5.72 8.5.71 8.5.70 8.5.69 8.5.68 8.5.35 8.5.36 8.5.37 8.5.38 8.5.39 8.5.4 All 221 releases
wpvr / includes / class-wpvr-telemetry.php

class-wpvr-telemetry.php in WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress 8.5.74, at includes/class-wpvr-telemetry.php

804 lines 28.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class WPVR_Telemetry
4 *
5 * Handles telemetry tracking for the WPVR plugin.
6 *
7 * @since 8.5.48
8 */
9
10 class WPVR_Telemetry {
11
12 /**
13 * WPVR_Telemetry constructor.
14 *
15 * Initialize telemetry hooks for the plugin.
16 *
17 * @since 8.5.48
18 */
19 public function __construct() {
20 add_action( 'wpvr_plugin_activated', array( $this, 'track_plugin_activation' ));
21 add_action( 'wpvr_plugin_deactivated', array( $this, 'track_plugin_deactivation' ));
22 add_action('setup_wizard_before_onboarding_content', array( $this, 'track_setup_wizard_start_event' ));
23 add_action( 'wp_ajax_wpvr_setup_wizard_completed', array($this, 'track_setup_wizard_completed' ) );
24 add_action( 'wp_ajax_wpvr_first_strike_completed', array($this, 'track_first_strike_completed' ) );
25
26
27 add_action( 'transition_post_status', array( $this, 'track_first_tour_published' ), 10, 3);
28 add_action( 'rex_wpvr_tour_created', array( $this, 'track_tour_created' ), 10, 1);
29 add_action( 'current_screen', array( $this, 'track_page_view' ) );
30 add_action( 'rex_wpvr_embadded_tour', array( $this, 'track_embadded_tour' ), 10, 1 );
31 add_action( 'rex_wpvr_tour_saved', array( $this, 'track_detailed_tour_events' ), 10, 1 );
32
33 add_action( 'wp_ajax_wpvr_track_telemetry_event', array( $this, 'track_telemetry_event' ) );
34 }
35
36 /**
37 * Track setup wizard started event
38 * Checks if already tracked before sending
39 *
40 * @since 8.5.48
41 */
42 public function track_setup_wizard_start_event() {
43 if ( ! current_user_can( 'manage_options' ) ) {
44 wp_send_json_error( array( 'message' => 'Unauthorized user' ), 403 );
45 return;
46 }
47
48 $setup_started = get_option('wpvr_setup_wizard_started', false);
49 if ( $setup_started ) {
50 return;
51 }
52
53 update_option('wpvr_setup_wizard_started', true);
54 if ( function_exists( 'coderex_telemetry_track' ) && defined( 'WPVR_FILE' ) ) {
55 coderex_telemetry_update_last_action( WPVR_FILE, 'setup_wizard_started' );
56 coderex_telemetry_track(
57 WPVR_FILE,
58 'setup_started',
59 array(
60 'time' => current_time('mysql'),
61 )
62 );
63 }
64 }
65
66
67 /**
68 * Track setup wizard completed event
69 * Checks if already tracked before sending
70 *
71 * @since 8.5.48
72 */
73 public function track_setup_wizard_completed() {
74 if ( ! current_user_can( 'manage_options' ) ) {
75 wp_send_json_error( array( 'message' => 'Unauthorized user' ), 403 );
76 return;
77 }
78
79 $nonce = isset($_POST['security']) ? sanitize_text_field(wp_unslash( $_POST['security'] )) : '';
80 if ( !wp_verify_nonce( $nonce, 'wpvr' ) ) {
81 wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 );
82 return;
83 }
84
85 $setup_completed = get_option('wpvr_setup_wizard_completed', false);
86 if ( $setup_completed ) {
87 wp_send_json_success( array( 'message' => 'Setup already tracked', 'already_tracked' => true ) );
88 return;
89 }
90
91 update_option('wpvr_setup_wizard_completed', true);
92
93 $industry = isset($_POST['industry']) ? sanitize_text_field(wp_unslash( $_POST['industry'] )) : '';
94
95 if ( function_exists( 'coderex_telemetry_track' ) && defined( 'WPVR_FILE' ) ) {
96 coderex_telemetry_update_last_action( WPVR_FILE, 'setup_wizard_completed' );
97 coderex_telemetry_track(
98 WPVR_FILE,
99 'setup_completed',
100 array(
101 'industry' => $industry,
102 'time' => current_time('mysql'),
103 )
104 );
105 }
106
107 do_action( 'wpvr_setup_wizard_completed_event', $industry );
108
109 wp_send_json_success( array( 'message' => 'Setup wizard completed tracked' ) );
110 }
111
112 /**
113 * Track first strike completed event
114 * Checks if already tracked before sending
115 *
116 * @since 8.5.48
117 */
118 public function track_first_strike_completed() {
119 if ( ! current_user_can( 'manage_options' ) ) {
120 wp_send_json_error( array( 'message' => 'Unauthorized user' ), 403 );
121 return;
122 }
123
124 $nonce = isset($_POST['security']) ? sanitize_text_field(wp_unslash( $_POST['security'] )) : '';
125 if ( !wp_verify_nonce( $nonce, 'wpvr' ) ) {
126 wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 );
127 return;
128 }
129
130 $first_strike_completed = get_option('wpvr_first_strike_completed', false);
131 if ( $first_strike_completed ) {
132 wp_send_json_success( array( 'message' => 'First strike already tracked', 'already_tracked' => true ) );
133 return;
134 }
135
136 update_option('wpvr_first_strike_completed', true);
137
138 $industry = isset($_POST['industry']) ? sanitize_text_field(wp_unslash( $_POST['industry'] )) : '';
139
140 if ( function_exists( 'coderex_telemetry_track' ) && defined( 'WPVR_FILE' ) ) {
141 coderex_telemetry_update_last_action( WPVR_FILE, 'first_strike_completed' );
142 coderex_telemetry_track(
143 WPVR_FILE,
144 'first_strike_completed',
145 array(
146 'industry' => $industry,
147 'time' => current_time('mysql'),
148 )
149 );
150 }
151
152 wp_send_json_success( array( 'message' => 'First strike completed tracked' ) );
153 }
154
155 /**
156 * Track generic telemetry event via AJAX
157 *
158 * @since 8.5.48
159 */
160 public function track_telemetry_event() {
161 if ( ! isset( $_POST['security'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['security'] ) ), 'wpvr' ) ) {
162 wp_send_json_error( array( 'message' => 'Nonce verification failed' ) );
163 }
164
165 if ( ! current_user_can( 'manage_options' ) ) {
166 wp_send_json_error( array( 'message' => 'Permission denied' ) );
167 }
168
169 $event_name = isset( $_POST['event_name'] ) ? sanitize_text_field( wp_unslash( $_POST['event_name'] ) ) : '';
170
171 if ( empty( $event_name ) ) {
172 wp_send_json_error( array( 'message' => 'Event name is required' ) );
173 }
174
175 $properties = isset( $_POST['properties'] ) ? map_deep( wp_unslash( $_POST['properties'] ), 'sanitize_text_field' ) : array();
176
177 $default_properties = array(
178 'time' => current_time( 'mysql' ),
179 );
180
181 $final_properties = array_merge( $default_properties, $properties );
182
183 // Update last core action
184 if ( function_exists( 'coderex_telemetry_update_last_action' ) ) {
185 coderex_telemetry_update_last_action( WPVR_FILE, $event_name );
186 }
187
188 if ( function_exists( 'coderex_telemetry_track' ) ) {
189 coderex_telemetry_track(
190 WPVR_FILE,
191 $event_name,
192 $final_properties
193 );
194 }
195
196 wp_send_json_success( array( 'message' => 'Event tracked successfully' ) );
197 }
198
199 /**
200 * Track detailed tour events on save/published
201 *
202 * @param int $post_id
203 * @since 8.5.48
204 */
205 public function track_detailed_tour_events( $post_id ) {
206 $this->track_tour_type_event( $post_id );
207 $this->track_feature_used_events( $post_id );
208 }
209
210 /**
211 * Track tour type
212 *
213 * @param int $post_id
214 */
215 private function track_tour_type_event( $post_id ) {
216 $tour_type = $this->get_tour_type( $post_id );
217
218 // Update last core action
219 if ( function_exists( 'coderex_telemetry_update_last_action' ) ) {
220 coderex_telemetry_update_last_action( WPVR_FILE, 'tour_type_set' );
221 }
222 if ( function_exists( 'coderex_telemetry_track' ) ) {
223 coderex_telemetry_track(
224 WPVR_FILE,
225 'tour_type',
226 array(
227 'tour_type' => $tour_type,
228 )
229 );
230 }
231 }
232
233 /**
234 * Get tour type from post meta
235 *
236 * @param int $post_id
237 * @return string
238 */
239 private function get_tour_type( $post_id ) {
240 $panodata = get_post_meta( $post_id, 'panodata', true );
241
242 $post_panovideo = isset( $_POST['panovideo'] ) ? sanitize_text_field( wp_unslash( $_POST['panovideo'] ) ) : '';
243 $post_streetview = isset( $_POST['streetview'] ) ? sanitize_text_field( wp_unslash( $_POST['streetview'] ) ) : '';
244
245 if ( $post_panovideo === 'on' || ( isset( $panodata['vidid'] ) && ! empty( $panodata['vidid'] ) ) ) {
246 return '360 Video Tour';
247 }
248
249 if ( $post_streetview === 'on' || ( isset( $panodata['streetview'] ) && ! empty( $panodata['streetview'] ) ) ) {
250 return 'Google Street View';
251 }
252
253 if ( ! empty( $panodata['panodata']['scene-list'] ) ) {
254 foreach ( $panodata['panodata']['scene-list'] as $scene ) {
255 if ( isset( $scene['scene-type'] ) && $scene['scene-type'] === 'cubemap' ) {
256 return 'Cubemap Tour';
257 }
258 }
259 }
260
261 // Default to image tour
262 return '360 Image Tour';
263 }
264
265 /**
266 * Track feature used events
267 *
268 * @param int $post_id
269 */
270 private function track_feature_used_events( $post_id ) {
271 $features = $this->get_enabled_features( $post_id );
272
273 foreach ( $features as $feature_name ) {
274 if ( function_exists( 'coderex_telemetry_track' ) ) {
275 coderex_telemetry_track(
276 WPVR_FILE,
277 'feature_used',
278 array(
279 'feature_name' => $feature_name,
280 )
281 );
282 }
283 }
284 }
285
286 /**
287 * Identify all enabled features for a tour
288 *
289 * @param int $post_id
290 * @return array
291 */
292 private function get_enabled_features( $post_id ) {
293 $enabled_features = array();
294 $panodata = get_post_meta( $post_id, 'panodata', true );
295
296 $post_panovideo = isset( $_POST['panovideo'] ) ? sanitize_text_field( wp_unslash( $_POST['panovideo'] ) ) : '';
297 $post_streetview = isset( $_POST['wpvrStreetView'] ) ? sanitize_text_field( wp_unslash( $_POST['wpvrStreetView'] ) ) : '';
298 $is_video_tour = $post_panovideo === 'on';
299 $is_street_view = $post_streetview === 'on';
300
301 // Feature Mapping Detection
302 $feature_map = array(
303 'Auto Rotation' => 'autorotation',
304 'Tour Autoload' => 'autoload',
305 'Control Buttons' => 'control',
306 'Gyroscope Control' => 'gyro',
307 'Auto Gyroscope' => 'deviceorientationcontrol',
308 'Scene Gallery' => 'vrgallery',
309 'Scene Navigation Menu' => 'vrscene_navigation',
310 'Compass' => 'compass',
311 'Background Music' => 'bg_music',
312 'Company Logo' => 'cpLogoSwitch',
313 'Explainer Video' => 'explainerSwitch',
314 'Background Tour' => 'wpvr_bg_tour_enabler',
315 'Generic Form (Contact)' => 'genericform',
316 'Call To Action Button' => 'button_enable',
317 'Custom CSS' => 'custom_css_switch',
318 'Global Zoom Limit' => 'gzoom',
319 'Floor Plan' => 'wpvr_floor_plan_enabler',
320 );
321
322 foreach ( $feature_map as $readable_name => $post_key ) {
323 if ( $is_street_view && 'autoload' === $post_key ) {
324 continue;
325 }
326
327 if ( isset( $_POST[$post_key] ) ) {
328 $val = sanitize_text_field( wp_unslash( $_POST[$post_key] ) );
329 if ( $val === 'on' || $val === '1' ) {
330 $enabled_features[] = $readable_name;
331 }
332 }
333 }
334
335 // Global Scene Transition
336 if ( isset( $_POST['scenefadeduration'] ) ) {
337 $scenefadeduration = floatval( wp_unslash( $_POST['scenefadeduration'] ) );
338 if ( $scenefadeduration > 0 ) {
339 $enabled_features[] = 'Scene Transition (Fade)';
340 }
341 }
342
343 // Add Video Specific Features
344 if ( $is_video_tour ) {
345 if ( isset( $_POST['autoplay'] ) && sanitize_text_field( wp_unslash( $_POST['autoplay'] ) ) === 'on' ) {
346 $enabled_features[] = 'Video Autoplay';
347 }
348 if ( isset( $_POST['loop'] ) && sanitize_text_field( wp_unslash( $_POST['loop'] ) ) === 'on' ) {
349 $enabled_features[] = 'Video Loop';
350 }
351
352 // Detect Video Source
353 if ( isset( $_POST['videourl'] ) && ! empty( $_POST['videourl'] ) ) {
354 $video_url = esc_url_raw( wp_unslash( $_POST['videourl'] ) );
355 $video_source = 'Self-hosted Video'; // Default
356
357 if ( strpos( $video_url, 'youtube.com' ) !== false || strpos( $video_url, 'youtu.be' ) !== false ) {
358 $video_source = 'YouTube Video';
359 } elseif ( strpos( $video_url, 'vimeo.com' ) !== false ) {
360 $video_source = 'Vimeo Video';
361 } elseif ( strpos( $video_url, 'instagram.com' ) !== false ) {
362 $video_source = 'Instagram Video';
363 } elseif ( strpos( $video_url, 'facebook.com' ) !== false || strpos( $video_url, 'fb.watch' ) !== false ) {
364 $video_source = 'Facebook Video';
365 }
366
367 $enabled_features[] = $video_source;
368 }
369 }
370
371 // Layouts (Skip for Video Tour and Street View)
372 if ( ! $is_video_tour && ! $is_street_view && isset( $_POST['tourLayout'] ) ) {
373 $tourLayout = sanitize_text_field( wp_unslash( $_POST['tourLayout'] ) );
374 if ( $tourLayout === 'default' ) {
375 $enabled_features[] = 'Classic Layout';
376 } elseif ( $tourLayout === 'layout1' ) {
377 $enabled_features[] = 'Modern Layout';
378 }
379 }
380
381 // Detailed JSON data checks
382 if ( ! empty( $_POST['panodata'] ) ) {
383 $post_panodata = json_decode( wp_unslash( $_POST['panodata'] ), true );
384 if ( ! empty( $post_panodata['scene-list'] ) ) {
385 foreach ( $post_panodata['scene-list'] as $scene ) {
386 // Hotspots (Skip for Video Tour and Street View)
387 if ( ! $is_video_tour && ! $is_street_view && ! empty( $scene['hotspot-list'] ) ) {
388 foreach ( $scene['hotspot-list'] as $hotspot ) {
389 $type = isset( $hotspot['hotspot-type'] ) ? $hotspot['hotspot-type'] : '';
390 if ( $type === 'info' ) {
391 $enabled_features[] = 'Info Hotspot';
392 } elseif ( $type === 'wc_product' ) {
393 $enabled_features[] = 'WooCommerce Product Hotspot';
394 } elseif ( $type === 'fluent_form' ) {
395 $enabled_features[] = 'Fluent Form Hotspot';
396 } else {
397 $enabled_features[] = 'Scene Type Hotspot';
398 }
399
400 if ( ! empty( $hotspot['hotspot-customclass'] ) || ! empty( $hotspot['hotspot-custom-icon'] ) ) {
401 $enabled_features[] = 'Custom Hotspot Icon';
402 }
403 }
404 }
405
406 // Scene specific settings from images (Usually skip for Video Tour as it has separate logic)
407 if ( ! $is_video_tour ) {
408 if ( ! empty( $scene['maxHfov'] ) || ! empty( $scene['minHfov'] ) || ! empty( $scene['maxPitch'] ) || ! empty( $scene['minPitch'] ) ) {
409 $enabled_features[] = 'Custom Viewing Limits';
410 }
411
412 if ( ! empty( $scene['haov'] ) || ! empty( $scene['vaov'] ) ) {
413 $enabled_features[] = 'Custom Default View';
414 }
415
416 if ( ! empty( $scene['latitude'] ) || ! empty( $scene['longitude'] ) ) {
417 $enabled_features[] = 'Map Location';
418 }
419 }
420 }
421 }
422 }
423
424 return array_unique( $enabled_features );
425 }
426
427 /**
428 * Track plugin activation
429 *
430 * Sends telemetry event when the plugin is activated.
431 *
432 * @since 8.5.48
433 */
434 public function track_plugin_activation() {
435
436 if ( function_exists( 'coderex_telemetry_track' ) ) {
437 coderex_telemetry_track(
438 WPVR_FILE,
439 'plugin_activation',
440 array(
441 'plugin_version' => defined('WPVR_VERSION') ? WPVR_VERSION : 'unknown',
442 'activation_time' => current_time( 'c' ),
443 )
444 );
445 }
446 }
447
448 /**
449 * Track plugin deactivation
450 *
451 * Sends telemetry event when the plugin is deactivated.
452 *
453 * @since 8.5.48
454 */
455 public function track_plugin_deactivation() {
456 // Calculate usage duration
457 $activation_time = get_option( 'wpvr_plugin_installed', 0 );
458 $usage_duration = 'Not available';
459 if ( $activation_time > 0 ) {
460 $duration_seconds = time() - $activation_time;
461 $usage_duration = $this->format_duration( $duration_seconds );
462 }
463
464 // Get last core action
465 $last_core_action = get_option( 'wpvr_last_core_action', '' );
466
467 // Track deactivation event
468 if ( function_exists( 'coderex_telemetry_track' ) ) {
469 coderex_telemetry_track(
470 WPVR_FILE,
471 'plugin_deactivated',
472 array(
473 'plugin_version' => defined('WPVR_VERSION') ? WPVR_VERSION : 'unknown',
474 'deactivation_time' => current_time( 'c' ),
475 'usage_duration' => $usage_duration,
476 'last_core_action' => $last_core_action,
477 )
478 );
479 }
480
481 // Clean up activation time option
482 delete_option( 'wpvr_plugin_installed' );
483 }
484
485 /**
486 * Track the first published tour
487 *
488 * Sends telemetry when the first tour is published for the plugin.
489 *
490 * @param string $new_status The new post status
491 * @param string $old_status The previous post status
492 * @param object $post \WP_Post The post object
493 * @since 8.5.48
494 */
495 public function track_first_tour_published( $new_status, $old_status, $post ) {
496 if ($post->post_type !== 'wpvr_item') {
497 return;
498 }
499
500 // Skip demo/sample tours created during plugin installation
501 $is_demo_tour = get_post_meta($post->ID, 'wpvr_is_demo_tour', true);
502 if ($is_demo_tour === '1') {
503 return;
504 }
505
506 if ($new_status === 'publish' && in_array($old_status, ['auto-draft', 'draft', 'new', ''])) {
507 // Count only non-demo tours
508 $args = array(
509 'post_type' => 'wpvr_item',
510 'post_status' => array('publish', 'draft'),
511 'posts_per_page' => -1,
512 'fields' => 'ids',
513 'meta_query' => array(
514 'relation' => 'OR',
515 array(
516 'key' => 'wpvr_is_demo_tour',
517 'compare' => 'NOT EXISTS',
518 ),
519 array(
520 'key' => 'wpvr_is_demo_tour',
521 'value' => '1',
522 'compare' => '!=',
523 ),
524 ),
525 );
526
527 $user_tours = get_posts($args);
528 $total_user_tours = is_array($user_tours) ? count($user_tours) : 0;
529
530 do_action('rex_wpvr_tour_created', $post->post_title);
531
532 // Detailed tracking for first publish
533 $this->track_detailed_tour_events( $post->ID );
534
535 if (1 === $total_user_tours) {
536 // Update last core action
537 if ( function_exists( 'coderex_telemetry_update_last_action' ) ) {
538 coderex_telemetry_update_last_action( WPVR_FILE, 'first_tour_published' );
539 }
540 if ( function_exists( 'coderex_telemetry_track' ) ) {
541 coderex_telemetry_track(
542 WPVR_FILE,
543 'first_tour_published',
544 array(
545 'tour_title' => $post->post_title,
546 'time' => current_time('mysql'),
547 )
548 );
549 }
550
551 do_action( 'wpvr_first_tour_published_event', $post->ID, $post->post_title );
552
553 // Also track first_strike (first successful value moment)
554 if ( function_exists( 'coderex_telemetry_update_last_action' ) ) {
555 coderex_telemetry_update_last_action( WPVR_FILE, 'first_strike' );
556 }
557 if ( function_exists( 'coderex_telemetry_track' ) ) {
558 coderex_telemetry_track(
559 WPVR_FILE,
560 'first_strike',
561 array(
562 'tour_title' => $post->post_title,
563 'time' => current_time('mysql'),
564 )
565 );
566 }
567 }
568 } elseif ($new_status === 'publish' && $old_status === 'publish') {
569 // Update last core action
570 if ( function_exists( 'coderex_telemetry_update_last_action' ) ) {
571 coderex_telemetry_update_last_action( WPVR_FILE, 'tour_updated' );
572 }
573 if ( function_exists( 'coderex_telemetry_track' ) ) {
574 coderex_telemetry_track(
575 WPVR_FILE,
576 'tour_updated',
577 array(
578 'tour_title' => $post->post_title,
579 'time' => current_time('mysql'),
580 )
581 );
582 }
583 // Detailed tracking on update
584 $this->track_detailed_tour_events( $post->ID );
585 }
586 }
587 /**
588 * Track tour creation
589 *
590 * Sends telemetry when a new tour is created.
591 *
592 * @param string $post_title The title of the created tour
593 * @since 8.5.48
594 */
595 public function track_tour_created( $post_title ) {
596 // Update last core action
597 if ( function_exists( 'coderex_telemetry_update_last_action' ) ) {
598 coderex_telemetry_update_last_action( WPVR_FILE, 'tour_created' );
599 }
600 if ( function_exists( 'coderex_telemetry_track' ) ) {
601 coderex_telemetry_track(
602 WPVR_FILE,
603 'tour_created',
604 array(
605 'tour_title' => isset( $post_title) ? $post_title : '',
606 'time' => current_time('mysql'),
607 )
608 );
609 }
610 }
611
612
613 /**
614 * Track page views
615 *
616 * Sends telemetry when specific admin pages for the plugin are viewed.
617 *
618 * @param WP_Screen $screen Current admin screen object
619 * @return void
620 * @since 8.5.48
621 */
622 public function track_page_view( $screen ) {
623 if ( ! is_admin() || empty( $screen->id ) ) {
624 return;
625 }
626
627 $page_map = array(
628 '/wp-admin/edit.php?post_type=wpvr_item' => 'Tour list',
629 '/wp-admin/admin.php?page=wpvr-settings' => 'Settings',
630 '/wp-admin/admin.php?page=wpvr-analytics' => 'Analytics',
631 '/wp-admin/admin.php?page=rex-wpvr-setup-wizard' => 'Setup wizard',
632 '/wp-admin/admin.php?page=wpvrpro' => 'License',
633 );
634
635 $current_page = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( $_SERVER['REQUEST_URI'] ) : '';
636 if ( '' === $current_page ) {
637 return;
638 }
639
640 $page_name = null;
641 foreach ( $page_map as $fragment => $name ) {
642 if ( strpos( $current_page, $fragment ) !== false ) {
643 $page_name = $name;
644 break;
645 }
646 }
647
648
649 if ( null === $page_name &&
650 strpos( $current_page, '/wp-admin/post.php' ) !== false &&
651 strpos( $current_page, 'action=edit' ) !== false ) {
652 if ( isset( $screen->post_type ) && 'wpvr_item' === $screen->post_type ) {
653 $page_name = 'Tour edit';
654 }
655 }
656
657 if ( null === $page_name ) {
658 return;
659 }
660
661 $current_user = wp_get_current_user();
662 if ( ! $current_user->exists() ) {
663 return;
664 }
665
666 if ( function_exists( 'coderex_telemetry_track' ) ) {
667 coderex_telemetry_track(
668 WPVR_FILE,
669 'page_view',
670 array(
671 'page' => $current_page,
672 'page_name' => $page_name,
673 'time' => current_time( 'mysql' ),
674 )
675 );
676 }
677 }
678
679
680 /**
681 * Track embedded tour event
682 *
683 * Sends telemetry when a tour is embedded.
684 *
685 * @param int $tour_id The ID of the tour being embedded.
686 * @return void
687 * @since 8.5.48
688 */
689 public function track_embadded_tour( $tour_id ) {
690 $this->track_weekly_unique_views_kui( $tour_id );
691
692 // Check if tracking is already done for this tour
693 if ( get_post_meta( $tour_id, '_wpvr_tour_embedded_tracked', true ) ) {
694 return;
695 }
696
697 $tour = get_post( $tour_id );
698
699 if ( ! $tour || $tour->post_type !== 'wpvr_item' ) {
700 return;
701 }
702
703 // Update last core action
704 if ( function_exists( 'coderex_telemetry_update_last_action' ) ) {
705 coderex_telemetry_update_last_action( WPVR_FILE, 'tour_embedded' );
706 }
707 if ( function_exists( 'coderex_telemetry_track' ) ) {
708 coderex_telemetry_track(
709 WPVR_FILE,
710 'tour_embedded',
711 array(
712 'title' => $tour->post_title,
713 )
714 );
715 }
716
717 // Mark as tracked
718 update_post_meta( $tour_id, '_wpvr_tour_embedded_tracked', true );
719 }
720
721 /**
722 * Track rolling 7-day unique views across all active tours for KUI.
723 *
724 * @param int $tour_id Current viewed tour ID.
725 * @return void
726 */
727 private function track_weekly_unique_views_kui( $tour_id ) {
728 $tour_id = absint( $tour_id );
729 if ( $tour_id <= 0 ) {
730 return;
731 }
732
733 $tour = get_post( $tour_id );
734 if ( ! $tour || 'wpvr_item' !== $tour->post_type || 'publish' !== $tour->post_status ) {
735 return;
736 }
737
738 $option_key = 'wpvr_kui_unique_views_7d';
739 $last_count_key = 'wpvr_kui_unique_views_7d_last_count';
740 $window_seconds = 7 * DAY_IN_SECONDS;
741 $now = time();
742
743 $tracked_views = get_option( $option_key, array() );
744 if ( ! is_array( $tracked_views ) ) {
745 $tracked_views = array();
746 }
747
748 foreach ( $tracked_views as $tracked_tour_id => $timestamp ) {
749 $tracked_tour_id = absint( $tracked_tour_id );
750 $timestamp = absint( $timestamp );
751
752 if (
753 $tracked_tour_id <= 0 ||
754 $timestamp <= 0 ||
755 ( $now - $timestamp ) > $window_seconds ||
756 'publish' !== get_post_status( $tracked_tour_id )
757 ) {
758 unset( $tracked_views[ $tracked_tour_id ] );
759 }
760 }
761
762 $tracked_views[ $tour_id ] = $now;
763 update_option( $option_key, $tracked_views );
764
765 $unique_views = count( $tracked_views );
766 $last_sent_count = (int) get_option( $last_count_key, -1 );
767
768 if ( $unique_views !== $last_sent_count ) {
769 update_option( $last_count_key, $unique_views );
770 do_action( 'wpvr_kui_unique_views_updated', $unique_views, $tour_id );
771 }
772 }
773
774 /**
775 * Format duration in seconds to human-readable format
776 *
777 * @param int $seconds Duration in seconds
778 * @return string Human-readable duration
779 * @since 8.5.48
780 */
781 private function format_duration( $seconds ) {
782 if ( $seconds < 60 ) {
783 return $seconds . ' seconds';
784 }
785
786 $minutes = floor( $seconds / 60 );
787 if ( $minutes < 60 ) {
788 return $minutes . ' minutes';
789 }
790
791 $hours = floor( $minutes / 60 );
792 if ( $hours < 24 ) {
793 $remaining_minutes = $minutes % 60;
794 return $hours . ' hours' . ( $remaining_minutes > 0 ? ', ' . $remaining_minutes . ' minutes' : '' );
795 }
796
797 $days = floor( $hours / 24 );
798 $remaining_hours = $hours % 24;
799 return $days . ' days' . ( $remaining_hours > 0 ? ', ' . $remaining_hours . ' hours' : '' );
800 }
801
802 }
803
804 //new WPVR_Telemetry();