| @@ -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 | } |