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-appointment-services/ajax/appointment_services__list.php +127 -66 11.5 → 11.9 View file →
@@ -1,80 +1,141 @@
1 1 <?php
2 -/** AJAX: list Appointment Services. @package Booking Calendar */
3 -if ( ! defined( 'ABSPATH' ) ) { exit; }
2 +/**
3 + * AJAX list endpoint for the template-driven Appointment Services catalog.
4 + *
5 + * @package Booking Calendar
6 + * @since 11.6.0
7 + */
4 8
9 +if ( ! defined( 'ABSPATH' ) ) {
10 + exit;
11 +}
12 +
5 13 /**
6 - * Return the filtered Service list and Provider presentation directory.
14 + * Return a safe request sequence from an untrusted payload.
7 15 *
8 - * Status counts are calculated from the complete provider-filtered result so
9 - * the management table can switch status without a separate count request.
16 + * @param mixed $request_values Untrusted request values.
10 17 *
11 - * @return void Terminates with a JSON success or error response.
18 + * @return int Non-negative request sequence or zero.
12 19 */
20 +function wpbc_appointment_services_get_catalog_request_id( $request_values ) {
21 + if ( ! is_array( $request_values ) || ! isset( $request_values['request_id'] ) || ! is_scalar( $request_values['request_id'] ) ) {
22 + return 0;
23 + }
24 +
25 + return preg_match( '/^\d+$/', (string) $request_values['request_id'] ) ? (int) $request_values['request_id'] : 0;
26 +}
27 +
28 +/**
29 + * Send a normalized Services catalog error.
30 + *
31 + * @param int $request_id Client request sequence.
32 + * @param WP_Error $error Safe error.
33 + * @param int $status HTTP status.
34 + * @param bool $retryable Whether the browser may retry.
35 + *
36 + * @return void Terminates the AJAX request.
37 + */
38 +function wpbc_appointment_services_send_catalog_error( $request_id, $error, $status, $retryable = false ) {
39 + wp_send_json(
40 + WPBC_UI_Catalog_Response::from_wp_error( 'appointment_services_catalog', $request_id, $error, $retryable ),
41 + absint( $status )
42 + );
43 +}
44 +
45 +/**
46 + * Serve the authorized Service list through the shared catalog contract.
47 + *
48 + * Transport authorization and request normalization stay here. The Service
49 + * repository owns SQL and ownership, while the DTO owns the item contract.
50 + *
51 + * @return void Terminates the AJAX request.
52 + */
13 53 function wpbc_appointment_services_ajax_list() {
14 - wpbc_appointment_services_ajax_authorize();
15 - $service_listing = wpbc_appointment_services_get_catalog_listing();
16 - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Authorized above; the flag only enables an allow-listed user preference write.
17 - $save_items_per_page = isset( $_POST['save_items_per_page'] )
18 - && is_scalar( $_POST['save_items_per_page'] )
19 - && '1' === sanitize_text_field( wp_unslash( $_POST['save_items_per_page'] ) );
20 - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Authorized above and normalized by WPBC_UI_Listing.
21 - if ( $save_items_per_page && isset( $_POST['items_per_page'] ) ) {
22 - $service_listing->save_items_per_page( wp_unslash( $_POST['items_per_page'] ) );
54 + $configuration = WPBC_UI_Catalog_Registry::get_instance()->get_configuration( 'appointment_services_catalog' );
55 + if ( empty( $configuration ) ) {
56 + wpbc_appointment_services_send_catalog_error( 0, new WP_Error( 'wpbc_appointment_services_unavailable', __( 'The Services catalog is unavailable.', 'booking' ) ), 503, true );
23 57 }
24 - $provider = wpbc_appointment_services_get_data_provider();
25 - if ( ! is_object( $provider ) || ! method_exists( $provider, 'list_items' ) || ! wpbc_appointment_services_storage_is_ready() ) {
26 - wp_send_json_success(
58 +
59 + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified immediately below.
60 + $raw_request = is_array( $_POST ) ? wp_unslash( $_POST ) : array();
61 + $request_id = wpbc_appointment_services_get_catalog_request_id( $raw_request );
62 + if ( false === check_ajax_referer( $configuration['nonce_name'], 'nonce', false ) ) {
63 + wpbc_appointment_services_send_catalog_error( $request_id, new WP_Error( 'wpbc_appointment_services_invalid_nonce', __( 'Security check failed.', 'booking' ) ), 403 );
64 + }
65 + if ( ! current_user_can( wpbc_appointment_services_get_manage_capability() ) ) {
66 + wpbc_appointment_services_send_catalog_error( $request_id, new WP_Error( 'wpbc_appointment_services_forbidden', __( 'You do not have permission to view Services.', 'booking' ) ), 403 );
67 + }
68 +
69 + $preference_action = isset( $raw_request['preference_action'] ) && is_scalar( $raw_request['preference_action'] ) ? sanitize_key( (string) $raw_request['preference_action'] ) : '';
70 + if ( ! in_array( $preference_action, array( '', 'save', 'reset' ), true ) ) {
71 + wpbc_appointment_services_send_catalog_error( $request_id, new WP_Error( 'wpbc_appointment_services_invalid_preferences', __( 'The catalog preference request is invalid.', 'booking' ) ), 400 );
72 + }
73 + $preference_revision = isset( $raw_request['preference_revision'] ) && is_scalar( $raw_request['preference_revision'] ) && preg_match( '/^\d+$/', (string) $raw_request['preference_revision'] )
74 + ? (string) $raw_request['preference_revision']
75 + : '0';
76 + if ( isset( $raw_request['preferences_only'] ) && ( ! is_scalar( $raw_request['preferences_only'] ) || ! in_array( (string) $raw_request['preferences_only'], array( '0', '1' ), true ) ) ) {
77 + wpbc_appointment_services_send_catalog_error( $request_id, new WP_Error( 'wpbc_appointment_services_invalid_preferences', __( 'The catalog preference request is invalid.', 'booking' ) ), 400 );
78 + }
79 + $preferences_only = isset( $raw_request['preferences_only'] ) && '1' === (string) $raw_request['preferences_only'];
80 + if ( '' !== $preference_action && '0' === $preference_revision ) {
81 + wpbc_appointment_services_send_catalog_error( $request_id, new WP_Error( 'wpbc_appointment_services_invalid_preferences', __( 'The catalog preference revision is invalid.', 'booking' ) ), 400 );
82 + }
83 + if ( $preferences_only && 'save' !== $preference_action ) {
84 + wpbc_appointment_services_send_catalog_error( $request_id, new WP_Error( 'wpbc_appointment_services_invalid_preferences', __( 'The catalog preference request is invalid.', 'booking' ) ), 400 );
85 + }
86 + if ( 'reset' === $preference_action && ! WPBC_UI_Catalog_Preferences::reset( 'appointment_services_catalog', 0, $preference_revision ) ) {
87 + wpbc_appointment_services_send_catalog_error( $request_id, new WP_Error( 'wpbc_appointment_services_preference_reset_failed', __( 'The catalog preferences could not be reset.', 'booking' ) ), 500, true );
88 + }
89 +
90 + $stored_preferences = WPBC_UI_Catalog_Preferences::load( 'appointment_services_catalog' );
91 + $shared_keys = array( 'request_id', 'page_number', 'items_per_page', 'sort_by', 'sort_order', 'search', 'visible_columns', 'column_order', 'template_pack' );
92 + $shared_request = WPBC_UI_Catalog_Request::create(
93 + $configuration,
94 + array_intersect_key( $raw_request, array_fill_keys( $shared_keys, true ) ),
95 + $stored_preferences
96 + );
97 + if ( is_wp_error( $shared_request ) ) {
98 + wpbc_appointment_services_send_catalog_error( $request_id, $shared_request, 400 );
99 + }
100 +
101 + $service_values = array(
102 + 'status' => isset( $stored_preferences['status'] ) ? $stored_preferences['status'] : 'all',
103 + 'resource_id' => isset( $stored_preferences['resource_id'] ) ? $stored_preferences['resource_id'] : 0,
104 + );
105 + foreach ( array( 'status', 'resource_id' ) as $service_key ) {
106 + if ( array_key_exists( $service_key, $raw_request ) ) {
107 + $service_values[ $service_key ] = $raw_request[ $service_key ];
108 + }
109 + }
110 + $service_request = WPBC_Appointment_Services_Catalog_Request::create( $service_values );
111 + if ( is_wp_error( $service_request ) ) {
112 + wpbc_appointment_services_send_catalog_error( $request_id, $service_request, 400 );
113 + }
114 +
115 + if ( 'save' === $preference_action ) {
116 + $preference_result = WPBC_UI_Catalog_Preferences::save(
117 + 'appointment_services_catalog',
118 + $shared_request,
27 119 array(
28 - 'storage_ready' => false,
29 - 'services' => array(),
30 - 'listing' => $service_listing->get_client_settings(),
31 - 'message' => wpbc_appointment_services_storage_error()->get_error_message(),
32 - )
120 + 'status' => $service_request->get( 'status', 'all' ),
121 + 'resource_id' => $service_request->get( 'resource_id', 0 ),
122 + ),
123 + 0,
124 + $preference_revision
33 125 );
126 + if ( is_wp_error( $preference_result ) ) {
127 + wpbc_appointment_services_send_catalog_error( $request_id, $preference_result, 400 );
128 + }
34 129 }
35 - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Authorized above; sanitized before the repository boundary.
36 - $search = isset( $_POST['search'] ) && is_scalar( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : '';
37 - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Authorized above; validated against the status allow-list.
38 - $status = isset( $_POST['status'] ) && is_scalar( $_POST['status'] ) ? sanitize_key( wp_unslash( $_POST['status'] ) ) : 'active';
39 - if ( ! in_array( $status, array( 'all', 'active', 'inactive', 'archived' ), true ) ) { $status = 'active'; }
40 - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Authorized above; normalized to a positive integer.
41 - $resource_id = isset( $_POST['resource_id'] ) && is_scalar( $_POST['resource_id'] ) ? absint( $_POST['resource_id'] ) : 0;
42 - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Authorized above; normalized by the shared listing component.
43 - $page_number = isset( $_POST['page_number'] ) ? wp_unslash( $_POST['page_number'] ) : 1;
44 - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Authorized above; normalized against the configured allow-list.
45 - $items_per_page = isset( $_POST['items_per_page'] ) ? wp_unslash( $_POST['items_per_page'] ) : $service_listing->get_items_per_page();
46 - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Authorized above; normalized against sortable listing columns.
47 - $sort_by = isset( $_POST['sort_by'] ) ? wp_unslash( $_POST['sort_by'] ) : null;
48 - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Authorized above; normalized to asc or desc.
49 - $sort_order = isset( $_POST['sort_order'] ) ? wp_unslash( $_POST['sort_order'] ) : null;
50 - $catalog_page = wpbc_appointment_services_get_catalog_page(
51 - $provider,
52 - array(
53 - 'search' => $search,
54 - 'status' => $status,
55 - 'resource_id' => $resource_id,
56 - 'page_number' => $page_number,
57 - 'items_per_page' => $items_per_page,
58 - 'sort_by' => $sort_by,
59 - 'sort_order' => $sort_order,
60 - ),
61 - $service_listing
62 - );
63 - if ( is_wp_error( $catalog_page ) ) {
64 - wpbc_appointment_services_send_provider_error( $catalog_page, __( 'Services could not be loaded.', 'booking' ) );
130 + if ( $preferences_only ) {
131 + wp_send_json( array( 'success' => true, 'request_id' => $request_id ), 200 );
65 132 }
66 - $directory = wpbc_appointment_services_get_provider_directory();
67 - wp_send_json_success(
68 - array(
69 - 'storage_ready' => true,
70 - 'services' => $catalog_page['services'],
71 - 'counts' => $catalog_page['counts'],
72 - 'pagination' => $catalog_page['pagination'],
73 - 'sorting' => $catalog_page['sorting'],
74 - 'providers' => array_values( $directory ),
75 - 'provider_count'=> count( $directory ),
76 - 'listing' => $service_listing->get_client_settings(),
77 - )
78 - );
133 +
134 + $response = ( new WPBC_Appointment_Services_Catalog_Provider( wpbc_appointment_services_get_data_provider(), null, $service_request ) )->get_response( $shared_request );
135 + if ( is_wp_error( $response ) ) {
136 + wpbc_appointment_services_send_catalog_error( $request_id, $response, 500, true );
137 + }
138 +
139 + wp_send_json( $response->to_array(), 200 );
79 140 }
80 141 add_action( 'wp_ajax_WPBC_AJX_APPOINTMENT_SERVICES_LIST', 'wpbc_appointment_services_ajax_list' );