From 084aed9e230e1c7331c70b872fa3dddb36a5f530 Mon Sep 17 00:00:00 2001 From: Alex Tselegidis Date: Mon, 16 Nov 2020 11:47:10 +0200 Subject: [PATCH] Notifications go out on the recipient's timezone (#914) --- application/libraries/Ics_file.php | 2 +- application/libraries/Notifications.php | 42 ++++---- engine/Notifications/Email.php | 133 +++++++++++++++--------- 3 files changed, 106 insertions(+), 71 deletions(-) diff --git a/application/libraries/Ics_file.php b/application/libraries/Ics_file.php index 28056de8..bfdcea95 100644 --- a/application/libraries/Ics_file.php +++ b/application/libraries/Ics_file.php @@ -156,7 +156,7 @@ class Ics_file { $calendar ->setProdId('-//EasyAppointments//Open Source Web Scheduler//EN') - ->setTimezone(new DateTimeZone(date_default_timezone_get())) + ->setTimezone(new DateTimeZone($provider['timezone'])) ->addEvent($event); // Setup exporter. diff --git a/application/libraries/Notifications.php b/application/libraries/Notifications.php index 4723b48f..f7917207 100644 --- a/application/libraries/Notifications.php +++ b/application/libraries/Notifications.php @@ -33,13 +33,18 @@ class Notifications { public function __construct() { $this->CI =& get_instance(); + $this->CI->load->model('providers_model'); $this->CI->load->model('secretaries_model'); $this->CI->load->model('secretaries_model'); $this->CI->load->model('admins_model'); $this->CI->load->model('appointments_model'); $this->CI->load->model('settings_model'); + $this->CI->load->library('ics_file'); + $this->CI->load->library('timezones'); + + $this->CI->config->load('email'); } /** @@ -54,44 +59,39 @@ class Notifications { */ public function notify_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode = FALSE) { - // Send email notifications to customer and provider. try { - $this->CI->config->load('email'); - $email = new EmailClient($this->CI, $this->CI->config->config); - if ($manage_mode === FALSE) - { - $customer_title = new Text(lang('appointment_booked')); - $customer_message = new Text(lang('thank_you_for_appointment')); - $provider_title = new Text(lang('appointment_added_to_your_plan')); - $provider_message = new Text(lang('appointment_link_description')); - - } - else + if ($manage_mode) { $customer_title = new Text(lang('appointment_changes_saved')); $customer_message = new Text(''); $provider_title = new Text(lang('appointment_details_changed')); $provider_message = new Text(''); } + else + { + $customer_title = new Text(lang('appointment_booked')); + $customer_message = new Text(lang('thank_you_for_appointment')); + $provider_title = new Text(lang('appointment_added_to_your_plan')); + $provider_message = new Text(lang('appointment_link_description')); + } $customer_link = new Url(site_url('appointments/index/' . $appointment['hash'])); $provider_link = new Url(site_url('backend/index/' . $appointment['hash'])); + $ics_stream = $this->CI->ics_file->get_stream($appointment, $service, $provider, $customer); + $send_customer = filter_var( $this->CI->settings_model->get_setting('customer_notifications'), FILTER_VALIDATE_BOOLEAN); - - $ics_stream = $this->CI->ics_file->get_stream($appointment, $service, $provider, $customer); - if ($send_customer === TRUE) { $email->sendAppointmentDetails($appointment, $provider, $service, $customer, $settings, $customer_title, - $customer_message, $customer_link, new Email($customer['email']), new Text($ics_stream)); + $customer_message, $customer_link, new Email($customer['email']), new Text($ics_stream), $customer['timezone']); } $send_provider = filter_var( @@ -102,7 +102,7 @@ class Notifications { { $email->sendAppointmentDetails($appointment, $provider, $service, $customer, $settings, $provider_title, - $provider_message, $provider_link, new Email($provider['email']), new Text($ics_stream)); + $provider_message, $provider_link, new Email($provider['email']), new Text($ics_stream), $provider['timezone']); } // Notify admins @@ -110,14 +110,14 @@ class Notifications { foreach ($admins as $admin) { - if ( ! $admin['settings']['notifications'] === '0') + if ($admin['settings']['notifications'] === '0') { continue; } $email->sendAppointmentDetails($appointment, $provider, $service, $customer, $settings, $provider_title, - $provider_message, $provider_link, new Email($admin['email']), new Text($ics_stream)); + $provider_message, $provider_link, new Email($admin['email']), new Text($ics_stream), $admin['timezone']); } // Notify secretaries @@ -125,7 +125,7 @@ class Notifications { foreach ($secretaries as $secretary) { - if ( ! $secretary['settings']['notifications'] === '0') + if ($secretary['settings']['notifications'] === '0') { continue; } @@ -137,7 +137,7 @@ class Notifications { $email->sendAppointmentDetails($appointment, $provider, $service, $customer, $settings, $provider_title, - $provider_message, $provider_link, new Email($secretary['email']), new Text($ics_stream)); + $provider_message, $provider_link, new Email($secretary['email']), new Text($ics_stream), $secretary['timezone']); } } catch (Exception $exception) diff --git a/engine/Notifications/Email.php b/engine/Notifications/Email.php index 0f461d12..bfbf6536 100755 --- a/engine/Notifications/Email.php +++ b/engine/Notifications/Email.php @@ -13,11 +13,16 @@ namespace EA\Engine\Notifications; -use \EA\Engine\Types\Text; -use \EA\Engine\Types\NonEmptyText; -use \EA\Engine\Types\Url; -use \EA\Engine\Types\Email as EmailAddress; -use \PHPMailer\PHPMailer\PHPMailer; +use DateTime; +use DateTimeZone; +use EA\Engine\Types\Email as EmailAddress; +use EA\Engine\Types\NonEmptyText; +use EA\Engine\Types\Text; +use EA\Engine\Types\Url; +use EA_Controller; +use Exception; +use PHPMailer\PHPMailer\PHPMailer; +use RuntimeException; /** * Email Notifications Class @@ -32,7 +37,7 @@ class Email { /** * Framework Instance * - * @var CI_Controller + * @var EA_Controller */ protected $framework; @@ -65,32 +70,37 @@ class Email { * @param array $provider Contains the provider data. * @param array $service Contains the service data. * @param array $customer Contains the customer data. - * @param array $company Contains settings of the company. By the time the - * "company_name", "company_link" and "company_email" values are required in the array. + * @param array $settings Contains settings of the company. At the time the "company_name", "company_link" and + * "company_email" values are required in the array. * @param \EA\Engine\Types\Text $title The email title may vary depending the receiver. * @param \EA\Engine\Types\Text $message The email message may vary depending the receiver. - * @param \EA\Engine\Types\Url $appointmentLink This link is going to enable the receiver to make changes - * to the appointment record. + * @param \EA\Engine\Types\Url $appointmentLink This link is going to enable the receiver to make changes to the + * appointment record. * @param \EA\Engine\Types\Email $recipientEmail The recipient email address. * @param \EA\Engine\Types\Text $icsStream Stream contents of the ICS file. + * @param string|null $timezone Custom timezone for the notification. + * + * @throws \PHPMailer\PHPMailer\Exception */ public function sendAppointmentDetails( array $appointment, array $provider, array $service, array $customer, - array $company, + array $settings, Text $title, Text $message, Url $appointmentLink, EmailAddress $recipientEmail, - Text $icsStream - ) { + Text $icsStream, + $timezone = NULL + ) + { $framework = get_instance(); $timezones = $framework->timezones->to_array(); - switch ($company['date_format']) + switch ($settings['date_format']) { case 'DMY': $date_format = 'd/m/Y'; @@ -102,19 +112,30 @@ class Email { $date_format = 'Y/m/d'; break; default: - throw new \Exception('Invalid date_format value: ' . $company['date_format']); + throw new Exception('Invalid date_format value: ' . $settings['date_format']); } - switch ($company['time_format']) + switch ($settings['time_format']) { case 'military': - $timeFormat = 'H:i'; + $time_format = 'H:i'; break; case 'regular': - $timeFormat = 'g:i A'; + $time_format = 'g:i A'; break; default: - throw new \Exception('Invalid time_format value: ' . $company['time_format']); + throw new Exception('Invalid time_format value: ' . $settings['time_format']); + } + + $appointment_timezone = new DateTimeZone($provider['timezone']); + $appointment_start = new DateTime($appointment['start_datetime'], $appointment_timezone); + $appointment_end = new DateTime($appointment['end_datetime'], $appointment_timezone); + + if ($timezone && $timezone !== $provider['timezone']) + { + $appointment_timezone = new DateTimeZone($timezone); + $appointment_start->setTimezone($appointment_timezone); + $appointment_end->setTimezone($appointment_timezone); } // Prepare template replace array. @@ -122,12 +143,12 @@ class Email { $email_message = $message->get(); $appointment_service = $service['name']; $appointment_provider = $provider['first_name'] . ' ' . $provider['last_name']; - $appointment_start_date = date($date_format . ' ' . $timeFormat, strtotime($appointment['start_datetime'])); - $appointment_end_date = date($date_format . ' ' . $timeFormat, strtotime($appointment['end_datetime'])); - $appointment_timezone = $timezones[$provider['timezone']]; + $appointment_start_date = $appointment_start->format($date_format . ' ' . $time_format); + $appointment_end_date = $appointment_end->format($date_format . ' ' . $time_format); + $appointment_timezone = $timezones[empty($timezone) ? $provider['timezone'] : $timezone]; $appointment_link = $appointmentLink->get(); - $company_link = $company['company_link']; - $company_name = $company['company_name']; + $company_link = $settings['company_link']; + $company_name = $settings['company_name']; $customer_name = $customer['first_name'] . ' ' . $customer['last_name']; $customer_email = $customer['email']; $customer_phone = $customer['phone_number']; @@ -137,9 +158,9 @@ class Email { require __DIR__ . '/../../application/views/emails/appointment_details.php'; $html = ob_get_clean(); - $mailer = $this->_createMailer(); - $mailer->From = $company['company_email']; - $mailer->FromName = $company['company_name']; + $mailer = $this->create_mailer(); + $mailer->From = $settings['company_email']; + $mailer->FromName = $settings['company_name']; $mailer->AddAddress($recipientEmail->get()); $mailer->Subject = $title->get(); $mailer->Body = $html; @@ -147,7 +168,7 @@ class Email { if ( ! $mailer->Send()) { - throw new \RuntimeException('Email could not been sent. Mailer Error (Line ' . __LINE__ . '): ' + throw new RuntimeException('Email could not been sent. Mailer Error (Line ' . __LINE__ . '): ' . $mailer->ErrorInfo); } } @@ -165,26 +186,31 @@ class Email { * @param array $provider The record data of the appointment provider. * @param array $service The record data of the appointment service. * @param array $customer The record data of the appointment customer. - * @param array $company Some settings that are required for this function. By now this array must contain + * @param array $settings Some settings that are required for this function. As of now this array must contain * the following values: "company_link", "company_name", "company_email". * @param \EA\Engine\Types\Email $recipientEmail The email address of the email recipient. * @param \EA\Engine\Types\Text $reason The reason why the appointment is deleted. + * @param string|null $timezone Custom timezone. + * + * @throws \PHPMailer\PHPMailer\Exception */ public function sendDeleteAppointment( array $appointment, array $provider, array $service, array $customer, - array $company, + array $settings, EmailAddress $recipientEmail, - Text $reason - ) { + Text $reason, + $timezone = NULL + ) + { $framework = get_instance(); $timezones = $framework->timezones->to_array(); - switch ($company['date_format']) + switch ($settings['date_format']) { case 'DMY': $date_format = 'd/m/Y'; @@ -196,29 +222,38 @@ class Email { $date_format = 'Y/m/d'; break; default: - throw new \Exception('Invalid date_format value: ' . $company['date_format']); + throw new Exception('Invalid date_format value: ' . $settings['date_format']); } - switch ($company['time_format']) + switch ($settings['time_format']) { case 'military': - $timeFormat = 'H:i'; + $time_format = 'H:i'; break; case 'regular': - $timeFormat = 'g:i A'; + $time_format = 'g:i A'; break; default: - throw new \Exception('Invalid time_format value: ' . $company['time_format']); + throw new Exception('Invalid time_format value: ' . $settings['time_format']); + } + + $appointment_timezone = new DateTimeZone($provider['timezone']); + $appointment_start = new DateTime($appointment['start_datetime'], $appointment_timezone); + + if ($timezone && $timezone !== $provider['timezone']) + { + $appointment_timezone = new DateTimeZone($timezone); + $appointment_start->setTimezone($appointment_timezone); } // Prepare email template data. $appointment_service = $service['name']; $appointment_provider = $provider['first_name'] . ' ' . $provider['last_name']; - $appointment_date = date($date_format . ' ' . $timeFormat, strtotime($appointment['start_datetime'])); + $appointment_date = $appointment_start->format($date_format . ' ' . $time_format); $appointment_duration = $service['duration'] . ' ' . $this->framework->lang->line('minutes'); - $appointment_timezone = $timezones[$provider['timezone']]; - $company_link = $company['company_link']; - $company_name = $company['company_name']; + $appointment_timezone = $timezones[empty($timezone) ? $provider['timezone'] : $timezone]; + $company_link = $settings['company_link']; + $company_name = $settings['company_name']; $customer_name = $customer['first_name'] . ' ' . $customer['last_name']; $customer_email = $customer['email']; $customer_phone = $customer['phone_number']; @@ -229,18 +264,18 @@ class Email { require __DIR__ . '/../../application/views/emails/delete_appointment.php'; $html = ob_get_clean(); - $mailer = $this->_createMailer(); + $mailer = $this->create_mailer(); // Send email to recipient. - $mailer->From = $company['company_email']; - $mailer->FromName = $company['company_name']; + $mailer->From = $settings['company_email']; + $mailer->FromName = $settings['company_name']; $mailer->AddAddress($recipientEmail->get()); // "Name" argument crushes the phpmailer class. $mailer->Subject = $this->framework->lang->line('appointment_cancelled_title'); $mailer->Body = $html; if ( ! $mailer->Send()) { - throw new \RuntimeException('Email could not been sent. Mailer Error (Line ' . __LINE__ . '): ' + throw new RuntimeException('Email could not been sent. Mailer Error (Line ' . __LINE__ . '): ' . $mailer->ErrorInfo); } } @@ -265,7 +300,7 @@ class Email { require __DIR__ . '/../../application/views/emails/new_password.php'; $html = ob_get_clean(); - $mailer = $this->_createMailer(); + $mailer = $this->create_mailer(); $mailer->From = $company['company_email']; $mailer->FromName = $company['company_name']; @@ -275,7 +310,7 @@ class Email { if ( ! $mailer->Send()) { - throw new \RuntimeException('Email could not been sent. Mailer Error (Line ' . __LINE__ . '): ' + throw new RuntimeException('Email could not been sent. Mailer Error (Line ' . __LINE__ . '): ' . $mailer->ErrorInfo); } } @@ -285,7 +320,7 @@ class Email { * * @return PHPMailer */ - protected function _createMailer() + protected function create_mailer() { $mailer = new PHPMailer();