Skip to content

Commit 5ef5330

Browse files
authored
gpb-daily-service-booking-limit.php: Added new snippet
1 parent 8fd3967 commit 5ef5330

File tree

1 file changed

+213
-0
lines changed

1 file changed

+213
-0
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<?php
2+
/**
3+
* Gravity Perks // GP Bookings // Daily Service Booking Limit
4+
* https://gravitywiz.com/documentation/gravity-forms-bookings/
5+
*
6+
* Enforce a daily capacity for one or more GP Bookings services. When the selected services
7+
* meet the limit, new submissions are blocked and the booking time field displays a
8+
* "fully booked" message for that day. List multiple service IDs to share the cap between them.
9+
*
10+
* Instructions:
11+
*
12+
* 1. Install this snippet by following the steps here:
13+
* https://gravitywiz.com/documentation/how-do-i-install-a-snippet/
14+
*
15+
* 2. Update the configuration at the bottom of the snippet:
16+
* - Set form_id to the Gravity Form that hosts your booking field (or leave false to run on every form).
17+
* - List the GP Bookings service IDs that should share the daily cap in service_ids.
18+
* - Adjust daily_limit to the maximum combined bookings allowed per day.
19+
* - Optionally customize capacity_message to change the validation text shown to users.
20+
*/
21+
class GPB_Daily_Service_Limit {
22+
23+
private $form_id;
24+
private $service_ids;
25+
private $daily_limit;
26+
private $capacity_message;
27+
28+
public function __construct( array $args ) {
29+
$args = wp_parse_args( $args, array(
30+
'form_id' => false,
31+
'service_ids' => array(),
32+
'daily_limit' => 10,
33+
'capacity_message' => __( 'We are fully booked for that day. Please choose another date.', 'gp-bookings' ),
34+
) );
35+
36+
$this->form_id = $args['form_id'];
37+
$this->service_ids = array_map( 'intval', (array) $args['service_ids'] );
38+
$this->daily_limit = (int) $args['daily_limit'];
39+
$this->capacity_message = $args['capacity_message'];
40+
41+
if ( empty( $this->service_ids ) ) {
42+
return;
43+
}
44+
45+
add_action( 'gpb_before_booking_created', array( $this, 'guard_booking_creation' ), 10, 2 );
46+
add_filter( 'gform_validation', array( $this, 'validate_submission' ) );
47+
}
48+
49+
public function guard_booking_creation( array $booking_data, $bookable ) {
50+
if ( ! $bookable instanceof \GP_Bookings\Service || ! in_array( $bookable->get_id(), $this->service_ids, true ) ) {
51+
return;
52+
}
53+
54+
// Normalize to the start date in the site timezone so nightly/range bookings count correctly.
55+
$date = $this->normalize_booking_date(
56+
$booking_data['start_datetime'] ?? '',
57+
$booking_data['end_datetime'] ?? ( $booking_data['start_datetime'] ?? '' ),
58+
$bookable
59+
);
60+
if ( ! $date ) {
61+
return;
62+
}
63+
$quantity = isset( $booking_data['quantity'] ) ? max( 1, (int) $booking_data['quantity'] ) : 1;
64+
65+
// Guard again at save time so last-second bookings can't slip past the form validation.
66+
if ( $this->get_total_for_date( $date ) + $quantity > $this->daily_limit ) {
67+
throw new \GP_Bookings\Exceptions\CapacityException( $this->capacity_message );
68+
}
69+
}
70+
71+
public function validate_submission( $result ) {
72+
$is_object = is_object( $result );
73+
$form = $is_object ? $result->form : $result['form'];
74+
75+
if ( $this->form_id && (int) $form['id'] !== (int) $this->form_id ) {
76+
return $result;
77+
}
78+
79+
$is_valid = $is_object ? $result->is_valid : $result['is_valid'];
80+
81+
// Track per-day totals so multiple booking fields in one submission don't exceed the cap.
82+
$daily_totals = [];
83+
84+
foreach ( $form['fields'] as &$field ) {
85+
if ( ! isset( $field->inputType ) || $field->inputType !== 'gpb_booking' ) {
86+
continue;
87+
}
88+
89+
$children = $field->get_child_fields( $form );
90+
$service = $children['service'] ?? null;
91+
$time = $children['booking_time'] ?? null;
92+
93+
if ( ! $service || ! $time ) {
94+
continue;
95+
}
96+
97+
$service_id = isset( $service->gpbService ) ? (int) $service->gpbService : 0;
98+
if ( ! $service_id || ! in_array( $service_id, $this->service_ids, true ) ) {
99+
continue;
100+
}
101+
102+
$service_model = \GP_Bookings\Service::get( $service_id );
103+
if ( ! $service_model ) {
104+
continue;
105+
}
106+
107+
$datetime = $this->get_posted_value( (int) $time->id );
108+
if ( ! $datetime ) {
109+
continue;
110+
}
111+
112+
$date = $this->normalize_booking_date( $datetime, $datetime, $service_model );
113+
if ( ! $date ) {
114+
continue;
115+
}
116+
117+
$quantity = rgpost( 'input_' . (int) $field->id . '_3' );
118+
$quantity = $quantity === null || $quantity === '' ? 1 : max( 1, (int) $quantity );
119+
120+
// Reuse the current total for this date so we only hit the database once per day per submission.
121+
$current_total = $daily_totals[ $date ] ?? $this->get_total_for_date( $date );
122+
123+
if ( $current_total + $quantity > $this->daily_limit ) {
124+
$this->flag_field_error( $form, (int) $time->id );
125+
$is_valid = false;
126+
continue;
127+
}
128+
129+
$daily_totals[ $date ] = $current_total + $quantity;
130+
}
131+
132+
unset( $field );
133+
134+
if ( ! $is_valid ) {
135+
$form['validation_message'] = $this->capacity_message;
136+
}
137+
138+
if ( $is_object ) {
139+
$result->form = $form;
140+
$result->is_valid = $is_valid;
141+
return $result;
142+
}
143+
144+
$result['form'] = $form;
145+
$result['is_valid'] = $is_valid;
146+
return $result;
147+
}
148+
149+
private function get_total_for_date( string $date ): int {
150+
$start = $date . ' 00:00:00';
151+
$end = $date . ' 23:59:59';
152+
// Count both pending and confirmed bookings to reflect in-progress reservations.
153+
$bookings = \GP_Bookings\Queries\Booking_Query::get_bookings_in_range(
154+
$start,
155+
$end,
156+
array(
157+
'object_id' => $this->service_ids,
158+
'object_type' => 'service',
159+
'status' => array( 'pending', 'confirmed' ),
160+
'exclude_service_with_resource' => false,
161+
)
162+
);
163+
164+
$total = 0;
165+
166+
foreach ( $bookings as $booking ) {
167+
$total += (int) $booking->get_quantity();
168+
}
169+
170+
return $total;
171+
}
172+
173+
private function normalize_booking_date( $start, $end, $bookable ): ?string {
174+
try {
175+
$normalized = \GP_Bookings\Booking::normalize_datetime_values( $start, $end, $bookable );
176+
} catch ( \Throwable $e ) {
177+
return null;
178+
}
179+
180+
return $normalized['start']->format( 'Y-m-d' );
181+
}
182+
183+
private function get_posted_value( int $field_id ) {
184+
$value = rgpost( 'input_' . $field_id );
185+
186+
if ( is_array( $value ) ) {
187+
$value = reset( $value );
188+
}
189+
190+
return $value === null || $value === '' ? null : $value;
191+
}
192+
193+
private function flag_field_error( array &$form, int $field_id ): void {
194+
foreach ( $form['fields'] as &$field ) {
195+
if ( (int) $field->id === $field_id ) {
196+
$field->failed_validation = true;
197+
$field->validation_message = $this->capacity_message;
198+
break;
199+
}
200+
}
201+
202+
unset( $field );
203+
}
204+
205+
}
206+
207+
# Configuration
208+
new GPB_Daily_Service_Limit( array(
209+
'form_id' => 123,
210+
'service_ids' => array( 45, 67 ),
211+
'daily_limit' => 10,
212+
// 'capacity_message' => '',
213+
) );

0 commit comments

Comments
 (0)