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