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 +143 -37 9.1.1 → 9.2.0 View file →
@@ -577,8 +577,50 @@
577 577 }
578 578
579 579 add_filter( 'wpvr_generate_tour_layout_html', 'wpvr_render_scene_info_row', 30, 3 );
580 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 +
581 623 if ( ! function_exists( 'wpvr_render_explainer_button' ) ) {
582 624 /**
583 625 * Render the WPVR explainer button independently.
584 626 *
@@ -788,12 +830,16 @@
788 830 function sanitize_content_preserve_styles($content, $allow_forms = false) {
789 831 // Decode HTML entities first (in case content was encoded in database)
790 832 $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
791 833
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);
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 + }
796 842
797 843 // Strip dangerous URL-based attributes
798 844 $content = preg_replace('/(href|action|formaction)\s*=\s*["\']?\s*(javascript|vbscript|data|about):/i', '$1=""', $content);
799 845
@@ -836,10 +882,16 @@
836 882 $css = preg_replace('/-moz-binding\s*:/i', '', $css);
837 883 return '<style>' . esc_html($css) . '</style>';
838 884 }, $content);
839 885
840 - // Allow iframes from safe sources only (e.g., YouTube, Vimeo)
886 + // Allow iframes and styles from safe sources only
841 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 + ];
842 894 $allowed_tags['iframe'] = [
843 895 'src' => true,
844 896 'width' => true,
845 897 'height' => true,
@@ -869,37 +921,47 @@
869 921 ];
870 922
871 923 if ($allow_forms) {
872 924 $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,
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,
902 964 ];
903 965 $allowed_tags['form'] = $form_attributes;
904 966 $allowed_tags['input'] = $form_attributes;
905 967 $allowed_tags['button'] = $form_attributes;
@@ -909,8 +971,18 @@
909 971 $allowed_tags['optgroup'] = $form_attributes;
910 972 $allowed_tags['label'] = $form_attributes;
911 973 $allowed_tags['fieldset'] = $form_attributes;
912 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;
913 985 }
914 986
915 987 // Apply wp_kses() to keep only allowed tags/attributes
916 988 $content = wp_kses($content, $allowed_tags);
@@ -1027,15 +1099,22 @@
1027 1099 }
1028 1100
1029 1101 function wpvr_rest_route_permission()
1030 1102 {
1031 - 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' );
1032 1106 }
1033 1107
1034 1108 function wpvr_rest_data_set()
1035 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 +
1036 1114 $query = new WP_Query(array(
1037 - 'post_type' => 'wpvr_item',
1115 + 'post_type' => 'wpvr_item',
1116 + 'post_status' => $post_status,
1038 1117 'posts_per_page' => -1,
1039 1118 ));
1040 1119
1041 1120 $wpvr_list = array();
@@ -1048,8 +1127,9 @@
1048 1127 $title = $post_id . ' : ' . $title;
1049 1128 $list_ob = array('value' => $post_id, 'label' => $title);
1050 1129 array_push($wpvr_list, $list_ob);
1051 1130 }
1131 + wp_reset_postdata();
1052 1132
1053 1133 return $wpvr_list;
1054 1134 }
1055 1135
@@ -1054,9 +1134,10 @@
1054 1134 }
1055 1135
1056 1136 function wpvr_isMobileDevice()
1057 1137 {
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"]);
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);
1059 1140 }
1060 1141
1061 1142 function wpvr_directory()
1062 1143 {
@@ -1358,5 +1439,30 @@
1358 1439 }
1359 1440 }
1360 1441
1361 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;
1362 1468 }