PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 9.2.0
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v9.2.0
9.2.0 9.1.4 9.1.3 9.1.2 9.1.1 9.1.0 9.0.3 9.0.2 9.0.1 9.0.0 8.5.79 8.5.78 8.5.77 8.5.76 8.5.75 8.5.74 8.5.73 8.5.72 8.5.71 8.5.70 8.5.69 8.5.68 8.5.35 8.5.36 8.5.37 All 224 releases
← All changes | legacy/wpvr.php +153 -43 9.1.0 → 9.2.0 View file →
@@ -376,9 +376,13 @@
376 376 * @return string
377 377 * @since 9.0.0
378 378 */
379 379 function wpvr_render_scene_info_row( $html, $postdata, $id ) {
380 - $pano_id = 'pano' . absint( $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 + }
381 385 $pano_id_json = wp_json_encode( $pano_id );
382 386 $by_label = wp_json_encode( __( 'By', 'wpvr' ) );
383 387 $tour_layout = is_array( $postdata['tourLayout'] ?? null )
384 388 ? ( $postdata['tourLayout']['layout'] ?? 'default' )
@@ -384,9 +388,9 @@
384 388 ? ( $postdata['tourLayout']['layout'] ?? 'default' )
385 389 : ( $postdata['tourLayout'] ?? 'default' );
386 390 $is_classic_layout = wp_json_encode( 'layout1' !== $tour_layout );
387 391
388 - $html .= '<style id="wpvr-scene-info-' . esc_attr( $id ) . '">
392 + $html .= '<style id="wpvr-scene-info-' . esc_attr( $pano_id ) . '">
389 393 #' . esc_attr( $pano_id ) . ' .pnlm-panorama-info.wpvr-scene-info-row {
390 394 display: flex !important;
391 395 align-items: center !important;
392 396 justify-content: center !important;
@@ -482,9 +486,9 @@
482 486 padding-left: 7px !important;
483 487 }
484 488 }
485 489 </style>
486 - <script id="wpvr-scene-info-script-' . esc_attr( $id ) . '">
490 + <script id="wpvr-scene-info-script-' . esc_attr( $pano_id ) . '">
487 491 (function () {
488 492 var pano = document.getElementById(' . $pano_id_json . ');
489 493 var byLabel = ' . $by_label . ';
490 494
@@ -497,9 +501,9 @@
497 501 return;
498 502 }
499 503
500 504 var firstNode = author.firstChild;
501 - if (firstNode && firstNode.nodeType === 3) {
505 + if (firstNode ? firstNode.nodeType === 3 : false) {
502 506 var prefix = byLabel + " ";
503 507 if (firstNode.nodeValue.indexOf(prefix) === 0) {
504 508 firstNode.nodeValue = firstNode.nodeValue.slice(prefix.length);
505 509 }
@@ -525,10 +529,10 @@
525 529 }
526 530
527 531 var title = info.querySelector(".pnlm-title-box");
528 532 var author = info.querySelector(".pnlm-author-box");
529 - var hasTitle = !!(title && title.textContent.trim());
530 - var hasAuthor = !!(author && author.textContent.trim());
533 + var hasTitle = Boolean(title ? title.textContent.trim() : false);
534 + var hasAuthor = Boolean(author ? author.textContent.trim() : false);
531 535
532 536 if (hasTitle) {
533 537 title.setAttribute("title", title.textContent.trim());
534 538 } else if (title) {
@@ -573,8 +577,50 @@
573 577 }
574 578
575 579 add_filter( 'wpvr_generate_tour_layout_html', 'wpvr_render_scene_info_row', 30, 3 );
576 580
581 +if ( ! function_exists( 'wpvr_text_overlay_hex_to_rgba' ) ) {
582 + /**
583 + * Convert HEX color and opacity percentage to RGBA CSS string.
584 + *
585 + * Backward-compatibility wrapper for RexTheme\WPVR\Frontend\TextOverlayRenderer::hex_to_rgba().
586 + *
587 + * @param string $hex Hex color code.
588 + * @param float|int $opacity_percent Opacity 0-100.
589 + * @return string
590 + */
591 + function wpvr_text_overlay_hex_to_rgba( $hex, $opacity_percent = 55 ) {
592 + if ( class_exists( 'RexTheme\WPVR\Frontend\TextOverlayRenderer' ) ) {
593 + return \RexTheme\WPVR\Frontend\TextOverlayRenderer::hex_to_rgba( $hex, $opacity_percent );
594 + }
595 + return 'rgba(40, 30, 25, 0.55)';
596 + }
597 +}
598 +
599 +if ( ! function_exists( 'wpvr_render_frontend_text_overlays' ) ) {
600 + /**
601 + * Render the text overlay layer(s) on the frontend for each scene in the tour.
602 + *
603 + * Backward-compatibility wrapper for RexTheme\WPVR\Frontend\TextOverlayRenderer::render().
604 + *
605 + * @param string $html Generated tour HTML.
606 + * @param array $postdata Saved tour settings.
607 + * @param int $id Tour post ID.
608 + *
609 + * @return string
610 + */
611 + function wpvr_render_frontend_text_overlays( $html, $postdata, $id ) {
612 + if ( class_exists( 'RexTheme\WPVR\Frontend\TextOverlayRenderer' ) ) {
613 + return \RexTheme\WPVR\Frontend\TextOverlayRenderer::render( $html, $postdata, $id );
614 + }
615 + return $html;
616 + }
617 +}
618 +
619 +if ( class_exists( 'RexTheme\WPVR\Frontend\TextOverlayRenderer' ) ) {
620 + \RexTheme\WPVR\Frontend\TextOverlayRenderer::init();
621 +}
622 +
577 623 if ( ! function_exists( 'wpvr_render_explainer_button' ) ) {
578 624 /**
579 625 * Render the WPVR explainer button independently.
580 626 *
@@ -784,12 +830,16 @@
784 830 function sanitize_content_preserve_styles($content, $allow_forms = false) {
785 831 // Decode HTML entities first (in case content was encoded in database)
786 832 $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
787 833
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);
834 + // Escape or strip <script> blocks
835 + if ($allow_forms) {
836 + $content = preg_replace('/<script\b[^>]*>[\s\S]*?<\/script>/i', '', $content);
837 + } else {
838 + $content = preg_replace_callback('/<script\b[^>]*>(.*?)<\/script>/si', function($matches) {
839 + return esc_html($matches[0]); // Convert to plain text
840 + }, $content);
841 + }
792 842
793 843 // Strip dangerous URL-based attributes
794 844 $content = preg_replace('/(href|action|formaction)\s*=\s*["\']?\s*(javascript|vbscript|data|about):/i', '$1=""', $content);
795 845
@@ -832,10 +882,16 @@
832 882 $css = preg_replace('/-moz-binding\s*:/i', '', $css);
833 883 return '<style>' . esc_html($css) . '</style>';
834 884 }, $content);
835 885
836 - // Allow iframes from safe sources only (e.g., YouTube, Vimeo)
886 + // Allow iframes and styles from safe sources only
837 887 $allowed_tags = wp_kses_allowed_html('post');
888 + $allowed_tags['style'] = [
889 + 'type' => true,
890 + 'id' => true,
891 + 'class' => true,
892 + 'media' => true,
893 + ];
838 894 $allowed_tags['iframe'] = [
839 895 'src' => true,
840 896 'width' => true,
841 897 'height' => true,
@@ -865,37 +921,47 @@
865 921 ];
866 922
867 923 if ($allow_forms) {
868 924 $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,
925 + 'id' => true,
926 + 'class' => true,
927 + 'style' => true,
928 + 'name' => true,
929 + 'value' => true,
930 + 'type' => true,
931 + 'placeholder' => true,
932 + 'action' => true,
933 + 'method' => true,
934 + 'target' => true,
935 + 'enctype' => true,
936 + 'disabled' => true,
937 + 'readonly' => true,
938 + 'required' => true,
939 + 'checked' => true,
940 + 'selected' => true,
941 + 'multiple' => true,
942 + 'size' => true,
943 + 'rows' => true,
944 + 'cols' => true,
945 + 'maxlength' => true,
946 + 'minlength' => true,
947 + 'min' => true,
948 + 'max' => true,
949 + 'step' => true,
950 + 'pattern' => true,
951 + 'autocomplete' => true,
952 + 'autofocus' => true,
953 + 'for' => true,
954 + 'data-*' => true,
955 + 'data-form_id' => true,
956 + 'data-form_instance' => true,
957 + 'data-name' => true,
958 + 'data-type' => true,
959 + 'aria-invalid' => true,
960 + 'aria-required' => true,
961 + 'aria-label' => true,
962 + 'aria-describedby' => true,
963 + 'aria-labelledby' => true,
898 964 ];
899 965 $allowed_tags['form'] = $form_attributes;
900 966 $allowed_tags['input'] = $form_attributes;
901 967 $allowed_tags['button'] = $form_attributes;
@@ -905,8 +971,18 @@
905 971 $allowed_tags['optgroup'] = $form_attributes;
906 972 $allowed_tags['label'] = $form_attributes;
907 973 $allowed_tags['fieldset'] = $form_attributes;
908 974 $allowed_tags['legend'] = $form_attributes;
975 + if (!isset($allowed_tags['div'])) {
976 + $allowed_tags['div'] = [];
977 + }
978 + $allowed_tags['div']['data-*'] = true;
979 + $allowed_tags['div']['data-form_id'] = true;
980 + $allowed_tags['div']['data-form_instance'] = true;
981 + if (!isset($allowed_tags['span'])) {
982 + $allowed_tags['span'] = [];
983 + }
984 + $allowed_tags['span']['data-*'] = true;
909 985 }
910 986
911 987 // Apply wp_kses() to keep only allowed tags/attributes
912 988 $content = wp_kses($content, $allowed_tags);
@@ -1023,15 +1099,22 @@
1023 1099 }
1024 1100
1025 1101 function wpvr_rest_route_permission()
1026 1102 {
1027 - return true;
1103 + $post_type_obj = get_post_type_object( 'wpvr_item' );
1104 + $edit_cap = $post_type_obj ? $post_type_obj->cap->edit_posts : 'edit_wpvr_tours';
1105 + return current_user_can( $edit_cap ) || current_user_can( 'edit_posts' );
1028 1106 }
1029 1107
1030 1108 function wpvr_rest_data_set()
1031 1109 {
1110 + $post_type_obj = get_post_type_object( 'wpvr_item' );
1111 + $edit_cap = $post_type_obj ? $post_type_obj->cap->edit_posts : 'edit_wpvr_tours';
1112 + $post_status = current_user_can( $edit_cap ) ? array( 'publish', 'draft', 'private' ) : 'publish';
1113 +
1032 1114 $query = new WP_Query(array(
1033 - 'post_type' => 'wpvr_item',
1115 + 'post_type' => 'wpvr_item',
1116 + 'post_status' => $post_status,
1034 1117 'posts_per_page' => -1,
1035 1118 ));
1036 1119
1037 1120 $wpvr_list = array();
@@ -1044,8 +1127,9 @@
1044 1127 $title = $post_id . ' : ' . $title;
1045 1128 $list_ob = array('value' => $post_id, 'label' => $title);
1046 1129 array_push($wpvr_list, $list_ob);
1047 1130 }
1131 + wp_reset_postdata();
1048 1132
1049 1133 return $wpvr_list;
1050 1134 }
1051 1135
@@ -1050,9 +1134,10 @@
1050 1134 }
1051 1135
1052 1136 function wpvr_isMobileDevice()
1053 1137 {
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"]);
1138 + $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
1139 + return (bool) preg_match("/(android|avantgo|blackberry|bolt|boost|cricket|docomo|fone|hiptop|ipad|mini|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i", $user_agent);
1055 1140 }
1056 1141
1057 1142 function wpvr_directory()
1058 1143 {
@@ -1354,5 +1439,30 @@
1354 1439 }
1355 1440 }
1356 1441
1357 1442 return wp_kses( $content, $allowed_tags );
1443 +}
1444 +
1445 +add_filter('fluentform/form_vars_for_JS', 'wpvr_fluent_form_register_inline_vars', 10, 2);
1446 +/**
1447 + * Register Fluent Forms JS vars safely via WordPress script API.
1448 + * Prevents raw script tag concatenation in HTML while ensuring form handlers initialize.
1449 + *
1450 + * @param array $vars Form configuration variables from Fluent Forms.
1451 + * @param object $form Fluent Form database record.
1452 + * @return array
1453 + */
1454 +function wpvr_fluent_form_register_inline_vars($vars, $form) {
1455 + if (!empty($vars['form_instance'])) {
1456 + $inline_js = 'window.fluent_form_' . esc_js($vars['form_instance']) . ' = ' . wp_json_encode($vars) . ';';
1457 + if (wp_script_is('fluent-form-submission', 'done') || wp_doing_ajax()) {
1458 + if (function_exists('wp_print_inline_script_tag')) {
1459 + wp_print_inline_script_tag($inline_js);
1460 + } else {
1461 + echo '<script type="text/javascript">' . $inline_js . '</script>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1462 + }
1463 + } else {
1464 + wp_add_inline_script('fluent-form-submission', $inline_js, 'before');
1465 + }
1466 + }
1467 + return $vars;
1358 1468 }