| 1 |
<?php |
| 2 |
namespace RexTheme\WPVR\Tracker; |
| 3 |
|
| 4 |
use LinnoSDK\Telemetry\Client; |
| 5 |
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly |
| 6 |
|
| 7 |
/** |
| 8 |
* Class WPVR_Linno_Telemetry |
| 9 |
* |
| 10 |
* Telemetry event bridge for the Linno SDK. |
| 11 |
* |
| 12 |
* Canonical events tracked: |
| 13 |
* - activation/plugin_activated — SDK lifecycle, no consent (automatic). |
| 14 |
* - activation/plugin_deactivated — SDK lifecycle, no consent (automatic). |
| 15 |
* - activation/onboarding_completed — non-PII, override (track_immediate). |
| 16 |
* - activation/aha_reached — consent-gated; wizard completion fires on consent grant, |
| 17 |
* active tour views fire via define_triggers → aha. |
| 18 |
* - retention/tour_created — consent-gated; first publish of a real tour, |
| 19 |
* emitted once per tour via transition_post_status. |
| 20 |
* - retention/feature_used — consent-gated (define_triggers → feature_used). |
| 21 |
* |
| 22 |
* @since 8.5.57 |
| 23 |
*/ |
| 24 |
|
| 25 |
class WPVRLinnoTelemetry { |
| 26 |
|
| 27 |
/** |
| 28 |
* Post meta key storing whether the creation event has already been emitted. |
| 29 |
* |
| 30 |
* @var string |
| 31 |
*/ |
| 32 |
const TOUR_CREATED_TRACKED_META_KEY = '_wpvr_linno_tour_created_tracked'; |
| 33 |
|
| 34 |
/** |
| 35 |
* Linno API key. |
| 36 |
* |
| 37 |
* @var string |
| 38 |
*/ |
| 39 |
private $api_key; |
| 40 |
|
| 41 |
/** |
| 42 |
* Linno API secret. |
| 43 |
* |
| 44 |
* @var string |
| 45 |
*/ |
| 46 |
private $api_secret; |
| 47 |
|
| 48 |
/** |
| 49 |
* Prevent duplicate client initialization. |
| 50 |
* |
| 51 |
* @var bool |
| 52 |
*/ |
| 53 |
private $client_initialized = false; |
| 54 |
|
| 55 |
/** |
| 56 |
* Constructor. |
| 57 |
*/ |
| 58 |
public function __construct() { |
| 59 |
$this->api_key = defined( 'WPVR_TELEMETRY_API_KEY' ) ? WPVR_TELEMETRY_API_KEY : ''; |
| 60 |
$this->api_secret = defined( 'WPVR_TELEMETRY_API_SECRET' ) ? WPVR_TELEMETRY_API_SECRET : ''; |
| 61 |
|
| 62 |
$this->init_client(); |
| 63 |
add_action( 'transition_post_status', array( $this, 'track_tour_created_event' ), 10, 3 ); |
| 64 |
add_filter( 'wpvr_telemetry_report_interval', array( $this, 'set_daily_telemetry_report_interval' ) ); |
| 65 |
add_action( 'rex_wpvr_embadded_tour', array( $this, 'handle_embedded_tour_view' ), 10, 1 ); |
| 66 |
add_action( 'wpvr_telemetry_consent_granted', array( $this, 'track_aha_after_consent' ) ); |
| 67 |
add_filter( 'wpvr_telemetry_deactivation_reasons', array( $this, 'set_deactivation_reasons' ) ); |
| 68 |
} |
| 69 |
|
| 70 |
/** |
| 71 |
* Force Linno telemetry queue interval to daily. |
| 72 |
* |
| 73 |
* @return string |
| 74 |
*/ |
| 75 |
public function set_daily_telemetry_report_interval() { |
| 76 |
return 'daily'; |
| 77 |
} |
| 78 |
|
| 79 |
/** |
| 80 |
* Initialize Linno telemetry client and trigger mappings. |
| 81 |
* |
| 82 |
* @return void |
| 83 |
*/ |
| 84 |
public function init_client() { |
| 85 |
global $wpvr_telemetry; |
| 86 |
|
| 87 |
if ( $this->client_initialized ) { |
| 88 |
return; |
| 89 |
} |
| 90 |
|
| 91 |
if ( ! class_exists( 'LinnoSDK\\Telemetry\\Client' ) || ! defined( 'WPVR_FILE' ) ) { |
| 92 |
error_log( 'WPVR Telemetry: Linno Client class unavailable or WPVR_FILE not defined.' ); |
| 93 |
return; |
| 94 |
} |
| 95 |
|
| 96 |
if ( empty( $this->api_key ) || empty( $this->api_secret ) ) { |
| 97 |
error_log( 'WPVR Telemetry: API key or API secret is empty. Telemetry client not initialized.' ); |
| 98 |
return; |
| 99 |
} |
| 100 |
|
| 101 |
Client::set_text_domain( 'wpvr' ); |
| 102 |
|
| 103 |
$wpvr_telemetry = new Client( |
| 104 |
array( |
| 105 |
'pluginFile' => WPVR_FILE, |
| 106 |
'slug' => 'wpvr', |
| 107 |
'pluginName' => 'WP VR', |
| 108 |
'version' => defined( 'WPVR_VERSION' ) ? WPVR_VERSION : '', |
| 109 |
'apiKey' => $this->api_key, |
| 110 |
'apiSecret' => $this->api_secret, |
| 111 |
'driver' => 'posthog', |
| 112 |
'driver_config' => array( |
| 113 |
'host' => defined( 'WPVR_TELEMETRY_HOST' ) ? WPVR_TELEMETRY_HOST : 'https://eu.i.posthog.com', |
| 114 |
'api_key' => $this->api_key, |
| 115 |
), |
| 116 |
) |
| 117 |
); |
| 118 |
|
| 119 |
// Non-PII onboarding event — bypasses consent via override=true on track_immediate(). |
| 120 |
add_action( 'wpvr_setup_wizard_completed_event', array( $this, 'track_onboarding_event' ), 10, 1 ); |
| 121 |
|
| 122 |
// Consent-gated events via SDK trigger system. |
| 123 |
// activation/aha_reached — queued when ≥60% of tours are actively viewed (recurring). |
| 124 |
// retention/feature_used — queued on every tour save. |
| 125 |
// Both require opt-in consent. |
| 126 |
$self = $this; |
| 127 |
$wpvr_telemetry->define_triggers( |
| 128 |
array( |
| 129 |
'aha' => array( |
| 130 |
'tours_actively_viewed' => array( |
| 131 |
'hook' => 'wpvr_kui_unique_views_updated', |
| 132 |
'callback' => function ( $unique_views, $tour_id ) use ( $self ) { |
| 133 |
return $self->build_kui_payload( $unique_views, $tour_id ); |
| 134 |
}, |
| 135 |
), |
| 136 |
), |
| 137 |
'feature_used' => array( |
| 138 |
'tour_creation' => array( |
| 139 |
'hook' => 'wpvr_tour_settings_saved', |
| 140 |
'callback' => function ( $tour_id ) use ( $self ) { |
| 141 |
return $self->build_tour_creation_payload( $tour_id ); |
| 142 |
}, |
| 143 |
), |
| 144 |
), |
| 145 |
) |
| 146 |
); |
| 147 |
|
| 148 |
$this->ensure_daily_telemetry_queue_schedule(); |
| 149 |
$this->client_initialized = true; |
| 150 |
} |
| 151 |
|
| 152 |
/** |
| 153 |
* Ensure telemetry queue cron hook runs daily. |
| 154 |
* |
| 155 |
* @return void |
| 156 |
*/ |
| 157 |
private function ensure_daily_telemetry_queue_schedule() { |
| 158 |
if ( ! defined( 'WPVR_FILE' ) ) { |
| 159 |
return; |
| 160 |
} |
| 161 |
|
| 162 |
$slug = dirname( plugin_basename( WPVR_FILE ) ); |
| 163 |
$hook = $slug . '_telemetry_queue_process'; |
| 164 |
$scheduled_event = wp_get_scheduled_event( $hook ); |
| 165 |
|
| 166 |
if ( $scheduled_event && 'daily' === $scheduled_event->schedule ) { |
| 167 |
return; |
| 168 |
} |
| 169 |
|
| 170 |
wp_clear_scheduled_hook( $hook ); |
| 171 |
wp_schedule_event( time(), 'daily', $hook ); |
| 172 |
} |
| 173 |
|
| 174 |
/** |
| 175 |
* Build setup event payload. |
| 176 |
* |
| 177 |
* @param string $industry Industry from setup wizard. |
| 178 |
* @return array |
| 179 |
*/ |
| 180 |
public function build_setup_payload( $industry = '' ) { |
| 181 |
return array( |
| 182 |
'industry' => sanitize_text_field( (string) $industry ), |
| 183 |
'time' => current_time( 'mysql' ), |
| 184 |
); |
| 185 |
} |
| 186 |
|
| 187 |
/** |
| 188 |
* Build KUI payload. |
| 189 |
* |
| 190 |
* @param int $unique_views Unique active-tour views in 7 days. |
| 191 |
* @param int $tour_id Last viewed tour ID. |
| 192 |
* @return array |
| 193 |
*/ |
| 194 |
public function build_kui_payload( $unique_views = 0, $tour_id = 0 ) { |
| 195 |
return array( |
| 196 |
'unique_views' => absint( $unique_views ), |
| 197 |
'tour_id' => absint( $tour_id ), |
| 198 |
'window_days' => 7, |
| 199 |
'time' => current_time( 'mysql' ), |
| 200 |
); |
| 201 |
} |
| 202 |
|
| 203 |
/** |
| 204 |
* Build tour creation payload with feature usage as properties. |
| 205 |
* |
| 206 |
* Fires when tour settings are saved. Inspects the stored panodata meta to |
| 207 |
* determine which core features the user has configured, so all feature |
| 208 |
* usage is captured in a single event instead of separate ones. |
| 209 |
* |
| 210 |
* @param int $tour_id Tour (post) ID. |
| 211 |
* @return array |
| 212 |
*/ |
| 213 |
public function build_tour_creation_payload( $tour_id = 0 ) { |
| 214 |
$tour_id = absint( $tour_id ); |
| 215 |
$panodata = get_post_meta( $tour_id, 'panodata', true ); |
| 216 |
|
| 217 |
$hotspot_editor = 'no'; |
| 218 |
$floor_plan = 'no'; |
| 219 |
$tour_builder = 'no'; |
| 220 |
|
| 221 |
if ( is_array( $panodata ) ) { |
| 222 |
// Floor plan — added to panodata by pro version via filter. |
| 223 |
if ( |
| 224 |
isset( $panodata['floor_plan_tour_enabler'] ) && |
| 225 |
'on' === $panodata['floor_plan_tour_enabler'] && |
| 226 |
! empty( $panodata['floor_plan_attachment_url'] ) |
| 227 |
) { |
| 228 |
$floor_plan = 'yes'; |
| 229 |
} |
| 230 |
|
| 231 |
// Scene list lives one level deeper inside panodata. |
| 232 |
$scene_list = isset( $panodata['panodata']['scene-list'] ) ? $panodata['panodata']['scene-list'] : array(); |
| 233 |
|
| 234 |
if ( is_array( $scene_list ) ) { |
| 235 |
// Tour builder = more than one scene configured. |
| 236 |
if ( count( $scene_list ) > 1 ) { |
| 237 |
$tour_builder = 'yes'; |
| 238 |
} |
| 239 |
|
| 240 |
// Hotspot editor = at least one non-empty hotspot in any scene. |
| 241 |
foreach ( $scene_list as $scene ) { |
| 242 |
if ( ! empty( $scene['hotspot-list'] ) && is_array( $scene['hotspot-list'] ) ) { |
| 243 |
$hotspot_editor = 'yes'; |
| 244 |
break; |
| 245 |
} |
| 246 |
} |
| 247 |
} |
| 248 |
} |
| 249 |
|
| 250 |
return array( |
| 251 |
'hotspot_editor' => $hotspot_editor, |
| 252 |
'floor_plan' => $floor_plan, |
| 253 |
'tour_builder' => $tour_builder, |
| 254 |
); |
| 255 |
} |
| 256 |
|
| 257 |
/** |
| 258 |
* Build real tour creation payload. |
| 259 |
* |
| 260 |
* @param int $tour_id Tour (post) ID. |
| 261 |
* @return array |
| 262 |
*/ |
| 263 |
public function build_real_tour_creation_payload( $tour_id = 0 ) { |
| 264 |
$tour_id = absint( $tour_id ); |
| 265 |
|
| 266 |
return array( |
| 267 |
'tour_id' => $tour_id, |
| 268 |
'created_via' => get_post_meta( $tour_id, 'wpvr_created_from_wizard', true ) ? 'wizard' : 'manual', |
| 269 |
'time' => current_time( 'mysql' ), |
| 270 |
); |
| 271 |
} |
| 272 |
|
| 273 |
/** |
| 274 |
* Track retention/tour_created when a real tour is first published. |
| 275 |
* |
| 276 |
* This event is consent-gated like other retention events. It is emitted |
| 277 |
* only once per tour and skipped for demo/sample tours. |
| 278 |
* |
| 279 |
* @param string $new_status New post status. |
| 280 |
* @param string $old_status Old post status. |
| 281 |
* @param WP_Post $post Post object. |
| 282 |
* @return void |
| 283 |
*/ |
| 284 |
public function track_tour_created_event( $new_status, $old_status, $post ) { |
| 285 |
global $wpvr_telemetry; |
| 286 |
|
| 287 |
if ( ! is_object( $wpvr_telemetry ) || ! $post instanceof WP_Post ) { |
| 288 |
return; |
| 289 |
} |
| 290 |
|
| 291 |
if ( 'wpvr_item' !== $post->post_type || ! $this->is_first_publish_transition( $new_status, $old_status ) ) { |
| 292 |
return; |
| 293 |
} |
| 294 |
|
| 295 |
if ( $this->is_seeded_tour( $post->ID ) || $this->has_tracked_tour_created_event( $post->ID ) ) { |
| 296 |
return; |
| 297 |
} |
| 298 |
|
| 299 |
$wpvr_telemetry->track( 'retention/tour_created', $this->build_real_tour_creation_payload( $post->ID ) ); |
| 300 |
update_post_meta( $post->ID, self::TOUR_CREATED_TRACKED_META_KEY, '1' ); |
| 301 |
} |
| 302 |
|
| 303 |
/** |
| 304 |
* Determine if a publish transition represents a first real publish. |
| 305 |
* |
| 306 |
* @param string $new_status New post status. |
| 307 |
* @param string $old_status Old post status. |
| 308 |
* @return bool |
| 309 |
*/ |
| 310 |
private function is_first_publish_transition( $new_status, $old_status ) { |
| 311 |
if ( 'publish' !== $new_status ) { |
| 312 |
return false; |
| 313 |
} |
| 314 |
|
| 315 |
return in_array( $old_status, array( 'auto-draft', 'draft', 'new', 'pending', 'private', 'future', '' ), true ); |
| 316 |
} |
| 317 |
|
| 318 |
/** |
| 319 |
* Determine whether a tour is seeded demo/sample content. |
| 320 |
* |
| 321 |
* @param int $tour_id Tour (post) ID. |
| 322 |
* @return bool |
| 323 |
*/ |
| 324 |
private function is_seeded_tour( $tour_id ) { |
| 325 |
return '1' === (string) get_post_meta( absint( $tour_id ), 'wpvr_is_demo_tour', true ); |
| 326 |
} |
| 327 |
|
| 328 |
/** |
| 329 |
* Check whether the creation event has already been emitted. |
| 330 |
* |
| 331 |
* @param int $tour_id Tour (post) ID. |
| 332 |
* @return bool |
| 333 |
*/ |
| 334 |
private function has_tracked_tour_created_event( $tour_id ) { |
| 335 |
return '1' === (string) get_post_meta( absint( $tour_id ), self::TOUR_CREATED_TRACKED_META_KEY, true ); |
| 336 |
} |
| 337 |
|
| 338 |
/** |
| 339 |
* Track onboarding completed event (non-PII, bypasses consent gate). |
| 340 |
* |
| 341 |
* @param string $industry Industry from setup wizard. |
| 342 |
* @return void |
| 343 |
*/ |
| 344 |
public function track_onboarding_event( $industry = '' ) { |
| 345 |
global $wpvr_telemetry; |
| 346 |
if ( ! is_object( $wpvr_telemetry ) ) { |
| 347 |
return; |
| 348 |
} |
| 349 |
if ( method_exists( $wpvr_telemetry, 'has_sent_event' ) && $wpvr_telemetry->has_sent_event( 'onboarding_completed' ) ) { |
| 350 |
return; |
| 351 |
} |
| 352 |
$wpvr_telemetry->track_immediate( 'activation/onboarding_completed', $this->build_setup_payload( $industry ), true ); |
| 353 |
if ( method_exists( $wpvr_telemetry, 'mark_event_sent' ) ) { |
| 354 |
$wpvr_telemetry->mark_event_sent( 'onboarding_completed' ); |
| 355 |
} |
| 356 |
} |
| 357 |
|
| 358 |
/** |
| 359 |
* Track activation/aha_reached after consent is granted. |
| 360 |
* |
| 361 |
* The wizard completion hook fires BEFORE consent is set (separate AJAX calls), |
| 362 |
* so aha cannot use define_triggers. Instead, this method fires once when |
| 363 |
* consent is granted on the Success step, if the wizard was completed. |
| 364 |
* |
| 365 |
* @return void |
| 366 |
*/ |
| 367 |
public function track_aha_after_consent() { |
| 368 |
global $wpvr_telemetry; |
| 369 |
if ( ! is_object( $wpvr_telemetry ) ) { |
| 370 |
return; |
| 371 |
} |
| 372 |
|
| 373 |
// Only fire if the wizard was actually completed. |
| 374 |
if ( ! get_option( 'wpvr_wizard_onboarding_done' ) ) { |
| 375 |
return; |
| 376 |
} |
| 377 |
|
| 378 |
// One-shot guard — never send twice. |
| 379 |
if ( method_exists( $wpvr_telemetry, 'has_sent_event' ) && $wpvr_telemetry->has_sent_event( 'aha_reached_wizard_completed' ) ) { |
| 380 |
return; |
| 381 |
} |
| 382 |
|
| 383 |
$industry = sanitize_text_field( get_option( 'wpvr_industry_name', '' ) ); |
| 384 |
|
| 385 |
$wpvr_telemetry->track_kui( |
| 386 |
'wizard_completed', |
| 387 |
array( |
| 388 |
'industry' => $industry, |
| 389 |
'time' => current_time( 'mysql' ), |
| 390 |
) |
| 391 |
); |
| 392 |
|
| 393 |
if ( method_exists( $wpvr_telemetry, 'mark_event_sent' ) ) { |
| 394 |
$wpvr_telemetry->mark_event_sent( 'aha_reached_wizard_completed' ); |
| 395 |
} |
| 396 |
} |
| 397 |
|
| 398 |
/** |
| 399 |
* Update rolling 7-day unique active-tour views and emit KUI trigger event. |
| 400 |
* |
| 401 |
* @param int $tour_id Tour ID. |
| 402 |
* @return void |
| 403 |
*/ |
| 404 |
public function handle_embedded_tour_view( $tour_id ) { |
| 405 |
$tour_id = absint( $tour_id ); |
| 406 |
if ( $tour_id <= 0 ) { |
| 407 |
return; |
| 408 |
} |
| 409 |
|
| 410 |
$tour = get_post( $tour_id ); |
| 411 |
if ( ! $tour || 'wpvr_item' !== $tour->post_type || 'publish' !== $tour->post_status ) { |
| 412 |
return; |
| 413 |
} |
| 414 |
|
| 415 |
$option_key = 'wpvr_kui_unique_views_7d'; |
| 416 |
$last_kui_sent_key = 'wpvr_kui_unique_views_7d_last_sent_at'; |
| 417 |
$window_seconds = 7 * DAY_IN_SECONDS; |
| 418 |
$now = time(); |
| 419 |
|
| 420 |
$tracked_views = get_option( $option_key, array() ); |
| 421 |
if ( ! is_array( $tracked_views ) ) { |
| 422 |
$tracked_views = array(); |
| 423 |
} |
| 424 |
|
| 425 |
foreach ( $tracked_views as $tracked_tour_id => $timestamp ) { |
| 426 |
$tracked_tour_id = absint( $tracked_tour_id ); |
| 427 |
$timestamp = absint( $timestamp ); |
| 428 |
|
| 429 |
if ( |
| 430 |
$tracked_tour_id <= 0 || |
| 431 |
$timestamp <= 0 || |
| 432 |
( $now - $timestamp ) > $window_seconds || |
| 433 |
'publish' !== get_post_status( $tracked_tour_id ) |
| 434 |
) { |
| 435 |
unset( $tracked_views[ $tracked_tour_id ] ); |
| 436 |
} |
| 437 |
} |
| 438 |
|
| 439 |
$tracked_views[ $tour_id ] = $now; |
| 440 |
update_option( $option_key, $tracked_views ); |
| 441 |
|
| 442 |
$unique_views = count( $tracked_views ); |
| 443 |
$published_tours = wp_count_posts( 'wpvr_item' ); |
| 444 |
$total_published_tours = 0; |
| 445 |
|
| 446 |
if ( is_object( $published_tours ) && isset( $published_tours->publish ) ) { |
| 447 |
$total_published_tours = absint( $published_tours->publish ); |
| 448 |
} |
| 449 |
|
| 450 |
$active_tour_ratio = 0; |
| 451 |
if ( $total_published_tours > 0 ) { |
| 452 |
$active_tour_ratio = (float) ( $unique_views / $total_published_tours ); |
| 453 |
} |
| 454 |
|
| 455 |
update_option( 'wpvr_last_active_tour_ratio', $active_tour_ratio ); |
| 456 |
|
| 457 |
$last_kui_sent_at = absint( get_option( $last_kui_sent_key, 0 ) ); |
| 458 |
$minimum_active_tour_ratio = 0.6; |
| 459 |
|
| 460 |
if ( $active_tour_ratio < $minimum_active_tour_ratio ) { |
| 461 |
return; |
| 462 |
} |
| 463 |
|
| 464 |
if ( $last_kui_sent_at > 0 && ( $now - $last_kui_sent_at ) < $window_seconds ) { |
| 465 |
return; |
| 466 |
} |
| 467 |
|
| 468 |
do_action( 'wpvr_kui_unique_views_updated', $unique_views, $tour_id ); |
| 469 |
update_option( $last_kui_sent_key, $now ); |
| 470 |
} |
| 471 |
|
| 472 |
/** |
| 473 |
* Replace generic deactivation reasons with VR-specific ones. |
| 474 |
* |
| 475 |
* @param array $reasons |
| 476 |
* @return array |
| 477 |
*/ |
| 478 |
public function set_deactivation_reasons( $reasons ) { |
| 479 |
return array( |
| 480 |
array( |
| 481 |
'id' => 'interface_too_complex', |
| 482 |
'text' => __( 'Setup / Interface is too complex', 'wpvr' ), |
| 483 |
'placeholder' => __( 'What was the biggest blocker?', 'wpvr' ), |
| 484 |
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 23 23"><g fill="none"><g fill="#3B86FF"><path d="M11.5 0C17.9 0 23 5.1 23 11.5 23 17.9 17.9 23 11.5 23 10.6 23 9.6 22.9 8.8 22.7L8.8 22.6C9.3 22.5 9.7 22.3 10 21.9 10.3 21.6 10.4 21.3 10.4 20.9 10.8 21 11.1 21 11.5 21 16.7 21 21 16.7 21 11.5 21 6.3 16.7 2 11.5 2 6.3 2 2 6.3 2 11.5 2 13 2.3 14.3 2.9 15.6 2.7 16 2.4 16.3 2.2 16.8L2.1 17.1 2.1 17.3C2 17.5 2 17.7 2 18 0.7 16.1 0 13.9 0 11.5 0 5.1 5.1 0 11.5 0ZM11.7 11.2C13.1 11.2 14.3 11.7 15.2 12.9 15.3 13 15.4 13.1 15.4 13.2 15.4 13.4 15.3 13.8 15.2 13.8 15 13.9 14.9 13.8 14.8 13.7 14.6 13.5 14.4 13.2 14.1 13.1 13.5 12.6 12.8 12.3 12 12.2 10.7 12.1 9.5 12.3 8.4 12.8 8.3 12.8 8.2 12.8 8.1 12.8 7.9 12.8 7.8 12.4 7.8 12.2 7.7 12.1 7.8 11.9 8 11.8 8.4 11.7 8.8 11.5 9.2 11.4 10 11.2 10.9 11.1 11.7 11.2ZM16.3 5.9C17.3 5.9 18 6.6 18 7.6 18 8.5 17.3 9.3 16.3 9.3 15.4 9.3 14.7 8.5 14.7 7.6 14.7 6.6 15.4 5.9 16.3 5.9ZM8.3 5C9.2 5 9.9 5.8 9.9 6.7 9.9 7.7 9.2 8.4 8.2 8.4 7.3 8.4 6.6 7.7 6.6 6.7 6.6 5.8 7.3 5 8.3 5Z"/></g></g></svg>', |
| 485 |
), |
| 486 |
array( |
| 487 |
'id' => 'missing_vr_feature', |
| 488 |
'text' => __( 'Missing specific VR feature', 'wpvr' ), |
| 489 |
'placeholder' => __( 'Which feature? (Floor Plan, WebVR, etc.)', 'wpvr' ), |
| 490 |
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="17" viewBox="0 0 24 17"><g fill="none"><g fill="#3B86FF"><path d="M19.4 0C19.7 0.6 19.8 1.3 19.8 2 19.8 3.2 19.4 4.4 18.5 5.3 17.6 6.2 16.5 6.7 15.2 6.7 15.2 6.7 15.2 6.7 15.2 6.7 14 6.7 12.9 6.2 12 5.3 11.2 4.4 10.7 3.3 10.7 2 10.7 1.3 10.8 0.6 11.1 0L7.6 0 7 0 6.5 0 6.5 5.7C6.3 5.6 5.9 5.3 5.6 5.1 5 4.6 4.3 4.3 3.5 4.3 3.5 4.3 3.5 4.3 3.4 4.3 1.6 4.4 0 5.9 0 7.9 0 8.6 0.2 9.2 0.5 9.7 1.1 10.8 2.2 11.5 3.5 11.5 4.3 11.5 5 11.2 5.6 10.8 6 10.5 6.3 10.3 6.5 10.2L6.5 10.2 6.5 17 6.5 17 7 17 7.6 17 22.5 17C23.3 17 24 16.3 24 15.5L24 0 19.4 0Z"/></g></g></svg>', |
| 491 |
), |
| 492 |
array( |
| 493 |
'id' => 'tour_not_displaying', |
| 494 |
'text' => __( 'Tour not displaying / Error', 'wpvr' ), |
| 495 |
'placeholder' => __( 'Any error message or URL?', 'wpvr' ), |
| 496 |
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 23 23"><g fill="none"><g fill="#3B86FF"><path d="M11.5 0C17.9 0 23 5.1 23 11.5 23 17.9 17.9 23 11.5 23 5.1 23 0 17.9 0 11.5 0 5.1 5.1 0 11.5 0ZM11.8 14.4C11.2 14.4 10.7 14.8 10.7 15.4 10.7 16 11.2 16.4 11.8 16.4 12.4 16.4 12.8 16 12.8 15.4 12.8 14.8 12.4 14.4 11.8 14.4ZM12 7C10.1 7 9.1 8.1 9 9.6L10.5 9.6C10.5 8.8 11.1 8.3 11.9 8.3 12.7 8.3 13.2 8.8 13.2 9.5 13.2 10.1 13 10.4 12.2 10.9 11.3 11.4 10.9 12 11 12.9L11 13.4 12.5 13.4 12.5 13C12.5 12.4 12.7 12.1 13.5 11.6 14.4 11.1 14.9 10.4 14.9 9.4 14.9 8 13.7 7 12 7Z"/></g></g></svg>', |
| 497 |
), |
| 498 |
array( |
| 499 |
'id' => 'performance_issue', |
| 500 |
'text' => __( 'Image quality / Performance issues', 'wpvr' ), |
| 501 |
'placeholder' => __( 'Image size or browser details help.', 'wpvr' ), |
| 502 |
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 23 23"><g fill="none"><g fill="#3B86FF"><path d="M11.5 0C17.9 0 23 5.1 23 11.5 23 17.9 17.9 23 11.5 23 5.1 23 0 17.9 0 11.5 0 5.1 5.1 0 11.5 0ZM11.5 2C6.3 2 2 6.3 2 11.5 2 16.7 6.3 21 11.5 21 16.7 21 21 16.7 21 11.5 21 6.3 16.7 2 11.5 2ZM12.5 12.9L12.7 5 10.2 5 10.5 12.9 12.5 12.9ZM11.5 17.4C12.4 17.4 13 16.8 13 15.9 13 15 12.4 14.4 11.5 14.4 10.6 14.4 10 15 10 15.9 10 16.8 10.6 17.4 11.5 17.4Z"/></g></g></svg>', |
| 503 |
), |
| 504 |
array( |
| 505 |
'id' => 'found_better_solution', |
| 506 |
'text' => __( 'Found a better solution', 'wpvr' ), |
| 507 |
'placeholder' => __( 'Which tool? What did it do better?', 'wpvr' ), |
| 508 |
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 23 23"><g fill="none"><g fill="#3B86FF"><path d="M17.1 14L22.4 19.3C23.2 20.2 23.2 21.5 22.4 22.4 21.5 23.2 20.2 23.2 19.3 22.4L19.3 22.4 14 17.1C15.3 16.3 16.3 15.3 17.1 14L17.1 14ZM8.6 0C13.4 0 17.3 3.9 17.3 8.6 17.3 13.4 13.4 17.2 8.6 17.2 3.9 17.2 0 13.4 0 8.6 0 3.9 3.9 0 8.6 0ZM8.6 2.2C5.1 2.2 2.2 5.1 2.2 8.6 2.2 12.2 5.1 15.1 8.6 15.1 12.2 15.1 15.1 12.2 15.1 8.6 15.1 5.1 12.2 2.2 8.6 2.2ZM8.6 3.6L8.6 5C6.6 5 5 6.6 5 8.6L5 8.6 3.6 8.6C3.6 5.9 5.9 3.6 8.6 3.6L8.6 3.6Z"/></g></g></svg>', |
| 509 |
), |
| 510 |
array( |
| 511 |
'id' => 'just_testing', |
| 512 |
'text' => __( 'Just testing / One-time project', 'wpvr' ), |
| 513 |
'placeholder' => __( 'What were you building?', 'wpvr' ), |
| 514 |
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="23" viewBox="0 0 24 6"><g fill="none"><g fill="#3B86FF"><path d="M3 0C4.7 0 6 1.3 6 3 6 4.7 4.7 6 3 6 1.3 6 0 4.7 0 3 0 1.3 1.3 0 3 0ZM12 0C13.7 0 15 1.3 15 3 15 4.7 13.7 6 12 6 10.3 6 9 4.7 9 3 9 1.3 10.3 0 12 0ZM21 0C22.7 0 24 1.3 24 3 24 4.7 22.7 6 21 6 19.3 6 18 4.7 18 3 18 1.3 19.3 0 21 0Z"/></g></g></svg>', |
| 515 |
), |
| 516 |
); |
| 517 |
} |
| 518 |
|
| 519 |
} |
| 520 |
|
| 521 |
|
| 522 |
new WPVRLinnoTelemetry(); |