-
Notifications
You must be signed in to change notification settings - Fork 92
gpb-daily-service-booking-limit.php
: Added new snippet.
#1172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
WalkthroughAdds new class GPB_Daily_Service_Limit that enforces a shared per-day booking cap across configured GP Bookings services by registering a pre-create guard and a Gravity Forms submission validator, normalizing booking dates, counting existing bookings per date, caching per-submission totals, flagging field errors, and throwing CapacityException on overages. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant GF as Gravity Forms
participant Validator as GPB_Daily_Service_Limit.validate_submission
participant DB as Booking Store
participant Guard as GPB_Daily_Service_Limit.guard_booking_creation
User->>GF: Submit form
GF->>Validator: Run validation
loop Each booking field/date in submission
Validator->>Validator: resolve posted service/time and qty
Validator->>DB: get_total_for_date(date) (uses cache when available)
DB-->>Validator: current count
Validator->>Validator: accumulate submission total
alt Accumulated + existing > daily_limit
Validator->>GF: flag field error and set form message
end
end
alt Validation passes
GF->>Guard: gpb_before_booking_created hook
Guard->>Guard: normalize_booking_date(start,end,bookable)
Guard->>DB: get_total_for_date(date)
DB-->>Guard: current count
alt Would exceed daily_limit
Guard-->>GF: throw CapacityException (prevent create)
else
Guard-->>GF: allow booking creation
end
else Validation fails
GF-->>User: show validation errors
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
gp-bookings/gpb-daily-service-booking-limit.php
(1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHPCS (Files Changed)
gp-bookings/gpb-daily-service-booking-limit.php
[warning] 209-209:
Array double arrow not aligned correctly; expected 5 space(s) between "'form_id'" and double arrow, but found 1.
[warning] 151-151:
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 6 spaces
[warning] 150-150:
Equals sign not aligned with surrounding assignments; expected 1 space but found 4 spaces
[failure] 33-33:
Tabs must be used to indent lines; spaces are not allowed
[failure] 32-32:
Tabs must be used to indent lines; spaces are not allowed
[failure] 31-31:
Tabs must be used to indent lines; spaces are not allowed
[failure] 30-30:
Tabs must be used to indent lines; spaces are not allowed
[failure] 29-29:
Tabs must be used to indent lines; spaces are not allowed
[failure] 28-28:
Tabs must be used to indent lines; spaces are not allowed
[failure] 26-26:
Tabs must be used to indent lines; spaces are not allowed
[failure] 25-25:
Tabs must be used to indent lines; spaces are not allowed
[failure] 24-24:
Tabs must be used to indent lines; spaces are not allowed
[failure] 23-23:
Tabs must be used to indent lines; spaces are not allowed
🪛 GitHub Actions: PHP Lint (PR)
gp-bookings/gpb-daily-service-booking-limit.php
[error] 23-23: Tabs must be used to indent lines; spaces are not allowed (Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed). PHPCS check failed.
🔇 Additional comments (2)
gp-bookings/gpb-daily-service-booking-limit.php (2)
54-69
: Good concurrency guard before booking creation.Validates against the cap with normalized dates and throws a CapacityException to prevent races. Solid safeguard.
112-116
: Confirm expected behavior for multi‑day bookings.Currently, the date normalized from start/end is reduced to the start day only. If multi‑day bookings should decrement capacity for each day in the range, this needs a loop over all dates in the span.
Do you intend to count only the start day, or every day in a multi‑day booking’s range?
$service_id = isset( $service->gpbService ) ? (int) $service->gpbService : 0; | ||
if ( ! $service_id || ! in_array( $service_id, $this->service_ids, true ) ) { | ||
continue; | ||
} | ||
|
||
$service_model = \GP_Bookings\Service::get( $service_id ); | ||
if ( ! $service_model ) { | ||
continue; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix service resolution for “Select Service” fields (use posted value).
Using only $service->gpbService
fails when the Booking field is configured as “Select Service” (no static service). Read the posted service ID as a fallback so validation applies to user-selected services.
Apply this diff:
- $service_id = isset( $service->gpbService ) ? (int) $service->gpbService : 0;
- if ( ! $service_id || ! in_array( $service_id, $this->service_ids, true ) ) {
- continue;
- }
-
- $service_model = \GP_Bookings\Service::get( $service_id );
+ // Resolve the target service. For "Single Service", gpbService is set.
+ // For "Select Service", read the posted value.
+ $service_id = isset( $service->gpbService ) ? (int) $service->gpbService : 0;
+ if ( ! $service_id ) {
+ $service_id = (int) $this->get_posted_value( (int) $service->id );
+ }
+ if ( ! $service_id || ! in_array( $service_id, $this->service_ids, true ) ) {
+ continue;
+ }
+ $service_model = \GP_Bookings\Service::get( $service_id );
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
$service_id = isset( $service->gpbService ) ? (int) $service->gpbService : 0; | |
if ( ! $service_id || ! in_array( $service_id, $this->service_ids, true ) ) { | |
continue; | |
} | |
$service_model = \GP_Bookings\Service::get( $service_id ); | |
if ( ! $service_model ) { | |
continue; | |
} | |
// Resolve the target service. For "Single Service", gpbService is set. | |
// For "Select Service", read the posted value. | |
$service_id = isset( $service->gpbService ) ? (int) $service->gpbService : 0; | |
if ( ! $service_id ) { | |
$service_id = (int) $this->get_posted_value( (int) $service->id ); | |
} | |
if ( ! $service_id || ! in_array( $service_id, $this->service_ids, true ) ) { | |
continue; | |
} | |
$service_model = \GP_Bookings\Service::get( $service_id ); | |
if ( ! $service_model ) { | |
continue; | |
} |
🤖 Prompt for AI Agents
In gp-bookings/gpb-daily-service-booking-limit.php around lines 97 to 105, the
code only reads $service->gpbService which breaks when the field is configured
as “Select Service”; modify the resolution to fall back to the posted service
value: if $service->gpbService is empty, read the posted service ID (cast to
int) from the request payload for that field (e.g. the gpbService form input),
then use that ID for the in_array check and to load \GP_Bookings\Service::get;
ensure you treat missing/invalid posted values as 0 and continue early as
before.
$quantity = rgpost( 'input_' . (int) $field->id . '_3' ); | ||
$quantity = $quantity === null || $quantity === '' ? 1 : max( 1, (int) $quantity ); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t hard‑code the quantity input index; resolve the Quantity child field.
Referencing input_{field->id}_3
is brittle and can miscount quantities, leading to overbooking. Use the Booking field’s Quantity child (with a legacy fallback).
Apply this diff:
- $quantity = rgpost( 'input_' . (int) $field->id . '_3' );
- $quantity = $quantity === null || $quantity === '' ? 1 : max( 1, (int) $quantity );
+ $quantity_field = $children['quantity'] ?? null;
+ $quantity_value = $quantity_field ? $this->get_posted_value( (int) $quantity_field->id ) : null;
+ // Fallback for legacy compound input indexing if needed.
+ if ( $quantity_value === null ) {
+ $quantity_value = rgpost( 'input_' . (int) $field->id . '_3' );
+ }
+ $quantity = $quantity_value === null || $quantity_value === '' ? 1 : max( 1, (int) $quantity_value );
🤖 Prompt for AI Agents
In gp-bookings/gpb-daily-service-booking-limit.php around lines 117-119, the
code currently reads the quantity from a hard-coded child input index
("input_{field->id}_3"), which is brittle; instead locate the Booking field's
Quantity child field (use the field object's get_child_by_type or equivalent
helper, falling back to the legacy index if not found), build the input name
from that child's id (e.g. 'input_' . (int)$field->id . '_' .
(int)$quantity_child->id or use the child's input name helper), then read rgpost
from that resolved input, and finally apply the same defaulting logic (treat
null/empty as 1 and coerce to max(1,(int)$value)). Ensure the fallback preserves
existing behavior when a Quantity child cannot be resolved.
gpb-daily-service-booking-limit.php
: Added new snippetgpb-daily-service-booking-limit.php
: Added new snippet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
gp-bookings/gpb-daily-service-booking-limit.php
(1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHPCS (Files Changed)
gp-bookings/gpb-daily-service-booking-limit.php
[warning] 209-209:
Array double arrow not aligned correctly; expected 5 space(s) between "'form_id'" and double arrow, but found 1.
[failure] 193-193:
void return type is not present in PHP version 7.0 or earlier
[failure] 173-173:
Nullable return types are not supported in PHP 7.0 or earlier.
[failure] 82-82:
Short array syntax is not allowed
🪛 GitHub Actions: PHP Lint (PR)
gp-bookings/gpb-daily-service-booking-limit.php
[error] 82-82: PHPCS: Short array syntax is not allowed (Generic.Arrays.DisallowShortArraySyntax.Found)
🔇 Additional comments (2)
gp-bookings/gpb-daily-service-booking-limit.php (2)
97-105
: Don't skip Select Service bookings when enforcing the capWhen the booking field uses “Select Service,”
gpbService
is empty, so$service_id
stays 0 and we skip validation entirely, effectively disabling the limit for those forms. Please fall back to the posted child value before bailing out.- $service_id = isset( $service->gpbService ) ? (int) $service->gpbService : 0; - if ( ! $service_id || ! in_array( $service_id, $this->service_ids, true ) ) { + $service_id = isset( $service->gpbService ) ? (int) $service->gpbService : 0; + if ( ! $service_id ) { + $posted_service = $this->get_posted_value( (int) $service->id ); + $service_id = $posted_service ? (int) $posted_service : 0; + } + if ( ! $service_id || ! in_array( $service_id, $this->service_ids, true ) ) {
117-129
: Quantity lookup must resolve the actual child inputHard-coding
_3
for the quantity input breaks as soon as the field is reordered or uses the Quantity child. That causes us to undercount bookings and miss the cap. Resolve the quantity child and fall back to the legacy index only if needed.- $quantity = rgpost( 'input_' . (int) $field->id . '_3' ); - $quantity = $quantity === null || $quantity === '' ? 1 : max( 1, (int) $quantity ); + $quantity_field = $children['quantity'] ?? null; + $quantity_value = $quantity_field ? $this->get_posted_value( (int) $quantity_field->id ) : null; + if ( $quantity_value === null ) { + $quantity_value = rgpost( 'input_' . (int) $field->id . '_3' ); + } + $quantity = $quantity_value === null || $quantity_value === '' ? 1 : max( 1, (int) $quantity_value );
$is_valid = $is_object ? $result->is_valid : $result['is_valid']; | ||
|
||
// Track per-day totals so multiple booking fields in one submission don't exceed the cap. | ||
$daily_totals = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace [] with array() to satisfy PHPCS
Our coding standard forbids short array syntax and the lint job is failing on this line. Switch to array()
.
- $daily_totals = [];
+ $daily_totals = array();
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
$daily_totals = []; | |
$daily_totals = array(); |
🧰 Tools
🪛 GitHub Check: PHPCS (Files Changed)
[failure] 82-82:
Short array syntax is not allowed
🪛 GitHub Actions: PHP Lint (PR)
[error] 82-82: PHPCS: Short array syntax is not allowed (Generic.Arrays.DisallowShortArraySyntax.Found)
🤖 Prompt for AI Agents
In gp-bookings/gpb-daily-service-booking-limit.php around line 82 the short
array syntax is used ($daily_totals = [];), which violates PHPCS rules; replace
the short syntax with the long form by assigning $daily_totals = array(); and
save/run the linter to confirm PHPCS passes.
private function normalize_booking_date( $start, $end, $bookable ): ?string { | ||
try { | ||
$normalized = \GP_Bookings\Booking::normalize_datetime_values( $start, $end, $bookable ); | ||
} catch ( \Throwable $e ) { | ||
return null; | ||
} | ||
|
||
return $normalized['start']->format( 'Y-m-d' ); | ||
} | ||
|
||
private function get_posted_value( int $field_id ) { | ||
$value = rgpost( 'input_' . $field_id ); | ||
|
||
if ( is_array( $value ) ) { | ||
$value = reset( $value ); | ||
} | ||
|
||
return $value === null || $value === '' ? null : $value; | ||
} | ||
|
||
private function flag_field_error( array &$form, int $field_id ): void { | ||
foreach ( $form['fields'] as &$field ) { | ||
if ( (int) $field->id === $field_id ) { | ||
$field->failed_validation = true; | ||
$field->validation_message = $this->capacity_message; | ||
break; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove PHP 7.1-only return types
?string
and : void
aren’t allowed under our PHP 7.0 minimum and PHPCS is failing. Drop those return type declarations so the snippet stays compatible.
- private function normalize_booking_date( $start, $end, $bookable ): ?string {
+ private function normalize_booking_date( $start, $end, $bookable ) {
@@
- private function flag_field_error( array &$form, int $field_id ): void {
+ private function flag_field_error( array &$form, int $field_id ) {
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
private function normalize_booking_date( $start, $end, $bookable ): ?string { | |
try { | |
$normalized = \GP_Bookings\Booking::normalize_datetime_values( $start, $end, $bookable ); | |
} catch ( \Throwable $e ) { | |
return null; | |
} | |
return $normalized['start']->format( 'Y-m-d' ); | |
} | |
private function get_posted_value( int $field_id ) { | |
$value = rgpost( 'input_' . $field_id ); | |
if ( is_array( $value ) ) { | |
$value = reset( $value ); | |
} | |
return $value === null || $value === '' ? null : $value; | |
} | |
private function flag_field_error( array &$form, int $field_id ): void { | |
foreach ( $form['fields'] as &$field ) { | |
if ( (int) $field->id === $field_id ) { | |
$field->failed_validation = true; | |
$field->validation_message = $this->capacity_message; | |
break; | |
} | |
} | |
private function normalize_booking_date( $start, $end, $bookable ) { | |
try { | |
$normalized = \GP_Bookings\Booking::normalize_datetime_values( $start, $end, $bookable ); | |
} catch ( \Throwable $e ) { | |
return null; | |
} | |
return $normalized['start']->format( 'Y-m-d' ); | |
} | |
private function get_posted_value( int $field_id ) { | |
$value = rgpost( 'input_' . $field_id ); | |
if ( is_array( $value ) ) { | |
$value = reset( $value ); | |
} | |
return $value === null || $value === '' ? null : $value; | |
} | |
private function flag_field_error( array &$form, int $field_id ) { | |
foreach ( $form['fields'] as &$field ) { | |
if ( (int) $field->id === $field_id ) { | |
$field->failed_validation = true; | |
$field->validation_message = $this->capacity_message; | |
break; | |
} | |
} | |
} |
🧰 Tools
🪛 GitHub Check: PHPCS (Files Changed)
[failure] 193-193:
void return type is not present in PHP version 7.0 or earlier
[failure] 173-173:
Nullable return types are not supported in PHP 7.0 or earlier.
🤖 Prompt for AI Agents
In gp-bookings/gpb-daily-service-booking-limit.php around lines 173 to 201, the
methods declare PHP 7.1+ return type hints (?string and : void) which break
compatibility with our PHP 7.0 minimum; remove the nullable and void return type
declarations from normalize_booking_date and flag_field_error (and any other
methods in this range using those return types), leaving the functions without
explicit return type hints and keeping their existing behavior and doc/comments
intact.
@SebastianWiz Strong! How hard/possible would it be to also prevent the day from being selected at all once the limit is reached? I wouldn't sink too much more time into this, just curious if it's an option? |
Context
⛑️ Ticket(s): https://secure.helpscout.net/conversation/3084918923/89679?viewId=8172236
Summary
This PR adds a snippet that lets you set a daily booking limit for one or multiple services. The snippet hooks into Gravity Forms validation to block submissions once the combined quantity for that day reaches the limit and shows the “fully booked” validation message on the booking-time field. It also hooks
gpb_before_booking_created
so the same check runs during booking creation, preventing overbookings if multiple requests land at the same time.Loom demo: https://www.loom.com/share/6cae41c053a647e48deec68868e23ef4