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 / Admin / Admin.php

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

672 lines 28.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace RexTheme\WPVR\Admin;
4
5 /**
6 * Manages the custom tour-editor admin page and CPT edit suppression.
7 *
8 * Step 1 — Register hidden admin page (wpvr-tour-editor).
9 * Step 2 — Redirect post.php / post-new.php for wpvr_item to custom page.
10 * Step 3 — Override edit links in WP posts list.
11 * Step 4 — Render page shell (#wpvr-tour-editor-root) + enqueue assets.
12 * Step 5 — New tour: React creates post on mount; history.replaceState URL.
13 */
14 class Admin {
15
16 const POST_TYPE = 'wpvr_item';
17 const PAGE_SLUG = 'wpvr-tour-editor';
18 const HOOK_SUFFIX = 'admin_page_wpvr-tour-editor';
19
20 private $editor_enabled;
21
22 public function __construct( bool $editor_enabled = true ) {
23 $this->editor_enabled = $editor_enabled;
24
25 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_listing_assets' ] );
26
27 if ( $this->editor_enabled ) {
28 add_action( 'admin_footer', [ $this, 'render_listing_modal' ] );
29 }
30
31 if ( ! $this->editor_enabled ) {
32 return;
33 }
34
35 add_action( 'admin_menu', [ $this, 'register_editor_page' ] );
36 add_action( 'load-post.php', [ $this, 'redirect_cpt_edit' ] );
37 add_action( 'load-post-new.php', [ $this, 'redirect_cpt_new' ] );
38 add_filter( 'get_edit_post_link', [ $this, 'filter_edit_link' ], 10, 3 );
39 add_filter( 'post_row_actions', [ $this, 'filter_row_actions' ], 10, 2 );
40 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_tour_editor_assets' ] );
41 add_filter( 'admin_body_class', [ $this, 'add_body_class' ] );
42 add_filter( 'admin_title', [ $this, 'filter_admin_title' ], 10, 2 );
43 add_action( 'current_screen', [ $this, 'set_screen_title' ] );
44 add_filter( 'parent_file', [ $this, 'set_menu_parent' ] );
45 add_filter( 'submenu_file', [ $this, 'set_menu_submenu' ] );
46 add_action( 'admin_head', [ $this, 'editor_head_scripts' ] );
47 add_action( 'admin_footer', [ $this, 'editor_footer_scripts' ] );
48 }
49
50 // -------------------------------------------------------------------------
51 // Step 1 — Register hidden admin page
52 // -------------------------------------------------------------------------
53
54 public function register_editor_page(): void {
55 $tour_id = isset( $_GET['tour_id'] ) ? (int) $_GET['tour_id'] : 0;
56 $page_title = $tour_id
57 ? __( 'Edit Tour', 'wpvr' )
58 : __( 'Add New Tour', 'wpvr' );
59
60 $post_type_obj = get_post_type_object( self::POST_TYPE );
61 $capability = $post_type_obj ? $post_type_obj->cap->edit_posts : 'edit_wpvr_tours';
62
63 add_submenu_page(
64 '',
65 $page_title,
66 $page_title,
67 $capability,
68 self::PAGE_SLUG,
69 [ $this, 'render_editor_page' ]
70 );
71 }
72
73 // -------------------------------------------------------------------------
74 // Step 2 — Redirect post.php / post-new.php
75 // -------------------------------------------------------------------------
76
77 public function redirect_cpt_edit(): void {
78 $action = isset( $_GET['action'] ) ? sanitize_key( $_GET['action'] ) : '';
79 if ( $action && $action !== 'edit' ) {
80 return;
81 }
82 $post_id = isset( $_GET['post'] ) ? (int) $_GET['post'] : 0;
83 if ( ! $post_id ) {
84 return;
85 }
86 $post = get_post( $post_id );
87 if ( ! $post || $post->post_type !== self::POST_TYPE ) {
88 return;
89 }
90 wp_redirect( $this->editor_url( $post_id ) );
91 exit;
92 }
93
94 public function redirect_cpt_new(): void {
95 $post_type = isset( $_GET['post_type'] ) ? sanitize_key( $_GET['post_type'] ) : '';
96 if ( $post_type !== self::POST_TYPE ) {
97 return;
98 }
99 wp_redirect( $this->editor_url() );
100 exit;
101 }
102
103 // -------------------------------------------------------------------------
104 // Step 3 — Override edit links
105 // -------------------------------------------------------------------------
106
107 public function filter_edit_link( string $url, int $post_id, string $context ): string {
108 $post = get_post( $post_id );
109 if ( ! $post || $post->post_type !== self::POST_TYPE ) {
110 return $url;
111 }
112 return $this->editor_url( $post_id );
113 }
114
115 public function filter_row_actions( array $actions, \WP_Post $post ): array {
116 if ( $post->post_type !== self::POST_TYPE ) {
117 return $actions;
118 }
119 if ( isset( $actions['edit'] ) ) {
120 $actions['edit'] = sprintf(
121 '<a href="%s">%s</a>',
122 esc_url( $this->editor_url( $post->ID ) ),
123 __( 'Edit', 'wpvr' )
124 );
125 }
126 return $actions;
127 }
128
129 // -------------------------------------------------------------------------
130 // Step 4 — Render + enqueue
131 // -------------------------------------------------------------------------
132
133 public function render_editor_page(): void {
134 $tour_id = isset( $_GET['tour_id'] ) ? (int) $_GET['tour_id'] : 0;
135 if ( $tour_id ) {
136 $post = get_post( $tour_id );
137 if ( ! $post || $post->post_type !== self::POST_TYPE || ! current_user_can( 'edit_post', $tour_id ) ) {
138 wp_die( esc_html__( 'Sorry, you are not allowed to edit this tour.', 'wpvr' ), 403 );
139 }
140 }
141
142 echo '<div id="wpvr-tour-editor-root"></div>';
143
144 if ( ! wpvr_is_pro_active() ) {
145 require WPVR_PLUGIN_LEGACY_DIR_PATH . 'admin/partials/wpvr-premium-feature-popup.php';
146 }
147
148 // Hidden wp_editor() output forces TinyMCE HTML/scripts to load so
149 // wp.editor.initialize() is available for WYSIWYG fields in React.
150 echo '<div style="display:none">';
151 wp_editor( '', 'wpvr_wysiwyg_seed', [
152 'tinymce' => true,
153 'quicktags' => false,
154 'media_buttons' => false,
155 ] );
156 echo '</div>';
157 }
158
159 public function enqueue_tour_editor_assets( string $hook ): void {
160 if ( ! $this->is_editor_page( $hook ) ) {
161 return;
162 }
163
164 $tour_id_cfg = isset( $_GET['tour_id'] ) ? (int) $_GET['tour_id'] : 0;
165 if ( $tour_id_cfg ) {
166 $post = get_post( $tour_id_cfg );
167 if ( ! $post || $post->post_type !== self::POST_TYPE || ! current_user_can( 'edit_post', $tour_id_cfg ) ) {
168 return;
169 }
170 }
171
172 $plugin_url = plugin_dir_url( WPVR_FILE );
173 $plugin_path = plugin_dir_path( WPVR_FILE );
174 $build_dir = $plugin_path . 'build/tour-editor/';
175
176 // Load Pannellum (from legacy bundle).
177 $pano_base = $plugin_url . 'legacy/admin/lib/pannellum/src/';
178 wp_enqueue_style( 'wpvr-pannellum-css', $pano_base . 'css/pannellum.css', [], WPVR_VERSION );
179 wp_enqueue_script( 'wpvr-libpannellum', $pano_base . 'js/libpannellum.js', [], WPVR_VERSION, true );
180 wp_enqueue_script( 'wpvr-pannellum', $pano_base . 'js/pannellum.js', [ 'wpvr-libpannellum' ], WPVR_VERSION, true );
181
182 // Load Video.js & VideoJS-VR (for 360 video tours).
183 $public_base = $plugin_url . 'legacy/public/';
184 wp_enqueue_style( 'wpvr-videojs-css', $public_base . 'lib/pannellum/src/css/video-js.css', [], WPVR_VERSION );
185 wp_enqueue_style( 'wpvr-videojs-vr-css', $public_base . 'lib/videojs-vr/videojs-vr.css', [], WPVR_VERSION );
186 wp_enqueue_script( 'wpvr-videojs', $public_base . 'js/video.js', [], WPVR_VERSION, true );
187 wp_enqueue_script( 'wpvr-videojs-vr', $public_base . 'lib/videojs-vr/videojs-vr.js', [ 'wpvr-videojs' ], WPVR_VERSION, true );
188
189 wp_enqueue_style( 'wpvr-fontawesome', $plugin_url . 'legacy/admin/lib/fontawesome/css/all.min.css', [], WPVR_VERSION );
190
191 // Tour editor React bundle.
192 $js_file = $build_dir . 'index.js';
193 $css_file = $build_dir . 'style-index.css';
194 $asset_file = $build_dir . 'index.asset.php';
195
196 if ( ! file_exists( $js_file ) ) {
197 return;
198 }
199
200 $asset = file_exists( $asset_file )
201 ? require $asset_file
202 : [ 'dependencies' => [ 'wp-element', 'wp-data', 'wp-api-fetch' ], 'version' => WPVR_VERSION ];
203
204 wp_enqueue_script(
205 'wpvr-tour-editor',
206 $plugin_url . 'build/tour-editor/index.js',
207 array_merge( $asset['dependencies'], [ 'wpvr-pannellum', 'wpvr-videojs-vr' ] ),
208 $asset['version'],
209 true
210 );
211
212 $tour_id_cfg = isset( $_GET['tour_id'] ) ? (int) $_GET['tour_id'] : 0;
213 $tour_status = $tour_id_cfg ? ( get_post_status( $tour_id_cfg ) ?: 'draft' ) : 'draft';
214
215 $fa_icons = [];
216 if ( class_exists( 'Wpvr_fontawesome_icons' ) ) {
217 $fa_icons = ( new \Wpvr_fontawesome_icons() )->icon;
218 }
219
220 $raw_hotspot_types = apply_filters( 'wpvr_hotspot_types', [
221 'info' => __( 'Info', 'wpvr' ),
222 'scene' => __( 'Scene', 'wpvr' ),
223 ] );
224
225 $hotspot_types = [];
226 if ( isset( $raw_hotspot_types['info'] ) ) {
227 $hotspot_types[] = [ 'value' => 'info', 'label' => $raw_hotspot_types['info'] ];
228 unset( $raw_hotspot_types['info'] );
229 } else {
230 $hotspot_types[] = [ 'value' => 'info', 'label' => __( 'Info', 'wpvr' ) ];
231 }
232
233 if ( isset( $raw_hotspot_types['scene'] ) ) {
234 $hotspot_types[] = [ 'value' => 'scene', 'label' => $raw_hotspot_types['scene'] ];
235 unset( $raw_hotspot_types['scene'] );
236 } else {
237 $hotspot_types[] = [ 'value' => 'scene', 'label' => __( 'Scene', 'wpvr' ) ];
238 }
239
240 $hotspot_types[] = [ 'value' => 'url', 'label' => __( 'External URL', 'wpvr' ) ];
241 if ( isset( $raw_hotspot_types['url'] ) ) {
242 unset( $raw_hotspot_types['url'] );
243 }
244
245 foreach ( $raw_hotspot_types as $type_key => $type_label ) {
246 $hotspot_types[] = [ 'value' => $type_key, 'label' => $type_label ];
247 }
248
249 $fluent_forms = [];
250 if ( defined( 'FLUENTFORM' ) ) {
251 global $wpdb;
252 $fluent_form_table = $wpdb->prefix . 'fluentform_forms';
253 $forms = $wpdb->get_results( "SELECT id, title FROM {$fluent_form_table} WHERE status = 'published' ORDER BY title ASC" );
254 if ( ! empty( $forms ) && is_array( $forms ) ) {
255 foreach ( $forms as $form ) {
256 $fluent_forms[] = [
257 'id' => (string) $form->id,
258 'title' => $form->title,
259 ];
260 }
261 }
262 }
263
264 $is_embed_addon_active = apply_filters( 'is_wpvr_embed_addon_premium', false );
265 if ( $is_embed_addon_active ) {
266 if ( wp_script_is( 'custom-qrcode', 'registered' ) ) {
267 wp_enqueue_script( 'custom-qrcode' );
268 } elseif ( defined( 'WPVR_PRO_PLUGIN_DIR_URL' ) ) {
269 wp_enqueue_script( 'custom-qrcode', trailingslashit( WPVR_PRO_PLUGIN_DIR_URL ) . 'admin/lib/qr-code/custom-qrcode.js', [ 'jquery' ], null, true );
270 }
271 }
272
273 wp_localize_script( 'wpvr-tour-editor', 'wpvrTourEditor', [
274 'tourId' => $tour_id_cfg ?: null,
275 'tourStatus' => $tour_status,
276 'tourTitle' => $tour_id_cfg ? get_the_title( $tour_id_cfg ) : '',
277 'homeUrl' => home_url( '/' ),
278 'isEmbedAddonActive' => $is_embed_addon_active,
279 'nonce' => wp_create_nonce( 'wp_rest' ),
280 'apiBase' => rest_url( 'wpvr/v1' ),
281 'mediaUrl' => rest_url( 'wp/v2/media' ),
282 'isPro' => wpvr_is_pro_active(),
283 'pluginUrl' => $plugin_url,
284 'listUrl' => admin_url( 'edit.php?post_type=' . self::POST_TYPE ),
285 'postEditUrl' => admin_url( 'post.php' ),
286 'faIcons' => $fa_icons,
287 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
288 'exportNonce' => wp_create_nonce( 'wpvr_export_tour' ),
289 'hotspotTypes' => $hotspot_types,
290 'fluentForms' => $fluent_forms,
291 'isFluentFormActive' => defined( 'FLUENTFORM' ),
292 'isWcActive' => class_exists( 'WooCommerce' ),
293 'imageResizeWarning' => [
294 'ajaxNonce' => wp_create_nonce( 'wpvr' ),
295 'canManageSettings' => current_user_can( 'manage_options' ),
296 'settingEnabled' => get_option( 'high_res_image' ) === 'true',
297 'heading' => __( 'Keep this panorama in High Resolution', 'wpvr' ),
298 'scaledMessage' => __( 'By default, a resized version is created while the original image is still available.', 'wpvr' ),
299 'disableHandler' => __( 'Disable WordPress Large Image Handler on WP VR for future uploads', 'wpvr' ),
300 'highResolutionNote'=> __( '*Note: Turn this on to use the high-resolution image.', 'wpvr' ),
301 'close' => __( 'Close', 'wpvr' ),
302 ],
303 ] );
304
305 if ( file_exists( $css_file ) ) {
306 wp_enqueue_style(
307 'wpvr-tour-editor',
308 $plugin_url . 'build/tour-editor/style-index.css',
309 [ 'wpvr-pannellum-css' ],
310 $asset['version']
311 );
312 }
313 }
314
315 public function filter_admin_title( string $admin_title, string $title ): string {
316 if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== self::PAGE_SLUG ) {
317 return $admin_title;
318 }
319 $tour_id = isset( $_GET['tour_id'] ) ? (int) $_GET['tour_id'] : 0;
320 $page_title = $tour_id ? __( 'Edit Tour', 'wpvr' ) : __( 'Add New Tour', 'wpvr' );
321 return $page_title . ' &lsaquo; ' . get_bloginfo( 'name' ) . ' &#8212; WordPress';
322 }
323
324 public function add_body_class( string $classes ): string {
325 if ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG ) {
326 $classes .= ' wpvr-tour-editor-page';
327 }
328 return $classes;
329 }
330
331 public function set_menu_parent( string $parent_file ): string {
332 if ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG ) {
333 return 'edit.php?post_type=' . self::POST_TYPE;
334 }
335 return $parent_file;
336 }
337
338 public function set_menu_submenu( ?string $submenu_file ): ?string {
339 if ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG ) {
340 return 'post-new.php?post_type=' . self::POST_TYPE;
341 }
342 return $submenu_file;
343 }
344
345 public function editor_head_scripts(): void {
346 if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== self::PAGE_SLUG ) {
347 return;
348 }
349 ?>
350 <script>
351 ( function() {
352 var narrowEditor = window.matchMedia( '(max-width: 1279px)' );
353
354 function applyEditorMenuState() {
355 if ( ! document.body ) {
356 window.requestAnimationFrame( applyEditorMenuState );
357 return;
358 }
359
360 document.body.classList.toggle( 'folded', narrowEditor.matches );
361 }
362
363 applyEditorMenuState();
364
365 if ( narrowEditor.addEventListener ) {
366 narrowEditor.addEventListener( 'change', applyEditorMenuState );
367 } else {
368 narrowEditor.addListener( applyEditorMenuState );
369 }
370 }() );
371 </script>
372 <?php
373 }
374
375 public function editor_footer_scripts(): void {
376 if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== self::PAGE_SLUG ) {
377 return;
378 }
379 ?>
380 <script>
381 jQuery( window ).on( 'load', function() {
382 // Intercept WP sidebar toggle so it doesn't persist state to user meta.
383 // The editor's initial state is viewport-dependent; manual toggling should
384 // work visually but must not overwrite the user's real preference.
385 jQuery( '#collapse-button' ).off( 'click' ).on( 'click', function() {
386 jQuery( 'body' ).toggleClass( 'folded' );
387 } );
388 } );
389 </script>
390 <?php
391 }
392
393 public function render_mode_switch_notice(): void {
394 if ( ! current_user_can( 'edit_posts' ) ) {
395 return;
396 }
397
398 if ( ! $this->is_listing_page() && ! $this->is_editor_request() ) {
399 return;
400 }
401
402 $tour_id = isset( $_GET['tour_id'] ) ? absint( $_GET['tour_id'] ) : 0;
403 $switch_url = $this->get_ui_mode_switch_url( 'legacy', $tour_id );
404 $button_label = __( 'Switch to Classic UI', 'wpvr' );
405 ?>
406 <div class="notice notice-info">
407 <p>
408 <?php esc_html_e( 'You are using the latest WP VR editor.', 'wpvr' ); ?>
409 <a class="button button-secondary" href="<?php echo esc_url( $switch_url ); ?>" style="margin-left:8px;">
410 <?php echo esc_html( $button_label ); ?>
411 </a>
412 </p>
413 </div>
414 <?php
415 }
416
417 public function handle_ui_mode_switch(): void {
418 if ( ! current_user_can( 'edit_posts' ) ) {
419 wp_die( esc_html__( 'You are not allowed to switch UI modes.', 'wpvr' ) );
420 }
421
422 check_admin_referer( 'wpvr_switch_ui_mode' );
423
424 $mode = isset( $_GET['mode'] ) && sanitize_key( $_GET['mode'] ) === 'latest' ? 'latest' : 'legacy';
425 $tour_id = isset( $_GET['tour_id'] ) ? absint( $_GET['tour_id'] ) : 0;
426
427 update_option( 'wpvr_ui_mode', $mode );
428
429 wp_safe_redirect( $this->get_ui_mode_redirect_url( $mode, $tour_id ) );
430 exit;
431 }
432
433 // -------------------------------------------------------------------------
434 // Step 5 — Set $title global so admin-header.php doesn't get null
435 // -------------------------------------------------------------------------
436
437 public function set_screen_title( \WP_Screen $screen ): void {
438 if ( $screen->id !== 'admin_page_' . self::PAGE_SLUG ) {
439 return;
440 }
441 global $title;
442 $tour_id = isset( $_GET['tour_id'] ) ? (int) $_GET['tour_id'] : 0;
443 $title = $tour_id ? __( 'Edit Tour', 'wpvr' ) : __( 'Add New Tour', 'wpvr' );
444 }
445
446 // -------------------------------------------------------------------------
447 // Helpers
448 // -------------------------------------------------------------------------
449
450 private function editor_url( int $post_id = 0 ): string {
451 $args = [ 'page' => self::PAGE_SLUG ];
452 if ( $post_id > 0 ) {
453 $args['tour_id'] = $post_id;
454 }
455 return add_query_arg( $args, admin_url( 'admin.php' ) );
456 }
457
458 private function is_editor_page( string $hook ): bool {
459 return $hook === self::HOOK_SUFFIX
460 || ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG );
461 }
462
463 private function is_editor_request(): bool {
464 return isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG;
465 }
466
467 private function is_listing_page(): bool {
468 $screen = get_current_screen();
469 return $screen && $screen->id === 'edit-' . self::POST_TYPE;
470 }
471
472 private function get_ui_mode_switch_url( string $mode, int $tour_id = 0 ): string {
473 $args = [
474 'action' => 'wpvr_switch_ui_mode',
475 'mode' => $mode,
476 ];
477
478 if ( $tour_id > 0 ) {
479 $args['tour_id'] = $tour_id;
480 }
481
482 return wp_nonce_url( add_query_arg( $args, admin_url( 'admin-post.php' ) ), 'wpvr_switch_ui_mode' );
483 }
484
485 private function get_ui_mode_redirect_url( string $mode, int $tour_id = 0 ): string {
486 if ( $mode === 'latest' ) {
487 return $this->editor_url( $tour_id );
488 }
489
490 if ( $tour_id > 0 ) {
491 return add_query_arg(
492 [
493 'post' => $tour_id,
494 'action' => 'edit',
495 ],
496 admin_url( 'post.php' )
497 );
498 }
499
500 return admin_url( 'edit.php?post_type=' . self::POST_TYPE );
501 }
502
503 // -------------------------------------------------------------------------
504 // Listing page — Create Tour modal
505 // -------------------------------------------------------------------------
506
507 public function enqueue_listing_assets( string $hook ): void {
508 if ( ! $this->is_listing_page() ) {
509 return;
510 }
511
512 $plugin_url = plugin_dir_url( WPVR_FILE );
513 $style_dependencies = [];
514 $script_dependencies = [];
515
516 if ( $this->editor_enabled ) {
517 wp_enqueue_style(
518 'wpvr-create-tour-modal',
519 $plugin_url . 'app/admin/create-tour-modal.css',
520 [],
521 WPVR_VERSION
522 );
523
524 wp_enqueue_script(
525 'wpvr-create-tour-modal',
526 $plugin_url . 'app/admin/create-tour-modal.js',
527 [],
528 WPVR_VERSION,
529 true
530 );
531
532 wp_localize_script( 'wpvr-create-tour-modal', 'wpvrListingModal', [
533 'nonce' => wp_create_nonce( 'wp_rest' ),
534 'apiBase' => rest_url( 'wpvr/v1' ),
535 'editorUrl' => admin_url( 'admin.php?page=' . self::PAGE_SLUG ),
536 'isPro' => wpvr_is_pro_active(),
537 'iconsUrl' => $plugin_url . 'app/assets/icons/tour-types/',
538 ] );
539
540 $style_dependencies[] = 'wpvr-create-tour-modal';
541 $script_dependencies[] = 'wpvr-create-tour-modal';
542 }
543
544 $tour_listing_css = plugin_dir_path( WPVR_FILE ) . 'app/admin/tour-listing.css';
545 $tour_listing_css_ver = file_exists( $tour_listing_css ) ? (string) filemtime( $tour_listing_css ) : WPVR_VERSION;
546
547 $tour_listing_js = plugin_dir_path( WPVR_FILE ) . 'app/admin/tour-listing.js';
548 $tour_listing_js_ver = file_exists( $tour_listing_js ) ? (string) filemtime( $tour_listing_js ) : WPVR_VERSION;
549
550 wp_enqueue_style(
551 'wpvr-tour-listing',
552 $plugin_url . 'app/admin/tour-listing.css',
553 $style_dependencies,
554 $tour_listing_css_ver
555 );
556
557 wp_enqueue_script(
558 'wpvr-tour-listing',
559 $plugin_url . 'app/admin/tour-listing.js',
560 $script_dependencies,
561 $tour_listing_js_ver,
562 true
563 );
564 }
565
566 public function render_listing_modal(): void {
567 if ( ! $this->editor_enabled || ! $this->is_listing_page() ) {
568 return;
569 }
570
571 $is_pro = apply_filters( 'is_wpvr_pro_active', false ) && get_option( 'wpvr_edd_license_status' ) === 'valid';
572 $icons_url = plugin_dir_url( WPVR_FILE ) . 'app/assets/icons/tour-types/';
573 ?>
574 <div id="wpvr-ctm-overlay" class="wpvr-ctm-overlay" style="display:none;" role="dialog" aria-modal="true" aria-label="Create New Tour">
575 <div class="wpvr-ctm">
576
577 <header class="wpvr-ctm__header">
578 <div>
579 <h2 class="wpvr-ctm__title"><?php esc_html_e( 'Create New Tour', 'wpvr' ); ?></h2>
580 <p class="wpvr-ctm__subtitle"><?php esc_html_e( 'Choose your tour type and get started', 'wpvr' ); ?></p>
581 </div>
582 <button class="wpvr-ctm__close" id="wpvr-ctm-close" type="button" aria-label="<?php esc_attr_e( 'Close', 'wpvr' ); ?>">×</button>
583 </header>
584
585 <div class="wpvr-ctm__body">
586
587 <div class="wpvr-ctm__field">
588 <label for="wpvr-ctm-name" class="wpvr-ctm__label">
589 <?php esc_html_e( 'Tour Name', 'wpvr' ); ?>
590 <span class="wpvr-ctm__required">*</span>
591 </label>
592 <input
593 type="text"
594 id="wpvr-ctm-name"
595 class="wpvr-ctm__input"
596 placeholder="<?php esc_attr_e( 'Enter tour name...', 'wpvr' ); ?>"
597 autocomplete="off"
598 />
599 <span class="wpvr-ctm__error" id="wpvr-ctm-name-error" style="display:none;">
600 <?php esc_html_e( 'Tour name is required.', 'wpvr' ); ?>
601 </span>
602 </div>
603
604 <div class="wpvr-ctm__field">
605 <label class="wpvr-ctm__label">
606 <?php esc_html_e( 'Tour Type', 'wpvr' ); ?>
607 <span class="wpvr-ctm__required">*</span>
608 </label>
609 <div class="wpvr-ctm__cards">
610
611 <?php /* 360° Image — always free, selected by default */ ?>
612 <button type="button" class="wpvr-ctm__card wpvr-ctm__card--selected" data-type="image">
613 <img class="wpvr-ctm__card-icon"
614 src="<?php echo esc_url( $icons_url . 'scene-image-filled.svg' ); ?>"
615 data-active="<?php echo esc_url( $icons_url . 'scene-image-filled.svg' ); ?>"
616 data-inactive="<?php echo esc_url( $icons_url . 'scene-image.svg' ); ?>"
617 alt="" />
618 <span class="wpvr-ctm__card-title"><?php esc_html_e( '360° Image', 'wpvr' ); ?></span>
619 <span class="wpvr-ctm__card-desc"><?php esc_html_e( 'Create immersive panoramic image tours', 'wpvr' ); ?></span>
620 <span class="wpvr-ctm__card-radio wpvr-ctm__card-radio--checked"></span>
621 </button>
622
623 <?php /* 360° Video — free */ ?>
624 <button type="button" class="wpvr-ctm__card" data-type="video">
625 <img class="wpvr-ctm__card-icon"
626 src="<?php echo esc_url( $icons_url . 'scene-video.svg' ); ?>"
627 data-active="<?php echo esc_url( $icons_url . 'scene-video-filled.svg' ); ?>"
628 data-inactive="<?php echo esc_url( $icons_url . 'scene-video.svg' ); ?>"
629 alt="" />
630 <span class="wpvr-ctm__card-title"><?php esc_html_e( '360° Video', 'wpvr' ); ?></span>
631 <span class="wpvr-ctm__card-desc"><?php esc_html_e( 'Build dynamic video-based experiences', 'wpvr' ); ?></span>
632 <span class="wpvr-ctm__card-radio"></span>
633 </button>
634
635 <?php /* Street View — PRO only */ ?>
636 <button type="button" class="wpvr-ctm__card<?php echo $is_pro ? '' : ' wpvr-ctm__card--disabled'; ?>"
637 data-type="street-view"
638 <?php echo $is_pro ? '' : 'disabled'; ?>
639 title="<?php echo $is_pro ? '' : esc_attr__( 'Requires WP VR Pro', 'wpvr' ); ?>">
640 <?php if ( ! $is_pro ) : ?>
641 <span class="is-pro" aria-hidden="true">Pro</span>
642 <?php endif; ?>
643 <img class="wpvr-ctm__card-icon"
644 src="<?php echo esc_url( $icons_url . 'scene-street.svg' ); ?>"
645 data-active="<?php echo esc_url( $icons_url . 'scene-street-filled.svg' ); ?>"
646 data-inactive="<?php echo esc_url( $icons_url . 'scene-street.svg' ); ?>"
647 alt="" />
648 <span class="wpvr-ctm__card-title"><?php esc_html_e( 'Street View', 'wpvr' ); ?></span>
649 <span class="wpvr-ctm__card-desc"><?php esc_html_e( 'Integrate Google Street View locations', 'wpvr' ); ?></span>
650 <span class="wpvr-ctm__card-radio"></span>
651 </button>
652
653 </div>
654 </div>
655
656 </div>
657
658 <div class="wpvr-ctm__footer">
659 <button type="button" class="wpvr-ctm__btn wpvr-ctm__btn--secondary" id="wpvr-ctm-cancel">
660 <?php esc_html_e( 'Cancel', 'wpvr' ); ?>
661 </button>
662 <button type="button" class="wpvr-ctm__btn wpvr-ctm__btn--primary" id="wpvr-ctm-submit">
663 <?php esc_html_e( '+ Create Tour', 'wpvr' ); ?>
664 </button>
665 </div>
666
667 </div>
668 </div>
669 <?php
670 }
671 }
672