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 / legacy / wpvr.php

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

1,363 lines 51.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // Legacy bootstrap for WP VR plugin
3 // This file is a direct copy of the original wpvr.php before architecture migration.
4 // Do not edit legacy logic here; only update if the legacy code itself changes.
5
6 if (!defined('WPINC')) {
7 die;
8 }
9
10 require WPVR_PLUGIN_LEGACY_DIR_PATH . 'elementor/elementor.php';
11 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'breakdance/breakdance.php';
12 if ( file_exists( dirname( WPVR_PLUGIN_LEGACY_DIR_PATH ) . '/vendor/autoload.php' ) ) {
13 require_once dirname( WPVR_PLUGIN_LEGACY_DIR_PATH ) . '/vendor/autoload.php';
14 } elseif ( file_exists( WPVR_PLUGIN_LEGACY_DIR_PATH . 'vendor/autoload.php' ) ) {
15 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'vendor/autoload.php';
16 }
17
18 if ( wp_get_theme('bricks')->exists() && 'bricks' === get_template()) {
19 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'bricks/bricks.php';
20 }
21
22
23 /**
24 * Currently plugin version.
25 * Start at version 1.0.0 and use SemVer - https://semver.org
26 * Rename this for your plugin and update it as you release new versions.
27 */
28 define( 'WPVR_WEBHOOK_URL', sanitize_url( 'https://rextheme.com/?mailmint=1&route=webhook&topic=contact&hash=bbd19901-6d42-4ae5-a7a8-01eb1013c553' ) );
29 if ( ! defined( 'WPVR_TELEMETRY_API_KEY' ) ) {
30 define( 'WPVR_TELEMETRY_API_KEY', 'phc_amk3VQz1P5ZqZOMRRoWM1B41QMFpf3hu8pg7yRfzSXW' );
31 }
32 if ( ! defined( 'WPVR_TELEMETRY_API_SECRET' ) ) {
33 define( 'WPVR_TELEMETRY_API_SECRET', 'sec_dfb2ab8e84391ae7a0f9' );
34 }
35 if ( ! defined( 'WPVR_TELEMETRY_HOST' ) ) {
36 define( 'WPVR_TELEMETRY_HOST', 'https://eu.i.posthog.com' );
37 }
38
39
40 if ( ! function_exists( 'activate_wpvr' ) ) {
41 /**
42 * The code that runs during plugin activation.
43 * This action is documented in includes/class-wpvr-activator.php
44 */
45 function activate_wpvr()
46 {
47 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'includes/class-wpvr-activator.php';
48 Wpvr_Activator::activate();
49
50 // Trigger plugin activation tracking
51 do_action( 'wpvr_plugin_activated' );
52 }
53 }
54
55 if ( ! function_exists( 'deactivate_wpvr' ) ) {
56 /**
57 * The code that runs during plugin deactivation.
58 * This action is documented in includes/class-wpvr-deactivator.php
59 */
60 function deactivate_wpvr()
61 {
62 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'includes/class-wpvr-deactivator.php';
63 Wpvr_Deactivator::deactivate();
64
65 // Trigger plugin deactivation tracking
66 do_action( 'wpvr_plugin_deactivated' );
67 }
68 }
69
70 register_activation_hook( WPVR_FILE, 'activate_wpvr' );
71 register_deactivation_hook( WPVR_FILE, 'deactivate_wpvr' );
72
73 /**
74 * The core plugin class that is used to define internationalization,
75 * admin-specific hooks, and public-facing site hooks.
76 */
77 require WPVR_PLUGIN_LEGACY_DIR_PATH . 'includes/class-wpvr.php';
78
79 // Include banner classes
80 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'admin/classes/class-wpvr-occasion-banner.php';
81 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'admin/classes/class-wpvr-sells-notification-bar.php';
82 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'admin/classes/class-wpvr-first-tour-banner.php';
83 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'admin/classes/class-wpvr-onboarding-notice.php';
84 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'admin/classes/class-wpvr-new-user-tour.php';
85
86 if ( defined( 'WPB_VC_VERSION' ) ) {
87 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'builders/wpbakery/wpvr-loader.php';
88 if ( class_exists( 'Vc_Manager' ) ) {
89 require_once WPVR_PLUGIN_LEGACY_DIR_PATH . 'builders/wpbakery/wpvr-element.php';
90 }
91 }
92
93
94 /**
95 * Begins execution of the plugin.
96 *
97 * Since everything within the plugin is registered via hooks,
98 * then kicking off the plugin from this point in the file does
99 * not affect the page life cycle.
100 *
101 * @since 7.3.6
102 */
103 function run_wpvr()
104 {
105
106 $plugin = new Wpvr();
107 $plugin->run();
108 // new Tracker();
109
110 // black friday banner class initialization
111 // new WPVR_Special_Occasion_Banner(
112 // 'halloween_deal_2025',
113 // '2025-10-01 00:00:01',
114 // '2025-11-05 23:59:59'
115 // );
116
117 if (!defined('WPVR_PRO_VERSION') && 'no' === get_option('wpvr_sell_eid_ul_fitr_2026_notification_bar', 'no')) {
118 new WPVR_Notification_Bar(
119 'Eid_Ul_Fitr_2026',
120 '2026-03-16 00:00:00',
121 '2026-03-24 23:59:59'
122 );
123 }
124
125 // Initialize first tour banner
126 new WPVR_First_Tour_Banner();
127
128 // Initialize listing-page onboarding notice ("Remind me later" flow)
129 new WPVR_Onboarding_Notice();
130
131 // Auto-launch guided tour for truly-new users on the Add New Tour page.
132 new WPVR_New_User_Tour();
133
134 // Pro preview banner (shown via JS when free user toggles Advanced Controls)
135 if (!defined('WPVR_PRO_VERSION')) {
136 add_action('admin_notices', 'wpvr_render_pro_preview_banner', 11);
137 }
138
139 }
140 run_wpvr();
141
142 /**
143 * Render Pro preview banner as admin notice.
144 * Hidden by default; shown via JS when free user toggles Advanced Controls.
145 *
146 * @since 8.5.44
147 */
148 function wpvr_render_pro_preview_banner() {
149 $screen = get_current_screen();
150 if (!$screen || $screen->id !== 'wpvr_item') {
151 return;
152 }
153 ?>
154 <div class="wpvr-pro-preview-banner" id="wpvr-pro-preview-banner" style="display:none;">
155 <div class="wpvr-pro-preview-banner__left">
156 <span class="wpvr-pro-preview-banner__icon">
157 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zm0 12.5c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
158 </span>
159 <span class="wpvr-pro-preview-banner__text"><?php esc_html_e('You are previewing Pro features in action.', 'wpvr'); ?></span>
160 </div>
161 <div class="wpvr-pro-preview-banner__pricing">
162 <div class="wpvr-pro-preview-banner__pricing-top">
163 <span class="wpvr-pro-preview-banner__old-price"><?php esc_html_e('Normally $99.99/year', 'wpvr'); ?></span>
164 <span class="wpvr-pro-preview-banner__badge"><?php esc_html_e('SAVE 20%', 'wpvr'); ?></span>
165 </div>
166 <span class="wpvr-pro-preview-banner__new-price"><?php esc_html_e('Starting at $79.99/year', 'wpvr'); ?></span>
167 </div>
168 <a href="<?php echo esc_url('https://rextheme.com/wpvr/wpvr-pricing/?utm_source=plugin&utm_medium=pro-preview-banner&utm_campaign=advanced-controls'); ?>" class="wpvr-pro-preview-banner__btn" target="_blank" rel="noopener noreferrer">
169 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M5 16L3 5l5.5 5L12 4l3.5 6L21 5l-2 11H5zm0 2h14v1c0 .55-.45 1-1 1H6c-.55 0-1-.45-1-1v-1z"/></svg>
170 <?php esc_html_e('Upgrade to save changes', 'wpvr'); ?>
171 </a>
172 </div>
173 <?php
174 }
175
176
177 /**
178 * Array information checker
179 *
180 * @param mixed $needle
181 * @param mixed $haystack
182 * @param bool $strict
183 *
184 * @return bool
185 * @since 7.3.6
186 */
187 function wpvr_in_array_r($needle, $haystack, $strict = false)
188 {
189 foreach ($haystack as $item) {
190 if ((($strict ? $item === $needle : $item == $needle)) || is_array($item) && wpvr_in_array_r($needle, $item, $strict)) {
191 return true;
192 }
193 }
194 return false;
195 }
196
197 if ( ! function_exists( 'wpvr_parse_custom_control' ) ) {
198 /**
199 * Normalize custom control data into an array.
200 *
201 * @param mixed $custom_control Raw custom control value from meta.
202 *
203 * @return array
204 * @since 8.5.63
205 */
206 function wpvr_parse_custom_control( $custom_control ) {
207 if ( is_array( $custom_control ) ) {
208 return $custom_control;
209 }
210
211 if ( is_object( $custom_control ) ) {
212 return (array) $custom_control;
213 }
214
215 if ( ! is_string( $custom_control ) || '' === trim( $custom_control ) ) {
216 return array();
217 }
218
219 $unserialized = maybe_unserialize( $custom_control );
220 if ( is_array( $unserialized ) ) {
221 return $unserialized;
222 }
223
224 $decoded = json_decode( wp_unslash( $custom_control ), true );
225 if ( is_array( $decoded ) ) {
226 return $decoded;
227 }
228
229 return array();
230 }
231 }
232
233 if ( ! function_exists( 'wpvr_restore_modern_layout_default_controls' ) ) {
234 /**
235 * Restore Pannellum's default controls after WP VR Pro adds Modern layout CSS.
236 *
237 * The Modern layout stylesheet is added by WP VR Pro at filter priority 10.
238 * Running at priority 20 keeps this compatibility rule in the Free plugin while
239 * ensuring it is output after the Pro stylesheet, regardless of plugin load order.
240 *
241 * @param string $html Generated tour HTML.
242 * @param array $postdata Saved tour settings.
243 * @param int $id Tour post ID.
244 *
245 * @return string
246 * @since 9.0.0
247 */
248 function wpvr_restore_modern_layout_default_controls( $html, $postdata, $id ) {
249 $tour_layout = is_array( $postdata['tourLayout'] ?? null )
250 ? ( $postdata['tourLayout']['layout'] ?? 'default' )
251 : ( $postdata['tourLayout'] ?? 'default' );
252
253 if (
254 ! defined( 'WPVR_PRO_VERSION' )
255 || 'layout1' !== $tour_layout
256 || empty( $postdata['showControls'] )
257 ) {
258 return $html;
259 }
260
261 $custom_control = wp_parse_args(
262 wpvr_parse_custom_control( $postdata['customcontrol'] ?? array() ),
263 array(
264 'panupSwitch' => 'off',
265 'panDownSwitch' => 'off',
266 'panLeftSwitch' => 'off',
267 'panRightSwitch' => 'off',
268 'panZoomInSwitch' => 'off',
269 'panZoomOutSwitch' => 'off',
270 'panFullscreenSwitch' => 'off',
271 'gyroscopeSwitch' => 'off',
272 'backToHomeSwitch' => 'off',
273 )
274 );
275
276 $gyro_enabled = isset( $postdata['gyro'] )
277 && in_array( $postdata['gyro'], array( true, 1, '1', 'on' ), true );
278 $gyro_button_enabled = function_exists( 'wpvr_isMobileDevice' )
279 && wpvr_isMobileDevice()
280 && $gyro_enabled
281 && 'on' === $custom_control['gyroscopeSwitch'];
282
283 $custom_navigation_enabled = $gyro_button_enabled;
284 foreach (
285 array(
286 'panupSwitch',
287 'panDownSwitch',
288 'panLeftSwitch',
289 'panRightSwitch',
290 'panZoomInSwitch',
291 'panZoomOutSwitch',
292 'panFullscreenSwitch',
293 'backToHomeSwitch',
294 ) as $switch
295 ) {
296 if ( 'on' === $custom_control[ $switch ] ) {
297 $custom_navigation_enabled = true;
298 break;
299 }
300 }
301
302 if ( $custom_navigation_enabled ) {
303 return $html;
304 }
305
306 $pano_id = 'pano' . absint( $id );
307 $sprites_url = esc_url( WPVR_PLUGIN_PUBLIC_DIR_URL . 'lib/pannellum/src/css/img/sprites.svg' );
308
309 $html .= '<style id="wpvr-modern-default-controls-' . esc_attr( $id ) . '">
310 #' . esc_attr( $pano_id ) . ' .pnlm-controls-container .pnlm-zoom-controls.pnlm-controls {
311 display: block !important;
312 width: 28px !important;
313 height: 52px !important;
314 right: auto !important;
315 bottom: auto !important;
316 margin-top: 4px !important;
317 border: 1px solid rgba(0, 0, 0, 0.4) !important;
318 border-radius: 3px !important;
319 background-color: #fff !important;
320 background-image: none !important;
321 background-position: 0 0 !important;
322 background-repeat: repeat !important;
323 background-size: auto !important;
324 }
325
326 #' . esc_attr( $pano_id ) . ' .pnlm-controls-container .pnlm-fullscreen-toggle-button {
327 display: block !important;
328 width: 28px !important;
329 height: 26px !important;
330 right: auto !important;
331 bottom: auto !important;
332 margin-top: 4px !important;
333 border: 1px solid rgba(0, 0, 0, 0.4) !important;
334 border-radius: 3px !important;
335 background-color: #fff !important;
336 background-image: url("' . $sprites_url . '") !important;
337 background-repeat: repeat !important;
338 background-size: auto !important;
339 }
340
341 #' . esc_attr( $pano_id ) . ' .pnlm-controls-container .pnlm-fullscreen-toggle-button-inactive {
342 background-position: 1px -52px !important;
343 }
344
345 #et-boc .et-l #' . esc_attr( $pano_id ) . ' .pnlm-controls-container .pnlm-fullscreen-toggle-button-inactive {
346 background-position: -1px -53px !important;
347 }
348
349 #' . esc_attr( $pano_id ) . ' .pnlm-controls-container .pnlm-fullscreen-toggle-button-active {
350 background-position: 0 -78px !important;
351 }
352
353 #' . esc_attr( $pano_id ) . ' .pnlm-controls-container .pnlm-control:hover {
354 background-color: #f8f8f8 !important;
355 }
356 </style>';
357
358 return $html;
359 }
360 }
361
362 add_filter( 'wpvr_generate_tour_layout_html', 'wpvr_restore_modern_layout_default_controls', 20, 3 );
363
364 if ( ! function_exists( 'wpvr_render_scene_info_row' ) ) {
365 /**
366 * Keep scene title and author metadata aligned for every tour layout.
367 *
368 * WP VR Pro can replace the Pannellum asset registered by the Free plugin and
369 * adds Modern layout CSS at priority 10. This late, tour-scoped layer keeps
370 * both Pannellum copies and both layouts consistent without modifying Pro.
371 *
372 * @param string $html Generated tour HTML.
373 * @param array $postdata Saved tour settings.
374 * @param int $id Tour post ID.
375 *
376 * @return string
377 * @since 9.0.0
378 */
379 function wpvr_render_scene_info_row( $html, $postdata, $id ) {
380 if ( preg_match( '/id=[\'"](pano' . absint( $id ) . '(?:_\d+)?)[\'"]/', $html, $m ) ) {
381 $pano_id = $m[1];
382 } else {
383 $pano_id = 'pano' . absint( $id );
384 }
385 $pano_id_json = wp_json_encode( $pano_id );
386 $by_label = wp_json_encode( __( 'By', 'wpvr' ) );
387 $tour_layout = is_array( $postdata['tourLayout'] ?? null )
388 ? ( $postdata['tourLayout']['layout'] ?? 'default' )
389 : ( $postdata['tourLayout'] ?? 'default' );
390 $is_classic_layout = wp_json_encode( 'layout1' !== $tour_layout );
391
392 $html .= '<style id="wpvr-scene-info-' . esc_attr( $pano_id ) . '">
393 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row {
394 display: flex !important;
395 align-items: center !important;
396 justify-content: center !important;
397 gap: 10px !important;
398 box-sizing: border-box !important;
399 min-width: 0 !important;
400 overflow: hidden !important;
401 text-align: center !important;
402 z-index: 2147483647 !important;
403 -webkit-transform: translateZ(10000px) !important;
404 transform: translateZ(10000px) !important;
405 }
406
407 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row .pnlm-title-box {
408 flex: 0 1 auto !important;
409 min-width: 0 !important;
410 max-width: calc(70% - 5px) !important;
411 width: auto !important;
412 margin: 0 !important;
413 padding: 0 !important;
414 display: block !important;
415 overflow: hidden !important;
416 text-overflow: ellipsis !important;
417 white-space: nowrap !important;
418 }
419
420 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row .pnlm-author-box {
421 flex: 0 1 auto !important;
422 min-width: 0 !important;
423 max-width: calc(30% - 5px) !important;
424 margin: 0 !important;
425 border-left: 1px solid rgba(255, 255, 255, 0.45) !important;
426 padding: 0 0 0 10px !important;
427 display: block !important;
428 overflow: hidden !important;
429 line-height: 1.2 !important;
430 text-overflow: ellipsis !important;
431 white-space: nowrap !important;
432 }
433
434 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info .wpvr-author-label {
435 flex: none !important;
436 margin-right: 4px !important;
437 font-size: 10px !important;
438 font-weight: 700 !important;
439 letter-spacing: 0.06em !important;
440 opacity: 0.8 !important;
441 text-transform: uppercase !important;
442 }
443
444 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info .pnlm-author-box a {
445 color: inherit !important;
446 text-decoration: underline !important;
447 text-underline-offset: 2px !important;
448 }
449
450 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-has-title:not(.wpvr-has-author) .pnlm-title-box,
451 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-has-author:not(.wpvr-has-title) .pnlm-author-box {
452 max-width: 100% !important;
453 border-left: 0 !important;
454 padding-left: 0 !important;
455 }
456
457 #' . esc_attr( $pano_id ) . '.wpvr-layout-classic.wpvr-has-scene-info .scene-gallery.wpvr-gallery--classic {
458 bottom: calc(var(--wpvr-scene-info-height, 34px) + 8px) !important;
459 }
460
461 #' . esc_attr( $pano_id ) . '.wpvr-layout-classic.wpvr-has-scene-info .vrgcontrols {
462 bottom: calc(var(--wpvr-scene-info-height, 34px) + 4px) !important;
463 }
464
465 #' . esc_attr( $pano_id ) . '.wpvr-layout-classic.wpvr-has-scene-info .wpvr_slider_nav {
466 bottom: calc(var(--wpvr-scene-info-height, 34px) + 15px) !important;
467 }
468
469 #' . esc_attr( $pano_id ) . '.wpvr-layout-classic.wpvr-has-scene-info .controls {
470 bottom: calc(var(--wpvr-scene-info-height, 34px) + 6px) !important;
471 }
472
473 @media (max-width: 767px) {
474 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row {
475 gap: 7px !important;
476 padding-right: 10px !important;
477 padding-left: 10px !important;
478 }
479
480 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row .pnlm-title-box {
481 max-width: calc(65% - 4px) !important;
482 }
483
484 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row .pnlm-author-box {
485 max-width: calc(35% - 4px) !important;
486 padding-left: 7px !important;
487 }
488 }
489 </style>
490 <script id="wpvr-scene-info-script-' . esc_attr( $pano_id ) . '">
491 (function () {
492 var pano = document.getElementById(' . $pano_id_json . ');
493 var byLabel = ' . $by_label . ';
494
495 if (!pano || !window.MutationObserver) {
496 return;
497 }
498
499 function addAuthorLabel(author) {
500 if (!author || !author.textContent.trim() || author.querySelector(".wpvr-author-label")) {
501 return;
502 }
503
504 var firstNode = author.firstChild;
505 if (firstNode ? firstNode.nodeType === 3 : false) {
506 var prefix = byLabel + " ";
507 if (firstNode.nodeValue.indexOf(prefix) === 0) {
508 firstNode.nodeValue = firstNode.nodeValue.slice(prefix.length);
509 }
510 }
511
512 var label = document.createElement("span");
513 label.className = "wpvr-author-label";
514 label.textContent = byLabel;
515 author.insertBefore(label, author.firstChild);
516 }
517
518 function syncSceneInfo() {
519 var info = pano.querySelector(".pnlm-panorama-info");
520 if (!info) {
521 return;
522 }
523
524 // Keep the Pannellum full-screen drag layer in its original
525 // stacking order so hotspots remain clickable. Promote only
526 // the compact metadata bar to the tour root instead.
527 if (info.parentNode !== pano) {
528 pano.appendChild(info);
529 }
530
531 var title = info.querySelector(".pnlm-title-box");
532 var author = info.querySelector(".pnlm-author-box");
533 var hasTitle = Boolean(title ? title.textContent.trim() : false);
534 var hasAuthor = Boolean(author ? author.textContent.trim() : false);
535
536 if (hasTitle) {
537 title.setAttribute("title", title.textContent.trim());
538 } else if (title) {
539 title.removeAttribute("title");
540 }
541
542 if (hasAuthor) {
543 addAuthorLabel(author);
544 var authorName = author.textContent.replace(byLabel, "").trim();
545 author.setAttribute("aria-label", byLabel + " " + authorName);
546 author.setAttribute("title", authorName);
547 } else if (author) {
548 author.removeAttribute("aria-label");
549 author.removeAttribute("title");
550 }
551
552 info.classList.toggle("wpvr-scene-info-row", hasTitle || hasAuthor);
553 info.classList.toggle("wpvr-has-title", hasTitle);
554 info.classList.toggle("wpvr-has-author", hasAuthor);
555 info.style.display = hasTitle || hasAuthor ? "flex" : "none";
556
557 var hasSceneInfo = hasTitle || hasAuthor;
558 var isClassicLayout = ' . $is_classic_layout . ';
559 pano.classList.toggle("wpvr-has-scene-info", hasSceneInfo);
560 pano.classList.toggle("wpvr-layout-classic", isClassicLayout);
561
562 if (hasSceneInfo) {
563 pano.style.setProperty("--wpvr-scene-info-height", info.offsetHeight + "px");
564 } else {
565 pano.style.removeProperty("--wpvr-scene-info-height");
566 }
567 }
568
569 var observer = new MutationObserver(syncSceneInfo);
570 observer.observe(pano, { childList: true, subtree: true, characterData: true });
571 syncSceneInfo();
572 }());
573 </script>';
574
575 return $html;
576 }
577 }
578
579 add_filter( 'wpvr_generate_tour_layout_html', 'wpvr_render_scene_info_row', 30, 3 );
580
581 if ( ! function_exists( 'wpvr_render_explainer_button' ) ) {
582 /**
583 * Render the WPVR explainer button independently.
584 *
585 * @param array|null $custom_control
586 * @param array $postdata
587 * @param bool $is_pro
588 * @param bool $autoload
589 * @param string $explainer_right
590 * @param int|string $id
591 *
592 * @return string
593 * @since 8.5.63
594 */
595 function wpvr_render_explainer_button( $custom_control, $postdata, $is_pro, $autoload, $explainer_right, $id ) {
596 $html = '';
597 $explainer_enabled = isset( $postdata['explainerSwitch'] )
598 && in_array( $postdata['explainerSwitch'], array( true, 1, '1', 'on' ), true );
599 $explainer_icon = 'fa fa-video';
600 $explainer_color = '#f7fffb';
601
602 if ( isset( $custom_control ) ) {
603 $explainer_icon = isset( $custom_control['explainerIcon'] ) ? $custom_control['explainerIcon'] : $explainer_icon;
604 $explainer_color = isset( $custom_control['explainerColor'] ) ? $custom_control['explainerColor'] : $explainer_color;
605 } elseif ( isset( $postdata['customcontrol'] ) ) {
606 $raw_control = wpvr_parse_custom_control( $postdata['customcontrol'] );
607 $explainer_icon = isset( $raw_control['explainerIcon'] ) ? $raw_control['explainerIcon'] : $explainer_icon;
608 $explainer_color = isset( $raw_control['explainerColor'] ) ? $raw_control['explainerColor'] : $explainer_color;
609 }
610
611 $pro_license_status = get_option( 'wpvr_edd_license_status' );
612 if ( $explainer_enabled && $pro_license_status === 'valid' && $is_pro ) {
613 $explainer_style = empty( $postdata['explainerContent'] )
614 ? 'pointer-events: none; opacity: 0.5;'
615 : '';
616
617 // Initial display:none to prevent flash for non-autoload tours.
618 $initial_display = ! $autoload ? 'display:none; ' : '';
619
620 $html .= '<div class="explainer_button" id="explainer_button_' . esc_attr( $id ) . '" style="' . $initial_display . 'right:' . esc_attr( $explainer_right ) . '; ' . esc_attr( $explainer_style ) . '">';
621 $html .= '<div class="ctrl" id="explainer_target_' . esc_attr( $id ) . '"><i class="' . esc_attr( $explainer_icon ) . '" style="color:' . esc_attr( $explainer_color ) . ';"></i></div>';
622 $html .= '</div>';
623 }
624
625 return $html;
626 }
627 }
628
629 // Linno telemetry integration
630 function wpvr_block()
631 {
632 wp_register_script(
633 'wpvr-block',
634 plugins_url('build/index.build.js', __FILE__),
635 array('wp-blocks', 'wp-element', 'wp-components', 'wp-editor')
636 );
637
638 if (is_admin()) {
639 wp_enqueue_style(
640 'gutyblocks/guty-block',
641 plugins_url('src/view.css', __FILE__),
642 array()
643 );
644 }
645
646 if (function_exists('register_block_type')) {
647 register_block_type('wpvr/wpvr-block', array(
648 'attributes' => array(
649 'id' => array(
650 'type' => 'string',
651 'default' => '0',
652 ),
653 'width' => array(
654 'type' => 'string',
655 'default' => '600',
656 ),
657 'width_unit' => array(
658 'type' => 'string',
659 'default' => 'px',
660 ),
661 'height' => array(
662 'type' => 'string',
663 'default' => '400',
664 ),
665 'height_unit' => array(
666 'type' => 'string',
667 'default' => 'px',
668 ),
669 'mobile_height' => array(
670 'type' => 'string',
671 'default' => '300',
672 ),
673 'mobile_height_unit' => array(
674 'type' => 'string',
675 'default' => 'px',
676 ),
677 'radius' => array(
678 'type' => 'string',
679 'default' => '0',
680 ),
681 'radius_unit' => array(
682 'type' => 'string',
683 'default' => 'px',
684 ),
685 'border_width' => array(
686 'type' => 'string',
687 'default' => '0',
688 ),
689 'border_style' => array(
690 'type' => 'string',
691 'default' => 'none',
692 ),
693 'border_color' => array(
694 'type' => 'string',
695 'default' => '',
696 ),
697 ),
698 'editor_script' => 'wpvr-block',
699 'render_callback' => 'wpvr_block_render',
700 ));
701 }
702 }
703
704 add_action('init', 'wpvr_block');
705
706 function wpvr_block_render($attributes)
707 {
708 $id = isset($attributes['id']) ? (int) $attributes['id'] : 0;
709 if (!$id) {
710 return '<div class="wpvr-no-tour-selected">' . esc_html__('Please select a tour from the block settings panel.', 'wpvr') . '</div>';
711 }
712
713 $width = isset($attributes['width']) ? $attributes['width'] : '600';
714 if ('fullwidth' === $width) {
715 $width_str = 'fullwidth';
716 } else {
717 $width_unit = isset($attributes['width_unit']) ? $attributes['width_unit'] : 'px';
718 if (preg_match('/(px|%|vw|vh)$/', (string) $width)) {
719 $width_str = $width;
720 } else {
721 $width_str = $width . $width_unit;
722 }
723 }
724
725 $height = isset($attributes['height']) ? $attributes['height'] : '400';
726 $height_unit = isset($attributes['height_unit']) ? $attributes['height_unit'] : 'px';
727 if (!preg_match('/(px|%|vw|vh)$/', (string) $height)) {
728 $height .= $height_unit;
729 }
730
731 $mobile_height = isset($attributes['mobile_height']) ? $attributes['mobile_height'] : '300';
732 $mobile_height_unit = isset($attributes['mobile_height_unit']) ? $attributes['mobile_height_unit'] : 'px';
733 if (!preg_match('/(px|%|vw|vh)$/', (string) $mobile_height)) {
734 $mobile_height .= $mobile_height_unit;
735 }
736
737 $radius = isset($attributes['radius']) ? $attributes['radius'] : '0';
738 $radius_unit = isset($attributes['radius_unit']) ? $attributes['radius_unit'] : 'px';
739 if (!preg_match('/(px|%|vw|vh)$/', (string) $radius)) {
740 $radius .= $radius_unit;
741 }
742
743 $shortcode = sprintf(
744 '[wpvr id="%d" width="%s" height="%s" mobile_height="%s" radius="%s"]',
745 $id,
746 esc_attr($width_str),
747 esc_attr($height),
748 esc_attr($mobile_height),
749 esc_attr($radius)
750 );
751
752 $output = do_shortcode($shortcode);
753
754 // Extract any <script> tags so they are not corrupted by wptexturize (which runs at priority 10 on the_content, after do_blocks at priority 9).
755 $scripts = '';
756 if (preg_match_all('/<script\b[^>]*>[\s\S]*?<\/script>/i', $output, $matches)) {
757 $scripts = implode("\n", $matches[0]);
758 $output = preg_replace('/<script\b[^>]*>[\s\S]*?<\/script>/i', '', $output);
759 }
760
761 if (!empty($scripts)) {
762 if (wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
763 $output .= "\n" . $scripts;
764 } else {
765 add_action('wp_footer', function() use ($scripts) {
766 echo $scripts; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
767 }, 20);
768 }
769 }
770
771 $border_css = '';
772 if (!empty($attributes['border_width']) && !empty($attributes['border_style']) && 'none' !== $attributes['border_style']) {
773 $border_width = rtrim((string) $attributes['border_width'], 'px') . 'px';
774 $border_color = !empty($attributes['border_color']) ? $attributes['border_color'] : 'transparent';
775 $border_css = 'border: ' . esc_attr($border_width . ' ' . $attributes['border_style'] . ' ' . $border_color) . ';';
776 }
777
778 $class_name = !empty($attributes['className']) ? ' ' . esc_attr($attributes['className']) : '';
779
780 if ('' !== $border_css || '' !== $class_name) {
781 $wrapper_style = $border_css ? ' style="' . $border_css . ' display: inline-block; width: 100%; max-width: ' . esc_attr('fullwidth' === $width_str ? '100%' : $width_str) . ';"' : '';
782 return '<div class="wpvr-block-wrapper' . $class_name . '"' . $wrapper_style . '>' . $output . '</div>';
783 }
784
785 return $output;
786 }
787
788 function sanitize_content_preserve_styles($content, $allow_forms = false) {
789 // Decode HTML entities first (in case content was encoded in database)
790 $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
791
792 // Escape <script> blocks to display as text instead of removing them
793 $content = preg_replace_callback('/<script\b[^>]*>(.*?)<\/script>/si', function($matches) {
794 return esc_html($matches[0]); // Convert to plain text
795 }, $content);
796
797 // Strip dangerous URL-based attributes
798 $content = preg_replace('/(href|action|formaction)\s*=\s*["\']?\s*(javascript|vbscript|data|about):/i', '$1=""', $content);
799
800 // Escape inline event handlers (onclick, onhover, etc.) to display as text
801 $content = preg_replace_callback('/\s*(on\w+)\s*=\s*(["\'])([^"\']*)\2/i', function($matches) {
802 // Escape the attribute value but keep the attribute name visible as text
803 return ' ' . esc_html($matches[1]) . '=' . $matches[2] . esc_html($matches[3]) . $matches[2];
804 }, $content);
805 $content = preg_replace_callback('/\s*(on\w+)\s*=\s*([^>\s]+)/i', function($matches) {
806 // Handle unquoted event handlers
807 return ' ' . esc_html($matches[1]) . '=' . esc_html($matches[2]);
808 }, $content);
809
810 // Remove unsafe embedded/interactive elements
811 if ($allow_forms) {
812 $content = preg_replace('/<(object|embed|applet|frame|frameset|meta|link|base)\b[^>]*>/i', '', $content);
813 $content = preg_replace('/<\/(object|embed|applet|frame|frameset|meta|link|base)>/i', '', $content);
814 } else {
815 $content = preg_replace('/<(object|embed|applet|frame|frameset|meta|link|base|form|input|button|textarea|select|option)\b[^>]*>/i', '', $content);
816 $content = preg_replace('/<\/(object|embed|applet|frame|frameset|meta|link|base|form|input|button|textarea|select|option)>/i', '', $content);
817 }
818
819 // Clean style attributes safely
820 $content = preg_replace_callback('/style\s*=\s*["\']([^"\']*)["\']/', function($matches) {
821 $style = $matches[1];
822 $style = preg_replace('/expression\s*\(/i', '', $style);
823 $style = preg_replace('/(javascript|vbscript|data|about)\s*:/i', '', $style);
824 $style = preg_replace('/url\s*\(\s*["\']?\s*(javascript|vbscript|data):/i', '', $style);
825 $style = preg_replace('/behavior\s*:/i', '', $style);
826 $style = preg_replace('/-moz-binding\s*:/i', '', $style);
827 return 'style="' . esc_attr($style) . '"';
828 }, $content);
829
830 // Sanitize <style> blocks
831 $content = preg_replace_callback('/<style\b[^>]*>(.*?)<\/style>/si', function($matches) {
832 $css = $matches[1];
833 $css = preg_replace('/(expression|javascript|vbscript|data|about)\s*:/i', '', $css);
834 $css = preg_replace('/url\s*\(\s*["\']?\s*(javascript|vbscript|data):/i', '', $css);
835 $css = preg_replace('/behavior\s*:/i', '', $css);
836 $css = preg_replace('/-moz-binding\s*:/i', '', $css);
837 return '<style>' . esc_html($css) . '</style>';
838 }, $content);
839
840 // Allow iframes from safe sources only (e.g., YouTube, Vimeo)
841 $allowed_tags = wp_kses_allowed_html('post');
842 $allowed_tags['iframe'] = [
843 'src' => true,
844 'width' => true,
845 'height' => true,
846 'frameborder' => true,
847 'allowfullscreen' => true,
848 'class' => true,
849 'style' => true,
850 'title' => true,
851 'allow' => true,
852 'name' => true,
853 'referrerpolicy' => true,
854 'loading' => true,
855 'sandbox' => true,
856 ];
857 $allowed_tags['img'] = [
858 'src' => true,
859 'alt' => true,
860 'title' => true,
861 'width' => true,
862 'height' => true,
863 'class' => true,
864 'id' => true,
865 'style' => true,
866 'loading' => true,
867 'srcset' => true,
868 'sizes' => true,
869 ];
870
871 if ($allow_forms) {
872 $form_attributes = [
873 'id' => true,
874 'class' => true,
875 'style' => true,
876 'name' => true,
877 'value' => true,
878 'type' => true,
879 'placeholder' => true,
880 'action' => true,
881 'method' => true,
882 'target' => true,
883 'enctype' => true,
884 'disabled' => true,
885 'readonly' => true,
886 'required' => true,
887 'checked' => true,
888 'selected' => true,
889 'multiple' => true,
890 'size' => true,
891 'rows' => true,
892 'cols' => true,
893 'maxlength' => true,
894 'minlength' => true,
895 'min' => true,
896 'max' => true,
897 'step' => true,
898 'pattern' => true,
899 'autocomplete'=> true,
900 'autofocus' => true,
901 'for' => true,
902 ];
903 $allowed_tags['form'] = $form_attributes;
904 $allowed_tags['input'] = $form_attributes;
905 $allowed_tags['button'] = $form_attributes;
906 $allowed_tags['textarea'] = $form_attributes;
907 $allowed_tags['select'] = $form_attributes;
908 $allowed_tags['option'] = $form_attributes;
909 $allowed_tags['optgroup'] = $form_attributes;
910 $allowed_tags['label'] = $form_attributes;
911 $allowed_tags['fieldset'] = $form_attributes;
912 $allowed_tags['legend'] = $form_attributes;
913 }
914
915 // Apply wp_kses() to keep only allowed tags/attributes
916 $content = wp_kses($content, $allowed_tags);
917
918 // Finally, validate iframe src for security (allow https only, block javascript: etc.)
919 $content = preg_replace_callback('/<iframe[^>]+src=["\']([^"\']+)["\'][^>]*>(?:<\/iframe>)?/i', function($matches) {
920 $src = $matches[1];
921 // Allow any https:// or http:// URL, but block javascript:, data:, vbscript:, etc.
922 if (preg_match('/^(https?:)?\/\//i', $src) && !preg_match('/^(javascript|data|vbscript|about):/i', $src)) {
923 return $matches[0]; // keep safe iframe
924 }
925 // Strip unsafe iframe
926 return '';
927 }, $content);
928
929 return $content;
930 }
931 function wpvr_hex2rgb($colour)
932 {
933 if (isset($colour[0]) && $colour[0] == '#') {
934 $colour = substr($colour, 1);
935 }
936 if (strlen($colour) == 6) {
937 list($r, $g, $b) = array($colour[0] . $colour[1], $colour[2] . $colour[3], $colour[4] . $colour[5]);
938 } elseif (strlen($colour) == 3) {
939 list($r, $g, $b) = array($colour[0] . $colour[0], $colour[1] . $colour[1], $colour[2] . $colour[2]);
940 } else {
941 return false;
942 }
943 $r = hexdec($r);
944 $g = hexdec($g);
945 $b = hexdec($b);
946 return array($r . ', ' . $g . ', ' . $b);
947 }
948
949 function wpvr_HTMLToRGB($htmlCode)
950 {
951 $r = 0;
952 $g = 0;
953 $b = 0;
954 if (isset($htmlCode[0]) && $htmlCode[0] == '#') {
955 $htmlCode = substr($htmlCode, 1);
956 }
957
958 if (strlen($htmlCode) == 3) {
959 $htmlCode = $htmlCode[0] . $htmlCode[0] . $htmlCode[1] . $htmlCode[1] . $htmlCode[2] . $htmlCode[2];
960 }
961
962 if (isset($htmlCode[0]) && isset($htmlCode[1])) {
963 $r = hexdec($htmlCode[0] . $htmlCode[1]);
964 }
965 if (isset($htmlCode[2]) && isset($htmlCode[3])) {
966 $g = hexdec($htmlCode[2] . $htmlCode[3]);
967 }
968 if (isset($htmlCode[4]) && isset($htmlCode[5])) {
969 $b = hexdec($htmlCode[4] . $htmlCode[5]);
970 }
971
972 return $b + ($g << 0x8) + ($r << 0x10);
973 }
974
975 function wpvr_RGBToHSL($RGB)
976 {
977 $r = 0xFF & ($RGB >> 0x10);
978 $g = 0xFF & ($RGB >> 0x8);
979 $b = 0xFF & $RGB;
980
981 $r = ((float)$r) / 255.0;
982 $g = ((float)$g) / 255.0;
983 $b = ((float)$b) / 255.0;
984
985 $maxC = max($r, $g, $b);
986 $minC = min($r, $g, $b);
987
988 $l = ($maxC + $minC) / 2.0;
989
990 if ($maxC == $minC) {
991 $s = 0;
992 $h = 0;
993 } else {
994 if ($l < .5) {
995 $s = ($maxC - $minC) / ($maxC + $minC);
996 } else {
997 $s = ($maxC - $minC) / (2.0 - $maxC - $minC);
998 }
999 if ($r == $maxC) {
1000 $h = ($g - $b) / ($maxC - $minC);
1001 }
1002 if ($g == $maxC) {
1003 $h = 2.0 + ($b - $r) / ($maxC - $minC);
1004 }
1005 if ($b == $maxC) {
1006 $h = 4.0 + ($r - $g) / ($maxC - $minC);
1007 }
1008
1009 $h = $h / 6.0;
1010 }
1011
1012 $h = (int)round(255.0 * $h);
1013 $s = (int)round(255.0 * $s);
1014 $l = (int)round(255.0 * $l);
1015
1016 return (object) array('hue' => $h, 'saturation' => $s, 'lightness' => $l);
1017 }
1018
1019 add_action('rest_api_init', 'wpvr_rest_data_route');
1020 function wpvr_rest_data_route()
1021 {
1022 register_rest_route('wpvr/v1', '/panodata/', array(
1023 'methods' => 'GET',
1024 'callback' => 'wpvr_rest_data_set',
1025 'permission_callback' => 'wpvr_rest_route_permission'
1026 ));
1027 }
1028
1029 function wpvr_rest_route_permission()
1030 {
1031 return true;
1032 }
1033
1034 function wpvr_rest_data_set()
1035 {
1036 $query = new WP_Query(array(
1037 'post_type' => 'wpvr_item',
1038 'posts_per_page' => -1,
1039 ));
1040
1041 $wpvr_list = array();
1042 $list_none = array('value' => 0, 'label' => 'None');
1043 array_push($wpvr_list, $list_none);
1044 while ($query->have_posts()) {
1045 $query->the_post();
1046 $title = mb_convert_encoding(get_the_title(), 'UTF-8', 'HTML-ENTITIES');
1047 $post_id = get_the_ID();
1048 $title = $post_id . ' : ' . $title;
1049 $list_ob = array('value' => $post_id, 'label' => $title);
1050 array_push($wpvr_list, $list_ob);
1051 }
1052
1053 return $wpvr_list;
1054 }
1055
1056 function wpvr_isMobileDevice()
1057 {
1058 return preg_match("/(android|avantgo|blackberry|bolt|boost|cricket|docomo|fone|hiptop|mini|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i", $_SERVER["HTTP_USER_AGENT"]);
1059 }
1060
1061 function wpvr_directory()
1062 {
1063 $upload = wp_upload_dir();
1064 $upload_dir = $upload['basedir'];
1065 $upload_dir_temp = $upload_dir . '/wpvr/temp/';
1066 if (!is_dir($upload_dir_temp)) {
1067 wp_mkdir_p($upload_dir_temp, 0700);
1068 }
1069 }
1070
1071 add_action('admin_init', 'wpvr_directory');
1072
1073
1074 function wpvr_add_role_cap()
1075 {
1076 $editor_active = get_option('wpvr_editor_active');
1077
1078 $author_active = get_option('wpvr_author_active');
1079
1080 $admin = get_role('administrator');
1081 $admin->add_cap('publish_wpvr_tour');
1082 $admin->add_cap('edit_wpvr_tours');
1083 $admin->add_cap('read_wpvr_tour');
1084 $admin->add_cap('edit_wpvr_tour');
1085 $admin->add_cap('edit_wpvr_tours');
1086 $admin->add_cap('publish_wpvr_tours');
1087 $admin->add_cap('publish_wpvr_tour');
1088 $admin->add_cap('delete_wpvr_tour');
1089 $admin->add_cap('edit_other_wpvr_tours');
1090 $admin->add_cap('delete_other_wpvr_tours');
1091
1092 if ($editor_active == "true") {
1093 $editor = get_role('editor');
1094 if ($editor) {
1095 $editor->add_cap('publish_wpvr_tour');
1096 $editor->add_cap('edit_wpvr_tours');
1097 $editor->add_cap('read_wpvr_tour');
1098 $editor->add_cap('edit_wpvr_tour');
1099 $editor->add_cap('edit_wpvr_tours');
1100 $editor->add_cap('publish_wpvr_tours');
1101 $editor->add_cap('publish_wpvr_tour');
1102 $editor->add_cap('delete_wpvr_tour');
1103 $editor->add_cap('edit_other_wpvr_tours');
1104 $editor->add_cap('delete_other_wpvr_tours');
1105 }
1106 } else {
1107 $editor = get_role('editor');
1108 if ($editor) {
1109 $editor->remove_cap('publish_wpvr_tour');
1110 $editor->remove_cap('edit_wpvr_tours');
1111 $editor->remove_cap('read_wpvr_tour');
1112 $editor->remove_cap('edit_wpvr_tour');
1113 $editor->remove_cap('edit_wpvr_tours');
1114 $editor->remove_cap('publish_wpvr_tours');
1115 $editor->remove_cap('publish_wpvr_tour');
1116 $editor->remove_cap('delete_wpvr_tour');
1117 $editor->remove_cap('edit_other_wpvr_tours');
1118 $editor->remove_cap('delete_other_wpvr_tours');
1119 }
1120 }
1121
1122 if ($author_active == "true") {
1123 $author = get_role('author');
1124 if ($author) {
1125 $author->add_cap('read_wpvr_tour');
1126 $author->add_cap('edit_wpvr_tour');
1127 $author->add_cap('edit_wpvr_tours');
1128 $author->add_cap('publish_wpvr_tours');
1129 $author->add_cap('publish_wpvr_tour');
1130 $author->add_cap('delete_wpvr_tour');
1131 }
1132 } else {
1133 $author = get_role('author');
1134 if ($author) {
1135 $author->remove_cap('read_wpvr_tour');
1136 $author->remove_cap('edit_wpvr_tour');
1137 $author->remove_cap('edit_wpvr_tours');
1138 $author->remove_cap('publish_wpvr_tours');
1139 $author->remove_cap('publish_wpvr_tour');
1140 $author->remove_cap('delete_wpvr_tour');
1141 }
1142 }
1143
1144 if(is_plugin_active( 'dokan-lite/dokan.php' ) || is_plugin_active( 'dokan-pro/dokan.php' )){
1145 $dokan_vendor_active = get_option('dokan_vendor_active');
1146
1147 if( 'true' === $dokan_vendor_active){
1148 $seller = get_role('seller');
1149 if ($seller) {
1150 $seller->add_cap('read_wpvr_tour');
1151 $seller->add_cap('edit_wpvr_tour');
1152 $seller->add_cap('edit_wpvr_tours');
1153 $seller->add_cap('publish_wpvr_tours');
1154 $seller->add_cap('publish_wpvr_tour');
1155 $seller->add_cap('delete_wpvr_tour');
1156 }
1157 } else{
1158 $seller = get_role('seller');
1159 if ($seller) {
1160 $seller->remove_cap('read_wpvr_tour');
1161 $seller->remove_cap('edit_wpvr_tour');
1162 $seller->remove_cap('edit_wpvr_tours');
1163 $seller->remove_cap('publish_wpvr_tours');
1164 $seller->remove_cap('publish_wpvr_tour');
1165 $seller->remove_cap('delete_wpvr_tour');
1166 }
1167 }
1168 }
1169
1170
1171 }
1172
1173 add_action('admin_init', 'wpvr_add_role_cap', 999);
1174
1175 function wpvr_role_management_from_post_type($args, $post_type)
1176 {
1177 if ('wpvr_item' !== $post_type) {
1178 return $args;
1179 }
1180
1181 $editor_active = get_option('wpvr_editor_active');
1182 $author_active = get_option('wpvr_author_active');
1183 $user = wp_get_current_user();
1184
1185 if ($editor_active == "true") {
1186 if (in_array('editor', (array) $user->roles)) {
1187 $args['show_in_menu'] = true;
1188 }
1189 }
1190
1191 if ($author_active == "true") {
1192 if (in_array('author', (array) $user->roles)) {
1193 $args['show_in_menu'] = true;
1194 }
1195 }
1196
1197 if(is_plugin_active( 'dokan-lite/dokan.php' ) || is_plugin_active( 'dokan-pro/dokan.php' )){
1198 $dokan_vendor_active = get_option('dokan_vendor_active');
1199 if( 'true' === $dokan_vendor_active){
1200 if (in_array('seller', (array) $user->roles)) {
1201 $args['show_in_menu'] = true;
1202 }
1203 }
1204
1205 }
1206
1207 return $args;
1208 }
1209 add_filter('register_post_type_args', 'wpvr_role_management_from_post_type', 10, 2);
1210
1211 function wpvr_cache_admin_notice()
1212 {
1213 $option = get_option('wpvr_warning');
1214 if (!$option) {
1215 ?>
1216 <div class="notice notice-warning" id="wpvr-warning" style="position: relative;">
1217 <p><?php _e('Since you have updated the plugin, please clear the browser cache for smooth functioning. Follow these steps if you are using <a href="https://support.google.com/accounts/answer/32050?co=GENIE.Platform%3DDesktop&hl=en" target="_blank">Google Chrome</a>, <a href="https://support.mozilla.org/en-US/kb/how-clear-firefox-cache" target="_blank">Mozilla Firefox</a>, <a href="https://clear-my-cache.com/en/apple-mac-os/safari.html" target="_blank">Safai</a> or <a href="https://support.microsoft.com/en-us/help/10607/microsoft-edge-view-delete-browser-history" target="_blank">Microsoft Edge</a>', 'wpvr'); ?></p>
1218 <button type="button" id="wpvr-dismissible" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button>
1219 </div>
1220 <?php
1221 }
1222 }
1223 // add_action('admin_notices', 'wpvr_cache_admin_notice');
1224
1225 //===Oxygen widget===//
1226 add_action('plugins_loaded', function () {
1227 if (!class_exists('OxyEl')) {
1228 return;
1229 }
1230 require_once __DIR__ . '/oxygen/oxy-manager.php';
1231 });
1232
1233 add_action('init', 'wpvr_mobile_media_handle');
1234 function wpvr_mobile_media_handle()
1235 {
1236 add_image_size('wpvr_mobile', 4096, 2048); //mobile
1237 }
1238
1239
1240 add_action(
1241 /**
1242 * @param $api \VisualComposer\Modules\Api\Factory
1243 */
1244 'vcv:api',
1245 function ($api) {
1246 $elementsToRegister = [
1247 'wpvrelement',
1248 ];
1249 $pluginBaseUrl = rtrim(WPVR_PLUGIN_DIR_URL, '\\/');
1250 /** @var \VisualComposer\Modules\Elements\ApiController $elementsApi */
1251 $elementsApi = $api->elements;
1252 foreach ($elementsToRegister as $tag) {
1253 $manifestPath = __DIR__ . '/vc/' . $tag . '/manifest.json';
1254 $elementBaseUrl = $pluginBaseUrl . '/vc/' . $tag;
1255 $elementsApi->add($manifestPath, $elementBaseUrl);
1256 }
1257 }
1258 );
1259
1260 function wpvr_redirect_after_activation($plugin)
1261 {
1262 if ($plugin == plugin_basename(__FILE__)) {
1263 $url = admin_url('admin.php?page=rex-wpvr-setup-wizard');
1264 $url = esc_url($url, FILTER_SANITIZE_URL);
1265 exit(wp_safe_redirect($url));
1266 }
1267 }
1268 //add_action('activated_plugin', 'wpvr_redirect_after_activation');
1269
1270 function replace_callback($matches)
1271 {
1272 foreach ($matches as $match) {
1273 return str_replace('<img', '<img decoding="async" ', $match);
1274 }
1275 }
1276
1277
1278 function wpvr_get_explainer_embed_url( $input ) {
1279 $url = esc_url_raw( trim( (string) $input ), array( 'http', 'https' ) );
1280
1281 if ( empty( $url ) || ! preg_match( '#^https?://#i', $url ) ) {
1282 return '';
1283 }
1284
1285 $host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) );
1286 $host = preg_replace( '/^www\./', '', $host );
1287 $path = trim( (string) wp_parse_url( $url, PHP_URL_PATH ), '/' );
1288
1289 if ( 'youtu.be' === $host ) {
1290 $segments = explode( '/', $path );
1291 $video_id = isset( $segments[0] ) ? preg_replace( '/[^A-Za-z0-9_-]/', '', $segments[0] ) : '';
1292
1293 return $video_id ? 'https://www.youtube.com/embed/' . $video_id : '';
1294 }
1295
1296 if ( in_array( $host, array( 'youtube.com', 'm.youtube.com', 'music.youtube.com', 'youtube-nocookie.com' ), true ) ) {
1297 $video_id = '';
1298 $segments = explode( '/', $path );
1299
1300 if ( 'watch' === $path ) {
1301 $query = array();
1302 wp_parse_str( (string) wp_parse_url( $url, PHP_URL_QUERY ), $query );
1303 $video_id = isset( $query['v'] ) ? $query['v'] : '';
1304 } elseif ( isset( $segments[0], $segments[1] ) && in_array( $segments[0], array( 'embed', 'shorts', 'live' ), true ) ) {
1305 $video_id = $segments[1];
1306 }
1307
1308 $video_id = preg_replace( '/[^A-Za-z0-9_-]/', '', (string) $video_id );
1309
1310 return $video_id ? 'https://www.youtube.com/embed/' . $video_id : '';
1311 }
1312
1313 if ( 'vimeo.com' === $host || 'player.vimeo.com' === $host ) {
1314 $segments = array_reverse( array_filter( explode( '/', $path ) ) );
1315
1316 foreach ( $segments as $segment ) {
1317 if ( ctype_digit( $segment ) ) {
1318 return 'https://player.vimeo.com/video/' . $segment;
1319 }
1320 }
1321
1322 return '';
1323 }
1324
1325 return $url;
1326 }
1327
1328 function wpvr_sanitize_iframe_only( $input ) {
1329 // Start with standard allowed post HTML (p, a, strong, etc.)
1330 $allowed_tags = wp_kses_allowed_html( 'post' );
1331
1332 // Explicitly allow <iframe> with specific safe attributes
1333 $allowed_tags['iframe'] = array(
1334 'src' => true,
1335 'width' => true,
1336 'height' => true,
1337 'title' => true,
1338 'frameborder' => true,
1339 'allow' => true,
1340 'allowfullscreen' => true,
1341 'referrerpolicy' => true,
1342 );
1343
1344 $content = trim( (string) $input );
1345
1346 if ( '' === $content ) {
1347 return '';
1348 }
1349
1350 if ( false === stripos( $content, '<iframe' ) ) {
1351 $embed_url = wpvr_get_explainer_embed_url( $content );
1352
1353 if ( $embed_url ) {
1354 $content = sprintf(
1355 '<iframe src="%s" width="100%%" height="100%%" title="Explainer video" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen referrerpolicy="strict-origin-when-cross-origin"></iframe>',
1356 esc_url( $embed_url )
1357 );
1358 }
1359 }
1360
1361 return wp_kses( $content, $allowed_tags );
1362 }
1363