PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 9.1.1
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v9.1.1
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.1, at src/Admin/Admin.php

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