Validate the guest e-mail before the booking is saved
An address like "asdf" used to be accepted: the field was type="text" with
only `required`, and there was no server-side check. createBooking() runs
long before sendEmail(), so the failure was silent rather than loud -
reproduced end to end on test:
- the booking row WAS created, with a manage_token
- PHPMailer's addAddress() threw, so nothing was ever sent
- the Location header was already queued, so the guest was redirected to
the normal "booking finished" page and saw success
- Evelin is a CC on that same message, so the salon was not told either
- the guest had no manage link, so they could not cancel
Fixes
- booking_process() rejects an empty or malformed address BEFORE any write,
returning invalid_email / HTTP 400. Message added in all three languages,
worded to say why it matters (the confirmation and the manage link go
there). filter_var is equal-or-stricter than PHPMailer's own validator -
checked against it on ten cases - so anything accepted here cannot throw
later.
- The three public booking forms use type="email", so most typos never
reach the server.
- Removed three debug echoes from User_model::sendEmail() that leaked $lang
and Hungarian strings ("Üzenet elküldve", "Üzenetküldési hiba. Mailer
Error: ...") into the guest-facing response.
Scope
- Public flow only. 508 existing bookings have an empty guest_email because
admin-created block bookings legitimately have none; those go through
Admin::booking_process(), which is untouched, and its form stays
type="text".
- Not covered: a valid address whose SMTP delivery fails still leaves the
booking created and the guest seeing success, logged only via
log_message(). Different failure mode, needs a separate decision.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -404,6 +404,23 @@ class Pages extends CI_Controller {
|
|||||||
'manage_token' => $manageToken,
|
'manage_token' => $manageToken,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Reject an unusable e-mail BEFORE anything is written. The booking is
|
||||||
|
// saved well before sendEmail() runs, so without this the row is created,
|
||||||
|
// PHPMailer's addAddress() throws, and the guest is redirected to the
|
||||||
|
// success page having received nothing - no confirmation and no manage
|
||||||
|
// link. Evelin is a CC on that same message, so the salon is not told
|
||||||
|
// either. filter_var is equal-or-stricter than PHPMailer's own check, so
|
||||||
|
// anything accepted here will not throw later.
|
||||||
|
//
|
||||||
|
// Public flow only: admin-created block bookings legitimately carry an
|
||||||
|
// empty guest_email and go through Admin::booking_process().
|
||||||
|
$guestEmailInput = isset($_POST['guest_email']) ? trim($_POST['guest_email']) : '';
|
||||||
|
if ($guestEmailInput === '' || !filter_var($guestEmailInput, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$this->_booking_error('invalid_email', 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$bookingArray['guest_email'] = $guestEmailInput;
|
||||||
|
|
||||||
$bookingDate = $bookingArray['booking_date'];
|
$bookingDate = $bookingArray['booking_date'];
|
||||||
$bookingTime = $bookingArray['booking_start_time'];
|
$bookingTime = $bookingArray['booking_start_time'];
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ class User_model extends CI_Model {
|
|||||||
public function sendEmail($addressList, $lang, $subpage){
|
public function sendEmail($addressList, $lang, $subpage){
|
||||||
// Import PHPMailer classes into the global namespace
|
// Import PHPMailer classes into the global namespace
|
||||||
// These must be at the top of your script, not inside a function
|
// These must be at the top of your script, not inside a function
|
||||||
echo $lang;
|
|
||||||
if(!empty($addressList)){
|
if(!empty($addressList)){
|
||||||
foreach($addressList as $addressListItem){
|
foreach($addressList as $addressListItem){
|
||||||
// Instantiation and passing `true` enables exceptions
|
// Instantiation and passing `true` enables exceptions
|
||||||
@@ -179,7 +178,6 @@ class User_model extends CI_Model {
|
|||||||
//$mail->AltBody = strip_tags($answer_message);
|
//$mail->AltBody = strip_tags($answer_message);
|
||||||
|
|
||||||
$mail->send();
|
$mail->send();
|
||||||
echo 'Üzenet elküldve';
|
|
||||||
if($subpage != ''){
|
if($subpage != ''){
|
||||||
header('location:'.base_url().$lang.'/booking-finished/'.$subpage);
|
header('location:'.base_url().$lang.'/booking-finished/'.$subpage);
|
||||||
}
|
}
|
||||||
@@ -187,7 +185,10 @@ class User_model extends CI_Model {
|
|||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
//header('location:'.base_url().'manage-applicants/?email-sent=false&error='.$mail->ErrorInfo);
|
//header('location:'.base_url().'manage-applicants/?email-sent=false&error='.$mail->ErrorInfo);
|
||||||
log_message('error', 'sendEmail FAILED: ' . $mail->ErrorInfo);
|
log_message('error', 'sendEmail FAILED: ' . $mail->ErrorInfo);
|
||||||
echo "Üzenetküldési hiba. Mailer Error: {$mail->ErrorInfo}";
|
// Do NOT echo the mailer error. This runs after the booking is
|
||||||
|
// saved and after the redirect header is queued, so it only leaks
|
||||||
|
// Hungarian internals into a response the guest never reads.
|
||||||
|
// The log_message() above is the record.
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
'too_far' => 'Bookings can only be made up to 3 months in advance.',
|
'too_far' => 'Bookings can only be made up to 3 months in advance.',
|
||||||
'category_mismatch' => 'The selected staff member does not perform the chosen services. Please start again.',
|
'category_mismatch' => 'The selected staff member does not perform the chosen services. Please start again.',
|
||||||
'single_service_only' => 'Only one treatment can be booked at a time. Please select just one.',
|
'single_service_only' => 'Only one treatment can be booked at a time. Please select just one.',
|
||||||
|
'invalid_email' => 'That e-mail address does not look valid. Please check it - your confirmation and the link to manage your booking are sent there.',
|
||||||
'no_service' => 'No service was selected. Please choose at least one service.',
|
'no_service' => 'No service was selected. Please choose at least one service.',
|
||||||
'default' => 'Something went wrong with your booking. Please try again.',
|
'default' => 'Something went wrong with your booking. Please try again.',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
'too_far' => 'Foglalás legfeljebb 3 hónappal előre adható le.',
|
'too_far' => 'Foglalás legfeljebb 3 hónappal előre adható le.',
|
||||||
'category_mismatch' => 'A kiválasztott kolléga nem végzi a kiválasztott szolgáltatásokat. Kérjük, válasszon újra.',
|
'category_mismatch' => 'A kiválasztott kolléga nem végzi a kiválasztott szolgáltatásokat. Kérjük, válasszon újra.',
|
||||||
'single_service_only' => 'Egyszerre csak egy kezelés foglalható. Kérjük, csak egyet válasszon.',
|
'single_service_only' => 'Egyszerre csak egy kezelés foglalható. Kérjük, csak egyet válasszon.',
|
||||||
|
'invalid_email' => 'Az e-mail cím nem tűnik érvényesnek. Kérjük, ellenőrizze - a visszaigazolást és a foglalás kezelésére szolgáló linket erre a címre küldjük.',
|
||||||
'no_service' => 'Nem választott ki szolgáltatást. Kérjük, válasszon legalább egyet.',
|
'no_service' => 'Nem választott ki szolgáltatást. Kérjük, válasszon legalább egyet.',
|
||||||
'default' => 'Hiba történt a foglalás során. Kérjük, próbálja újra.',
|
'default' => 'Hiba történt a foglalás során. Kérjük, próbálja újra.',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
'too_far' => 'Du kan kun bestille time inntil 3 måneder frem i tid.',
|
'too_far' => 'Du kan kun bestille time inntil 3 måneder frem i tid.',
|
||||||
'category_mismatch' => 'Den valgte medarbeideren utfører ikke de valgte tjenestene. Vennligst velg på nytt.',
|
'category_mismatch' => 'Den valgte medarbeideren utfører ikke de valgte tjenestene. Vennligst velg på nytt.',
|
||||||
'single_service_only' => 'Du kan kun bestille én behandling om gangen. Vennligst velg kun én.',
|
'single_service_only' => 'Du kan kun bestille én behandling om gangen. Vennligst velg kun én.',
|
||||||
|
'invalid_email' => 'E-postadressen ser ikke ut til å være gyldig. Vennligst kontroller den - bekreftelsen og lenken for å endre bestillingen sendes dit.',
|
||||||
'no_service' => 'Du har ikke valgt noen tjeneste. Vennligst velg minst én tjeneste.',
|
'no_service' => 'Du har ikke valgt noen tjeneste. Vennligst velg minst én tjeneste.',
|
||||||
'default' => 'Noe gikk galt med bestillingen. Vennligst prøv igjen.',
|
'default' => 'Noe gikk galt med bestillingen. Vennligst prøv igjen.',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -177,7 +177,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingEmailTitle">E-mail:</label>
|
<label class="bookingEmailTitle">E-mail:</label>
|
||||||
<input type="text" class="bookingEmail" name="guest_email" value="" required>
|
<input type="email" class="bookingEmail" name="guest_email" value="" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingPhoneTitle">Phone:</label>
|
<label class="bookingPhoneTitle">Phone:</label>
|
||||||
|
|||||||
@@ -178,7 +178,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingEmailTitle">E-mail:</label>
|
<label class="bookingEmailTitle">E-mail:</label>
|
||||||
<input type="text" class="bookingEmail" name="guest_email" value="" required>
|
<input type="email" class="bookingEmail" name="guest_email" value="" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingPhoneTitle">Telefon:</label>
|
<label class="bookingPhoneTitle">Telefon:</label>
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ if (is_array($services) && count($services) > 0) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingEmailTitle">E-mail:</label>
|
<label class="bookingEmailTitle">E-mail:</label>
|
||||||
<input type="text" class="bookingEmail" name="guest_email" value="" required>
|
<input type="email" class="bookingEmail" name="guest_email" value="" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<label class="bookingPhoneTitle">Telefon:</label>
|
<label class="bookingPhoneTitle">Telefon:</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user