PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 9.1.3
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v9.1.3
9.1.3 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 All 222 releases
wpvr / src / Tracker / WPVRLinnoTelemetry.php

WPVRLinnoTelemetry.php in WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress 9.1.3, at src/Tracker/WPVRLinnoTelemetry.php

521 lines 21.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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/tour_created — consent-gated; first publish of a real tour,
17 * emitted once per tour via transition_post_status.
18 * - retention/feature_used — consent-gated (define_triggers → feature_used).
19 *
20 * activation/aha_reached is temporarily disabled (both producers guarded off below)
21 * pending removal — see handle_embedded_tour_view() and track_aha_after_consent().
22 *
23 * @since 8.5.57
24 */
25
26 class WPVRLinnoTelemetry {
27
28 /**
29 * Post meta key storing whether the creation event has already been emitted.
30 *
31 * @var string
32 */
33 const TOUR_CREATED_TRACKED_META_KEY = '_wpvr_linno_tour_created_tracked';
34
35 /**
36 * Linno API key.
37 *
38 * @var string
39 */
40 private $api_key;
41
42 /**
43 * Linno API secret.
44 *
45 * @var string
46 */
47 private $api_secret;
48
49 /**
50 * Prevent duplicate client initialization.
51 *
52 * @var bool
53 */
54 private $client_initialized = false;
55
56 /**
57 * Constructor.
58 */
59 public function __construct() {
60 $this->api_key = defined( 'WPVR_TELEMETRY_API_KEY' ) ? WPVR_TELEMETRY_API_KEY : '';
61 $this->api_secret = defined( 'WPVR_TELEMETRY_API_SECRET' ) ? WPVR_TELEMETRY_API_SECRET : '';
62
63 $this->init_client();
64 add_action( 'transition_post_status', array( $this, 'track_tour_created_event' ), 10, 3 );
65 add_filter( 'wpvr_telemetry_report_interval', array( $this, 'set_daily_telemetry_report_interval' ) );
66 add_action( 'rex_wpvr_embadded_tour', array( $this, 'handle_embedded_tour_view' ), 10, 1 );
67 add_action( 'wpvr_telemetry_consent_granted', array( $this, 'track_aha_after_consent' ) );
68 add_filter( 'wpvr_telemetry_deactivation_reasons', array( $this, 'set_deactivation_reasons' ) );
69 }
70
71 /**
72 * Force Linno telemetry queue interval to daily.
73 *
74 * @return string
75 */
76 public function set_daily_telemetry_report_interval() {
77 return 'daily';
78 }
79
80 /**
81 * Initialize Linno telemetry client and trigger mappings.
82 *
83 * @return void
84 */
85 public function init_client() {
86 global $wpvr_telemetry;
87
88 if ( $this->client_initialized ) {
89 return;
90 }
91
92 if ( ! class_exists( 'LinnoSDK\\Telemetry\\Client' ) || ! defined( 'WPVR_FILE' ) ) {
93 error_log( 'WPVR Telemetry: Linno Client class unavailable or WPVR_FILE not defined.' );
94 return;
95 }
96
97 if ( empty( $this->api_key ) || empty( $this->api_secret ) ) {
98 error_log( 'WPVR Telemetry: API key or API secret is empty. Telemetry client not initialized.' );
99 return;
100 }
101
102 Client::set_text_domain( 'wpvr' );
103
104 $wpvr_telemetry = new Client(
105 array(
106 'pluginFile' => WPVR_FILE,
107 'slug' => 'wpvr',
108 'pluginName' => 'WP VR',
109 'version' => defined( 'WPVR_VERSION' ) ? WPVR_VERSION : '',
110 'apiKey' => $this->api_key,
111 'apiSecret' => $this->api_secret,
112 'driver' => 'posthog',
113 'driver_config' => array(
114 'host' => defined( 'WPVR_TELEMETRY_HOST' ) ? WPVR_TELEMETRY_HOST : 'https://eu.i.posthog.com',
115 'api_key' => $this->api_key,
116 ),
117 )
118 );
119
120 // Non-PII onboarding event — bypasses consent via override=true on track_immediate().
121 add_action( 'wpvr_setup_wizard_completed_event', array( $this, 'track_onboarding_event' ), 10, 1 );
122
123 // Consent-gated events via SDK trigger system.
124 // retention/feature_used — queued on every tour save. Requires opt-in consent.
125 // activation/aha_reached (tours_actively_viewed) is temporarily disabled — see
126 // handle_embedded_tour_view() below, which no longer emits the underlying hook.
127 $self = $this;
128 $wpvr_telemetry->define_triggers(
129 array(
130 'feature_used' => array(
131 'tour_creation' => array(
132 'hook' => 'wpvr_tour_settings_saved',
133 'callback' => function ( $tour_id ) use ( $self ) {
134 return $self->build_tour_creation_payload( $tour_id );
135 },
136 ),
137 ),
138 )
139 );
140
141 $this->ensure_daily_telemetry_queue_schedule();
142 $this->client_initialized = true;
143 }
144
145 /**
146 * Ensure telemetry queue cron hook runs daily.
147 *
148 * @return void
149 */
150 private function ensure_daily_telemetry_queue_schedule() {
151 if ( ! defined( 'WPVR_FILE' ) ) {
152 return;
153 }
154
155 $slug = dirname( plugin_basename( WPVR_FILE ) );
156 $hook = $slug . '_telemetry_queue_process';
157 $scheduled_event = wp_get_scheduled_event( $hook );
158
159 if ( $scheduled_event && 'daily' === $scheduled_event->schedule ) {
160 return;
161 }
162
163 wp_clear_scheduled_hook( $hook );
164 wp_schedule_event( time(), 'daily', $hook );
165 }
166
167 /**
168 * Build setup event payload.
169 *
170 * @param string $industry Industry from setup wizard.
171 * @return array
172 */
173 public function build_setup_payload( $industry = '' ) {
174 return array(
175 'industry' => sanitize_text_field( (string) $industry ),
176 'time' => current_time( 'mysql' ),
177 );
178 }
179
180 /**
181 * Build KUI payload.
182 *
183 * @param int $unique_views Unique active-tour views in 7 days.
184 * @param int $tour_id Last viewed tour ID.
185 * @return array
186 */
187 public function build_kui_payload( $unique_views = 0, $tour_id = 0 ) {
188 return array(
189 'unique_views' => absint( $unique_views ),
190 'tour_id' => absint( $tour_id ),
191 'window_days' => 7,
192 'time' => current_time( 'mysql' ),
193 );
194 }
195
196 /**
197 * Build tour creation payload with feature usage as properties.
198 *
199 * Fires when tour settings are saved. Inspects the stored panodata meta to
200 * determine which core features the user has configured, so all feature
201 * usage is captured in a single event instead of separate ones.
202 *
203 * @param int $tour_id Tour (post) ID.
204 * @return array
205 */
206 public function build_tour_creation_payload( $tour_id = 0 ) {
207 $tour_id = absint( $tour_id );
208 $panodata = get_post_meta( $tour_id, 'panodata', true );
209
210 $hotspot_editor = 'no';
211 $floor_plan = 'no';
212 $tour_builder = 'no';
213
214 if ( is_array( $panodata ) ) {
215 // Floor plan — added to panodata by pro version via filter.
216 if (
217 isset( $panodata['floor_plan_tour_enabler'] ) &&
218 'on' === $panodata['floor_plan_tour_enabler'] &&
219 ! empty( $panodata['floor_plan_attachment_url'] )
220 ) {
221 $floor_plan = 'yes';
222 }
223
224 // Scene list lives one level deeper inside panodata.
225 $scene_list = isset( $panodata['panodata']['scene-list'] ) ? $panodata['panodata']['scene-list'] : array();
226
227 if ( is_array( $scene_list ) ) {
228 // Tour builder = more than one scene configured.
229 if ( count( $scene_list ) > 1 ) {
230 $tour_builder = 'yes';
231 }
232
233 // Hotspot editor = at least one non-empty hotspot in any scene.
234 foreach ( $scene_list as $scene ) {
235 if ( ! empty( $scene['hotspot-list'] ) && is_array( $scene['hotspot-list'] ) ) {
236 $hotspot_editor = 'yes';
237 break;
238 }
239 }
240 }
241 }
242
243 return array(
244 'hotspot_editor' => $hotspot_editor,
245 'floor_plan' => $floor_plan,
246 'tour_builder' => $tour_builder,
247 );
248 }
249
250 /**
251 * Build real tour creation payload.
252 *
253 * @param int $tour_id Tour (post) ID.
254 * @return array
255 */
256 public function build_real_tour_creation_payload( $tour_id = 0 ) {
257 $tour_id = absint( $tour_id );
258
259 return array(
260 'tour_id' => $tour_id,
261 'created_via' => get_post_meta( $tour_id, 'wpvr_created_from_wizard', true ) ? 'wizard' : 'manual',
262 'time' => current_time( 'mysql' ),
263 );
264 }
265
266 /**
267 * Track retention/tour_created when a real tour is first published.
268 *
269 * This event is consent-gated like other retention events. It is emitted
270 * only once per tour and skipped for demo/sample tours.
271 *
272 * @param string $new_status New post status.
273 * @param string $old_status Old post status.
274 * @param WP_Post $post Post object.
275 * @return void
276 */
277 public function track_tour_created_event( $new_status, $old_status, $post ) {
278 global $wpvr_telemetry;
279
280 if ( ! is_object( $wpvr_telemetry ) || ! $post instanceof WP_Post ) {
281 return;
282 }
283
284 if ( 'wpvr_item' !== $post->post_type || ! $this->is_first_publish_transition( $new_status, $old_status ) ) {
285 return;
286 }
287
288 if ( $this->is_seeded_tour( $post->ID ) || $this->has_tracked_tour_created_event( $post->ID ) ) {
289 return;
290 }
291
292 $wpvr_telemetry->track( 'activation/tour_created', $this->build_real_tour_creation_payload( $post->ID ) );
293 update_post_meta( $post->ID, self::TOUR_CREATED_TRACKED_META_KEY, '1' );
294 }
295
296 /**
297 * Determine if a publish transition represents a first real publish.
298 *
299 * @param string $new_status New post status.
300 * @param string $old_status Old post status.
301 * @return bool
302 */
303 private function is_first_publish_transition( $new_status, $old_status ) {
304 if ( 'publish' !== $new_status ) {
305 return false;
306 }
307
308 return in_array( $old_status, array( 'auto-draft', 'draft', 'new', 'pending', 'private', 'future', '' ), true );
309 }
310
311 /**
312 * Determine whether a tour is seeded demo/sample content.
313 *
314 * @param int $tour_id Tour (post) ID.
315 * @return bool
316 */
317 private function is_seeded_tour( $tour_id ) {
318 return '1' === (string) get_post_meta( absint( $tour_id ), 'wpvr_is_demo_tour', true );
319 }
320
321 /**
322 * Check whether the creation event has already been emitted.
323 *
324 * @param int $tour_id Tour (post) ID.
325 * @return bool
326 */
327 private function has_tracked_tour_created_event( $tour_id ) {
328 return '1' === (string) get_post_meta( absint( $tour_id ), self::TOUR_CREATED_TRACKED_META_KEY, true );
329 }
330
331 /**
332 * Track onboarding completed event (non-PII, bypasses consent gate).
333 *
334 * @param string $industry Industry from setup wizard.
335 * @return void
336 */
337 public function track_onboarding_event( $industry = '' ) {
338 global $wpvr_telemetry;
339 if ( ! is_object( $wpvr_telemetry ) ) {
340 return;
341 }
342 if ( method_exists( $wpvr_telemetry, 'has_sent_event' ) && $wpvr_telemetry->has_sent_event( 'onboarding_completed' ) ) {
343 return;
344 }
345 $wpvr_telemetry->track_immediate( 'activation/onboarding_completed', $this->build_setup_payload( $industry ), true );
346 if ( method_exists( $wpvr_telemetry, 'mark_event_sent' ) ) {
347 $wpvr_telemetry->mark_event_sent( 'onboarding_completed' );
348 }
349 }
350
351 /**
352 * Track activation/aha_reached after consent is granted.
353 *
354 * The wizard completion hook fires BEFORE consent is set (separate AJAX calls),
355 * so aha cannot use define_triggers. Instead, this method fires once when
356 * consent is granted on the Success step, if the wizard was completed.
357 *
358 * @return void
359 */
360 public function track_aha_after_consent() {
361 // Temporarily disabled — activation/aha_reached is being retired.
362 return;
363
364 global $wpvr_telemetry; // phpcs:ignore
365 if ( ! is_object( $wpvr_telemetry ) ) {
366 return;
367 }
368
369 // Only fire if the wizard was actually completed.
370 if ( ! get_option( 'wpvr_wizard_onboarding_done' ) ) {
371 return;
372 }
373
374 // One-shot guard — never send twice.
375 if ( method_exists( $wpvr_telemetry, 'has_sent_event' ) && $wpvr_telemetry->has_sent_event( 'aha_reached_wizard_completed' ) ) {
376 return;
377 }
378
379 $industry = sanitize_text_field( get_option( 'wpvr_industry_name', '' ) );
380
381 $wpvr_telemetry->track_kui(
382 'wizard_completed',
383 array(
384 'industry' => $industry,
385 'time' => current_time( 'mysql' ),
386 )
387 );
388
389 if ( method_exists( $wpvr_telemetry, 'mark_event_sent' ) ) {
390 $wpvr_telemetry->mark_event_sent( 'aha_reached_wizard_completed' );
391 }
392 }
393
394 /**
395 * Update rolling 7-day unique active-tour views and emit KUI trigger event.
396 *
397 * @param int $tour_id Tour ID.
398 * @return void
399 */
400 public function handle_embedded_tour_view( $tour_id ) {
401 // Temporarily disabled — activation/aha_reached is being retired.
402 return;
403
404 $tour_id = absint( $tour_id ); // phpcs:ignore
405 if ( $tour_id <= 0 ) {
406 return;
407 }
408
409 $tour = get_post( $tour_id );
410 if ( ! $tour || 'wpvr_item' !== $tour->post_type || 'publish' !== $tour->post_status ) {
411 return;
412 }
413
414 $option_key = 'wpvr_kui_unique_views_7d';
415 $last_kui_sent_key = 'wpvr_kui_unique_views_7d_last_sent_at';
416 $window_seconds = 7 * DAY_IN_SECONDS;
417 $now = time();
418
419 $tracked_views = get_option( $option_key, array() );
420 if ( ! is_array( $tracked_views ) ) {
421 $tracked_views = array();
422 }
423
424 foreach ( $tracked_views as $tracked_tour_id => $timestamp ) {
425 $tracked_tour_id = absint( $tracked_tour_id );
426 $timestamp = absint( $timestamp );
427
428 if (
429 $tracked_tour_id <= 0 ||
430 $timestamp <= 0 ||
431 ( $now - $timestamp ) > $window_seconds ||
432 'publish' !== get_post_status( $tracked_tour_id )
433 ) {
434 unset( $tracked_views[ $tracked_tour_id ] );
435 }
436 }
437
438 $tracked_views[ $tour_id ] = $now;
439 update_option( $option_key, $tracked_views );
440
441 $unique_views = count( $tracked_views );
442 $published_tours = wp_count_posts( 'wpvr_item' );
443 $total_published_tours = 0;
444
445 if ( is_object( $published_tours ) && isset( $published_tours->publish ) ) {
446 $total_published_tours = absint( $published_tours->publish );
447 }
448
449 $active_tour_ratio = 0;
450 if ( $total_published_tours > 0 ) {
451 $active_tour_ratio = (float) ( $unique_views / $total_published_tours );
452 }
453
454 update_option( 'wpvr_last_active_tour_ratio', $active_tour_ratio );
455
456 $last_kui_sent_at = absint( get_option( $last_kui_sent_key, 0 ) );
457 $minimum_active_tour_ratio = 0.6;
458
459 if ( $active_tour_ratio < $minimum_active_tour_ratio ) {
460 return;
461 }
462
463 if ( $last_kui_sent_at > 0 && ( $now - $last_kui_sent_at ) < $window_seconds ) {
464 return;
465 }
466
467 do_action( 'wpvr_kui_unique_views_updated', $unique_views, $tour_id );
468 update_option( $last_kui_sent_key, $now );
469 }
470
471 /**
472 * Replace generic deactivation reasons with VR-specific ones.
473 *
474 * @param array $reasons
475 * @return array
476 */
477 public function set_deactivation_reasons( $reasons ) {
478 return array(
479 array(
480 'id' => 'interface_too_complex',
481 'text' => __( 'Setup / Interface is too complex', 'wpvr' ),
482 'placeholder' => __( 'What was the biggest blocker?', 'wpvr' ),
483 '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>',
484 ),
485 array(
486 'id' => 'missing_vr_feature',
487 'text' => __( 'Missing specific VR feature', 'wpvr' ),
488 'placeholder' => __( 'Which feature? (Floor Plan, WebVR, etc.)', 'wpvr' ),
489 '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>',
490 ),
491 array(
492 'id' => 'tour_not_displaying',
493 'text' => __( 'Tour not displaying / Error', 'wpvr' ),
494 'placeholder' => __( 'Any error message or URL?', 'wpvr' ),
495 '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>',
496 ),
497 array(
498 'id' => 'performance_issue',
499 'text' => __( 'Image quality / Performance issues', 'wpvr' ),
500 'placeholder' => __( 'Image size or browser details help.', 'wpvr' ),
501 '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>',
502 ),
503 array(
504 'id' => 'found_better_solution',
505 'text' => __( 'Found a better solution', 'wpvr' ),
506 'placeholder' => __( 'Which tool? What did it do better?', 'wpvr' ),
507 '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>',
508 ),
509 array(
510 'id' => 'just_testing',
511 'text' => __( 'Just testing / One-time project', 'wpvr' ),
512 'placeholder' => __( 'What were you building?', 'wpvr' ),
513 '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>',
514 ),
515 );
516 }
517
518 }
519
520
521 new WPVRLinnoTelemetry();