PluginProbe
WP VR – 360 Panorama and Virtual Tour Builder / 9.0.3
WP VR – 360 Panorama and Virtual Tour Builder v9.0.3
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 8.5.40 All 220 releases
wpvr / wpvr.php

wpvr.php in WP VR – 360 Panorama and Virtual Tour Builder 9.0.3, at wpvr.php

666 lines 23.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use RexTheme\WPVR\Tracker\WPVRLinnoTelemetry;
4
5 /**
6 * The plugin bootstrap file
7 *
8 * This file is read by WordPress to generate the plugin information in the plugin
9 * admin area. This file also includes all of the dependencies used by the plugin,
10 * registers the activation and deactivation functions, and defines a function
11 * that starts the plugin.
12 *
13 * @link http://rextheme.com/
14 * @since 7.3.6
15 * @package Wpvr
16 *
17 * @wordpress-plugin
18 * Plugin Name: WP VR - 360 Panorama and Virtual Tour Builder
19 * Plugin URI: https://rextheme.com/wpvr/
20 * Description: WP VR - 360 Panorama and virtual tour creator is a customized panaroma & virtual builder tool for your website.
21 * Version: 9.0.3
22 * Tested up to: 7.1
23 * Author: Rextheme
24 * Author URI: http://rextheme.com/
25 * License: GPL-2.0+
26 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
27 * Text Domain: wpvr
28 * Domain Path: /languages
29 */
30
31 // If this file is called directly, abort.
32 if (!defined('WPINC')) {
33 die;
34 }
35
36 define('WPVR_VERSION', '9.0.3');
37 define('WPVR_FILE', __FILE__);
38 define("WPVR_PLUGIN_DIR_URL", plugin_dir_url(__FILE__).'legacy/');
39 define("WPVR_PLUGIN_DIR_PATH", plugin_dir_path(__FILE__).'legacy/');
40 define("WPVR_PLUGIN_LEGACY_DIR_PATH", plugin_dir_path(__FILE__) . 'legacy/');
41 define("WPVR_PLUGIN_PUBLIC_DIR_URL", plugin_dir_url(__FILE__) . 'legacy/public/');
42 define('WPVR_BASE', plugin_basename(WPVR_FILE));
43 define('WPVR_DEV_MODE', false);
44 define('WPVR_JS_PATH', plugin_dir_url(__FILE__) . 'legacy/admin/js/');
45 define('WPVR_CSS_PATH', plugin_dir_url(__FILE__) . 'legacy/admin/css/');
46 define('WPVR_ASSET_PATH', plugin_dir_url(__FILE__) . 'legacy/admin/');
47
48 $wpvr_composer_autoload = plugin_dir_path( __FILE__ ) . 'vendor/autoload.php';
49 if ( file_exists( $wpvr_composer_autoload ) ) {
50 require_once $wpvr_composer_autoload;
51 }
52
53 if ( ! defined( 'WPVR_TELEMETRY_API_KEY' ) ) {
54 define( 'WPVR_TELEMETRY_API_KEY', 'phc_amk3VQz1P5ZqZOMRRoWM1B41QMFpf3hu8pg7yRfzSXW' );
55 }
56 if ( ! defined( 'WPVR_TELEMETRY_API_SECRET' ) ) {
57 define( 'WPVR_TELEMETRY_API_SECRET', 'sec_dfb2ab8e84391ae7a0f9' );
58 }
59 if ( ! defined( 'WPVR_TELEMETRY_HOST' ) ) {
60 define( 'WPVR_TELEMETRY_HOST', 'https://eu.i.posthog.com' );
61 }
62
63 if ( ! function_exists( 'activate_wpvr' ) ) {
64 /**
65 * The code that runs during plugin activation.
66 */
67 function activate_wpvr() {
68 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'includes/class-wpvr-activator.php';
69 Wpvr_Activator::activate();
70
71 // Trigger plugin activation tracking
72 do_action( 'wpvr_plugin_activated' );
73 }
74 }
75
76 if ( ! function_exists( 'deactivate_wpvr' ) ) {
77 /**
78 * The code that runs during plugin deactivation.
79 */
80 function deactivate_wpvr() {
81 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'includes/class-wpvr-deactivator.php';
82 Wpvr_Deactivator::deactivate();
83
84 // Trigger plugin deactivation tracking
85 do_action( 'wpvr_plugin_deactivated' );
86 }
87 }
88
89 register_activation_hook( WPVR_FILE, 'activate_wpvr' );
90 register_deactivation_hook( WPVR_FILE, 'deactivate_wpvr' );
91
92 if ( class_exists( WPVRLinnoTelemetry::class ) ) {
93 new WPVRLinnoTelemetry();
94 }
95
96 if ( ! function_exists( 'wpvr_is_pro_active' ) ) {
97 /**
98 * Return the effective WP VR Pro entitlement state.
99 *
100 * A loaded Pro plugin alone is not enough; its license must also be valid.
101 */
102 function wpvr_is_pro_active(): bool {
103 return defined( 'WPVR_PRO_VERSION' )
104 && get_option( 'wpvr_edd_license_status' ) === 'valid'
105 && (bool) apply_filters( 'is_wpvr_pro_active', false );
106 }
107 }
108
109 if ( ! function_exists( 'wpvr_get_effective_panodata' ) ) {
110 /**
111 * Build a runtime-only Free-plan view of saved tour data.
112 *
113 * The stored meta is never changed, so reactivating Pro restores every value.
114 * Also normalizes values written by newer editor clients to the legacy
115 * string format expected by the shared preview and shortcode renderers.
116 */
117 function wpvr_get_effective_panodata( array $postdata ): array {
118 if ( isset( $postdata['scene_navigation'] ) ) {
119 $postdata['scene_navigation'] = in_array( $postdata['scene_navigation'], [ true, 1, '1', 'on' ], true ) ? 'on' : 'off';
120 }
121
122 if ( isset( $postdata['autoLoad'] ) ) {
123 $postdata['autoLoad'] = in_array( $postdata['autoLoad'], [ true, 1, '1', 'on', 'true' ], true );
124 }
125
126 if ( wpvr_is_pro_active() ) {
127 return $postdata;
128 }
129
130 $false_keys = [
131 'gyro', 'deviceorientationcontrol', 'compass', 'vrgallery',
132 'vrgallery_title', 'vrgallery_icon_size', 'vrgallery_display',
133 ];
134 foreach ( $false_keys as $key ) {
135 $postdata[ $key ] = false;
136 }
137
138 $off_keys = [
139 'scene_navigation', 'sceneAnimation', 'bg_music',
140 'autoplay_bg_music', 'loop_bg_music', 'explainerSwitch',
141 'cpLogoSwitch', 'genericform', 'calltoaction', 'customcss_enable',
142 'floorplan-enabled', 'floorplan-compass',
143 'floor_plan_tour_enabler', 'floor_plan_direction_indicator',
144 'bg_tour_enabler', 'video-autoplay', 'video-loop',
145 ];
146 foreach ( $off_keys as $key ) {
147 $postdata[ $key ] = 'off';
148 }
149
150 $postdata['tourLayout'] = [
151 'layout' => 'default',
152 'layout_icon_bg_color' => '#5a536e',
153 'layout_icon_color' => '#ffffff',
154 ];
155 $postdata['draggable'] = true;
156 $postdata['mouseZoom'] = true;
157 $postdata['diskeyboard'] = false;
158 $postdata['keyboardzoom'] = true;
159 $postdata['hfov'] = '';
160 $postdata['maxHfov'] = '';
161 $postdata['minHfov'] = '';
162 $postdata['cpLogoImg'] = '';
163 $postdata['cpLogoContent'] = '';
164
165 if ( ( $postdata['tour-type'] ?? '' ) === 'street-view' || isset( $postdata['streetviewdata'] ) ) {
166 $postdata['tour-type'] = 'image';
167 $postdata['streetview'] = 'off';
168 $postdata['streetviewurl'] = '';
169 unset( $postdata['streetviewdata'] );
170 }
171
172 $custom_control = isset( $postdata['customcontrol'] ) && is_array( $postdata['customcontrol'] )
173 ? $postdata['customcontrol']
174 : [];
175 foreach ( [
176 'panupSwitch', 'panDownSwitch', 'panLeftSwitch', 'panRightSwitch',
177 'panZoomInSwitch', 'panZoomOutSwitch', 'panFullscreenSwitch',
178 'gyroscopeSwitch', 'backToHomeSwitch',
179 ] as $key ) {
180 $custom_control[ $key ] = 'off';
181 }
182 $postdata['customcontrol'] = $custom_control;
183
184 if ( isset( $postdata['panodata']['scene-list'] ) && is_array( $postdata['panodata']['scene-list'] ) ) {
185 foreach ( $postdata['panodata']['scene-list'] as &$scene ) {
186 if ( ! is_array( $scene ) ) {
187 continue;
188 }
189
190 if ( ( $scene['scene-type'] ?? 'equirectangular' ) === 'cubemap' ) {
191 $scene['scene-type'] = 'equirectangular';
192 }
193
194 foreach ( [ 'ptyscene', 'cvgscene', 'chgscene', 'czscene' ] as $key ) {
195 $scene[ $key ] = 'off';
196 }
197 foreach ( [
198 'scene-author', 'scene-author-url', 'scene-vaov', 'scene-haov',
199 'scene-vertical-offset', 'scene-pitch', 'scene-yaw',
200 'scene-maxpitch', 'scene-minpitch', 'scene-maxyaw', 'scene-minyaw',
201 'scene-zoom', 'scene-maxzoom', 'scene-minzoom',
202 ] as $key ) {
203 $scene[ $key ] = '';
204 }
205 for ( $face = 0; $face < 6; $face++ ) {
206 $scene[ 'scene-attachment-url-face' . $face ] = '';
207 }
208
209 if ( isset( $scene['hotspot-list'] ) && is_array( $scene['hotspot-list'] ) ) {
210 foreach ( $scene['hotspot-list'] as &$hotspot ) {
211 if ( ! is_array( $hotspot ) ) {
212 continue;
213 }
214 $hotspot['hotspot-customclass-pro'] = 'none';
215 $hotspot['hotspot-blink'] = 'off';
216 $hotspot['hotspot-shape'] = 'round';
217 $hotspot['hotspot-border'] = 'off';
218 $hotspot['hotspot-scene-pitch'] = '';
219 $hotspot['hotspot-scene-yaw'] = '';
220 }
221 unset( $hotspot );
222 }
223 }
224 unset( $scene );
225 }
226
227 return $postdata;
228 }
229 }
230
231 if ( ! function_exists( 'wpvr_tour_uses_pro_features' ) ) {
232 /**
233 * Determine whether a saved tour contains enabled WP VR Pro features.
234 *
235 * @param mixed $postdata Saved tour data.
236 * @return bool
237 */
238 function wpvr_tour_uses_pro_features( $postdata ) {
239 if ( ! is_array( $postdata ) ) {
240 return false;
241 }
242
243 $is_on = static function ( $value ) {
244 return in_array( $value, array( true, 1, '1', 'on', 'true' ), true );
245 };
246
247 if (
248 isset( $postdata['streetviewdata'] )
249 || ( isset( $postdata['streetview'] ) && $is_on( $postdata['streetview'] ) )
250 || ! empty( $postdata['streetviewurl'] )
251 ) {
252 return true;
253 }
254
255 foreach ( array(
256 'bg_tour_enabler', 'floorplan-enabled', 'floor_plan_tour_enabler',
257 'floor_plan_compass', 'floor_plan_direction_indicator',
258 'gyro', 'deviceorientationcontrol', 'compass', 'vrgallery',
259 'vrgallery_title', 'vrgallery_icon_size', 'vrgallery_display',
260 'scene_navigation', 'sceneAnimation', 'bg_music',
261 'autoplay_bg_music', 'loop_bg_music', 'explainerSwitch',
262 'cpLogoSwitch', 'genericform', 'calltoaction', 'customcss_enable',
263 ) as $key ) {
264 if ( isset( $postdata[ $key ] ) && $is_on( $postdata[ $key ] ) ) {
265 return true;
266 }
267 }
268
269 if (
270 ! empty( $postdata['hfov'] )
271 || ! empty( $postdata['maxHfov'] )
272 || ! empty( $postdata['minHfov'] )
273 ) {
274 return true;
275 }
276
277 $tour_layout = isset( $postdata['tourLayout'] ) ? $postdata['tourLayout'] : 'default';
278 $tour_layout = is_array( $tour_layout )
279 ? ( isset( $tour_layout['layout'] ) ? $tour_layout['layout'] : 'default' )
280 : $tour_layout;
281 if ( $tour_layout !== '' && $tour_layout !== 'default' ) {
282 return true;
283 }
284
285 $custom_control = isset( $postdata['customcontrol'] ) && is_array( $postdata['customcontrol'] )
286 ? $postdata['customcontrol']
287 : array();
288 foreach ( array(
289 'panupSwitch', 'panDownSwitch', 'panLeftSwitch', 'panRightSwitch',
290 'panZoomInSwitch', 'panZoomOutSwitch', 'panFullscreenSwitch',
291 'gyroscopeSwitch', 'backToHomeSwitch',
292 ) as $key ) {
293 if ( isset( $custom_control[ $key ] ) && $is_on( $custom_control[ $key ] ) ) {
294 return true;
295 }
296 }
297
298 $scenes = isset( $postdata['panodata']['scene-list'] ) ? $postdata['panodata']['scene-list'] : array();
299 if ( ! is_array( $scenes ) ) {
300 return false;
301 }
302
303 $pro_scene_fields = array(
304 'scene-vaov', 'scene-haov', 'scene-vertical-offset', 'scene-pitch',
305 'scene-yaw', 'scene-maxpitch', 'scene-minpitch', 'scene-maxyaw',
306 'scene-minyaw', 'scene-zoom', 'scene-maxzoom', 'scene-minzoom',
307 );
308
309 foreach ( $scenes as $scene ) {
310 if ( ! is_array( $scene ) ) {
311 continue;
312 }
313
314 if ( isset( $scene['scene-type'] ) && 'cubemap' === $scene['scene-type'] ) {
315 return true;
316 }
317
318 foreach ( array( 'ptyscene', 'cvgscene', 'chgscene', 'czscene' ) as $key ) {
319 if ( isset( $scene[ $key ] ) && $is_on( $scene[ $key ] ) ) {
320 return true;
321 }
322 }
323
324 foreach ( $pro_scene_fields as $key ) {
325 if ( isset( $scene[ $key ] ) && '' !== $scene[ $key ] && null !== $scene[ $key ] ) {
326 return true;
327 }
328 }
329
330 $hotspots = isset( $scene['hotspot-list'] ) ? $scene['hotspot-list'] : array();
331 if ( ! is_array( $hotspots ) ) {
332 continue;
333 }
334
335 foreach ( $hotspots as $hotspot ) {
336 if ( ! is_array( $hotspot ) ) {
337 continue;
338 }
339
340 if (
341 ( ! empty( $hotspot['hotspot-customclass-pro'] ) && 'none' !== $hotspot['hotspot-customclass-pro'] )
342 || ( ! empty( $hotspot['hotspot-shape'] ) && 'round' !== $hotspot['hotspot-shape'] )
343 || ( isset( $hotspot['hotspot-border'] ) && $is_on( $hotspot['hotspot-border'] ) )
344 || ! empty( $hotspot['hotspot-scene-pitch'] )
345 || ! empty( $hotspot['hotspot-scene-yaw'] )
346 || ( isset( $hotspot['hotspot-scene-entry-point-mode'] ) && 'custom' === $hotspot['hotspot-scene-entry-point-mode'] )
347 ) {
348 return true;
349 }
350 }
351 }
352
353 return false;
354 }
355 }
356
357 if ( ! function_exists( 'wpvr_get_pro_feature_tour_ids' ) ) {
358 /**
359 * Return IDs of tours that use at least one enabled Pro feature.
360 *
361 * @return array
362 */
363 function wpvr_get_pro_feature_tour_ids() {
364 static $tour_ids = null;
365
366 if ( is_array( $tour_ids ) ) {
367 return $tour_ids;
368 }
369
370 $tour_ids = array();
371 $posts = get_posts( array(
372 'post_type' => 'wpvr_item',
373 'post_status' => 'any',
374 'posts_per_page' => -1,
375 'fields' => 'ids',
376 'no_found_rows' => true,
377 'update_post_meta_cache' => true,
378 'update_post_term_cache' => false,
379 ) );
380
381 foreach ( $posts as $tour_id ) {
382 $postdata = get_post_meta( (int) $tour_id, 'panodata', true );
383 if ( wpvr_tour_uses_pro_features( $postdata ) ) {
384 $tour_ids[] = (int) $tour_id;
385 }
386 }
387
388 return $tour_ids;
389 }
390 }
391
392 if ( ! function_exists( 'wpvr_has_pro_feature_tours' ) ) {
393 function wpvr_has_pro_feature_tours() {
394 return ! empty( wpvr_get_pro_feature_tour_ids() );
395 }
396 }
397
398 if ( ! function_exists( 'wpvr_enqueue_license_notice_assets' ) ) {
399 /**
400 * Enqueue the global license notice styles while Pro is installed and inactive.
401 *
402 * @return void
403 */
404 function wpvr_enqueue_license_notice_assets() {
405 if (
406 ! is_admin()
407 || ! defined( 'WPVR_PRO_VERSION' )
408 || wpvr_is_pro_active()
409 ) {
410 return;
411 }
412
413 wp_enqueue_style(
414 'wpvr-license-notice-fontawesome',
415 WPVR_PLUGIN_DIR_URL . 'admin/lib/fontawesome/css/all.css',
416 array(),
417 WPVR_VERSION
418 );
419 wp_enqueue_style(
420 'wpvr-license-notice',
421 WPVR_PLUGIN_DIR_URL . 'admin/css/wpvr-license-notice.css',
422 array( 'wpvr-license-notice-fontawesome' ),
423 WPVR_VERSION
424 );
425 wp_enqueue_script(
426 'wpvr-license-notice',
427 WPVR_PLUGIN_DIR_URL . 'admin/js/wpvr-license-notice.js',
428 array(),
429 WPVR_VERSION,
430 true
431 );
432 }
433 }
434
435 add_action( 'admin_enqueue_scripts', 'wpvr_enqueue_license_notice_assets', 10 );
436
437 if ( ! function_exists( 'wpvr_render_pro_feature_fallback_notice' ) ) {
438 /**
439 * Render the global Pro license notice and explain the saved-tour fallback.
440 *
441 * @return void
442 */
443 function wpvr_render_pro_feature_fallback_notice() {
444 if (
445 ! is_admin()
446 || ! current_user_can( 'edit_posts' )
447 || ! defined( 'WPVR_PRO_VERSION' )
448 || wpvr_is_pro_active()
449 ) {
450 return;
451 }
452
453 $status = strtolower( (string) get_option( 'wpvr_edd_license_status', 'no' ) );
454 $needs_activation = in_array( $status, array( '', 'no' ), true );
455
456 if ( ! $needs_activation && ! wpvr_has_pro_feature_tours() ) {
457 return;
458 }
459
460 if ( $needs_activation ) {
461 $title = __( 'Please Activate your license key to enjoy the pro features for WPVR', 'wpvr' );
462 $subtitle = '';
463 $action_label = __( 'Activate', 'wpvr' );
464 $action_url = admin_url( 'admin.php?page=wpvrpro' );
465 $action_target = '';
466 $action_icon = 'fa-key';
467 } else {
468 $title = 'expired' === $status
469 ? __( 'Your WPVR Pro license has expired.', 'wpvr' )
470 : __( 'Your WPVR Pro license is not active.', 'wpvr' );
471 $subtitle = __( 'Pro features are no longer available. Upgrade to Pro now to restore access to all premium features.', 'wpvr' );
472 $action_label = __( 'Upgrade to Pro', 'wpvr' );
473 $action_url = 'https://rextheme.com/your-account/#purchase';
474 $action_target = ' target="_blank" rel="noopener noreferrer"';
475 $action_icon = 'fa-crown';
476 }
477
478 $notice_classes = 'notice notice-error is-dismissible wpvr-license-fallback-notice';
479 if ( '' === $subtitle ) {
480 $notice_classes .= ' wpvr-license-fallback-notice--single-line';
481 }
482
483 echo '<div id="wpvr-license-fallback-notice" class="' . esc_attr( $notice_classes ) . '">';
484 echo '<img class="wpvr-license-fallback-notice__icon" src="' . esc_url( WPVR_PLUGIN_DIR_URL . 'admin/images/wpvr-license-warning.svg' ) . '" width="20" height="19" alt="" aria-hidden="true">';
485 echo '<div class="wpvr-license-fallback-notice__content">';
486 echo '<p class="wpvr-license-fallback-notice__title">' . esc_html( $title ) . '</p>';
487 if ( '' !== $subtitle ) {
488 echo '<p class="wpvr-license-fallback-notice__subtitle">' . esc_html( $subtitle ) . '</p>';
489 }
490 echo '</div>';
491 echo '<a class="wpvr-license-fallback-notice__action" href="' . esc_url( $action_url ) . '"' . $action_target . '>';
492 echo '<span class="wpvr-license-fallback-notice__action-icon fa-solid ' . esc_attr( $action_icon ) . '" aria-hidden="true"></span>';
493 echo '<span>' . esc_html( $action_label ) . '</span>';
494 echo '</a>';
495 echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__( 'Dismiss this notice.', 'wpvr' ) . '</span></button>';
496 echo '</div>';
497 }
498 }
499
500 add_action( 'admin_notices', 'wpvr_render_pro_feature_fallback_notice', 10 );
501
502 if ( ! function_exists( 'wpvr_add_pro_feature_tour_row_class' ) ) {
503 /**
504 * Add a class to affected tour rows while the Pro entitlement is inactive.
505 *
506 * @param array $classes Existing post classes.
507 * @param array $class Additional classes supplied by WordPress.
508 * @param int $post_id Post ID.
509 * @return array
510 */
511 function wpvr_add_pro_feature_tour_row_class( $classes, $class = array(), $post_id = 0 ) {
512 if (
513 ! is_admin()
514 || ! defined( 'WPVR_PRO_VERSION' )
515 || wpvr_is_pro_active()
516 || 'wpvr_item' !== get_post_type( $post_id )
517 ) {
518 return $classes;
519 }
520
521 $postdata = get_post_meta( (int) $post_id, 'panodata', true );
522 if ( wpvr_tour_uses_pro_features( $postdata ) ) {
523 $classes[] = 'wpvr-pro-feature-tour';
524 }
525
526 return $classes;
527 }
528 }
529
530 add_filter( 'post_class', 'wpvr_add_pro_feature_tour_row_class', 10, 3 );
531
532 require_once plugin_dir_path( __FILE__ ) . 'src/Admin/UiModeSwitcher.php';
533 \RexTheme\WPVR\Admin\UiModeSwitcher::init();
534
535 add_action( 'wp_ajax_wpvr_export_tour', 'wpvr_handle_export_tour' );
536 /**
537 * AJAX handler: return a signed URL for the Pro tour exporter.
538 *
539 * WPVR Pro creates and streams the ZIP through its admin-post endpoint. This
540 * handler authorizes the New UI request and returns that endpoint URL.
541 */
542 function wpvr_handle_export_tour(): void {
543 check_ajax_referer( 'wpvr_export_tour', 'nonce' );
544
545 if ( ! current_user_can( 'edit_posts' ) ) {
546 wp_send_json_error( [ 'message' => 'Insufficient permissions.' ], 403 );
547 }
548
549 if ( ! wpvr_is_pro_active() ) {
550 wp_send_json_error( [ 'message' => 'A valid WP VR Pro license is required.' ], 403 );
551 }
552
553 $tour_id = isset( $_POST['tour_id'] ) ? absint( $_POST['tour_id'] ) : 0;
554 if ( ! $tour_id ) {
555 wp_send_json_error( [ 'message' => 'Missing tour ID.' ] );
556 }
557
558 $post = get_post( $tour_id );
559 if ( ! $post || $post->post_type !== 'wpvr_item' ) {
560 wp_send_json_error( [ 'message' => 'Tour not found.' ] );
561 }
562
563 if ( ! current_user_can( 'edit_post', $tour_id ) ) {
564 wp_send_json_error( [ 'message' => 'You are not allowed to export this tour.' ], 403 );
565 }
566
567 $export_url = add_query_arg(
568 [
569 'action' => 'wpvr_export_tour',
570 'post_id' => $tour_id,
571 '_wpnonce' => wp_create_nonce( 'wpvr_export_tour_' . $tour_id ),
572 ],
573 admin_url( 'admin-post.php' )
574 );
575
576 wp_send_json_success( [ 'url' => $export_url ] );
577 }
578
579 /**
580 * Ensure legacy Street View keys always exist in panodata.
581 *
582 * WPVR Pro reads these keys directly in classic editor screens. Tours created
583 * through newer flows may miss them, which triggers PHP warnings.
584 */
585 function wpvr_backfill_legacy_streetview_keys() {
586 if ( ! is_admin() ) {
587 return;
588 }
589
590 $post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0;
591 if ( ! $post_id ) {
592 return;
593 }
594
595 $post = get_post( $post_id );
596 if ( ! $post || $post->post_type !== 'wpvr_item' ) {
597 return;
598 }
599
600 $panodata = get_post_meta( $post_id, 'panodata', true );
601 if ( ! is_array( $panodata ) ) {
602 return;
603 }
604
605 $changed = false;
606
607 if ( ! array_key_exists( 'streetview', $panodata ) ) {
608 $panodata['streetview'] = 'off';
609 $changed = true;
610 }
611
612 if ( ! array_key_exists( 'streetviewurl', $panodata ) ) {
613 $panodata['streetviewurl'] = '';
614 $changed = true;
615 }
616
617 if ( $changed ) {
618 update_post_meta( $post_id, 'panodata', $panodata );
619 }
620 }
621
622 add_action( 'load-post.php', 'wpvr_backfill_legacy_streetview_keys', 1 );
623
624 /**
625 * Register a lightweight PSR-4 autoloader for src/ classes.
626 */
627 function wpvr_register_src_autoloader() {
628 static $loaded = false;
629
630 if ( $loaded ) {
631 return;
632 }
633
634 spl_autoload_register(
635 static function ( $class ) {
636 $prefix = 'RexTheme\\WPVR\\';
637
638 if ( strpos( $class, $prefix ) !== 0 ) {
639 return;
640 }
641
642 $relative_class = substr( $class, strlen( $prefix ) );
643 $file = plugin_dir_path( __FILE__ ) . 'src/' . str_replace( '\\', '/', $relative_class ) . '.php';
644
645 if ( file_exists( $file ) ) {
646 require_once $file;
647 }
648 }
649 );
650
651 $loaded = true;
652 }
653
654 // UI/UX Mode Switch: legacy/classic or latest
655 // Option: wpvr_ui_mode (default: legacy)
656 wpvr_register_src_autoloader();
657
658 $wpvr_ui_mode = get_option('wpvr_ui_mode', 'legacy');
659 if ($wpvr_ui_mode === 'latest') {
660 require_once plugin_dir_path(__FILE__) . 'src/Bootstrap.php';
661 \RexTheme\WPVR\Bootstrap::init();
662 } else {
663 require_once plugin_dir_path(__FILE__) . 'legacy/wpvr.php';
664 new \RexTheme\WPVR\Admin\Admin( false );
665 }
666