| @@ -103,9 +103,9 @@ | ||
| 103 | 103 | } |
| 104 | 104 | |
| 105 | 105 | $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; |
| 106 | 106 | |
| 107 | - if (!wp_verify_nonce($nonce, 'wpvr')) { | |
| 107 | + if ( ! wp_verify_nonce( $nonce, 'wpvr_setup_wizard' ) && ! wp_verify_nonce( $nonce, 'wpvr' ) ) { | |
| 108 | 108 | wp_send_json_error(array('message' => 'Permission denied.'), 403); |
| 109 | 109 | } |
| 110 | 110 | |
| 111 | 111 | update_option('high_res_image', 'true'); |
| @@ -464,9 +464,11 @@ | ||
| 464 | 464 | */ |
| 465 | 465 | public function wpvr_file_import() |
| 466 | 466 | { |
| 467 | 467 | //===Current user capabilities check===// |
| 468 | - if (!current_user_can('edit_posts')) { | |
| 468 | + $post_type_obj = get_post_type_object( 'wpvr_item' ); | |
| 469 | + $edit_cap = $post_type_obj ? $post_type_obj->cap->edit_posts : 'edit_wpvr_tours'; | |
| 470 | + if ( ! current_user_can( $edit_cap ) || ! current_user_can( 'upload_files' ) ) { | |
| 469 | 471 | $response = array( |
| 470 | 472 | 'success' => false, |
| 471 | 473 | 'data' => 'Permission denied.' |
| 472 | 474 | ); |
| @@ -607,9 +609,9 @@ | ||
| 607 | 609 | update_option('wpvr_webp_conversion', $wpvr_webp_conversion); |
| 608 | 610 | update_option('mobile_media_resize', $mobile_media_resize); |
| 609 | 611 | update_option('high_res_image', $high_res_image); |
| 610 | 612 | update_option('dis_on_hover', $dis_on_hover); |
| 611 | - update_option('wpvr_mobile_hotspot_tip', $wpvr_mobile_hotspot_tip ? 'true' : 'false'); | |
| 613 | + update_option('wpvr_mobile_hotspot_tip', 'true' === $wpvr_mobile_hotspot_tip ? 'true' : 'false'); | |
| 612 | 614 | update_option('wpvr_frontend_notice', $wpvr_frontend_notice); |
| 613 | 615 | update_option('wpvr_frontend_notice_area', $wpvr_frontend_notice_area); |
| 614 | 616 | update_option('wpvr_script_control', $wpvr_script_control); |
| 615 | 617 | update_option('wpvr_script_list', $wpvr_script_list); |
| @@ -715,9 +717,9 @@ | ||
| 715 | 717 | return; |
| 716 | 718 | } |
| 717 | 719 | $nonce = filter_input(INPUT_POST, 'security', FILTER_SANITIZE_FULL_SPECIAL_CHARS); |
| 718 | 720 | $nonce = !empty( $nonce ) ? $nonce : null; |
| 719 | - if ( !wp_verify_nonce( $nonce, 'wpvr' ) ) { | |
| 721 | + if ( ! wp_verify_nonce( $nonce, 'wpvr_setup_wizard' ) && ! wp_verify_nonce( $nonce, 'wpvr' ) ) { | |
| 720 | 722 | wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 ); |
| 721 | 723 | return; |
| 722 | 724 | } |
| 723 | 725 | |
| @@ -793,9 +795,9 @@ | ||
| 793 | 795 | return; |
| 794 | 796 | } |
| 795 | 797 | |
| 796 | 798 | $nonce = isset($_POST['security']) ? sanitize_text_field(wp_unslash( $_POST['security'] )) : ''; |
| 797 | - if ( !wp_verify_nonce( $nonce, 'wpvr' ) ) { | |
| 799 | + if ( ! wp_verify_nonce( $nonce, 'wpvr_setup_wizard' ) && ! wp_verify_nonce( $nonce, 'wpvr' ) ) { | |
| 798 | 800 | wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 ); |
| 799 | 801 | return; |
| 800 | 802 | } |
| 801 | 803 | |
| @@ -867,9 +869,9 @@ | ||
| 867 | 869 | return; |
| 868 | 870 | } |
| 869 | 871 | |
| 870 | 872 | $nonce = isset($_POST['security']) ? sanitize_text_field(wp_unslash( $_POST['security'] )) : ''; |
| 871 | - if ( !wp_verify_nonce( $nonce, 'wpvr' ) ) { | |
| 873 | + if ( ! wp_verify_nonce( $nonce, 'wpvr_setup_wizard' ) && ! wp_verify_nonce( $nonce, 'wpvr' ) ) { | |
| 872 | 874 | wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 ); |
| 873 | 875 | return; |
| 874 | 876 | } |
| 875 | 877 | |
| @@ -947,8 +949,14 @@ | ||
| 947 | 949 | 'post_title' => $title, |
| 948 | 950 | 'post_status' => 'publish', |
| 949 | 951 | 'post_type' => 'wpvr_item', |
| 950 | 952 | 'post_author' => get_current_user_id(), |
| 953 | + // meta_input is written before wp_insert_post() fires transition_post_status, | |
| 954 | + // so telemetry listening on that hook sees these flags already set. | |
| 955 | + 'meta_input' => array( | |
| 956 | + 'wpvr_created_from_wizard' => true, | |
| 957 | + 'wpvr_wizard_industry' => $industry, | |
| 958 | + ), | |
| 951 | 959 | ); |
| 952 | 960 | |
| 953 | 961 | $post_id = wp_insert_post( $post_data ); |
| 954 | 962 | if ( is_wp_error( $post_id ) ) { |
| @@ -965,10 +973,8 @@ | ||
| 965 | 973 | } |
| 966 | 974 | $remote_meta['panodata'] = $panodata; |
| 967 | 975 | |
| 968 | 976 | update_post_meta( $post_id, 'panodata', $panodata ); |
| 969 | - update_post_meta( $post_id, 'wpvr_created_from_wizard', true ); | |
| 970 | - update_post_meta( $post_id, 'wpvr_wizard_industry', $industry ); | |
| 971 | 977 | |
| 972 | 978 | if ( ! empty( $remote_meta ) ) { |
| 973 | 979 | foreach ( $remote_meta as $meta_key => $meta_value ) { |
| 974 | 980 | $sanitized_key = sanitize_key( $meta_key ); |
| @@ -1057,9 +1063,18 @@ | ||
| 1057 | 1063 | } |
| 1058 | 1064 | |
| 1059 | 1065 | $attachment_id = attachment_url_to_postid( $source_url ); |
| 1060 | 1066 | if ( ! $attachment_id ) { |
| 1061 | - $attachment_id = media_sideload_image( $source_url, $post_id, null, 'id' ); | |
| 1067 | + if ( ! current_user_can( 'upload_files' ) ) { | |
| 1068 | + continue; | |
| 1069 | + } | |
| 1070 | + | |
| 1071 | + $valid_url = wp_http_validate_url( $source_url ); | |
| 1072 | + if ( ! $valid_url ) { | |
| 1073 | + continue; | |
| 1074 | + } | |
| 1075 | + | |
| 1076 | + $attachment_id = media_sideload_image( $valid_url, $post_id, null, 'id' ); | |
| 1062 | 1077 | if ( is_wp_error( $attachment_id ) ) { |
| 1063 | 1078 | continue; |
| 1064 | 1079 | } |
| 1065 | 1080 | } |
| @@ -1114,9 +1129,9 @@ | ||
| 1114 | 1129 | return; |
| 1115 | 1130 | } |
| 1116 | 1131 | |
| 1117 | 1132 | $nonce = isset($_POST['security']) ? sanitize_text_field(wp_unslash( $_POST['security'] )) : ''; |
| 1118 | - if ( !wp_verify_nonce( $nonce, 'wpvr' ) ) { | |
| 1133 | + if ( ! wp_verify_nonce( $nonce, 'wpvr_setup_wizard' ) && ! wp_verify_nonce( $nonce, 'wpvr' ) ) { | |
| 1119 | 1134 | wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 ); |
| 1120 | 1135 | return; |
| 1121 | 1136 | } |
| 1122 | 1137 | |
| @@ -1176,15 +1191,17 @@ | ||
| 1176 | 1191 | * |
| 1177 | 1192 | * @since 8.5.48 |
| 1178 | 1193 | */ |
| 1179 | 1194 | public function wpvr_create_tour_from_wizard() { |
| 1180 | - if ( ! current_user_can( 'edit_posts' ) ) { | |
| 1195 | + $post_type_obj = get_post_type_object( 'wpvr_item' ); | |
| 1196 | + $create_cap = $post_type_obj ? $post_type_obj->cap->edit_posts : 'edit_wpvr_tours'; | |
| 1197 | + if ( ! current_user_can( $create_cap ) ) { | |
| 1181 | 1198 | wp_send_json_error( array( 'message' => 'Unauthorized user' ), 403 ); |
| 1182 | 1199 | return; |
| 1183 | 1200 | } |
| 1184 | 1201 | |
| 1185 | 1202 | $nonce = isset($_POST['security']) ? sanitize_text_field(wp_unslash( $_POST['security'] )) : ''; |
| 1186 | - if ( !wp_verify_nonce( $nonce, 'wpvr' ) ) { | |
| 1203 | + if ( ! wp_verify_nonce( $nonce, 'wpvr_setup_wizard' ) && ! wp_verify_nonce( $nonce, 'wpvr' ) ) { | |
| 1187 | 1204 | wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 ); |
| 1188 | 1205 | return; |
| 1189 | 1206 | } |
| 1190 | 1207 | |
| @@ -1197,8 +1214,30 @@ | ||
| 1197 | 1214 | wp_send_json_error( array( 'message' => 'Panodata is required' ) ); |
| 1198 | 1215 | return; |
| 1199 | 1216 | } |
| 1200 | 1217 | |
| 1218 | + if ( is_array( $panodata ) ) { | |
| 1219 | + if ( isset( $panodata['previewtext'] ) ) { | |
| 1220 | + $panodata['previewtext'] = sanitize_text_field( $panodata['previewtext'] ); | |
| 1221 | + } | |
| 1222 | + if ( isset( $panodata['defaultscene'] ) && is_string( $panodata['defaultscene'] ) ) { | |
| 1223 | + $panodata['defaultscene'] = preg_replace( '/[^0-9a-zA-Z_\-]/', '', $panodata['defaultscene'] ); | |
| 1224 | + } | |
| 1225 | + if ( isset( $panodata['panodata']['firstScene'] ) && is_string( $panodata['panodata']['firstScene'] ) ) { | |
| 1226 | + $panodata['panodata']['firstScene'] = preg_replace( '/[^0-9a-zA-Z_\-]/', '', $panodata['panodata']['firstScene'] ); | |
| 1227 | + } | |
| 1228 | + if ( isset( $panodata['panodata']['scene-list'] ) && is_array( $panodata['panodata']['scene-list'] ) ) { | |
| 1229 | + foreach ( $panodata['panodata']['scene-list'] as $s_idx => $s_val ) { | |
| 1230 | + if ( isset( $s_val['scene-id'] ) && is_string( $s_val['scene-id'] ) ) { | |
| 1231 | + $panodata['panodata']['scene-list'][ $s_idx ]['scene-id'] = preg_replace( '/[^0-9a-zA-Z_\-]/', '', $s_val['scene-id'] ); | |
| 1232 | + } | |
| 1233 | + } | |
| 1234 | + } | |
| 1235 | + } | |
| 1236 | + | |
| 1237 | + $publish_cap = $post_type_obj ? $post_type_obj->cap->publish_posts : 'publish_wpvr_tours'; | |
| 1238 | + $target_status = current_user_can( $publish_cap ) ? 'publish' : 'draft'; | |
| 1239 | + | |
| 1201 | 1240 | if ( $existing_post_id > 0 ) { |
| 1202 | 1241 | $existing_post = get_post( $existing_post_id ); |
| 1203 | 1242 | if ( ! $existing_post || 'wpvr_item' !== $existing_post->post_type || ! current_user_can( 'edit_post', $existing_post_id ) ) { |
| 1204 | 1243 | wp_send_json_error( array( 'message' => 'Invalid existing tour ID' ) ); |
| @@ -1209,9 +1248,14 @@ | ||
| 1209 | 1248 | wp_update_post( |
| 1210 | 1249 | array( |
| 1211 | 1250 | 'ID' => $post_id, |
| 1212 | 1251 | 'post_title' => $title, |
| 1213 | - 'post_status' => 'publish', | |
| 1252 | + 'post_status' => $target_status, | |
| 1253 | + // See note below: meta_input lands before transition_post_status fires. | |
| 1254 | + 'meta_input' => array( | |
| 1255 | + 'wpvr_created_from_wizard' => true, | |
| 1256 | + 'wpvr_wizard_industry' => $industry, | |
| 1257 | + ), | |
| 1214 | 1258 | ) |
| 1215 | 1259 | ); |
| 1216 | 1260 | } else { |
| 1217 | 1261 | // Create new post |
| @@ -1216,11 +1260,17 @@ | ||
| 1216 | 1260 | } else { |
| 1217 | 1261 | // Create new post |
| 1218 | 1262 | $post_data = array( |
| 1219 | 1263 | 'post_title' => $title, |
| 1220 | - 'post_status' => 'publish', | |
| 1264 | + 'post_status' => $target_status, | |
| 1221 | 1265 | 'post_type' => 'wpvr_item', |
| 1222 | 1266 | 'post_author' => get_current_user_id(), |
| 1267 | + // meta_input is written before wp_insert_post() fires transition_post_status, | |
| 1268 | + // so telemetry listening on that hook sees these flags already set. | |
| 1269 | + 'meta_input' => array( | |
| 1270 | + 'wpvr_created_from_wizard' => true, | |
| 1271 | + 'wpvr_wizard_industry' => $industry, | |
| 1272 | + ), | |
| 1223 | 1273 | ); |
| 1224 | 1274 | |
| 1225 | 1275 | $post_id = wp_insert_post( $post_data ); |
| 1226 | 1276 | |
| @@ -1243,22 +1293,34 @@ | ||
| 1243 | 1293 | |
| 1244 | 1294 | // Save panodata as post meta |
| 1245 | 1295 | update_post_meta( $post_id, 'panodata', $panodata ); |
| 1246 | 1296 | |
| 1247 | - // Mark as created from wizard | |
| 1248 | - update_post_meta( $post_id, 'wpvr_created_from_wizard', true ); | |
| 1249 | - update_post_meta( $post_id, 'wpvr_wizard_industry', $industry ); | |
| 1250 | - | |
| 1251 | 1297 | // Save template meta fields if provided (dynamic meta from API) |
| 1252 | 1298 | $template_meta = isset($_POST['templateMeta']) ? json_decode( wp_unslash( $_POST['templateMeta'] ), true ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 1253 | 1299 | if ( ! empty( $template_meta ) && is_array( $template_meta ) ) { |
| 1300 | + $allowed_meta_keys = apply_filters( | |
| 1301 | + 'wpvr_wizard_allowed_template_meta_keys', | |
| 1302 | + array( | |
| 1303 | + 'wpvr_created_from_wizard', | |
| 1304 | + 'wpvr_wizard_industry', | |
| 1305 | + 'wpvr_tour_layout', | |
| 1306 | + 'wpvr_custom_css', | |
| 1307 | + 'wpvr_streetview_settings', | |
| 1308 | + 'wpvr_general_settings', | |
| 1309 | + 'wpvr_controls_settings', | |
| 1310 | + 'wpvr_floor_plan_settings', | |
| 1311 | + 'wpvr_checklist', | |
| 1312 | + ) | |
| 1313 | + ); | |
| 1314 | + | |
| 1254 | 1315 | foreach ( $template_meta as $meta_key => $meta_value ) { |
| 1255 | 1316 | // Sanitize meta key to ensure it's a valid meta key |
| 1256 | 1317 | $sanitized_key = sanitize_key( $meta_key ); |
| 1257 | - if ( ! empty( $sanitized_key ) && 'panodata' !== $sanitized_key ) { | |
| 1318 | + if ( ! empty( $sanitized_key ) && in_array( $sanitized_key, $allowed_meta_keys, true ) ) { | |
| 1258 | 1319 | // Handle different value types |
| 1259 | 1320 | if ( is_array( $meta_value ) ) { |
| 1260 | - update_post_meta( $post_id, $sanitized_key, $meta_value ); | |
| 1321 | + $sanitized_value = map_deep( $meta_value, 'sanitize_text_field' ); | |
| 1322 | + update_post_meta( $post_id, $sanitized_key, $sanitized_value ); | |
| 1261 | 1323 | } else { |
| 1262 | 1324 | update_post_meta( $post_id, $sanitized_key, sanitize_text_field( $meta_value ) ); |
| 1263 | 1325 | } |
| 1264 | 1326 | } |
| @@ -1268,13 +1330,15 @@ | ||
| 1268 | 1330 | // Trigger tour saved action for telemetry |
| 1269 | 1331 | do_action('wpvr_rex_wpvr_tour_saved', $post_id); |
| 1270 | 1332 | do_action( 'wpvr_setup_wizard_completed_event', $industry ); |
| 1271 | 1333 | |
| 1272 | - // Persist industry selection for telemetry (aha event fires later from consent handler). | |
| 1273 | - update_option( 'wpvr_industry_name', sanitize_text_field( $industry ), false ); | |
| 1334 | + if ( current_user_can( 'manage_options' ) ) { | |
| 1335 | + // Persist industry selection for telemetry (aha event fires later from consent handler). | |
| 1336 | + update_option( 'wpvr_industry_name', sanitize_text_field( $industry ), false ); | |
| 1274 | 1337 | |
| 1275 | - // Mark wizard as permanently done so the onboarding notice is suppressed. | |
| 1276 | - update_option( 'wpvr_wizard_onboarding_done', '1', false ); | |
| 1338 | + // Mark wizard as permanently done so the onboarding notice is suppressed. | |
| 1339 | + update_option( 'wpvr_wizard_onboarding_done', '1', false ); | |
| 1340 | + } | |
| 1277 | 1341 | |
| 1278 | 1342 | wp_send_json_success( array( |
| 1279 | 1343 | 'post_id' => $post_id, |
| 1280 | 1344 | 'edit_url' => admin_url( 'post.php?action=edit&post=' . $post_id ), |