PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 9.1.3
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v9.1.3
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
← All changes | legacy/wpvr.php +109 -42 9.1.09.1.3 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) {
@@ -784,12 +788,16 @@
784 788 function sanitize_content_preserve_styles($content, $allow_forms = false) {
785 789 // Decode HTML entities first (in case content was encoded in database)
786 790 $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
787 791
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 + // Escape or strip <script> blocks
793 + if ($allow_forms) {
794 + $content = preg_replace('/<script\b[^>]*>[\s\S]*?<\/script>/i', '', $content);
795 + } else {
796 + $content = preg_replace_callback('/<script\b[^>]*>(.*?)<\/script>/si', function($matches) {
797 + return esc_html($matches[0]); // Convert to plain text
798 + }, $content);
799 + }
792 800
793 801 // Strip dangerous URL-based attributes
794 802 $content = preg_replace('/(href|action|formaction)\s*=\s*["\']?\s*(javascript|vbscript|data|about):/i', '$1=""', $content);
795 803
@@ -832,10 +840,16 @@
832 840 $css = preg_replace('/-moz-binding\s*:/i', '', $css);
833 841 return '<style>' . esc_html($css) . '</style>';
834 842 }, $content);
835 843
836 - // Allow iframes from safe sources only (e.g., YouTube, Vimeo)
844 + // Allow iframes and styles from safe sources only
837 845 $allowed_tags = wp_kses_allowed_html('post');
846 + $allowed_tags['style'] = [
847 + 'type' => true,
848 + 'id' => true,
849 + 'class' => true,
850 + 'media' => true,
851 + ];
838 852 $allowed_tags['iframe'] = [
839 853 'src' => true,
840 854 'width' => true,
841 855 'height' => true,
@@ -865,37 +879,47 @@
865 879 ];
866 880
867 881 if ($allow_forms) {
868 882 $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,
883 + 'id' => true,
884 + 'class' => true,
885 + 'style' => true,
886 + 'name' => true,
887 + 'value' => true,
888 + 'type' => true,
889 + 'placeholder' => true,
890 + 'action' => true,
891 + 'method' => true,
892 + 'target' => true,
893 + 'enctype' => true,
894 + 'disabled' => true,
895 + 'readonly' => true,
896 + 'required' => true,
897 + 'checked' => true,
898 + 'selected' => true,
899 + 'multiple' => true,
900 + 'size' => true,
901 + 'rows' => true,
902 + 'cols' => true,
903 + 'maxlength' => true,
904 + 'minlength' => true,
905 + 'min' => true,
906 + 'max' => true,
907 + 'step' => true,
908 + 'pattern' => true,
909 + 'autocomplete' => true,
910 + 'autofocus' => true,
911 + 'for' => true,
912 + 'data-*' => true,
913 + 'data-form_id' => true,
914 + 'data-form_instance' => true,
915 + 'data-name' => true,
916 + 'data-type' => true,
917 + 'aria-invalid' => true,
918 + 'aria-required' => true,
919 + 'aria-label' => true,
920 + 'aria-describedby' => true,
921 + 'aria-labelledby' => true,
898 922 ];
899 923 $allowed_tags['form'] = $form_attributes;
900 924 $allowed_tags['input'] = $form_attributes;
901 925 $allowed_tags['button'] = $form_attributes;
@@ -905,8 +929,18 @@
905 929 $allowed_tags['optgroup'] = $form_attributes;
906 930 $allowed_tags['label'] = $form_attributes;
907 931 $allowed_tags['fieldset'] = $form_attributes;
908 932 $allowed_tags['legend'] = $form_attributes;
933 + if (!isset($allowed_tags['div'])) {
934 + $allowed_tags['div'] = [];
935 + }
936 + $allowed_tags['div']['data-*'] = true;
937 + $allowed_tags['div']['data-form_id'] = true;
938 + $allowed_tags['div']['data-form_instance'] = true;
939 + if (!isset($allowed_tags['span'])) {
940 + $allowed_tags['span'] = [];
941 + }
942 + $allowed_tags['span']['data-*'] = true;
909 943 }
910 944
911 945 // Apply wp_kses() to keep only allowed tags/attributes
912 946 $content = wp_kses($content, $allowed_tags);
@@ -1023,15 +1057,22 @@
1023 1057 }
1024 1058
1025 1059 function wpvr_rest_route_permission()
1026 1060 {
1027 - return true;
1061 + $post_type_obj = get_post_type_object( 'wpvr_item' );
1062 + $edit_cap = $post_type_obj ? $post_type_obj->cap->edit_posts : 'edit_wpvr_tours';
1063 + return current_user_can( $edit_cap ) || current_user_can( 'edit_posts' );
1028 1064 }
1029 1065
1030 1066 function wpvr_rest_data_set()
1031 1067 {
1068 + $post_type_obj = get_post_type_object( 'wpvr_item' );
1069 + $edit_cap = $post_type_obj ? $post_type_obj->cap->edit_posts : 'edit_wpvr_tours';
1070 + $post_status = current_user_can( $edit_cap ) ? array( 'publish', 'draft', 'private' ) : 'publish';
1071 +
1032 1072 $query = new WP_Query(array(
1033 - 'post_type' => 'wpvr_item',
1073 + 'post_type' => 'wpvr_item',
1074 + 'post_status' => $post_status,
1034 1075 'posts_per_page' => -1,
1035 1076 ));
1036 1077
1037 1078 $wpvr_list = array();
@@ -1044,8 +1085,9 @@
1044 1085 $title = $post_id . ' : ' . $title;
1045 1086 $list_ob = array('value' => $post_id, 'label' => $title);
1046 1087 array_push($wpvr_list, $list_ob);
1047 1088 }
1089 + wp_reset_postdata();
1048 1090
1049 1091 return $wpvr_list;
1050 1092 }
1051 1093
@@ -1354,5 +1396,30 @@
1354 1396 }
1355 1397 }
1356 1398
1357 1399 return wp_kses( $content, $allowed_tags );
1400 +}
1401 +
1402 +add_filter('fluentform/form_vars_for_JS', 'wpvr_fluent_form_register_inline_vars', 10, 2);
1403 +/**
1404 + * Register Fluent Forms JS vars safely via WordPress script API.
1405 + * Prevents raw script tag concatenation in HTML while ensuring form handlers initialize.
1406 + *
1407 + * @param array $vars Form configuration variables from Fluent Forms.
1408 + * @param object $form Fluent Form database record.
1409 + * @return array
1410 + */
1411 +function wpvr_fluent_form_register_inline_vars($vars, $form) {
1412 + if (!empty($vars['form_instance'])) {
1413 + $inline_js = 'window.fluent_form_' . esc_js($vars['form_instance']) . ' = ' . wp_json_encode($vars) . ';';
1414 + if (wp_script_is('fluent-form-submission', 'done') || wp_doing_ajax()) {
1415 + if (function_exists('wp_print_inline_script_tag')) {
1416 + wp_print_inline_script_tag($inline_js);
1417 + } else {
1418 + echo '<script type="text/javascript">' . $inline_js . '</script>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1419 + }
1420 + } else {
1421 + wp_add_inline_script('fluent-form-submission', $inline_js, 'before');
1422 + }
1423 + }
1424 + return $vars;
1358 1425 }