'Unauthorized user' ), 403 ); return; } $setup_started = get_option('wpvr_setup_wizard_started', false); if ( $setup_started ) { return; } update_option('wpvr_setup_wizard_started', true); if ( function_exists( 'coderex_telemetry_track' ) && defined( 'WPVR_FILE' ) ) { coderex_telemetry_update_last_action( WPVR_FILE, 'setup_wizard_started' ); coderex_telemetry_track( WPVR_FILE, 'setup_started', array( 'time' => current_time('mysql'), ) ); } } /** * Track setup wizard completed event * Checks if already tracked before sending * * @since 8.5.48 */ public function track_setup_wizard_completed() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Unauthorized user' ), 403 ); return; } $nonce = isset($_POST['security']) ? sanitize_text_field(wp_unslash( $_POST['security'] )) : ''; if ( !wp_verify_nonce( $nonce, 'wpvr' ) ) { wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 ); return; } $setup_completed = get_option('wpvr_setup_wizard_completed', false); if ( $setup_completed ) { wp_send_json_success( array( 'message' => 'Setup already tracked', 'already_tracked' => true ) ); return; } update_option('wpvr_setup_wizard_completed', true); $industry = isset($_POST['industry']) ? sanitize_text_field(wp_unslash( $_POST['industry'] )) : ''; if ( function_exists( 'coderex_telemetry_track' ) && defined( 'WPVR_FILE' ) ) { coderex_telemetry_update_last_action( WPVR_FILE, 'setup_wizard_completed' ); coderex_telemetry_track( WPVR_FILE, 'setup_completed', array( 'industry' => $industry, 'time' => current_time('mysql'), ) ); } do_action( 'wpvr_setup_wizard_completed_event', $industry ); wp_send_json_success( array( 'message' => 'Setup wizard completed tracked' ) ); } /** * Track first strike completed event * Checks if already tracked before sending * * @since 8.5.48 */ public function track_first_strike_completed() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Unauthorized user' ), 403 ); return; } $nonce = isset($_POST['security']) ? sanitize_text_field(wp_unslash( $_POST['security'] )) : ''; if ( !wp_verify_nonce( $nonce, 'wpvr' ) ) { wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 ); return; } $first_strike_completed = get_option('wpvr_first_strike_completed', false); if ( $first_strike_completed ) { wp_send_json_success( array( 'message' => 'First strike already tracked', 'already_tracked' => true ) ); return; } update_option('wpvr_first_strike_completed', true); $industry = isset($_POST['industry']) ? sanitize_text_field(wp_unslash( $_POST['industry'] )) : ''; if ( function_exists( 'coderex_telemetry_track' ) && defined( 'WPVR_FILE' ) ) { coderex_telemetry_update_last_action( WPVR_FILE, 'first_strike_completed' ); coderex_telemetry_track( WPVR_FILE, 'first_strike_completed', array( 'industry' => $industry, 'time' => current_time('mysql'), ) ); } wp_send_json_success( array( 'message' => 'First strike completed tracked' ) ); } /** * Track generic telemetry event via AJAX * * @since 8.5.48 */ public function track_telemetry_event() { if ( ! isset( $_POST['security'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['security'] ) ), 'wpvr' ) ) { wp_send_json_error( array( 'message' => 'Nonce verification failed' ) ); } if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Permission denied' ) ); } $event_name = isset( $_POST['event_name'] ) ? sanitize_text_field( wp_unslash( $_POST['event_name'] ) ) : ''; if ( empty( $event_name ) ) { wp_send_json_error( array( 'message' => 'Event name is required' ) ); } $properties = isset( $_POST['properties'] ) ? map_deep( wp_unslash( $_POST['properties'] ), 'sanitize_text_field' ) : array(); $default_properties = array( 'time' => current_time( 'mysql' ), ); $final_properties = array_merge( $default_properties, $properties ); // Update last core action if ( function_exists( 'coderex_telemetry_update_last_action' ) ) { coderex_telemetry_update_last_action( WPVR_FILE, $event_name ); } if ( function_exists( 'coderex_telemetry_track' ) ) { coderex_telemetry_track( WPVR_FILE, $event_name, $final_properties ); } wp_send_json_success( array( 'message' => 'Event tracked successfully' ) ); } /** * Track detailed tour events on save/published * * @param int $post_id * @since 8.5.48 */ public function track_detailed_tour_events( $post_id ) { $this->track_tour_type_event( $post_id ); $this->track_feature_used_events( $post_id ); } /** * Track tour type * * @param int $post_id */ private function track_tour_type_event( $post_id ) { $tour_type = $this->get_tour_type( $post_id ); // Update last core action if ( function_exists( 'coderex_telemetry_update_last_action' ) ) { coderex_telemetry_update_last_action( WPVR_FILE, 'tour_type_set' ); } if ( function_exists( 'coderex_telemetry_track' ) ) { coderex_telemetry_track( WPVR_FILE, 'tour_type', array( 'tour_type' => $tour_type, ) ); } } /** * Get tour type from post meta * * @param int $post_id * @return string */ private function get_tour_type( $post_id ) { $panodata = get_post_meta( $post_id, 'panodata', true ); $post_panovideo = isset( $_POST['panovideo'] ) ? sanitize_text_field( wp_unslash( $_POST['panovideo'] ) ) : ''; $post_streetview = isset( $_POST['streetview'] ) ? sanitize_text_field( wp_unslash( $_POST['streetview'] ) ) : ''; if ( $post_panovideo === 'on' || ( isset( $panodata['vidid'] ) && ! empty( $panodata['vidid'] ) ) ) { return '360 Video Tour'; } if ( $post_streetview === 'on' || ( isset( $panodata['streetview'] ) && ! empty( $panodata['streetview'] ) ) ) { return 'Google Street View'; } if ( ! empty( $panodata['panodata']['scene-list'] ) ) { foreach ( $panodata['panodata']['scene-list'] as $scene ) { if ( isset( $scene['scene-type'] ) && $scene['scene-type'] === 'cubemap' ) { return 'Cubemap Tour'; } } } // Default to image tour return '360 Image Tour'; } /** * Track feature used events * * @param int $post_id */ private function track_feature_used_events( $post_id ) { $features = $this->get_enabled_features( $post_id ); foreach ( $features as $feature_name ) { if ( function_exists( 'coderex_telemetry_track' ) ) { coderex_telemetry_track( WPVR_FILE, 'feature_used', array( 'feature_name' => $feature_name, ) ); } } } /** * Identify all enabled features for a tour * * @param int $post_id * @return array */ private function get_enabled_features( $post_id ) { $enabled_features = array(); $panodata = get_post_meta( $post_id, 'panodata', true ); $post_panovideo = isset( $_POST['panovideo'] ) ? sanitize_text_field( wp_unslash( $_POST['panovideo'] ) ) : ''; $post_streetview = isset( $_POST['wpvrStreetView'] ) ? sanitize_text_field( wp_unslash( $_POST['wpvrStreetView'] ) ) : ''; $is_video_tour = $post_panovideo === 'on'; $is_street_view = $post_streetview === 'on'; // Feature Mapping Detection $feature_map = array( 'Auto Rotation' => 'autorotation', 'Tour Autoload' => 'autoload', 'Control Buttons' => 'control', 'Gyroscope Control' => 'gyro', 'Auto Gyroscope' => 'deviceorientationcontrol', 'Scene Gallery' => 'vrgallery', 'Scene Navigation Menu' => 'vrscene_navigation', 'Compass' => 'compass', 'Background Music' => 'bg_music', 'Company Logo' => 'cpLogoSwitch', 'Explainer Video' => 'explainerSwitch', 'Background Tour' => 'wpvr_bg_tour_enabler', 'Generic Form (Contact)' => 'genericform', 'Call To Action Button' => 'button_enable', 'Custom CSS' => 'custom_css_switch', 'Global Zoom Limit' => 'gzoom', 'Floor Plan' => 'wpvr_floor_plan_enabler', ); foreach ( $feature_map as $readable_name => $post_key ) { if ( $is_street_view && 'autoload' === $post_key ) { continue; } if ( isset( $_POST[$post_key] ) ) { $val = sanitize_text_field( wp_unslash( $_POST[$post_key] ) ); if ( $val === 'on' || $val === '1' ) { $enabled_features[] = $readable_name; } } } // Global Scene Transition if ( isset( $_POST['scenefadeduration'] ) ) { $scenefadeduration = floatval( wp_unslash( $_POST['scenefadeduration'] ) ); if ( $scenefadeduration > 0 ) { $enabled_features[] = 'Scene Transition (Fade)'; } } // Add Video Specific Features if ( $is_video_tour ) { if ( isset( $_POST['autoplay'] ) && sanitize_text_field( wp_unslash( $_POST['autoplay'] ) ) === 'on' ) { $enabled_features[] = 'Video Autoplay'; } if ( isset( $_POST['loop'] ) && sanitize_text_field( wp_unslash( $_POST['loop'] ) ) === 'on' ) { $enabled_features[] = 'Video Loop'; } // Detect Video Source if ( isset( $_POST['videourl'] ) && ! empty( $_POST['videourl'] ) ) { $video_url = esc_url_raw( wp_unslash( $_POST['videourl'] ) ); $video_source = 'Self-hosted Video'; // Default if ( strpos( $video_url, 'youtube.com' ) !== false || strpos( $video_url, 'youtu.be' ) !== false ) { $video_source = 'YouTube Video'; } elseif ( strpos( $video_url, 'vimeo.com' ) !== false ) { $video_source = 'Vimeo Video'; } elseif ( strpos( $video_url, 'instagram.com' ) !== false ) { $video_source = 'Instagram Video'; } elseif ( strpos( $video_url, 'facebook.com' ) !== false || strpos( $video_url, 'fb.watch' ) !== false ) { $video_source = 'Facebook Video'; } $enabled_features[] = $video_source; } } // Layouts (Skip for Video Tour and Street View) if ( ! $is_video_tour && ! $is_street_view && isset( $_POST['tourLayout'] ) ) { $tourLayout = sanitize_text_field( wp_unslash( $_POST['tourLayout'] ) ); if ( $tourLayout === 'default' ) { $enabled_features[] = 'Classic Layout'; } elseif ( $tourLayout === 'layout1' ) { $enabled_features[] = 'Modern Layout'; } } // Detailed JSON data checks if ( ! empty( $_POST['panodata'] ) ) { $post_panodata = json_decode( wp_unslash( $_POST['panodata'] ), true ); if ( ! empty( $post_panodata['scene-list'] ) ) { foreach ( $post_panodata['scene-list'] as $scene ) { // Hotspots (Skip for Video Tour and Street View) if ( ! $is_video_tour && ! $is_street_view && ! empty( $scene['hotspot-list'] ) ) { foreach ( $scene['hotspot-list'] as $hotspot ) { $type = isset( $hotspot['hotspot-type'] ) ? $hotspot['hotspot-type'] : ''; if ( $type === 'info' ) { $enabled_features[] = 'Info Hotspot'; } elseif ( $type === 'wc_product' ) { $enabled_features[] = 'WooCommerce Product Hotspot'; } elseif ( $type === 'fluent_form' ) { $enabled_features[] = 'Fluent Form Hotspot'; } else { $enabled_features[] = 'Scene Type Hotspot'; } if ( ! empty( $hotspot['hotspot-customclass'] ) || ! empty( $hotspot['hotspot-custom-icon'] ) ) { $enabled_features[] = 'Custom Hotspot Icon'; } } } // Scene specific settings from images (Usually skip for Video Tour as it has separate logic) if ( ! $is_video_tour ) { if ( ! empty( $scene['maxHfov'] ) || ! empty( $scene['minHfov'] ) || ! empty( $scene['maxPitch'] ) || ! empty( $scene['minPitch'] ) ) { $enabled_features[] = 'Custom Viewing Limits'; } if ( ! empty( $scene['haov'] ) || ! empty( $scene['vaov'] ) ) { $enabled_features[] = 'Custom Default View'; } if ( ! empty( $scene['latitude'] ) || ! empty( $scene['longitude'] ) ) { $enabled_features[] = 'Map Location'; } } } } } return array_unique( $enabled_features ); } /** * Track plugin activation * * Sends telemetry event when the plugin is activated. * * @since 8.5.48 */ public function track_plugin_activation() { if ( function_exists( 'coderex_telemetry_track' ) ) { coderex_telemetry_track( WPVR_FILE, 'plugin_activation', array( 'plugin_version' => defined('WPVR_VERSION') ? WPVR_VERSION : 'unknown', 'activation_time' => current_time( 'c' ), ) ); } } /** * Track plugin deactivation * * Sends telemetry event when the plugin is deactivated. * * @since 8.5.48 */ public function track_plugin_deactivation() { // Calculate usage duration $activation_time = get_option( 'wpvr_plugin_installed', 0 ); $usage_duration = 'Not available'; if ( $activation_time > 0 ) { $duration_seconds = time() - $activation_time; $usage_duration = $this->format_duration( $duration_seconds ); } // Get last core action $last_core_action = get_option( 'wpvr_last_core_action', '' ); // Track deactivation event if ( function_exists( 'coderex_telemetry_track' ) ) { coderex_telemetry_track( WPVR_FILE, 'plugin_deactivated', array( 'plugin_version' => defined('WPVR_VERSION') ? WPVR_VERSION : 'unknown', 'deactivation_time' => current_time( 'c' ), 'usage_duration' => $usage_duration, 'last_core_action' => $last_core_action, ) ); } // Clean up activation time option delete_option( 'wpvr_plugin_installed' ); } /** * Track the first published tour * * Sends telemetry when the first tour is published for the plugin. * * @param string $new_status The new post status * @param string $old_status The previous post status * @param object $post \WP_Post The post object * @since 8.5.48 */ public function track_first_tour_published( $new_status, $old_status, $post ) { if ($post->post_type !== 'wpvr_item') { return; } // Skip demo/sample tours created during plugin installation $is_demo_tour = get_post_meta($post->ID, 'wpvr_is_demo_tour', true); if ($is_demo_tour === '1') { return; } if ($new_status === 'publish' && in_array($old_status, ['auto-draft', 'draft', 'new', ''])) { // Count only non-demo tours $args = array( 'post_type' => 'wpvr_item', 'post_status' => array('publish', 'draft'), 'posts_per_page' => -1, 'fields' => 'ids', 'meta_query' => array( 'relation' => 'OR', array( 'key' => 'wpvr_is_demo_tour', 'compare' => 'NOT EXISTS', ), array( 'key' => 'wpvr_is_demo_tour', 'value' => '1', 'compare' => '!=', ), ), ); $user_tours = get_posts($args); $total_user_tours = is_array($user_tours) ? count($user_tours) : 0; do_action('rex_wpvr_tour_created', $post->post_title); // Detailed tracking for first publish $this->track_detailed_tour_events( $post->ID ); if (1 === $total_user_tours) { // Update last core action if ( function_exists( 'coderex_telemetry_update_last_action' ) ) { coderex_telemetry_update_last_action( WPVR_FILE, 'first_tour_published' ); } if ( function_exists( 'coderex_telemetry_track' ) ) { coderex_telemetry_track( WPVR_FILE, 'first_tour_published', array( 'tour_title' => $post->post_title, 'time' => current_time('mysql'), ) ); } do_action( 'wpvr_first_tour_published_event', $post->ID, $post->post_title ); // Also track first_strike (first successful value moment) if ( function_exists( 'coderex_telemetry_update_last_action' ) ) { coderex_telemetry_update_last_action( WPVR_FILE, 'first_strike' ); } if ( function_exists( 'coderex_telemetry_track' ) ) { coderex_telemetry_track( WPVR_FILE, 'first_strike', array( 'tour_title' => $post->post_title, 'time' => current_time('mysql'), ) ); } } } elseif ($new_status === 'publish' && $old_status === 'publish') { // Update last core action if ( function_exists( 'coderex_telemetry_update_last_action' ) ) { coderex_telemetry_update_last_action( WPVR_FILE, 'tour_updated' ); } if ( function_exists( 'coderex_telemetry_track' ) ) { coderex_telemetry_track( WPVR_FILE, 'tour_updated', array( 'tour_title' => $post->post_title, 'time' => current_time('mysql'), ) ); } // Detailed tracking on update $this->track_detailed_tour_events( $post->ID ); } } /** * Track tour creation * * Sends telemetry when a new tour is created. * * @param string $post_title The title of the created tour * @since 8.5.48 */ public function track_tour_created( $post_title ) { // Update last core action if ( function_exists( 'coderex_telemetry_update_last_action' ) ) { coderex_telemetry_update_last_action( WPVR_FILE, 'tour_created' ); } if ( function_exists( 'coderex_telemetry_track' ) ) { coderex_telemetry_track( WPVR_FILE, 'tour_created', array( 'tour_title' => isset( $post_title) ? $post_title : '', 'time' => current_time('mysql'), ) ); } } /** * Track page views * * Sends telemetry when specific admin pages for the plugin are viewed. * * @param WP_Screen $screen Current admin screen object * @return void * @since 8.5.48 */ public function track_page_view( $screen ) { if ( ! is_admin() || empty( $screen->id ) ) { return; } $page_map = array( '/wp-admin/edit.php?post_type=wpvr_item' => 'Tour list', '/wp-admin/admin.php?page=wpvr-settings' => 'Settings', '/wp-admin/admin.php?page=wpvr-analytics' => 'Analytics', '/wp-admin/admin.php?page=rex-wpvr-setup-wizard' => 'Setup wizard', '/wp-admin/admin.php?page=wpvrpro' => 'License', ); $current_page = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( $_SERVER['REQUEST_URI'] ) : ''; if ( '' === $current_page ) { return; } $page_name = null; foreach ( $page_map as $fragment => $name ) { if ( strpos( $current_page, $fragment ) !== false ) { $page_name = $name; break; } } if ( null === $page_name && strpos( $current_page, '/wp-admin/post.php' ) !== false && strpos( $current_page, 'action=edit' ) !== false ) { if ( isset( $screen->post_type ) && 'wpvr_item' === $screen->post_type ) { $page_name = 'Tour edit'; } } if ( null === $page_name ) { return; } $current_user = wp_get_current_user(); if ( ! $current_user->exists() ) { return; } if ( function_exists( 'coderex_telemetry_track' ) ) { coderex_telemetry_track( WPVR_FILE, 'page_view', array( 'page' => $current_page, 'page_name' => $page_name, 'time' => current_time( 'mysql' ), ) ); } } /** * Track embedded tour event * * Sends telemetry when a tour is embedded. * * @param int $tour_id The ID of the tour being embedded. * @return void * @since 8.5.48 */ public function track_embadded_tour( $tour_id ) { $this->track_weekly_unique_views_kui( $tour_id ); // Check if tracking is already done for this tour if ( get_post_meta( $tour_id, '_wpvr_tour_embedded_tracked', true ) ) { return; } $tour = get_post( $tour_id ); if ( ! $tour || $tour->post_type !== 'wpvr_item' ) { return; } // Update last core action if ( function_exists( 'coderex_telemetry_update_last_action' ) ) { coderex_telemetry_update_last_action( WPVR_FILE, 'tour_embedded' ); } if ( function_exists( 'coderex_telemetry_track' ) ) { coderex_telemetry_track( WPVR_FILE, 'tour_embedded', array( 'title' => $tour->post_title, ) ); } // Mark as tracked update_post_meta( $tour_id, '_wpvr_tour_embedded_tracked', true ); } /** * Track rolling 7-day unique views across all active tours for KUI. * * @param int $tour_id Current viewed tour ID. * @return void */ private function track_weekly_unique_views_kui( $tour_id ) { $tour_id = absint( $tour_id ); if ( $tour_id <= 0 ) { return; } $tour = get_post( $tour_id ); if ( ! $tour || 'wpvr_item' !== $tour->post_type || 'publish' !== $tour->post_status ) { return; } $option_key = 'wpvr_kui_unique_views_7d'; $last_count_key = 'wpvr_kui_unique_views_7d_last_count'; $window_seconds = 7 * DAY_IN_SECONDS; $now = time(); $tracked_views = get_option( $option_key, array() ); if ( ! is_array( $tracked_views ) ) { $tracked_views = array(); } foreach ( $tracked_views as $tracked_tour_id => $timestamp ) { $tracked_tour_id = absint( $tracked_tour_id ); $timestamp = absint( $timestamp ); if ( $tracked_tour_id <= 0 || $timestamp <= 0 || ( $now - $timestamp ) > $window_seconds || 'publish' !== get_post_status( $tracked_tour_id ) ) { unset( $tracked_views[ $tracked_tour_id ] ); } } $tracked_views[ $tour_id ] = $now; update_option( $option_key, $tracked_views ); $unique_views = count( $tracked_views ); $last_sent_count = (int) get_option( $last_count_key, -1 ); if ( $unique_views !== $last_sent_count ) { update_option( $last_count_key, $unique_views ); do_action( 'wpvr_kui_unique_views_updated', $unique_views, $tour_id ); } } /** * Format duration in seconds to human-readable format * * @param int $seconds Duration in seconds * @return string Human-readable duration * @since 8.5.48 */ private function format_duration( $seconds ) { if ( $seconds < 60 ) { return $seconds . ' seconds'; } $minutes = floor( $seconds / 60 ); if ( $minutes < 60 ) { return $minutes . ' minutes'; } $hours = floor( $minutes / 60 ); if ( $hours < 24 ) { $remaining_minutes = $minutes % 60; return $hours . ' hours' . ( $remaining_minutes > 0 ? ', ' . $remaining_minutes . ' minutes' : '' ); } $days = floor( $hours / 24 ); $remaining_hours = $hours % 24; return $days . ' days' . ( $remaining_hours > 0 ? ', ' . $remaining_hours . ' hours' : '' ); } } //new WPVR_Telemetry();