PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 9.1.2
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v9.1.2
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 8.5.4 All 221 releases
wpvr / src / Admin / Admin.php

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

618 lines 26.3 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 $is_embed_addon_active = apply_filters( 'is_wpvr_embed_addon_premium', false );
221 if ( $is_embed_addon_active ) {
222 if ( wp_script_is( 'custom-qrcode', 'registered' ) ) {
223 wp_enqueue_script( 'custom-qrcode' );
224 } elseif ( defined( 'WPVR_PRO_PLUGIN_DIR_URL' ) ) {
225 wp_enqueue_script( 'custom-qrcode', trailingslashit( WPVR_PRO_PLUGIN_DIR_URL ) . 'admin/lib/qr-code/custom-qrcode.js', [ 'jquery' ], null, true );
226 }
227 }
228
229 wp_localize_script( 'wpvr-tour-editor', 'wpvrTourEditor', [
230 'tourId' => $tour_id_cfg ?: null,
231 'tourStatus' => $tour_status,
232 'tourTitle' => $tour_id_cfg ? get_the_title( $tour_id_cfg ) : '',
233 'homeUrl' => home_url( '/' ),
234 'isEmbedAddonActive' => $is_embed_addon_active,
235 'nonce' => wp_create_nonce( 'wp_rest' ),
236 'apiBase' => rest_url( 'wpvr/v1' ),
237 'mediaUrl' => rest_url( 'wp/v2/media' ),
238 'isPro' => wpvr_is_pro_active(),
239 'pluginUrl' => $plugin_url,
240 'listUrl' => admin_url( 'edit.php?post_type=' . self::POST_TYPE ),
241 'postEditUrl' => admin_url( 'post.php' ),
242 'faIcons' => $fa_icons,
243 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
244 'exportNonce' => wp_create_nonce( 'wpvr_export_tour' ),
245 'imageResizeWarning' => [
246 'ajaxNonce' => wp_create_nonce( 'wpvr' ),
247 'canManageSettings' => current_user_can( 'manage_options' ),
248 'settingEnabled' => get_option( 'high_res_image' ) === 'true',
249 'heading' => __( 'Keep this panorama in High Resolution', 'wpvr' ),
250 'scaledMessage' => __( 'By default, a resized version is created while the original image is still available.', 'wpvr' ),
251 'disableHandler' => __( 'Disable WordPress Large Image Handler on WP VR for future uploads', 'wpvr' ),
252 'highResolutionNote'=> __( '*Note: Turn this on to use the high-resolution image.', 'wpvr' ),
253 'close' => __( 'Close', 'wpvr' ),
254 ],
255 ] );
256
257 if ( file_exists( $css_file ) ) {
258 wp_enqueue_style(
259 'wpvr-tour-editor',
260 $plugin_url . 'build/tour-editor/style-index.css',
261 [ 'wpvr-pannellum-css' ],
262 $asset['version']
263 );
264 }
265 }
266
267 public function filter_admin_title( string $admin_title, string $title ): string {
268 if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== self::PAGE_SLUG ) {
269 return $admin_title;
270 }
271 $tour_id = isset( $_GET['tour_id'] ) ? (int) $_GET['tour_id'] : 0;
272 $page_title = $tour_id ? __( 'Edit Tour', 'wpvr' ) : __( 'Add New Tour', 'wpvr' );
273 return $page_title . ' &lsaquo; ' . get_bloginfo( 'name' ) . ' &#8212; WordPress';
274 }
275
276 public function add_body_class( string $classes ): string {
277 if ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG ) {
278 $classes .= ' wpvr-tour-editor-page';
279 }
280 return $classes;
281 }
282
283 public function set_menu_parent( string $parent_file ): string {
284 if ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG ) {
285 return 'edit.php?post_type=' . self::POST_TYPE;
286 }
287 return $parent_file;
288 }
289
290 public function set_menu_submenu( ?string $submenu_file ): ?string {
291 if ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG ) {
292 return 'post-new.php?post_type=' . self::POST_TYPE;
293 }
294 return $submenu_file;
295 }
296
297 public function editor_head_scripts(): void {
298 if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== self::PAGE_SLUG ) {
299 return;
300 }
301 ?>
302 <script>
303 ( function() {
304 var narrowEditor = window.matchMedia( '(max-width: 1279px)' );
305
306 function applyEditorMenuState() {
307 if ( ! document.body ) {
308 window.requestAnimationFrame( applyEditorMenuState );
309 return;
310 }
311
312 document.body.classList.toggle( 'folded', narrowEditor.matches );
313 }
314
315 applyEditorMenuState();
316
317 if ( narrowEditor.addEventListener ) {
318 narrowEditor.addEventListener( 'change', applyEditorMenuState );
319 } else {
320 narrowEditor.addListener( applyEditorMenuState );
321 }
322 }() );
323 </script>
324 <?php
325 }
326
327 public function editor_footer_scripts(): void {
328 if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== self::PAGE_SLUG ) {
329 return;
330 }
331 ?>
332 <script>
333 jQuery( window ).on( 'load', function() {
334 // Intercept WP sidebar toggle so it doesn't persist state to user meta.
335 // The editor's initial state is viewport-dependent; manual toggling should
336 // work visually but must not overwrite the user's real preference.
337 jQuery( '#collapse-button' ).off( 'click' ).on( 'click', function() {
338 jQuery( 'body' ).toggleClass( 'folded' );
339 } );
340 } );
341 </script>
342 <?php
343 }
344
345 public function render_mode_switch_notice(): void {
346 if ( ! current_user_can( 'edit_posts' ) ) {
347 return;
348 }
349
350 if ( ! $this->is_listing_page() && ! $this->is_editor_request() ) {
351 return;
352 }
353
354 $tour_id = isset( $_GET['tour_id'] ) ? absint( $_GET['tour_id'] ) : 0;
355 $switch_url = $this->get_ui_mode_switch_url( 'legacy', $tour_id );
356 $button_label = __( 'Switch to Classic UI', 'wpvr' );
357 ?>
358 <div class="notice notice-info">
359 <p>
360 <?php esc_html_e( 'You are using the latest WP VR editor.', 'wpvr' ); ?>
361 <a class="button button-secondary" href="<?php echo esc_url( $switch_url ); ?>" style="margin-left:8px;">
362 <?php echo esc_html( $button_label ); ?>
363 </a>
364 </p>
365 </div>
366 <?php
367 }
368
369 public function handle_ui_mode_switch(): void {
370 if ( ! current_user_can( 'edit_posts' ) ) {
371 wp_die( esc_html__( 'You are not allowed to switch UI modes.', 'wpvr' ) );
372 }
373
374 check_admin_referer( 'wpvr_switch_ui_mode' );
375
376 $mode = isset( $_GET['mode'] ) && sanitize_key( $_GET['mode'] ) === 'latest' ? 'latest' : 'legacy';
377 $tour_id = isset( $_GET['tour_id'] ) ? absint( $_GET['tour_id'] ) : 0;
378
379 update_option( 'wpvr_ui_mode', $mode );
380
381 wp_safe_redirect( $this->get_ui_mode_redirect_url( $mode, $tour_id ) );
382 exit;
383 }
384
385 // -------------------------------------------------------------------------
386 // Step 5 — Set $title global so admin-header.php doesn't get null
387 // -------------------------------------------------------------------------
388
389 public function set_screen_title( \WP_Screen $screen ): void {
390 if ( $screen->id !== 'admin_page_' . self::PAGE_SLUG ) {
391 return;
392 }
393 global $title;
394 $tour_id = isset( $_GET['tour_id'] ) ? (int) $_GET['tour_id'] : 0;
395 $title = $tour_id ? __( 'Edit Tour', 'wpvr' ) : __( 'Add New Tour', 'wpvr' );
396 }
397
398 // -------------------------------------------------------------------------
399 // Helpers
400 // -------------------------------------------------------------------------
401
402 private function editor_url( int $post_id = 0 ): string {
403 $args = [ 'page' => self::PAGE_SLUG ];
404 if ( $post_id > 0 ) {
405 $args['tour_id'] = $post_id;
406 }
407 return add_query_arg( $args, admin_url( 'admin.php' ) );
408 }
409
410 private function is_editor_page( string $hook ): bool {
411 return $hook === self::HOOK_SUFFIX
412 || ( isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG );
413 }
414
415 private function is_editor_request(): bool {
416 return isset( $_GET['page'] ) && sanitize_key( $_GET['page'] ) === self::PAGE_SLUG;
417 }
418
419 private function is_listing_page(): bool {
420 $screen = get_current_screen();
421 return $screen && $screen->id === 'edit-' . self::POST_TYPE;
422 }
423
424 private function get_ui_mode_switch_url( string $mode, int $tour_id = 0 ): string {
425 $args = [
426 'action' => 'wpvr_switch_ui_mode',
427 'mode' => $mode,
428 ];
429
430 if ( $tour_id > 0 ) {
431 $args['tour_id'] = $tour_id;
432 }
433
434 return wp_nonce_url( add_query_arg( $args, admin_url( 'admin-post.php' ) ), 'wpvr_switch_ui_mode' );
435 }
436
437 private function get_ui_mode_redirect_url( string $mode, int $tour_id = 0 ): string {
438 if ( $mode === 'latest' ) {
439 return $this->editor_url( $tour_id );
440 }
441
442 if ( $tour_id > 0 ) {
443 return add_query_arg(
444 [
445 'post' => $tour_id,
446 'action' => 'edit',
447 ],
448 admin_url( 'post.php' )
449 );
450 }
451
452 return admin_url( 'edit.php?post_type=' . self::POST_TYPE );
453 }
454
455 // -------------------------------------------------------------------------
456 // Listing page — Create Tour modal
457 // -------------------------------------------------------------------------
458
459 public function enqueue_listing_assets( string $hook ): void {
460 if ( ! $this->is_listing_page() ) {
461 return;
462 }
463
464 $plugin_url = plugin_dir_url( WPVR_FILE );
465 $style_dependencies = [];
466 $script_dependencies = [];
467
468 if ( $this->editor_enabled ) {
469 wp_enqueue_style(
470 'wpvr-create-tour-modal',
471 $plugin_url . 'app/admin/create-tour-modal.css',
472 [],
473 WPVR_VERSION
474 );
475
476 wp_enqueue_script(
477 'wpvr-create-tour-modal',
478 $plugin_url . 'app/admin/create-tour-modal.js',
479 [],
480 WPVR_VERSION,
481 true
482 );
483
484 wp_localize_script( 'wpvr-create-tour-modal', 'wpvrListingModal', [
485 'nonce' => wp_create_nonce( 'wp_rest' ),
486 'apiBase' => rest_url( 'wpvr/v1' ),
487 'editorUrl' => admin_url( 'admin.php?page=' . self::PAGE_SLUG ),
488 'isPro' => wpvr_is_pro_active(),
489 'iconsUrl' => $plugin_url . 'app/assets/icons/tour-types/',
490 ] );
491
492 $style_dependencies[] = 'wpvr-create-tour-modal';
493 $script_dependencies[] = 'wpvr-create-tour-modal';
494 }
495
496 wp_enqueue_style(
497 'wpvr-tour-listing',
498 $plugin_url . 'app/admin/tour-listing.css',
499 $style_dependencies,
500 WPVR_VERSION
501 );
502
503 wp_enqueue_script(
504 'wpvr-tour-listing',
505 $plugin_url . 'app/admin/tour-listing.js',
506 $script_dependencies,
507 WPVR_VERSION,
508 true
509 );
510 }
511
512 public function render_listing_modal(): void {
513 if ( ! $this->editor_enabled || ! $this->is_listing_page() ) {
514 return;
515 }
516
517 $is_pro = apply_filters( 'is_wpvr_pro_active', false ) && get_option( 'wpvr_edd_license_status' ) === 'valid';
518 $icons_url = plugin_dir_url( WPVR_FILE ) . 'app/assets/icons/tour-types/';
519 ?>
520 <div id="wpvr-ctm-overlay" class="wpvr-ctm-overlay" style="display:none;" role="dialog" aria-modal="true" aria-label="Create New Tour">
521 <div class="wpvr-ctm">
522
523 <header class="wpvr-ctm__header">
524 <div>
525 <h2 class="wpvr-ctm__title"><?php esc_html_e( 'Create New Tour', 'wpvr' ); ?></h2>
526 <p class="wpvr-ctm__subtitle"><?php esc_html_e( 'Choose your tour type and get started', 'wpvr' ); ?></p>
527 </div>
528 <button class="wpvr-ctm__close" id="wpvr-ctm-close" type="button" aria-label="<?php esc_attr_e( 'Close', 'wpvr' ); ?>">×</button>
529 </header>
530
531 <div class="wpvr-ctm__body">
532
533 <div class="wpvr-ctm__field">
534 <label for="wpvr-ctm-name" class="wpvr-ctm__label">
535 <?php esc_html_e( 'Tour Name', 'wpvr' ); ?>
536 <span class="wpvr-ctm__required">*</span>
537 </label>
538 <input
539 type="text"
540 id="wpvr-ctm-name"
541 class="wpvr-ctm__input"
542 placeholder="<?php esc_attr_e( 'Enter tour name...', 'wpvr' ); ?>"
543 autocomplete="off"
544 />
545 <span class="wpvr-ctm__error" id="wpvr-ctm-name-error" style="display:none;">
546 <?php esc_html_e( 'Tour name is required.', 'wpvr' ); ?>
547 </span>
548 </div>
549
550 <div class="wpvr-ctm__field">
551 <label class="wpvr-ctm__label">
552 <?php esc_html_e( 'Tour Type', 'wpvr' ); ?>
553 <span class="wpvr-ctm__required">*</span>
554 </label>
555 <div class="wpvr-ctm__cards">
556
557 <?php /* 360° Image — always free, selected by default */ ?>
558 <button type="button" class="wpvr-ctm__card wpvr-ctm__card--selected" data-type="image">
559 <img class="wpvr-ctm__card-icon"
560 src="<?php echo esc_url( $icons_url . 'scene-image-filled.svg' ); ?>"
561 data-active="<?php echo esc_url( $icons_url . 'scene-image-filled.svg' ); ?>"
562 data-inactive="<?php echo esc_url( $icons_url . 'scene-image.svg' ); ?>"
563 alt="" />
564 <span class="wpvr-ctm__card-title"><?php esc_html_e( '360° Image', 'wpvr' ); ?></span>
565 <span class="wpvr-ctm__card-desc"><?php esc_html_e( 'Create immersive panoramic image tours', 'wpvr' ); ?></span>
566 <span class="wpvr-ctm__card-radio wpvr-ctm__card-radio--checked"></span>
567 </button>
568
569 <?php /* 360° Video — free */ ?>
570 <button type="button" class="wpvr-ctm__card" data-type="video">
571 <img class="wpvr-ctm__card-icon"
572 src="<?php echo esc_url( $icons_url . 'scene-video.svg' ); ?>"
573 data-active="<?php echo esc_url( $icons_url . 'scene-video-filled.svg' ); ?>"
574 data-inactive="<?php echo esc_url( $icons_url . 'scene-video.svg' ); ?>"
575 alt="" />
576 <span class="wpvr-ctm__card-title"><?php esc_html_e( '360° Video', 'wpvr' ); ?></span>
577 <span class="wpvr-ctm__card-desc"><?php esc_html_e( 'Build dynamic video-based experiences', 'wpvr' ); ?></span>
578 <span class="wpvr-ctm__card-radio"></span>
579 </button>
580
581 <?php /* Street View — PRO only */ ?>
582 <button type="button" class="wpvr-ctm__card<?php echo $is_pro ? '' : ' wpvr-ctm__card--disabled'; ?>"
583 data-type="street-view"
584 <?php echo $is_pro ? '' : 'disabled'; ?>
585 title="<?php echo $is_pro ? '' : esc_attr__( 'Requires WP VR Pro', 'wpvr' ); ?>">
586 <?php if ( ! $is_pro ) : ?>
587 <span class="is-pro" aria-hidden="true">Pro</span>
588 <?php endif; ?>
589 <img class="wpvr-ctm__card-icon"
590 src="<?php echo esc_url( $icons_url . 'scene-street.svg' ); ?>"
591 data-active="<?php echo esc_url( $icons_url . 'scene-street-filled.svg' ); ?>"
592 data-inactive="<?php echo esc_url( $icons_url . 'scene-street.svg' ); ?>"
593 alt="" />
594 <span class="wpvr-ctm__card-title"><?php esc_html_e( 'Street View', 'wpvr' ); ?></span>
595 <span class="wpvr-ctm__card-desc"><?php esc_html_e( 'Integrate Google Street View locations', 'wpvr' ); ?></span>
596 <span class="wpvr-ctm__card-radio"></span>
597 </button>
598
599 </div>
600 </div>
601
602 </div>
603
604 <div class="wpvr-ctm__footer">
605 <button type="button" class="wpvr-ctm__btn wpvr-ctm__btn--secondary" id="wpvr-ctm-cancel">
606 <?php esc_html_e( 'Cancel', 'wpvr' ); ?>
607 </button>
608 <button type="button" class="wpvr-ctm__btn wpvr-ctm__btn--primary" id="wpvr-ctm-submit">
609 <?php esc_html_e( '+ Create Tour', 'wpvr' ); ?>
610 </button>
611 </div>
612
613 </div>
614 </div>
615 <?php
616 }
617 }
618