Require bookings to be at least 5 days ahead

Guests could previously book for the same day or tomorrow. The earliest
selectable date is now today + 5 days, configured in one place via the
BOOKING_MIN_LEAD_DAYS constant.

- constants.php: new BOOKING_MIN_LEAD_DAYS define (5)
- Service_model::getAvailableTimes(): optional $minLeadDays param, returns
  no slots for dates closer than that; defaults to 0 so existing callers
  are unaffected
- Pages::ajax(): guest actions (getAvailableTimes, isTimeAvailable) pass the
  constant. The admin action getAvailableTimeOptions deliberately does not,
  so staff can still enter phone bookings for tomorrow.
- Pages::booking_process(): rejects an early booking_date POST and redirects
  back to /booking, guarding against a stale page or a crafted request
- booking-form.php: datepicker minDate/defaultDate and the hidden
  booking_date default all derive from the constant

Deployed and verified on test, dev and prod.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MyYsTAAA1kJLMLCResmgTE
This commit is contained in:
Astral04
2026-08-06 11:01:44 +00:00
co-authored by Claude Opus 5
parent 4e1a69c8d4
commit 69ec9e62a6
4 changed files with 31 additions and 5 deletions
+12
View File
@@ -83,3 +83,15 @@ defined('EXIT_USER_INPUT') OR define('EXIT_USER_INPUT', 7); // invalid user
defined('EXIT_DATABASE') OR define('EXIT_DATABASE', 8); // database error defined('EXIT_DATABASE') OR define('EXIT_DATABASE', 8); // database error
defined('EXIT__AUTO_MIN') OR define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code defined('EXIT__AUTO_MIN') OR define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code
defined('EXIT__AUTO_MAX') OR define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code defined('EXIT__AUTO_MAX') OR define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code
/*
|--------------------------------------------------------------------------
| Booking lead time
|--------------------------------------------------------------------------
|
| Minimum number of days ahead a guest can book a job. 5 means the earliest
| selectable date is today + 5 days. Applies to the public booking form only
| - the admin portal can still book any date.
|
*/
defined('BOOKING_MIN_LEAD_DAYS') OR define('BOOKING_MIN_LEAD_DAYS', 5);
+9 -2
View File
@@ -197,7 +197,7 @@ class Pages extends CI_Controller {
} }
if(isset($_POST['action']) && $_POST['action'] == 'isTimeAvailable' && isset($_POST['selectedDate'])){ if(isset($_POST['action']) && $_POST['action'] == 'isTimeAvailable' && isset($_POST['selectedDate'])){
$results = $this->Service_model->getAvailableTimes($_POST['group_id'], $_POST['selectedDate'], $_POST['servicelength']); $results = $this->Service_model->getAvailableTimes($_POST['group_id'], $_POST['selectedDate'], $_POST['servicelength'], BOOKING_MIN_LEAD_DAYS);
$timeIsAvailable = 0; $timeIsAvailable = 0;
if(is_array($results)){ if(is_array($results)){
@@ -215,7 +215,7 @@ class Pages extends CI_Controller {
} }
if(isset($_POST['action']) && $_POST['action'] == 'getAvailableTimes' && isset($_POST['selectedDate'])){ if(isset($_POST['action']) && $_POST['action'] == 'getAvailableTimes' && isset($_POST['selectedDate'])){
$results = $this->Service_model->getAvailableTimes($_POST['group_id'], $_POST['selectedDate'], $_POST['servicelength'].':00'); $results = $this->Service_model->getAvailableTimes($_POST['group_id'], $_POST['selectedDate'], $_POST['servicelength'].':00', BOOKING_MIN_LEAD_DAYS);
$rowIndex = 1; $rowIndex = 1;
if(!empty($results)){ if(!empty($results)){
@@ -515,6 +515,13 @@ class Pages extends CI_Controller {
if(isset($_POST['sendBooking'])){ if(isset($_POST['sendBooking'])){
//guest bookings must be at least BOOKING_MIN_LEAD_DAYS days ahead
$earliestBookingDate = date('Y-m-d', strtotime('+'.BOOKING_MIN_LEAD_DAYS.' days'));
if(!isset($_POST['booking_date']) || $_POST['booking_date'] < $earliestBookingDate){
header('Location:'.SITEURL.'booking');
exit;
}
//calculate finish time //calculate finish time
$serviceStartTime = date("H:i:s", strtotime($_POST['booking_start_time'])); $serviceStartTime = date("H:i:s", strtotime($_POST['booking_start_time']));
$serviceLengthTime = date("H:i:s", strtotime($_POST['servicelength'])); $serviceLengthTime = date("H:i:s", strtotime($_POST['servicelength']));
+7 -1
View File
@@ -342,7 +342,7 @@ class Service_model extends CI_Model {
} }
} }
public function getAvailableTimes($group_id, $selectedDate, $servicelength){ public function getAvailableTimes($group_id, $selectedDate, $servicelength, $minLeadDays = 0){
$this->load->database(); $this->load->database();
$serviceLengthDateTime = new DateTime($selectedDate.' '.$servicelength); $serviceLengthDateTime = new DateTime($selectedDate.' '.$servicelength);
$service_length_time = strtotime($servicelength); $service_length_time = strtotime($servicelength);
@@ -366,6 +366,12 @@ class Service_model extends CI_Model {
$selectedDay=date_create($selectedDate); $selectedDay=date_create($selectedDate);
$dateDiff=date_diff($today,$selectedDay); $dateDiff=date_diff($today,$selectedDay);
//minimum booking lead time - no times offered before today + $minLeadDays days.
//$minLeadDays = 0 means no restriction (admin portal).
if($minLeadDays > 0 && intval($dateDiff->format('%R%a')) < $minLeadDays){
return array();
}
$tmpDateStart = date_create(date('2024-02-29')); $tmpDateStart = date_create(date('2024-02-29'));
$tmpDateEnd = date_create(date('2024-03-07')); $tmpDateEnd = date_create(date('2024-03-07'));
@@ -787,7 +787,7 @@
<div class="bookingResultsWrapper"> <div class="bookingResultsWrapper">
<div class="availableTimesContainerTitle"></div> <div class="availableTimesContainerTitle"></div>
<div class="availableTimesContainer" style="padding:5px;"> <div class="availableTimesContainer" style="padding:5px;">
<input type="hidden" name="booking_date" id="booking_date" value="<?php echo date('Y-m-d');?>"> <input type="hidden" name="booking_date" id="booking_date" value="<?php echo date('Y-m-d', strtotime('+'.BOOKING_MIN_LEAD_DAYS.' days'));?>">
<input type="hidden" name="booking_start_time" id="booking_time" value=""> <input type="hidden" name="booking_start_time" id="booking_time" value="">
<div class="availableTimes"> <div class="availableTimes">
@@ -1122,7 +1122,8 @@
$("#datepicker").datepicker({ $("#datepicker").datepicker({
firstDay: 1, firstDay: 1,
defaultDate: new Date(), minDate: <?php echo intval(BOOKING_MIN_LEAD_DAYS);?>,
defaultDate: +<?php echo intval(BOOKING_MIN_LEAD_DAYS);?>,
dateFormat: 'yy-mm-dd', dateFormat: 'yy-mm-dd',
onSelect: function(dateText) { onSelect: function(dateText) {
$('#booking_date').val(this.value); $('#booking_date').val(this.value);