PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 9.2.0
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v9.2.0
9.2.0 9.1.4 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 All 224 releases
wpvr / src / Admin / Admin.php

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

676 lines 28.9 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 $inter_font_path = $plugin_path . 'legacy/public/css/inter.css';
306 $inter_font_ver = file_exists( $inter_font_path ) ? filemtime( $inter_font_path ) : WPVR_VERSION;
307 wp_enqueue_style( 'wpvr-inter-font', $public_base . 'css/inter.css', [], $inter_font_ver, 'all' );
308
309 if ( file_exists( $css_file ) ) {
310 wp_enqueue_style(
311 'wpvr-tour-editor',
312 $plugin_url . 'build/tour-editor/style-index.css',
313 [ 'wpvr-pannellum-css', 'wpvr-inter-font' ],
314 $asset['version']
315 );
316 }
317 }
318
319 public function filter_admin_title( string $admin_title, string $title ): string {
320 if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== self::PAGE_SLUG ) {
321 return $admin_title;
322 }
323 $tour_id = isset( $_GET['tour_id'] ) ? (int) $_GET['tour_id'] : 0;
324 $page_title = $tour_id ? __( 'Edit Tour', 'wpvr' ) : __( 'Add New Tour', 'wpvr' );
325 return $page_title . ' &lsaquo; ' . get_bloginfo( 'name' ) . ' &#8212; WordPress';
326 }
327
328 public function add_body_class( string $classes ): string {
329 if ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG ) {
330 $classes .= ' wpvr-tour-editor-page';
331 }
332 return $classes;
333 }
334
335 public function set_menu_parent( string $parent_file ): string {
336 if ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG ) {
337 return 'edit.php?post_type=' . self::POST_TYPE;
338 }
339 return $parent_file;
340 }
341
342 public function set_menu_submenu( ?string $submenu_file ): ?string {
343 if ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG ) {
344 return 'post-new.php?post_type=' . self::POST_TYPE;
345 }
346 return $submenu_file;
347 }
348
349 public function editor_head_scripts(): void {
350 if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== self::PAGE_SLUG ) {
351 return;
352 }
353 ?>
354 <script>
355 ( function() {
356 var narrowEditor = window.matchMedia( '(max-width: 1279px)' );
357
358 function applyEditorMenuState() {
359 if ( ! document.body ) {
360 window.requestAnimationFrame( applyEditorMenuState );
361 return;
362 }
363
364 document.body.classList.toggle( 'folded', narrowEditor.matches );
365 }
366
367 applyEditorMenuState();
368
369 if ( narrowEditor.addEventListener ) {
370 narrowEditor.addEventListener( 'change', applyEditorMenuState );
371 } else {
372 narrowEditor.addListener( applyEditorMenuState );
373 }
374 }() );
375 </script>
376 <?php
377 }
378
379 public function editor_footer_scripts(): void {
380 if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== self::PAGE_SLUG ) {
381 return;
382 }
383 ?>
384 <script>
385 jQuery( window ).on( 'load', function() {
386 // Intercept WP sidebar toggle so it doesn't persist state to user meta.
387 // The editor's initial state is viewport-dependent; manual toggling should
388 // work visually but must not overwrite the user's real preference.
389 jQuery( '#collapse-button' ).off( 'click' ).on( 'click', function() {
390 jQuery( 'body' ).toggleClass( 'folded' );
391 } );
392 } );
393 </script>
394 <?php
395 }
396
397 public function render_mode_switch_notice(): void {
398 if ( ! current_user_can( 'edit_posts' ) ) {
399 return;
400 }
401
402 if ( ! $this->is_listing_page() && ! $this->is_editor_request() ) {
403 return;
404 }
405
406 $tour_id = isset( $_GET['tour_id'] ) ? absint( $_GET['tour_id'] ) : 0;
407 $switch_url = $this->get_ui_mode_switch_url( 'legacy', $tour_id );
408 $button_label = __( 'Switch to Classic UI', 'wpvr' );
409 ?>
410 <div class="notice notice-info">
411 <p>
412 <?php esc_html_e( 'You are using the latest WP VR editor.', 'wpvr' ); ?>
413 <a class="button button-secondary" href="<?php echo esc_url( $switch_url ); ?>" style="margin-left:8px;">
414 <?php echo esc_html( $button_label ); ?>
415 </a>
416 </p>
417 </div>
418 <?php
419 }
420
421 public function handle_ui_mode_switch(): void {
422 if ( ! current_user_can( 'edit_posts' ) ) {
423 wp_die( esc_html__( 'You are not allowed to switch UI modes.', 'wpvr' ) );
424 }
425
426 check_admin_referer( 'wpvr_switch_ui_mode' );
427
428 $mode = isset( $_GET['mode'] ) && sanitize_key( $_GET['mode'] ) === 'latest' ? 'latest' : 'legacy';
429 $tour_id = isset( $_GET['tour_id'] ) ? absint( $_GET['tour_id'] ) : 0;
430
431 update_option( 'wpvr_ui_mode', $mode );
432
433 wp_safe_redirect( $this->get_ui_mode_redirect_url( $mode, $tour_id ) );
434 exit;
435 }
436
437 // -------------------------------------------------------------------------
438 // Step 5 — Set $title global so admin-header.php doesn't get null
439 // -------------------------------------------------------------------------
440
441 public function set_screen_title( \WP_Screen $screen ): void {
442 if ( $screen->id !== 'admin_page_' . self::PAGE_SLUG ) {
443 return;
444 }
445 global $title;
446 $tour_id = isset( $_GET['tour_id'] ) ? (int) $_GET['tour_id'] : 0;
447 $title = $tour_id ? __( 'Edit Tour', 'wpvr' ) : __( 'Add New Tour', 'wpvr' );
448 }
449
450 // -------------------------------------------------------------------------
451 // Helpers
452 // -------------------------------------------------------------------------
453
454 private function editor_url( int $post_id = 0 ): string {
455 $args = [ 'page' => self::PAGE_SLUG ];
456 if ( $post_id > 0 ) {
457 $args['tour_id'] = $post_id;
458 }
459 return add_query_arg( $args, admin_url( 'admin.php' ) );
460 }
461
462 private function is_editor_page( string $hook ): bool {
463 return $hook === self::HOOK_SUFFIX
464 || ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG );
465 }
466
467 private function is_editor_request(): bool {
468 return isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG;
469 }
470
471 private function is_listing_page(): bool {
472 $screen = get_current_screen();
473 return $screen && $screen->id === 'edit-' . self::POST_TYPE;
474 }
475
476 private function get_ui_mode_switch_url( string $mode, int $tour_id = 0 ): string {
477 $args = [
478 'action' => 'wpvr_switch_ui_mode',
479 'mode' => $mode,
480 ];
481
482 if ( $tour_id > 0 ) {
483 $args['tour_id'] = $tour_id;
484 }
485
486 return wp_nonce_url( add_query_arg( $args, admin_url( 'admin-post.php' ) ), 'wpvr_switch_ui_mode' );
487 }
488
489 private function get_ui_mode_redirect_url( string $mode, int $tour_id = 0 ): string {
490 if ( $mode === 'latest' ) {
491 return $this->editor_url( $tour_id );
492 }
493
494 if ( $tour_id > 0 ) {
495 return add_query_arg(
496 [
497 'post' => $tour_id,
498 'action' => 'edit',
499 ],
500 admin_url( 'post.php' )
501 );
502 }
503
504 return admin_url( 'edit.php?post_type=' . self::POST_TYPE );
505 }
506
507 // -------------------------------------------------------------------------
508 // Listing page — Create Tour modal
509 // -------------------------------------------------------------------------
510
511 public function enqueue_listing_assets( string $hook ): void {
512 if ( ! $this->is_listing_page() ) {
513 return;
514 }
515
516 $plugin_url = plugin_dir_url( WPVR_FILE );
517 $style_dependencies = [];
518 $script_dependencies = [];
519
520 if ( $this->editor_enabled ) {
521 wp_enqueue_style(
522 'wpvr-create-tour-modal',
523 $plugin_url . 'app/admin/create-tour-modal.css',
524 [],
525 WPVR_VERSION
526 );
527
528 wp_enqueue_script(
529 'wpvr-create-tour-modal',
530 $plugin_url . 'app/admin/create-tour-modal.js',
531 [],
532 WPVR_VERSION,
533 true
534 );
535
536 wp_localize_script( 'wpvr-create-tour-modal', 'wpvrListingModal', [
537 'nonce' => wp_create_nonce( 'wp_rest' ),
538 'apiBase' => rest_url( 'wpvr/v1' ),
539 'editorUrl' => admin_url( 'admin.php?page=' . self::PAGE_SLUG ),
540 'isPro' => wpvr_is_pro_active(),
541 'iconsUrl' => $plugin_url . 'app/assets/icons/tour-types/',
542 ] );
543
544 $style_dependencies[] = 'wpvr-create-tour-modal';
545 $script_dependencies[] = 'wpvr-create-tour-modal';
546 }
547
548 $tour_listing_css = plugin_dir_path( WPVR_FILE ) . 'app/admin/tour-listing.css';
549 $tour_listing_css_ver = file_exists( $tour_listing_css ) ? (string) filemtime( $tour_listing_css ) : WPVR_VERSION;
550
551 $tour_listing_js = plugin_dir_path( WPVR_FILE ) . 'app/admin/tour-listing.js';
552 $tour_listing_js_ver = file_exists( $tour_listing_js ) ? (string) filemtime( $tour_listing_js ) : WPVR_VERSION;
553
554 wp_enqueue_style(
555 'wpvr-tour-listing',
556 $plugin_url . 'app/admin/tour-listing.css',
557 $style_dependencies,
558 $tour_listing_css_ver
559 );
560
561 wp_enqueue_script(
562 'wpvr-tour-listing',
563 $plugin_url . 'app/admin/tour-listing.js',
564 $script_dependencies,
565 $tour_listing_js_ver,
566 true
567 );
568 }
569
570 public function render_listing_modal(): void {
571 if ( ! $this->editor_enabled || ! $this->is_listing_page() ) {
572 return;
573 }
574
575 $is_pro = apply_filters( 'is_wpvr_pro_active', false ) && get_option( 'wpvr_edd_license_status' ) === 'valid';
576 $icons_url = plugin_dir_url( WPVR_FILE ) . 'app/assets/icons/tour-types/';
577 ?>
578 <div id="wpvr-ctm-overlay" class="wpvr-ctm-overlay" style="display:none;" role="dialog" aria-modal="true" aria-label="Create New Tour">
579 <div class="wpvr-ctm">
580
581 <header class="wpvr-ctm__header">
582 <div>
583 <h2 class="wpvr-ctm__title"><?php esc_html_e( 'Create New Tour', 'wpvr' ); ?></h2>
584 <p class="wpvr-ctm__subtitle"><?php esc_html_e( 'Choose your tour type and get started', 'wpvr' ); ?></p>
585 </div>
586 <button class="wpvr-ctm__close" id="wpvr-ctm-close" type="button" aria-label="<?php esc_attr_e( 'Close', 'wpvr' ); ?>">×</button>
587 </header>
588
589 <div class="wpvr-ctm__body">
590
591 <div class="wpvr-ctm__field">
592 <label for="wpvr-ctm-name" class="wpvr-ctm__label">
593 <?php esc_html_e( 'Tour Name', 'wpvr' ); ?>
594 <span class="wpvr-ctm__required">*</span>
595 </label>
596 <input
597 type="text"
598 id="wpvr-ctm-name"
599 class="wpvr-ctm__input"
600 placeholder="<?php esc_attr_e( 'Enter tour name...', 'wpvr' ); ?>"
601 autocomplete="off"
602 />
603 <span class="wpvr-ctm__error" id="wpvr-ctm-name-error" style="display:none;">
604 <?php esc_html_e( 'Tour name is required.', 'wpvr' ); ?>
605 </span>
606 </div>
607
608 <div class="wpvr-ctm__field">
609 <label class="wpvr-ctm__label">
610 <?php esc_html_e( 'Tour Type', 'wpvr' ); ?>
611 <span class="wpvr-ctm__required">*</span>
612 </label>
613 <div class="wpvr-ctm__cards">
614
615 <?php /* 360° Image — always free, selected by default */ ?>
616 <button type="button" class="wpvr-ctm__card wpvr-ctm__card--selected" data-type="image">
617 <img class="wpvr-ctm__card-icon"
618 src="<?php echo esc_url( $icons_url . 'scene-image-filled.svg' ); ?>"
619 data-active="<?php echo esc_url( $icons_url . 'scene-image-filled.svg' ); ?>"
620 data-inactive="<?php echo esc_url( $icons_url . 'scene-image.svg' ); ?>"
621 alt="" />
622 <span class="wpvr-ctm__card-title"><?php esc_html_e( '360° Image', 'wpvr' ); ?></span>
623 <span class="wpvr-ctm__card-desc"><?php esc_html_e( 'Create immersive panoramic image tours', 'wpvr' ); ?></span>
624 <span class="wpvr-ctm__card-radio wpvr-ctm__card-radio--checked"></span>
625 </button>
626
627 <?php /* 360° Video — free */ ?>
628 <button type="button" class="wpvr-ctm__card" data-type="video">
629 <img class="wpvr-ctm__card-icon"
630 src="<?php echo esc_url( $icons_url . 'scene-video.svg' ); ?>"
631 data-active="<?php echo esc_url( $icons_url . 'scene-video-filled.svg' ); ?>"
632 data-inactive="<?php echo esc_url( $icons_url . 'scene-video.svg' ); ?>"
633 alt="" />
634 <span class="wpvr-ctm__card-title"><?php esc_html_e( '360° Video', 'wpvr' ); ?></span>
635 <span class="wpvr-ctm__card-desc"><?php esc_html_e( 'Build dynamic video-based experiences', 'wpvr' ); ?></span>
636 <span class="wpvr-ctm__card-radio"></span>
637 </button>
638
639 <?php /* Street View — PRO only */ ?>
640 <button type="button" class="wpvr-ctm__card<?php echo $is_pro ? '' : ' wpvr-ctm__card--disabled'; ?>"
641 data-type="street-view"
642 <?php echo $is_pro ? '' : 'disabled'; ?>
643 title="<?php echo $is_pro ? '' : esc_attr__( 'Requires WP VR Pro', 'wpvr' ); ?>">
644 <?php if ( ! $is_pro ) : ?>
645 <span class="is-pro" aria-hidden="true">Pro</span>
646 <?php endif; ?>
647 <img class="wpvr-ctm__card-icon"
648 src="<?php echo esc_url( $icons_url . 'scene-street.svg' ); ?>"
649 data-active="<?php echo esc_url( $icons_url . 'scene-street-filled.svg' ); ?>"
650 data-inactive="<?php echo esc_url( $icons_url . 'scene-street.svg' ); ?>"
651 alt="" />
652 <span class="wpvr-ctm__card-title"><?php esc_html_e( 'Street View', 'wpvr' ); ?></span>
653 <span class="wpvr-ctm__card-desc"><?php esc_html_e( 'Integrate Google Street View locations', 'wpvr' ); ?></span>
654 <span class="wpvr-ctm__card-radio"></span>
655 </button>
656
657 </div>
658 </div>
659
660 </div>
661
662 <div class="wpvr-ctm__footer">
663 <button type="button" class="wpvr-ctm__btn wpvr-ctm__btn--secondary" id="wpvr-ctm-cancel">
664 <?php esc_html_e( 'Cancel', 'wpvr' ); ?>
665 </button>
666 <button type="button" class="wpvr-ctm__btn wpvr-ctm__btn--primary" id="wpvr-ctm-submit">
667 <?php esc_html_e( '+ Create Tour', 'wpvr' ); ?>
668 </button>
669 </div>
670
671 </div>
672 </div>
673 <?php
674 }
675 }
676