PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
← All changes | includes/page-form-builder/ajax/bfb-ajax.php +177 -66 11.6.1 → 11.9 View file →
@@ -140,8 +140,23 @@
140 140 '--wpbc-bfb-col-ai',
141 141 '--wpbc-bfb-col-gap',
142 142 '--wpbc-bfb-col-ac',
143 143 '--wpbc-bfb-col-aself',
144 + '--wpbc-bfb-col-padding',
145 + '--wpbc-bfb-col-margin',
146 + '--wpbc-bfb-col-padding-top',
147 + '--wpbc-bfb-col-padding-right',
148 + '--wpbc-bfb-col-padding-bottom',
149 + '--wpbc-bfb-col-padding-left',
150 + '--wpbc-bfb-col-margin-top',
151 + '--wpbc-bfb-col-margin-right',
152 + '--wpbc-bfb-col-margin-bottom',
153 + '--wpbc-bfb-col-margin-left',
154 + '--wpbc-bfb-col-max-width',
155 + '--wpbc-bfb-col-max-height',
156 + '--wpbc-bfb-col-overflow',
157 + '--wpbc-bfb-col-overflow-x',
158 + '--wpbc-bfb-col-overflow-y',
144 159 '--wpbc-bfb-form-background',
145 160 '--wpbc-bfb-form-border-color',
146 161 '--wpbc-bfb-form-border-width',
147 162 '--wpbc-bfb-form-border-radius',
@@ -722,22 +737,43 @@
722 737 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
723 738 $preview_form_style_raw = isset( $_POST['preview_form_style'] ) ? wp_unslash( $_POST['preview_form_style'] ) : '';
724 739
725 740 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
726 - $content_form_raw = isset( $_POST['content_form'] ) ? wp_unslash( $_POST['content_form'] ) : '';
727 - $content_form = wpbc_bfb_sanitize_form_text( $content_form_raw );
741 + $content_form_raw = isset( $_POST['content_form'] ) && is_scalar( $_POST['content_form'] )
742 + ? wp_unslash( $_POST['content_form'] )
743 + : '';
744 + $content_form = wpbc_bfb_sanitize_form_text( $content_form_raw );
745 +
746 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
747 + $advanced_form_raw = isset( $_POST['advanced_form'] ) && is_scalar( $_POST['advanced_form'] )
748 + ? wp_unslash( $_POST['advanced_form'] )
749 + : '';
750 + $advanced_form = wpbc_bfb_sanitize_form_text( $advanced_form_raw );
728 751
729 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
730 - $advanced_form_raw = isset( $_POST['advanced_form'] ) ? wp_unslash( $_POST['advanced_form'] ) : '';
731 - $advanced_form = wpbc_bfb_sanitize_form_text( $advanced_form_raw );
732 752
733 -
734 753 // Validate structure JSON.
735 754 $structure_arr = json_decode( $structure_raw, true );
736 - if ( ! is_array( $structure_arr ) ) {
737 - wp_send_json_error( array( 'code' => 'invalid_structure', 'message' => __( 'Form structure is not a valid JSON object.', 'booking' ) ) );
738 - }
739 -
755 + if ( ! is_array( $structure_arr ) ) {
756 + wp_send_json_error( array( 'code' => 'invalid_structure', 'message' => __( 'Form structure is not a valid JSON object.', 'booking' ) ) );
757 + }
758 + $preview_structure_arr = $structure_arr;
759 +
760 + /**
761 + * Filter and sanitize a decoded Form Builder structure before persistence.
762 + *
763 + * Field packs may normalize only their own stored properties. Callbacks must
764 + * return the complete structure and must not perform persistence or output.
765 + *
766 + * @since 11.8.4
767 + *
768 + * @param array $structure_arr Decoded Form Builder structure.
769 + */
770 + $structure_arr = apply_filters( 'wpbc_bfb_sanitize_structure_before_save', $structure_arr );
771 +
772 + if ( ! is_array( $structure_arr ) ) {
773 + wp_send_json_error( array( 'code' => 'invalid_structure', 'message' => __( 'Form structure could not be normalized.', 'booking' ) ) );
774 + }
775 +
740 776 // Settings JSON (normalized to the ONLY supported schema).
741 777 $settings_arr = wpbc_bfb__normalize_settings_array( $settings_raw );
742 778 $preview_form_style = wpbc_bfb__normalize_preview_form_style( $preview_form_style_raw );
743 779 // $advanced_mode_source = ( isset( $settings_arr['bfb_options']['advanced_mode_source'] ) ) ? (string) $settings_arr['bfb_options']['advanced_mode_source'] : 'builder';
@@ -874,9 +910,17 @@
874 910 if ( $return_preview_url && 'preview' === $status && class_exists( 'WPBC_BFB_Preview_Service' ) ) {
875 911
876 912 $preview_service = WPBC_BFB_Preview_Service::get_instance();
877 913
878 - $res = $preview_service->create_preview_session( $preview_form_id, wpbc_get_current_user_id(), $structure_arr, $form_name, $advanced_form, $content_form, $preview_form_style );
914 + $res = $preview_service->create_preview_session(
915 + $preview_form_id,
916 + get_current_user_id(),
917 + $preview_structure_arr,
918 + $form_name,
919 + $advanced_form_raw,
920 + $content_form_raw,
921 + $preview_form_style
922 + );
879 923
880 924 if ( is_array( $res ) && ! empty( $res['preview_url'] ) ) {
881 925 $preview_url = (string) $res['preview_url'];
882 926 $preview_token = ! empty( $res['token'] ) ? (string) $res['token'] : '';
@@ -882,27 +926,9 @@
882 926 $preview_token = ! empty( $res['token'] ) ? (string) $res['token'] : '';
883 927 }
884 928 }
885 929
886 - $setup_step_saved = false;
887 - $setup_step = isset( $_POST['wpbc_setup_step'] ) ? sanitize_key( wp_unslash( $_POST['wpbc_setup_step'] ) ) : '';
888 - if ( ! empty( $setup_step ) && class_exists( 'WPBC_SETUP_WIZARD_STEPS' ) ) {
889 - $setup_steps = new WPBC_SETUP_WIZARD_STEPS();
890 - $steps_arr = $setup_steps->get_steps_arr();
891 - if ( function_exists( 'wpbc_setup_wizard__detect_step_from_admin_url' ) ) {
892 - $referer_step = wpbc_setup_wizard__detect_step_from_admin_url( wp_get_referer() );
893 - if ( ! empty( $referer_step ) && isset( $steps_arr[ $referer_step ] ) ) {
894 - $setup_step = $referer_step;
895 - }
896 - }
897 - if ( isset( $steps_arr[ $setup_step ] ) ) {
898 - $setup_steps->db__set_step_as_saved( $setup_step, true );
899 - $setup_steps->db__save_current_step_name( $setup_step );
900 - $setup_step_saved = true;
901 - }
902 - }
903 -
904 - wp_send_json_success(
930 + wp_send_json_success(
905 931 array(
906 932 'booking_form_id' => $booking_form_id,
907 933 'form_name' => $form_name,
908 934 'engine' => $engine,
@@ -911,10 +937,9 @@
911 937 'token' => $preview_token,
912 938 'title' => isset( $form_config['title'] ) ? (string) $form_config['title'] : '',
913 939 'description' => isset( $form_config['description'] ) ? (string) $form_config['description'] : '',
914 940 'picture_url' => isset( $form_config['picture_url'] ) ? (string) $form_config['picture_url'] : '',
915 - 'setup_step_saved' => $setup_step_saved,
916 - )
941 + )
917 942 );
918 943
919 944 }
920 945 add_action( 'wp_ajax_' . 'WPBC_AJX_BFB_SAVE_FORM_CONFIG', 'wpbc_bfb_ajax_save_form_config' );
@@ -1283,32 +1308,101 @@
1283 1308 }
1284 1309 add_action( 'wp_ajax_' . 'WPBC_AJX_BFB_CREATE_FORM_CONFIG', 'wpbc_bfb_ajax_create_form_config' );
1285 1310
1286 1311
1287 -/**
1288 - * Handle AJAX request: list booking forms for current user (and optionally global ones).
1289 - *
1290 - * Security:
1291 - * - Verifies wpbc_bfb_form_list nonce (sent as 'nonce').
1292 - * - Requires current_user_can( wpbc_bfb_get_manage_cap() ).
1293 - *
1294 - * Expects POST:
1295 - * - nonce : string Nonce for 'wpbc_bfb_form_list'.
1296 - * - include_global : 0|1 If 1, include global forms (owner_user_id=0/NULL) in addition to user-owned.
1297 - * - status : string Default 'published'. Allowed: published|preview|draft|archived|template
1298 - * - search : string Optional filter by title/slug/description
1299 - * - limit : int Optional max rows (default 20, max 500)
1300 - * - page : int Optional page number, starts from 1
1301 - *
1302 - * Response (JSON):
1303 - * - success: true|false
1304 - * - data: { forms: [ ... ] }
1305 - *
1306 - * @since 11.0.0
1307 - *
1308 - * @return void
1309 - */
1310 -function wpbc_bfb_ajax_list_forms() {
1312 +/**
1313 + * Build optional adjacency-aware ordering for the template library.
1314 + *
1315 + * Domains may register stable before/after slug pairs through
1316 + * `wpbc_bfb_template_library_adjacencies`. The list endpoint keeps every pair
1317 + * together at the existing target template's chronological position without
1318 + * placing domain slugs or other business knowledge in the shared controller.
1319 + *
1320 + * @param string $table_name Trusted Booking Form structures table name.
1321 + *
1322 + * @return array SQL fragments and ordered prepare arguments.
1323 + */
1324 +function wpbc_bfb_get_template_library_order_clauses( $table_name ) {
1325 +
1326 + $adjacencies = apply_filters( 'wpbc_bfb_template_library_adjacencies', array() );
1327 +
1328 + if ( ! is_array( $adjacencies ) ) {
1329 + $adjacencies = array();
1330 + }
1331 +
1332 + $effective_updated_at_cases = array();
1333 + $adjacency_rank_cases = array();
1334 + $effective_updated_at_args = array();
1335 + $adjacency_rank_args = array();
1336 +
1337 + foreach ( $adjacencies as $adjacency ) {
1338 + if ( ! is_array( $adjacency ) ) {
1339 + continue;
1340 + }
1341 +
1342 + $before_slug = isset( $adjacency['before'] ) ? sanitize_title( (string) $adjacency['before'] ) : '';
1343 + $after_slug = isset( $adjacency['after'] ) ? sanitize_title( (string) $adjacency['after'] ) : '';
1344 +
1345 + if ( '' === $before_slug || '' === $after_slug || $before_slug === $after_slug ) {
1346 + continue;
1347 + }
1348 +
1349 + $effective_updated_at_cases[] = "WHEN form_slug = %s THEN COALESCE(
1350 + ( SELECT MAX( wpbc_adjacent_target.updated_at )
1351 + FROM {$table_name} AS wpbc_adjacent_target
1352 + WHERE wpbc_adjacent_target.form_slug = %s
1353 + AND wpbc_adjacent_target.status = 'template'
1354 + AND ( wpbc_adjacent_target.owner_user_id = 0 OR wpbc_adjacent_target.owner_user_id IS NULL ) ),
1355 + updated_at
1356 + )";
1357 + $effective_updated_at_args[] = $before_slug;
1358 + $effective_updated_at_args[] = $after_slug;
1359 +
1360 + $adjacency_rank_cases[] = 'WHEN form_slug = %s THEN 2 WHEN form_slug = %s THEN 1';
1361 + $adjacency_rank_args[] = $before_slug;
1362 + $adjacency_rank_args[] = $after_slug;
1363 + }
1364 +
1365 + if ( empty( $effective_updated_at_cases ) ) {
1366 + return array(
1367 + 'effective_updated_at_sql' => 'updated_at',
1368 + 'adjacency_rank_sql' => '',
1369 + 'args' => array(),
1370 + );
1371 + }
1372 +
1373 + return array(
1374 + 'effective_updated_at_sql' => 'CASE ' . implode( ' ', $effective_updated_at_cases ) . ' ELSE updated_at END',
1375 + 'adjacency_rank_sql' => 'CASE ' . implode( ' ', $adjacency_rank_cases ) . ' ELSE 0 END',
1376 + 'args' => array_merge( $effective_updated_at_args, $adjacency_rank_args ),
1377 + );
1378 +}
1379 +
1380 +/**
1381 + * Handle AJAX request: list booking forms for current user (and optionally global ones).
1382 + *
1383 + * Security:
1384 + * - Verifies wpbc_bfb_form_list nonce (sent as 'nonce').
1385 + * - Requires current_user_can( wpbc_bfb_get_manage_cap() ).
1386 + *
1387 + * Expects POST:
1388 + * - nonce : string Nonce for 'wpbc_bfb_form_list'.
1389 + * - include_global : 0|1 If 1, include global forms (owner_user_id=0/NULL) in addition to user-owned.
1390 + * - status : string Default 'published'. Allowed: published|preview|draft|archived|template
1391 + * - search : string Optional filter by title/slug/description
1392 + * - limit : int Optional max rows (default 20, max 500)
1393 + * - page : int Optional page number, starts from 1
1394 + *
1395 + * Response (JSON):
1396 + * - success: true|false
1397 + * - data: { forms: [ ... ] }
1398 + *
1399 + * @since 11.0.0
1400 + *
1401 + * @return void
1402 + */
1403 +
1404 +function wpbc_bfb_ajax_list_forms() {
1311 1405
1312 1406 global $wpdb;
1313 1407
1314 1408 if ( ! check_ajax_referer( 'wpbc_bfb_form_list', 'nonce', false ) ) {
@@ -1422,18 +1516,35 @@
1422 1516 $where_sql .= " AND ( " . implode( ' OR ', $or_groups ) . " ) ";
1423 1517 }
1424 1518 }
1425 1519
1426 - // Order:
1427 - // - prefer user-owned rows first (when include_global + owner_user_id > 0)
1428 - // - default forms first
1429 - // - newest first
1430 - $order_sql = " ORDER BY is_default DESC, updated_at DESC, version DESC, booking_form_id DESC ";
1520 + // Order:
1521 + // - prefer user-owned rows first (when include_global + owner_user_id > 0)
1522 + // - default forms first
1523 + // - preserve registered template adjacency at the target template's position
1524 + // - newest first
1525 + $template_order_clauses = array(
1526 + 'effective_updated_at_sql' => 'updated_at',
1527 + 'adjacency_rank_sql' => '',
1528 + 'args' => array(),
1529 + );
1530 +
1531 + if ( 'template' === $status ) {
1532 + $template_order_clauses = wpbc_bfb_get_template_library_order_clauses( $table );
1533 + }
1534 +
1535 + $effective_updated_at_sql = $template_order_clauses['effective_updated_at_sql'];
1536 + $adjacency_rank_order_sql = '' !== $template_order_clauses['adjacency_rank_sql']
1537 + ? ', ' . $template_order_clauses['adjacency_rank_sql'] . ' DESC'
1538 + : '';
1539 + $order_sql = " ORDER BY is_default DESC, {$effective_updated_at_sql} DESC{$adjacency_rank_order_sql}, version DESC, booking_form_id DESC ";
1540 +
1541 + if ( $owner_user_id > 0 && $include_global ) {
1542 + $order_sql = " ORDER BY ( owner_user_id = " . intval( $owner_user_id ) . " ) DESC, is_default DESC, {$effective_updated_at_sql} DESC{$adjacency_rank_order_sql}, version DESC, booking_form_id DESC ";
1543 + }
1544 +
1545 + $query_args = array_merge( $where_args, $template_order_clauses['args'] );
1431 1546
1432 - if ( $owner_user_id > 0 && $include_global ) {
1433 - $order_sql = " ORDER BY ( owner_user_id = " . intval( $owner_user_id ) . " ) DESC, is_default DESC, updated_at DESC, version DESC, booking_form_id DESC ";
1434 - }
1435 -
1436 1547 $limit_plus_one = $limit + 1;
1437 1548
1438 1549 $sql = "SELECT booking_form_id, form_slug, title, description, picture_url, updated_at, owner_user_id, status, scope, is_default, version
1439 1550 FROM {$table}
@@ -1441,9 +1552,9 @@
1441 1552 {$order_sql}
1442 1553 LIMIT " . intval( $limit_plus_one ) . ' OFFSET ' . intval( $offset );
1443 1554
1444 1555 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1445 - $rows = $wpdb->get_results( $wpdb->prepare( $sql, $where_args ) );
1556 + $rows = $wpdb->get_results( $wpdb->prepare( $sql, $query_args ) );
1446 1557
1447 1558 $has_more = ( count( (array) $rows ) > $limit );
1448 1559 if ( $has_more ) {
1449 1560 $rows = array_slice( (array) $rows, 0, $limit );