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 +194 -70 11.4.3 → 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',
@@ -227,9 +242,9 @@
227 242 $allowed_tags['p']['name'] = true;
228 243 }
229 244
230 245 // Extra attributes for layout/structure wrappers.
231 - foreach ( array( 'div', 'span', 'hr' ) as $tag ) {
246 + foreach ( array( 'div', 'span', 'hr' ) as $tag ) {
232 247 if ( ! isset( $allowed_tags[ $tag ] ) ) {
233 248 $allowed_tags[ $tag ] = array();
234 249 }
235 250 $allowed_tags[ $tag ]['data-bfb-type'] = true;
@@ -234,10 +249,19 @@
234 249 }
235 250 $allowed_tags[ $tag ]['data-bfb-type'] = true;
236 251 $allowed_tags[ $tag ]['data-orientation'] = true;
237 252 $allowed_tags[ $tag ]['name'] = true;
238 - $allowed_tags[ $tag ]['aria-orientation'] = true;
239 - }
253 + $allowed_tags[ $tag ]['aria-orientation'] = true;
254 + }
255 +
256 + // Appointment Form Builder control: permit only its declarative action.
257 + if ( ! isset( $allowed_tags['button'] ) ) {
258 + $allowed_tags['button'] = array();
259 + }
260 + $allowed_tags['button']['type'] = true;
261 + $allowed_tags['button']['class'] = true;
262 + $allowed_tags['button']['id'] = true;
263 + $allowed_tags['button']['data-wpbc-appointment-action'] = true;
240 264
241 265 // Temporarily allow extra inline style properties for BFB.
242 266 add_filter( 'safe_style_css', 'wpbc_bfb_safe_style_props_filter', 10, 1 );
243 267
@@ -464,14 +488,18 @@
464 488
465 489 $custom_options = function_exists( 'wpbc_bfb_settings__get_custom_form_style_options' )
466 490 ? wpbc_bfb_settings__get_custom_form_style_options( $preview_form_style )
467 491 : array();
492 + $accent_options = function_exists( 'wpbc_bfb_settings__get_form_accent_options' )
493 + ? wpbc_bfb_settings__get_form_accent_options( $preview_form_style )
494 + : array();
468 495
469 496 return array_merge(
470 497 array(
471 498 'booking_form_style' => $style,
472 499 ),
473 - $custom_options
500 + $custom_options,
501 + $accent_options
474 502 );
475 503 }
476 504
477 505 /**
@@ -709,22 +737,43 @@
709 737 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
710 738 $preview_form_style_raw = isset( $_POST['preview_form_style'] ) ? wp_unslash( $_POST['preview_form_style'] ) : '';
711 739
712 740 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
713 - $content_form_raw = isset( $_POST['content_form'] ) ? wp_unslash( $_POST['content_form'] ) : '';
714 - $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 );
715 751
716 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
717 - $advanced_form_raw = isset( $_POST['advanced_form'] ) ? wp_unslash( $_POST['advanced_form'] ) : '';
718 - $advanced_form = wpbc_bfb_sanitize_form_text( $advanced_form_raw );
719 752
720 -
721 753 // Validate structure JSON.
722 754 $structure_arr = json_decode( $structure_raw, true );
723 - if ( ! is_array( $structure_arr ) ) {
724 - wp_send_json_error( array( 'code' => 'invalid_structure', 'message' => __( 'Form structure is not a valid JSON object.', 'booking' ) ) );
725 - }
726 -
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 +
727 776 // Settings JSON (normalized to the ONLY supported schema).
728 777 $settings_arr = wpbc_bfb__normalize_settings_array( $settings_raw );
729 778 $preview_form_style = wpbc_bfb__normalize_preview_form_style( $preview_form_style_raw );
730 779 // $advanced_mode_source = ( isset( $settings_arr['bfb_options']['advanced_mode_source'] ) ) ? (string) $settings_arr['bfb_options']['advanced_mode_source'] : 'builder';
@@ -861,9 +910,17 @@
861 910 if ( $return_preview_url && 'preview' === $status && class_exists( 'WPBC_BFB_Preview_Service' ) ) {
862 911
863 912 $preview_service = WPBC_BFB_Preview_Service::get_instance();
864 913
865 - $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 + );
866 923
867 924 if ( is_array( $res ) && ! empty( $res['preview_url'] ) ) {
868 925 $preview_url = (string) $res['preview_url'];
869 926 $preview_token = ! empty( $res['token'] ) ? (string) $res['token'] : '';
@@ -869,27 +926,9 @@
869 926 $preview_token = ! empty( $res['token'] ) ? (string) $res['token'] : '';
870 927 }
871 928 }
872 929
873 - $setup_step_saved = false;
874 - $setup_step = isset( $_POST['wpbc_setup_step'] ) ? sanitize_key( wp_unslash( $_POST['wpbc_setup_step'] ) ) : '';
875 - if ( ! empty( $setup_step ) && class_exists( 'WPBC_SETUP_WIZARD_STEPS' ) ) {
876 - $setup_steps = new WPBC_SETUP_WIZARD_STEPS();
877 - $steps_arr = $setup_steps->get_steps_arr();
878 - if ( function_exists( 'wpbc_setup_wizard__detect_step_from_admin_url' ) ) {
879 - $referer_step = wpbc_setup_wizard__detect_step_from_admin_url( wp_get_referer() );
880 - if ( ! empty( $referer_step ) && isset( $steps_arr[ $referer_step ] ) ) {
881 - $setup_step = $referer_step;
882 - }
883 - }
884 - if ( isset( $steps_arr[ $setup_step ] ) ) {
885 - $setup_steps->db__set_step_as_saved( $setup_step, true );
886 - $setup_steps->db__save_current_step_name( $setup_step );
887 - $setup_step_saved = true;
888 - }
889 - }
890 -
891 - wp_send_json_success(
930 + wp_send_json_success(
892 931 array(
893 932 'booking_form_id' => $booking_form_id,
894 933 'form_name' => $form_name,
895 934 'engine' => $engine,
@@ -898,10 +937,9 @@
898 937 'token' => $preview_token,
899 938 'title' => isset( $form_config['title'] ) ? (string) $form_config['title'] : '',
900 939 'description' => isset( $form_config['description'] ) ? (string) $form_config['description'] : '',
901 940 'picture_url' => isset( $form_config['picture_url'] ) ? (string) $form_config['picture_url'] : '',
902 - 'setup_step_saved' => $setup_step_saved,
903 - )
941 + )
904 942 );
905 943
906 944 }
907 945 add_action( 'wp_ajax_' . 'WPBC_AJX_BFB_SAVE_FORM_CONFIG', 'wpbc_bfb_ajax_save_form_config' );
@@ -1270,32 +1308,101 @@
1270 1308 }
1271 1309 add_action( 'wp_ajax_' . 'WPBC_AJX_BFB_CREATE_FORM_CONFIG', 'wpbc_bfb_ajax_create_form_config' );
1272 1310
1273 1311
1274 -/**
1275 - * Handle AJAX request: list booking forms for current user (and optionally global ones).
1276 - *
1277 - * Security:
1278 - * - Verifies wpbc_bfb_form_list nonce (sent as 'nonce').
1279 - * - Requires current_user_can( wpbc_bfb_get_manage_cap() ).
1280 - *
1281 - * Expects POST:
1282 - * - nonce : string Nonce for 'wpbc_bfb_form_list'.
1283 - * - include_global : 0|1 If 1, include global forms (owner_user_id=0/NULL) in addition to user-owned.
1284 - * - status : string Default 'published'. Allowed: published|preview|draft|archived|template
1285 - * - search : string Optional filter by title/slug/description
1286 - * - limit : int Optional max rows (default 20, max 500)
1287 - * - page : int Optional page number, starts from 1
1288 - *
1289 - * Response (JSON):
1290 - * - success: true|false
1291 - * - data: { forms: [ ... ] }
1292 - *
1293 - * @since 11.0.0
1294 - *
1295 - * @return void
1296 - */
1297 -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() {
1298 1405
1299 1406 global $wpdb;
1300 1407
1301 1408 if ( ! check_ajax_referer( 'wpbc_bfb_form_list', 'nonce', false ) ) {
@@ -1409,18 +1516,35 @@
1409 1516 $where_sql .= " AND ( " . implode( ' OR ', $or_groups ) . " ) ";
1410 1517 }
1411 1518 }
1412 1519
1413 - // Order:
1414 - // - prefer user-owned rows first (when include_global + owner_user_id > 0)
1415 - // - default forms first
1416 - // - newest first
1417 - $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'] );
1418 1546
1419 - if ( $owner_user_id > 0 && $include_global ) {
1420 - $order_sql = " ORDER BY ( owner_user_id = " . intval( $owner_user_id ) . " ) DESC, is_default DESC, updated_at DESC, version DESC, booking_form_id DESC ";
1421 - }
1422 -
1423 1547 $limit_plus_one = $limit + 1;
1424 1548
1425 1549 $sql = "SELECT booking_form_id, form_slug, title, description, picture_url, updated_at, owner_user_id, status, scope, is_default, version
1426 1550 FROM {$table}
@@ -1428,9 +1552,9 @@
1428 1552 {$order_sql}
1429 1553 LIMIT " . intval( $limit_plus_one ) . ' OFFSET ' . intval( $offset );
1430 1554
1431 1555 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
1432 - $rows = $wpdb->get_results( $wpdb->prepare( $sql, $where_args ) );
1556 + $rows = $wpdb->get_results( $wpdb->prepare( $sql, $query_args ) );
1433 1557
1434 1558 $has_more = ( count( (array) $rows ) > $limit );
1435 1559 if ( $has_more ) {
1436 1560 $rows = array_slice( (array) $rows, 0, $limit );