PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 9.1.0
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v9.1.0
9.1.3 9.1.2 9.1.1 9.1.0 9.0.3 9.0.2 9.0.1 9.0.0 8.5.79 8.5.78 8.5.77 8.5.76 8.5.75 8.5.74 8.5.73 8.5.72 8.5.71 8.5.70 8.5.69 8.5.68 8.5.35 8.5.36 8.5.37 8.5.38 8.5.39 All 222 releases
wpvr / legacy / wpvr.php

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

1,359 lines 50.9 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 $pano_id = 'pano' . absint( $id );
381 $pano_id_json = wp_json_encode( $pano_id );
382 $by_label = wp_json_encode( __( 'By', 'wpvr' ) );
383 $tour_layout = is_array( $postdata['tourLayout'] ?? null )
384 ? ( $postdata['tourLayout']['layout'] ?? 'default' )
385 : ( $postdata['tourLayout'] ?? 'default' );
386 $is_classic_layout = wp_json_encode( 'layout1' !== $tour_layout );
387
388 $html .= '<style id="wpvr-scene-info-' . esc_attr( $id ) . '">
389 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row {
390 display: flex !important;
391 align-items: center !important;
392 justify-content: center !important;
393 gap: 10px !important;
394 box-sizing: border-box !important;
395 min-width: 0 !important;
396 overflow: hidden !important;
397 text-align: center !important;
398 z-index: 2147483647 !important;
399 -webkit-transform: translateZ(10000px) !important;
400 transform: translateZ(10000px) !important;
401 }
402
403 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row .pnlm-title-box {
404 flex: 0 1 auto !important;
405 min-width: 0 !important;
406 max-width: calc(70% - 5px) !important;
407 width: auto !important;
408 margin: 0 !important;
409 padding: 0 !important;
410 display: block !important;
411 overflow: hidden !important;
412 text-overflow: ellipsis !important;
413 white-space: nowrap !important;
414 }
415
416 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row .pnlm-author-box {
417 flex: 0 1 auto !important;
418 min-width: 0 !important;
419 max-width: calc(30% - 5px) !important;
420 margin: 0 !important;
421 border-left: 1px solid rgba(255, 255, 255, 0.45) !important;
422 padding: 0 0 0 10px !important;
423 display: block !important;
424 overflow: hidden !important;
425 line-height: 1.2 !important;
426 text-overflow: ellipsis !important;
427 white-space: nowrap !important;
428 }
429
430 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info .wpvr-author-label {
431 flex: none !important;
432 margin-right: 4px !important;
433 font-size: 10px !important;
434 font-weight: 700 !important;
435 letter-spacing: 0.06em !important;
436 opacity: 0.8 !important;
437 text-transform: uppercase !important;
438 }
439
440 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info .pnlm-author-box a {
441 color: inherit !important;
442 text-decoration: underline !important;
443 text-underline-offset: 2px !important;
444 }
445
446 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-has-title:not(.wpvr-has-author) .pnlm-title-box,
447 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-has-author:not(.wpvr-has-title) .pnlm-author-box {
448 max-width: 100% !important;
449 border-left: 0 !important;
450 padding-left: 0 !important;
451 }
452
453 #' . esc_attr( $pano_id ) . '.wpvr-layout-classic.wpvr-has-scene-info .scene-gallery.wpvr-gallery--classic {
454 bottom: calc(var(--wpvr-scene-info-height, 34px) + 8px) !important;
455 }
456
457 #' . esc_attr( $pano_id ) . '.wpvr-layout-classic.wpvr-has-scene-info .vrgcontrols {
458 bottom: calc(var(--wpvr-scene-info-height, 34px) + 4px) !important;
459 }
460
461 #' . esc_attr( $pano_id ) . '.wpvr-layout-classic.wpvr-has-scene-info .wpvr_slider_nav {
462 bottom: calc(var(--wpvr-scene-info-height, 34px) + 15px) !important;
463 }
464
465 #' . esc_attr( $pano_id ) . '.wpvr-layout-classic.wpvr-has-scene-info .controls {
466 bottom: calc(var(--wpvr-scene-info-height, 34px) + 6px) !important;
467 }
468
469 @media (max-width: 767px) {
470 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row {
471 gap: 7px !important;
472 padding-right: 10px !important;
473 padding-left: 10px !important;
474 }
475
476 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row .pnlm-title-box {
477 max-width: calc(65% - 4px) !important;
478 }
479
480 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row .pnlm-author-box {
481 max-width: calc(35% - 4px) !important;
482 padding-left: 7px !important;
483 }
484 }
485 </style>
486 <script id="wpvr-scene-info-script-' . esc_attr( $id ) . '">
487 (function () {
488 var pano = document.getElementById(' . $pano_id_json . ');
489 var byLabel = ' . $by_label . ';
490
491 if (!pano || !window.MutationObserver) {
492 return;
493 }
494
495 function addAuthorLabel(author) {
496 if (!author || !author.textContent.trim() || author.querySelector(".wpvr-author-label")) {
497 return;
498 }
499
500 var firstNode = author.firstChild;
501 if (firstNode && firstNode.nodeType === 3) {
502 var prefix = byLabel + " ";
503 if (firstNode.nodeValue.indexOf(prefix) === 0) {
504 firstNode.nodeValue = firstNode.nodeValue.slice(prefix.length);
505 }
506 }
507
508 var label = document.createElement("span");
509 label.className = "wpvr-author-label";
510 label.textContent = byLabel;
511 author.insertBefore(label, author.firstChild);
512 }
513
514 function syncSceneInfo() {
515 var info = pano.querySelector(".pnlm-panorama-info");
516 if (!info) {
517 return;
518 }
519
520 // Keep the Pannellum full-screen drag layer in its original
521 // stacking order so hotspots remain clickable. Promote only
522 // the compact metadata bar to the tour root instead.
523 if (info.parentNode !== pano) {
524 pano.appendChild(info);
525 }
526
527 var title = info.querySelector(".pnlm-title-box");
528 var author = info.querySelector(".pnlm-author-box");
529 var hasTitle = !!(title && title.textContent.trim());
530 var hasAuthor = !!(author && author.textContent.trim());
531
532 if (hasTitle) {
533 title.setAttribute("title", title.textContent.trim());
534 } else if (title) {
535 title.removeAttribute("title");
536 }
537
538 if (hasAuthor) {
539 addAuthorLabel(author);
540 var authorName = author.textContent.replace(byLabel, "").trim();
541 author.setAttribute("aria-label", byLabel + " " + authorName);
542 author.setAttribute("title", authorName);
543 } else if (author) {
544 author.removeAttribute("aria-label");
545 author.removeAttribute("title");
546 }
547
548 info.classList.toggle("wpvr-scene-info-row", hasTitle || hasAuthor);
549 info.classList.toggle("wpvr-has-title", hasTitle);
550 info.classList.toggle("wpvr-has-author", hasAuthor);
551 info.style.display = hasTitle || hasAuthor ? "flex" : "none";
552
553 var hasSceneInfo = hasTitle || hasAuthor;
554 var isClassicLayout = ' . $is_classic_layout . ';
555 pano.classList.toggle("wpvr-has-scene-info", hasSceneInfo);
556 pano.classList.toggle("wpvr-layout-classic", isClassicLayout);
557
558 if (hasSceneInfo) {
559 pano.style.setProperty("--wpvr-scene-info-height", info.offsetHeight + "px");
560 } else {
561 pano.style.removeProperty("--wpvr-scene-info-height");
562 }
563 }
564
565 var observer = new MutationObserver(syncSceneInfo);
566 observer.observe(pano, { childList: true, subtree: true, characterData: true });
567 syncSceneInfo();
568 }());
569 </script>';
570
571 return $html;
572 }
573 }
574
575 add_filter( 'wpvr_generate_tour_layout_html', 'wpvr_render_scene_info_row', 30, 3 );
576
577 if ( ! function_exists( 'wpvr_render_explainer_button' ) ) {
578 /**
579 * Render the WPVR explainer button independently.
580 *
581 * @param array|null $custom_control
582 * @param array $postdata
583 * @param bool $is_pro
584 * @param bool $autoload
585 * @param string $explainer_right
586 * @param int|string $id
587 *
588 * @return string
589 * @since 8.5.63
590 */
591 function wpvr_render_explainer_button( $custom_control, $postdata, $is_pro, $autoload, $explainer_right, $id ) {
592 $html = '';
593 $explainer_enabled = isset( $postdata['explainerSwitch'] )
594 && in_array( $postdata['explainerSwitch'], array( true, 1, '1', 'on' ), true );
595 $explainer_icon = 'fa fa-video';
596 $explainer_color = '#f7fffb';
597
598 if ( isset( $custom_control ) ) {
599 $explainer_icon = isset( $custom_control['explainerIcon'] ) ? $custom_control['explainerIcon'] : $explainer_icon;
600 $explainer_color = isset( $custom_control['explainerColor'] ) ? $custom_control['explainerColor'] : $explainer_color;
601 } elseif ( isset( $postdata['customcontrol'] ) ) {
602 $raw_control = wpvr_parse_custom_control( $postdata['customcontrol'] );
603 $explainer_icon = isset( $raw_control['explainerIcon'] ) ? $raw_control['explainerIcon'] : $explainer_icon;
604 $explainer_color = isset( $raw_control['explainerColor'] ) ? $raw_control['explainerColor'] : $explainer_color;
605 }
606
607 $pro_license_status = get_option( 'wpvr_edd_license_status' );
608 if ( $explainer_enabled && $pro_license_status === 'valid' && $is_pro ) {
609 $explainer_style = empty( $postdata['explainerContent'] )
610 ? 'pointer-events: none; opacity: 0.5;'
611 : '';
612
613 // Initial display:none to prevent flash for non-autoload tours.
614 $initial_display = ! $autoload ? 'display:none; ' : '';
615
616 $html .= '<div class="explainer_button" id="explainer_button_' . esc_attr( $id ) . '" style="' . $initial_display . 'right:' . esc_attr( $explainer_right ) . '; ' . esc_attr( $explainer_style ) . '">';
617 $html .= '<div class="ctrl" id="explainer_target_' . esc_attr( $id ) . '"><i class="' . esc_attr( $explainer_icon ) . '" style="color:' . esc_attr( $explainer_color ) . ';"></i></div>';
618 $html .= '</div>';
619 }
620
621 return $html;
622 }
623 }
624
625 // Linno telemetry integration
626 function wpvr_block()
627 {
628 wp_register_script(
629 'wpvr-block',
630 plugins_url('build/index.build.js', __FILE__),
631 array('wp-blocks', 'wp-element', 'wp-components', 'wp-editor')
632 );
633
634 if (is_admin()) {
635 wp_enqueue_style(
636 'gutyblocks/guty-block',
637 plugins_url('src/view.css', __FILE__),
638 array()
639 );
640 }
641
642 if (function_exists('register_block_type')) {
643 register_block_type('wpvr/wpvr-block', array(
644 'attributes' => array(
645 'id' => array(
646 'type' => 'string',
647 'default' => '0',
648 ),
649 'width' => array(
650 'type' => 'string',
651 'default' => '600',
652 ),
653 'width_unit' => array(
654 'type' => 'string',
655 'default' => 'px',
656 ),
657 'height' => array(
658 'type' => 'string',
659 'default' => '400',
660 ),
661 'height_unit' => array(
662 'type' => 'string',
663 'default' => 'px',
664 ),
665 'mobile_height' => array(
666 'type' => 'string',
667 'default' => '300',
668 ),
669 'mobile_height_unit' => array(
670 'type' => 'string',
671 'default' => 'px',
672 ),
673 'radius' => array(
674 'type' => 'string',
675 'default' => '0',
676 ),
677 'radius_unit' => array(
678 'type' => 'string',
679 'default' => 'px',
680 ),
681 'border_width' => array(
682 'type' => 'string',
683 'default' => '0',
684 ),
685 'border_style' => array(
686 'type' => 'string',
687 'default' => 'none',
688 ),
689 'border_color' => array(
690 'type' => 'string',
691 'default' => '',
692 ),
693 ),
694 'editor_script' => 'wpvr-block',
695 'render_callback' => 'wpvr_block_render',
696 ));
697 }
698 }
699
700 add_action('init', 'wpvr_block');
701
702 function wpvr_block_render($attributes)
703 {
704 $id = isset($attributes['id']) ? (int) $attributes['id'] : 0;
705 if (!$id) {
706 return '<div class="wpvr-no-tour-selected">' . esc_html__('Please select a tour from the block settings panel.', 'wpvr') . '</div>';
707 }
708
709 $width = isset($attributes['width']) ? $attributes['width'] : '600';
710 if ('fullwidth' === $width) {
711 $width_str = 'fullwidth';
712 } else {
713 $width_unit = isset($attributes['width_unit']) ? $attributes['width_unit'] : 'px';
714 if (preg_match('/(px|%|vw|vh)$/', (string) $width)) {
715 $width_str = $width;
716 } else {
717 $width_str = $width . $width_unit;
718 }
719 }
720
721 $height = isset($attributes['height']) ? $attributes['height'] : '400';
722 $height_unit = isset($attributes['height_unit']) ? $attributes['height_unit'] : 'px';
723 if (!preg_match('/(px|%|vw|vh)$/', (string) $height)) {
724 $height .= $height_unit;
725 }
726
727 $mobile_height = isset($attributes['mobile_height']) ? $attributes['mobile_height'] : '300';
728 $mobile_height_unit = isset($attributes['mobile_height_unit']) ? $attributes['mobile_height_unit'] : 'px';
729 if (!preg_match('/(px|%|vw|vh)$/', (string) $mobile_height)) {
730 $mobile_height .= $mobile_height_unit;
731 }
732
733 $radius = isset($attributes['radius']) ? $attributes['radius'] : '0';
734 $radius_unit = isset($attributes['radius_unit']) ? $attributes['radius_unit'] : 'px';
735 if (!preg_match('/(px|%|vw|vh)$/', (string) $radius)) {
736 $radius .= $radius_unit;
737 }
738
739 $shortcode = sprintf(
740 '[wpvr id="%d" width="%s" height="%s" mobile_height="%s" radius="%s"]',
741 $id,
742 esc_attr($width_str),
743 esc_attr($height),
744 esc_attr($mobile_height),
745 esc_attr($radius)
746 );
747
748 $output = do_shortcode($shortcode);
749
750 // 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).
751 $scripts = '';
752 if (preg_match_all('/<script\b[^>]*>[\s\S]*?<\/script>/i', $output, $matches)) {
753 $scripts = implode("\n", $matches[0]);
754 $output = preg_replace('/<script\b[^>]*>[\s\S]*?<\/script>/i', '', $output);
755 }
756
757 if (!empty($scripts)) {
758 if (wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
759 $output .= "\n" . $scripts;
760 } else {
761 add_action('wp_footer', function() use ($scripts) {
762 echo $scripts; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
763 }, 20);
764 }
765 }
766
767 $border_css = '';
768 if (!empty($attributes['border_width']) && !empty($attributes['border_style']) && 'none' !== $attributes['border_style']) {
769 $border_width = rtrim((string) $attributes['border_width'], 'px') . 'px';
770 $border_color = !empty($attributes['border_color']) ? $attributes['border_color'] : 'transparent';
771 $border_css = 'border: ' . esc_attr($border_width . ' ' . $attributes['border_style'] . ' ' . $border_color) . ';';
772 }
773
774 $class_name = !empty($attributes['className']) ? ' ' . esc_attr($attributes['className']) : '';
775
776 if ('' !== $border_css || '' !== $class_name) {
777 $wrapper_style = $border_css ? ' style="' . $border_css . ' display: inline-block; width: 100%; max-width: ' . esc_attr('fullwidth' === $width_str ? '100%' : $width_str) . ';"' : '';
778 return '<div class="wpvr-block-wrapper' . $class_name . '"' . $wrapper_style . '>' . $output . '</div>';
779 }
780
781 return $output;
782 }
783
784 function sanitize_content_preserve_styles($content, $allow_forms = false) {
785 // Decode HTML entities first (in case content was encoded in database)
786 $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
787
788 // Escape <script> blocks to display as text instead of removing them
789 $content = preg_replace_callback('/<script\b[^>]*>(.*?)<\/script>/si', function($matches) {
790 return esc_html($matches[0]); // Convert to plain text
791 }, $content);
792
793 // Strip dangerous URL-based attributes
794 $content = preg_replace('/(href|action|formaction)\s*=\s*["\']?\s*(javascript|vbscript|data|about):/i', '$1=""', $content);
795
796 // Escape inline event handlers (onclick, onhover, etc.) to display as text
797 $content = preg_replace_callback('/\s*(on\w+)\s*=\s*(["\'])([^"\']*)\2/i', function($matches) {
798 // Escape the attribute value but keep the attribute name visible as text
799 return ' ' . esc_html($matches[1]) . '=' . $matches[2] . esc_html($matches[3]) . $matches[2];
800 }, $content);
801 $content = preg_replace_callback('/\s*(on\w+)\s*=\s*([^>\s]+)/i', function($matches) {
802 // Handle unquoted event handlers
803 return ' ' . esc_html($matches[1]) . '=' . esc_html($matches[2]);
804 }, $content);
805
806 // Remove unsafe embedded/interactive elements
807 if ($allow_forms) {
808 $content = preg_replace('/<(object|embed|applet|frame|frameset|meta|link|base)\b[^>]*>/i', '', $content);
809 $content = preg_replace('/<\/(object|embed|applet|frame|frameset|meta|link|base)>/i', '', $content);
810 } else {
811 $content = preg_replace('/<(object|embed|applet|frame|frameset|meta|link|base|form|input|button|textarea|select|option)\b[^>]*>/i', '', $content);
812 $content = preg_replace('/<\/(object|embed|applet|frame|frameset|meta|link|base|form|input|button|textarea|select|option)>/i', '', $content);
813 }
814
815 // Clean style attributes safely
816 $content = preg_replace_callback('/style\s*=\s*["\']([^"\']*)["\']/', function($matches) {
817 $style = $matches[1];
818 $style = preg_replace('/expression\s*\(/i', '', $style);
819 $style = preg_replace('/(javascript|vbscript|data|about)\s*:/i', '', $style);
820 $style = preg_replace('/url\s*\(\s*["\']?\s*(javascript|vbscript|data):/i', '', $style);
821 $style = preg_replace('/behavior\s*:/i', '', $style);
822 $style = preg_replace('/-moz-binding\s*:/i', '', $style);
823 return 'style="' . esc_attr($style) . '"';
824 }, $content);
825
826 // Sanitize <style> blocks
827 $content = preg_replace_callback('/<style\b[^>]*>(.*?)<\/style>/si', function($matches) {
828 $css = $matches[1];
829 $css = preg_replace('/(expression|javascript|vbscript|data|about)\s*:/i', '', $css);
830 $css = preg_replace('/url\s*\(\s*["\']?\s*(javascript|vbscript|data):/i', '', $css);
831 $css = preg_replace('/behavior\s*:/i', '', $css);
832 $css = preg_replace('/-moz-binding\s*:/i', '', $css);
833 return '<style>' . esc_html($css) . '</style>';
834 }, $content);
835
836 // Allow iframes from safe sources only (e.g., YouTube, Vimeo)
837 $allowed_tags = wp_kses_allowed_html('post');
838 $allowed_tags['iframe'] = [
839 'src' => true,
840 'width' => true,
841 'height' => true,
842 'frameborder' => true,
843 'allowfullscreen' => true,
844 'class' => true,
845 'style' => true,
846 'title' => true,
847 'allow' => true,
848 'name' => true,
849 'referrerpolicy' => true,
850 'loading' => true,
851 'sandbox' => true,
852 ];
853 $allowed_tags['img'] = [
854 'src' => true,
855 'alt' => true,
856 'title' => true,
857 'width' => true,
858 'height' => true,
859 'class' => true,
860 'id' => true,
861 'style' => true,
862 'loading' => true,
863 'srcset' => true,
864 'sizes' => true,
865 ];
866
867 if ($allow_forms) {
868 $form_attributes = [
869 'id' => true,
870 'class' => true,
871 'style' => true,
872 'name' => true,
873 'value' => true,
874 'type' => true,
875 'placeholder' => true,
876 'action' => true,
877 'method' => true,
878 'target' => true,
879 'enctype' => true,
880 'disabled' => true,
881 'readonly' => true,
882 'required' => true,
883 'checked' => true,
884 'selected' => true,
885 'multiple' => true,
886 'size' => true,
887 'rows' => true,
888 'cols' => true,
889 'maxlength' => true,
890 'minlength' => true,
891 'min' => true,
892 'max' => true,
893 'step' => true,
894 'pattern' => true,
895 'autocomplete'=> true,
896 'autofocus' => true,
897 'for' => true,
898 ];
899 $allowed_tags['form'] = $form_attributes;
900 $allowed_tags['input'] = $form_attributes;
901 $allowed_tags['button'] = $form_attributes;
902 $allowed_tags['textarea'] = $form_attributes;
903 $allowed_tags['select'] = $form_attributes;
904 $allowed_tags['option'] = $form_attributes;
905 $allowed_tags['optgroup'] = $form_attributes;
906 $allowed_tags['label'] = $form_attributes;
907 $allowed_tags['fieldset'] = $form_attributes;
908 $allowed_tags['legend'] = $form_attributes;
909 }
910
911 // Apply wp_kses() to keep only allowed tags/attributes
912 $content = wp_kses($content, $allowed_tags);
913
914 // Finally, validate iframe src for security (allow https only, block javascript: etc.)
915 $content = preg_replace_callback('/<iframe[^>]+src=["\']([^"\']+)["\'][^>]*>(?:<\/iframe>)?/i', function($matches) {
916 $src = $matches[1];
917 // Allow any https:// or http:// URL, but block javascript:, data:, vbscript:, etc.
918 if (preg_match('/^(https?:)?\/\//i', $src) && !preg_match('/^(javascript|data|vbscript|about):/i', $src)) {
919 return $matches[0]; // keep safe iframe
920 }
921 // Strip unsafe iframe
922 return '';
923 }, $content);
924
925 return $content;
926 }
927 function wpvr_hex2rgb($colour)
928 {
929 if (isset($colour[0]) && $colour[0] == '#') {
930 $colour = substr($colour, 1);
931 }
932 if (strlen($colour) == 6) {
933 list($r, $g, $b) = array($colour[0] . $colour[1], $colour[2] . $colour[3], $colour[4] . $colour[5]);
934 } elseif (strlen($colour) == 3) {
935 list($r, $g, $b) = array($colour[0] . $colour[0], $colour[1] . $colour[1], $colour[2] . $colour[2]);
936 } else {
937 return false;
938 }
939 $r = hexdec($r);
940 $g = hexdec($g);
941 $b = hexdec($b);
942 return array($r . ', ' . $g . ', ' . $b);
943 }
944
945 function wpvr_HTMLToRGB($htmlCode)
946 {
947 $r = 0;
948 $g = 0;
949 $b = 0;
950 if (isset($htmlCode[0]) && $htmlCode[0] == '#') {
951 $htmlCode = substr($htmlCode, 1);
952 }
953
954 if (strlen($htmlCode) == 3) {
955 $htmlCode = $htmlCode[0] . $htmlCode[0] . $htmlCode[1] . $htmlCode[1] . $htmlCode[2] . $htmlCode[2];
956 }
957
958 if (isset($htmlCode[0]) && isset($htmlCode[1])) {
959 $r = hexdec($htmlCode[0] . $htmlCode[1]);
960 }
961 if (isset($htmlCode[2]) && isset($htmlCode[3])) {
962 $g = hexdec($htmlCode[2] . $htmlCode[3]);
963 }
964 if (isset($htmlCode[4]) && isset($htmlCode[5])) {
965 $b = hexdec($htmlCode[4] . $htmlCode[5]);
966 }
967
968 return $b + ($g << 0x8) + ($r << 0x10);
969 }
970
971 function wpvr_RGBToHSL($RGB)
972 {
973 $r = 0xFF & ($RGB >> 0x10);
974 $g = 0xFF & ($RGB >> 0x8);
975 $b = 0xFF & $RGB;
976
977 $r = ((float)$r) / 255.0;
978 $g = ((float)$g) / 255.0;
979 $b = ((float)$b) / 255.0;
980
981 $maxC = max($r, $g, $b);
982 $minC = min($r, $g, $b);
983
984 $l = ($maxC + $minC) / 2.0;
985
986 if ($maxC == $minC) {
987 $s = 0;
988 $h = 0;
989 } else {
990 if ($l < .5) {
991 $s = ($maxC - $minC) / ($maxC + $minC);
992 } else {
993 $s = ($maxC - $minC) / (2.0 - $maxC - $minC);
994 }
995 if ($r == $maxC) {
996 $h = ($g - $b) / ($maxC - $minC);
997 }
998 if ($g == $maxC) {
999 $h = 2.0 + ($b - $r) / ($maxC - $minC);
1000 }
1001 if ($b == $maxC) {
1002 $h = 4.0 + ($r - $g) / ($maxC - $minC);
1003 }
1004
1005 $h = $h / 6.0;
1006 }
1007
1008 $h = (int)round(255.0 * $h);
1009 $s = (int)round(255.0 * $s);
1010 $l = (int)round(255.0 * $l);
1011
1012 return (object) array('hue' => $h, 'saturation' => $s, 'lightness' => $l);
1013 }
1014
1015 add_action('rest_api_init', 'wpvr_rest_data_route');
1016 function wpvr_rest_data_route()
1017 {
1018 register_rest_route('wpvr/v1', '/panodata/', array(
1019 'methods' => 'GET',
1020 'callback' => 'wpvr_rest_data_set',
1021 'permission_callback' => 'wpvr_rest_route_permission'
1022 ));
1023 }
1024
1025 function wpvr_rest_route_permission()
1026 {
1027 return true;
1028 }
1029
1030 function wpvr_rest_data_set()
1031 {
1032 $query = new WP_Query(array(
1033 'post_type' => 'wpvr_item',
1034 'posts_per_page' => -1,
1035 ));
1036
1037 $wpvr_list = array();
1038 $list_none = array('value' => 0, 'label' => 'None');
1039 array_push($wpvr_list, $list_none);
1040 while ($query->have_posts()) {
1041 $query->the_post();
1042 $title = mb_convert_encoding(get_the_title(), 'UTF-8', 'HTML-ENTITIES');
1043 $post_id = get_the_ID();
1044 $title = $post_id . ' : ' . $title;
1045 $list_ob = array('value' => $post_id, 'label' => $title);
1046 array_push($wpvr_list, $list_ob);
1047 }
1048
1049 return $wpvr_list;
1050 }
1051
1052 function wpvr_isMobileDevice()
1053 {
1054 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"]);
1055 }
1056
1057 function wpvr_directory()
1058 {
1059 $upload = wp_upload_dir();
1060 $upload_dir = $upload['basedir'];
1061 $upload_dir_temp = $upload_dir . '/wpvr/temp/';
1062 if (!is_dir($upload_dir_temp)) {
1063 wp_mkdir_p($upload_dir_temp, 0700);
1064 }
1065 }
1066
1067 add_action('admin_init', 'wpvr_directory');
1068
1069
1070 function wpvr_add_role_cap()
1071 {
1072 $editor_active = get_option('wpvr_editor_active');
1073
1074 $author_active = get_option('wpvr_author_active');
1075
1076 $admin = get_role('administrator');
1077 $admin->add_cap('publish_wpvr_tour');
1078 $admin->add_cap('edit_wpvr_tours');
1079 $admin->add_cap('read_wpvr_tour');
1080 $admin->add_cap('edit_wpvr_tour');
1081 $admin->add_cap('edit_wpvr_tours');
1082 $admin->add_cap('publish_wpvr_tours');
1083 $admin->add_cap('publish_wpvr_tour');
1084 $admin->add_cap('delete_wpvr_tour');
1085 $admin->add_cap('edit_other_wpvr_tours');
1086 $admin->add_cap('delete_other_wpvr_tours');
1087
1088 if ($editor_active == "true") {
1089 $editor = get_role('editor');
1090 if ($editor) {
1091 $editor->add_cap('publish_wpvr_tour');
1092 $editor->add_cap('edit_wpvr_tours');
1093 $editor->add_cap('read_wpvr_tour');
1094 $editor->add_cap('edit_wpvr_tour');
1095 $editor->add_cap('edit_wpvr_tours');
1096 $editor->add_cap('publish_wpvr_tours');
1097 $editor->add_cap('publish_wpvr_tour');
1098 $editor->add_cap('delete_wpvr_tour');
1099 $editor->add_cap('edit_other_wpvr_tours');
1100 $editor->add_cap('delete_other_wpvr_tours');
1101 }
1102 } else {
1103 $editor = get_role('editor');
1104 if ($editor) {
1105 $editor->remove_cap('publish_wpvr_tour');
1106 $editor->remove_cap('edit_wpvr_tours');
1107 $editor->remove_cap('read_wpvr_tour');
1108 $editor->remove_cap('edit_wpvr_tour');
1109 $editor->remove_cap('edit_wpvr_tours');
1110 $editor->remove_cap('publish_wpvr_tours');
1111 $editor->remove_cap('publish_wpvr_tour');
1112 $editor->remove_cap('delete_wpvr_tour');
1113 $editor->remove_cap('edit_other_wpvr_tours');
1114 $editor->remove_cap('delete_other_wpvr_tours');
1115 }
1116 }
1117
1118 if ($author_active == "true") {
1119 $author = get_role('author');
1120 if ($author) {
1121 $author->add_cap('read_wpvr_tour');
1122 $author->add_cap('edit_wpvr_tour');
1123 $author->add_cap('edit_wpvr_tours');
1124 $author->add_cap('publish_wpvr_tours');
1125 $author->add_cap('publish_wpvr_tour');
1126 $author->add_cap('delete_wpvr_tour');
1127 }
1128 } else {
1129 $author = get_role('author');
1130 if ($author) {
1131 $author->remove_cap('read_wpvr_tour');
1132 $author->remove_cap('edit_wpvr_tour');
1133 $author->remove_cap('edit_wpvr_tours');
1134 $author->remove_cap('publish_wpvr_tours');
1135 $author->remove_cap('publish_wpvr_tour');
1136 $author->remove_cap('delete_wpvr_tour');
1137 }
1138 }
1139
1140 if(is_plugin_active( 'dokan-lite/dokan.php' ) || is_plugin_active( 'dokan-pro/dokan.php' )){
1141 $dokan_vendor_active = get_option('dokan_vendor_active');
1142
1143 if( 'true' === $dokan_vendor_active){
1144 $seller = get_role('seller');
1145 if ($seller) {
1146 $seller->add_cap('read_wpvr_tour');
1147 $seller->add_cap('edit_wpvr_tour');
1148 $seller->add_cap('edit_wpvr_tours');
1149 $seller->add_cap('publish_wpvr_tours');
1150 $seller->add_cap('publish_wpvr_tour');
1151 $seller->add_cap('delete_wpvr_tour');
1152 }
1153 } else{
1154 $seller = get_role('seller');
1155 if ($seller) {
1156 $seller->remove_cap('read_wpvr_tour');
1157 $seller->remove_cap('edit_wpvr_tour');
1158 $seller->remove_cap('edit_wpvr_tours');
1159 $seller->remove_cap('publish_wpvr_tours');
1160 $seller->remove_cap('publish_wpvr_tour');
1161 $seller->remove_cap('delete_wpvr_tour');
1162 }
1163 }
1164 }
1165
1166
1167 }
1168
1169 add_action('admin_init', 'wpvr_add_role_cap', 999);
1170
1171 function wpvr_role_management_from_post_type($args, $post_type)
1172 {
1173 if ('wpvr_item' !== $post_type) {
1174 return $args;
1175 }
1176
1177 $editor_active = get_option('wpvr_editor_active');
1178 $author_active = get_option('wpvr_author_active');
1179 $user = wp_get_current_user();
1180
1181 if ($editor_active == "true") {
1182 if (in_array('editor', (array) $user->roles)) {
1183 $args['show_in_menu'] = true;
1184 }
1185 }
1186
1187 if ($author_active == "true") {
1188 if (in_array('author', (array) $user->roles)) {
1189 $args['show_in_menu'] = true;
1190 }
1191 }
1192
1193 if(is_plugin_active( 'dokan-lite/dokan.php' ) || is_plugin_active( 'dokan-pro/dokan.php' )){
1194 $dokan_vendor_active = get_option('dokan_vendor_active');
1195 if( 'true' === $dokan_vendor_active){
1196 if (in_array('seller', (array) $user->roles)) {
1197 $args['show_in_menu'] = true;
1198 }
1199 }
1200
1201 }
1202
1203 return $args;
1204 }
1205 add_filter('register_post_type_args', 'wpvr_role_management_from_post_type', 10, 2);
1206
1207 function wpvr_cache_admin_notice()
1208 {
1209 $option = get_option('wpvr_warning');
1210 if (!$option) {
1211 ?>
1212 <div class="notice notice-warning" id="wpvr-warning" style="position: relative;">
1213 <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>
1214 <button type="button" id="wpvr-dismissible" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button>
1215 </div>
1216 <?php
1217 }
1218 }
1219 // add_action('admin_notices', 'wpvr_cache_admin_notice');
1220
1221 //===Oxygen widget===//
1222 add_action('plugins_loaded', function () {
1223 if (!class_exists('OxyEl')) {
1224 return;
1225 }
1226 require_once __DIR__ . '/oxygen/oxy-manager.php';
1227 });
1228
1229 add_action('init', 'wpvr_mobile_media_handle');
1230 function wpvr_mobile_media_handle()
1231 {
1232 add_image_size('wpvr_mobile', 4096, 2048); //mobile
1233 }
1234
1235
1236 add_action(
1237 /**
1238 * @param $api \VisualComposer\Modules\Api\Factory
1239 */
1240 'vcv:api',
1241 function ($api) {
1242 $elementsToRegister = [
1243 'wpvrelement',
1244 ];
1245 $pluginBaseUrl = rtrim(WPVR_PLUGIN_DIR_URL, '\\/');
1246 /** @var \VisualComposer\Modules\Elements\ApiController $elementsApi */
1247 $elementsApi = $api->elements;
1248 foreach ($elementsToRegister as $tag) {
1249 $manifestPath = __DIR__ . '/vc/' . $tag . '/manifest.json';
1250 $elementBaseUrl = $pluginBaseUrl . '/vc/' . $tag;
1251 $elementsApi->add($manifestPath, $elementBaseUrl);
1252 }
1253 }
1254 );
1255
1256 function wpvr_redirect_after_activation($plugin)
1257 {
1258 if ($plugin == plugin_basename(__FILE__)) {
1259 $url = admin_url('admin.php?page=rex-wpvr-setup-wizard');
1260 $url = esc_url($url, FILTER_SANITIZE_URL);
1261 exit(wp_safe_redirect($url));
1262 }
1263 }
1264 //add_action('activated_plugin', 'wpvr_redirect_after_activation');
1265
1266 function replace_callback($matches)
1267 {
1268 foreach ($matches as $match) {
1269 return str_replace('<img', '<img decoding="async" ', $match);
1270 }
1271 }
1272
1273
1274 function wpvr_get_explainer_embed_url( $input ) {
1275 $url = esc_url_raw( trim( (string) $input ), array( 'http', 'https' ) );
1276
1277 if ( empty( $url ) || ! preg_match( '#^https?://#i', $url ) ) {
1278 return '';
1279 }
1280
1281 $host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) );
1282 $host = preg_replace( '/^www\./', '', $host );
1283 $path = trim( (string) wp_parse_url( $url, PHP_URL_PATH ), '/' );
1284
1285 if ( 'youtu.be' === $host ) {
1286 $segments = explode( '/', $path );
1287 $video_id = isset( $segments[0] ) ? preg_replace( '/[^A-Za-z0-9_-]/', '', $segments[0] ) : '';
1288
1289 return $video_id ? 'https://www.youtube.com/embed/' . $video_id : '';
1290 }
1291
1292 if ( in_array( $host, array( 'youtube.com', 'm.youtube.com', 'music.youtube.com', 'youtube-nocookie.com' ), true ) ) {
1293 $video_id = '';
1294 $segments = explode( '/', $path );
1295
1296 if ( 'watch' === $path ) {
1297 $query = array();
1298 wp_parse_str( (string) wp_parse_url( $url, PHP_URL_QUERY ), $query );
1299 $video_id = isset( $query['v'] ) ? $query['v'] : '';
1300 } elseif ( isset( $segments[0], $segments[1] ) && in_array( $segments[0], array( 'embed', 'shorts', 'live' ), true ) ) {
1301 $video_id = $segments[1];
1302 }
1303
1304 $video_id = preg_replace( '/[^A-Za-z0-9_-]/', '', (string) $video_id );
1305
1306 return $video_id ? 'https://www.youtube.com/embed/' . $video_id : '';
1307 }
1308
1309 if ( 'vimeo.com' === $host || 'player.vimeo.com' === $host ) {
1310 $segments = array_reverse( array_filter( explode( '/', $path ) ) );
1311
1312 foreach ( $segments as $segment ) {
1313 if ( ctype_digit( $segment ) ) {
1314 return 'https://player.vimeo.com/video/' . $segment;
1315 }
1316 }
1317
1318 return '';
1319 }
1320
1321 return $url;
1322 }
1323
1324 function wpvr_sanitize_iframe_only( $input ) {
1325 // Start with standard allowed post HTML (p, a, strong, etc.)
1326 $allowed_tags = wp_kses_allowed_html( 'post' );
1327
1328 // Explicitly allow <iframe> with specific safe attributes
1329 $allowed_tags['iframe'] = array(
1330 'src' => true,
1331 'width' => true,
1332 'height' => true,
1333 'title' => true,
1334 'frameborder' => true,
1335 'allow' => true,
1336 'allowfullscreen' => true,
1337 'referrerpolicy' => true,
1338 );
1339
1340 $content = trim( (string) $input );
1341
1342 if ( '' === $content ) {
1343 return '';
1344 }
1345
1346 if ( false === stripos( $content, '<iframe' ) ) {
1347 $embed_url = wpvr_get_explainer_embed_url( $content );
1348
1349 if ( $embed_url ) {
1350 $content = sprintf(
1351 '<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>',
1352 esc_url( $embed_url )
1353 );
1354 }
1355 }
1356
1357 return wp_kses( $content, $allowed_tags );
1358 }
1359