Moved duplicated code into re-usable library classes

This commit is contained in:
Alex Tselegidis 2020-10-21 21:37:47 +03:00
parent d2dd9925ec
commit f7ca104836
6 changed files with 1023 additions and 968 deletions

File diff suppressed because it is too large Load diff

View file

@ -38,6 +38,8 @@ use EA\Engine\Types\Url;
* @property Customers_Model $customers_model * @property Customers_Model $customers_model
* @property Settings_Model $settings_model * @property Settings_Model $settings_model
* @property Timezones $timezones * @property Timezones $timezones
* @property Synchronization $synchronization
* @property Notifications $notifications
* @property Roles_Model $roles_model * @property Roles_Model $roles_model
* @property Secretaries_Model $secretaries_model * @property Secretaries_Model $secretaries_model
* @property Admins_Model $admins_model * @property Admins_Model $admins_model
@ -283,6 +285,8 @@ class Backend_api extends CI_Controller {
$this->load->model('customers_model'); $this->load->model('customers_model');
$this->load->model('settings_model'); $this->load->model('settings_model');
$this->load->library('timezones'); $this->load->library('timezones');
$this->load->library('synchronization');
$this->load->library('notifications');
$this->load->model('user_model'); $this->load->model('user_model');
// Save customer changes to the database. // Save customer changes to the database.
@ -290,10 +294,10 @@ class Backend_api extends CI_Controller {
{ {
$customer = json_decode($this->input->post('customer_data'), TRUE); $customer = json_decode($this->input->post('customer_data'), TRUE);
$required_privilegesileges = ( ! isset($customer['id'])) $required_privileges = ( ! isset($customer['id']))
? $this->privileges[PRIV_CUSTOMERS]['add'] ? $this->privileges[PRIV_CUSTOMERS]['add']
: $this->privileges[PRIV_CUSTOMERS]['edit']; : $this->privileges[PRIV_CUSTOMERS]['edit'];
if ($required_privilegesileges == FALSE) if ($required_privileges == FALSE)
{ {
throw new Exception('You do not have the required privileges for this task.'); throw new Exception('You do not have the required privileges for this task.');
} }
@ -306,17 +310,17 @@ class Backend_api extends CI_Controller {
{ {
$appointment = json_decode($this->input->post('appointment_data'), TRUE); $appointment = json_decode($this->input->post('appointment_data'), TRUE);
$required_privilegesileges = ( ! isset($appointment['id'])) $required_privileges = ( ! isset($appointment['id']))
? $this->privileges[PRIV_APPOINTMENTS]['add'] ? $this->privileges[PRIV_APPOINTMENTS]['add']
: $this->privileges[PRIV_APPOINTMENTS]['edit']; : $this->privileges[PRIV_APPOINTMENTS]['edit'];
if ($required_privilegesileges == FALSE) if ($required_privileges == FALSE)
{ {
throw new Exception('You do not have the required privileges for this task.'); throw new Exception('You do not have the required privileges for this task.');
} }
$manage_mode = isset($appointment['id']); $manage_mode = isset($appointment['id']);
// If the appointment does not contain the customer record id, then it // If the appointment does not contain the customer record id, then it means that is is going to be
// means that is is going to be inserted. Get the customer's record id. // inserted. Get the customer's record ID.
if ( ! isset($appointment['id_users_customer'])) if ( ! isset($appointment['id_users_customer']))
{ {
$appointment['id_users_customer'] = $customer['id']; $appointment['id_users_customer'] = $customer['id'];
@ -348,140 +352,11 @@ class Backend_api extends CI_Controller {
'time_format' => $this->settings_model->get_setting('time_format') 'time_format' => $this->settings_model->get_setting('time_format')
]; ];
// Sync appointment changes with Google Calendar. $this->synchronization->sync_appointment_deleted($appointment, $provider);
try $this->notifications->notify_appointment_deleted($appointment, $service, $provider, $customer, $settings);
{
$google_sync = $this->providers_model->get_setting('google_sync',
$appointment['id_users_provider']);
if ($google_sync == TRUE)
{
$google_token = json_decode($this->providers_model->get_setting('google_token',
$appointment['id_users_provider']));
$this->load->library('Google_sync');
$this->google_sync->refresh_token($google_token->refresh_token);
if ($appointment['id_google_calendar'] == NULL)
{
$google_event = $this->google_sync->add_appointment($appointment, $provider,
$service, $customer, $settings);
$appointment['id_google_calendar'] = $google_event->id;
$this->appointments_model->add($appointment); // Store google calendar id.
}
else
{
$this->google_sync->update_appointment($appointment, $provider,
$service, $customer, $settings);
}
}
}
catch (Exception $exception)
{
$warnings[] = [
'message' => $exception->getMessage(),
'trace' => config('debug') ? $exception->getTrace() : []
];
}
// Send email notifications to provider and customer.
try
{
$this->config->load('email');
$email = new EmailClient($this, $this->config->config);
$send_provider = $this->providers_model
->get_setting('notifications', $provider['id']);
if ( ! $manage_mode)
{
$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
{
$customer_title = new Text(lang('appointment_details_changed'));
$customer_message = new Text('');
$provider_title = new Text(lang('appointment_changes_saved'));
$provider_message = new Text('');
}
$customer_link = new Url(site_url('appointments/index/' . $appointment['hash']));
$provider_link = new Url(site_url('backend/index/' . $appointment['hash']));
$send_customer = $this->settings_model->get_setting('customer_notifications');
$this->load->library('ics_file');
$ics_stream = $this->ics_file->get_stream($appointment, $service, $provider, $customer);
if ((bool)$send_customer === TRUE)
{
$email->sendAppointmentDetails($appointment, $provider,
$service, $customer, $settings, $customer_title,
$customer_message, $customer_link, new Email($customer['email']), new Text($ics_stream));
}
if ($send_provider == TRUE)
{
$email->sendAppointmentDetails($appointment, $provider,
$service, $customer, $settings, $provider_title,
$provider_message, $provider_link, new Email($provider['email']), new Text($ics_stream));
}
// Notify admins
$admins = $this->admins_model->get_batch();
foreach($admins as $admin)
{
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));
}
// Notify secretaries
$secretaries = $this->secretaries_model->get_batch();
foreach($secretaries as $secretary)
{
if (!$secretary['settings']['notifications'] === '0')
{
continue;
}
if (in_array($provider['id'], $secretary['providers']))
{
continue;
}
$email->sendAppointmentDetails($appointment, $provider,
$service, $customer, $settings, $provider_title,
$provider_message, $provider_link, new Email($secretary['email']), new Text($ics_stream));
}
}
catch (Exception $exception)
{
$warnings[] = [
'message' => $exception->getMessage(),
'trace' => config('debug') ? $exception->getTrace() : []
];
}
if (empty($warnings))
{
$response = AJAX_SUCCESS; $response = AJAX_SUCCESS;
} }
else
{
$response = ['warnings' => $warnings];
}
}
catch (Exception $exception) catch (Exception $exception)
{ {
$this->output->set_status_header(500); $this->output->set_status_header(500);
@ -1032,10 +907,10 @@ class Backend_api extends CI_Controller {
$this->load->model('customers_model'); $this->load->model('customers_model');
$customer = json_decode($this->input->post('customer'), TRUE); $customer = json_decode($this->input->post('customer'), TRUE);
$required_privilegesileges = ( ! isset($customer['id'])) $required_privileges = ( ! isset($customer['id']))
? $this->privileges[PRIV_CUSTOMERS]['add'] ? $this->privileges[PRIV_CUSTOMERS]['add']
: $this->privileges[PRIV_CUSTOMERS]['edit']; : $this->privileges[PRIV_CUSTOMERS]['edit'];
if ($required_privilegesileges == FALSE) if ($required_privileges == FALSE)
{ {
throw new Exception('You do not have the required privileges for this task.'); throw new Exception('You do not have the required privileges for this task.');
} }
@ -1105,10 +980,10 @@ class Backend_api extends CI_Controller {
$this->load->model('services_model'); $this->load->model('services_model');
$service = json_decode($this->input->post('service'), TRUE); $service = json_decode($this->input->post('service'), TRUE);
$required_privilegesileges = ( ! isset($service['id'])) $required_privileges = ( ! isset($service['id']))
? $this->privileges[PRIV_SERVICES]['add'] ? $this->privileges[PRIV_SERVICES]['add']
: $this->privileges[PRIV_SERVICES]['edit']; : $this->privileges[PRIV_SERVICES]['edit'];
if ($required_privilegesileges == FALSE) if ($required_privileges == FALSE)
{ {
throw new Exception('You do not have the required privileges for this task.'); throw new Exception('You do not have the required privileges for this task.');
} }
@ -1215,10 +1090,10 @@ class Backend_api extends CI_Controller {
$this->load->model('services_model'); $this->load->model('services_model');
$category = json_decode($this->input->post('category'), TRUE); $category = json_decode($this->input->post('category'), TRUE);
$required_privilegesileges = ( ! isset($category['id'])) $required_privileges = ( ! isset($category['id']))
? $this->privileges[PRIV_SERVICES]['add'] ? $this->privileges[PRIV_SERVICES]['add']
: $this->privileges[PRIV_SERVICES]['edit']; : $this->privileges[PRIV_SERVICES]['edit'];
if ($required_privilegesileges == FALSE) if ($required_privileges == FALSE)
{ {
throw new Exception('You do not have the required privileges for this task.'); throw new Exception('You do not have the required privileges for this task.');
} }

View file

@ -37,6 +37,8 @@ use EA\Engine\Types\NonEmptyText;
* @property Customers_Model $customers_model * @property Customers_Model $customers_model
* @property Settings_Model $settings_model * @property Settings_Model $settings_model
* @property Timezones $timezones * @property Timezones $timezones
* @property Notifications $notifications
* @property Synchronization $synchronization
* @property Roles_Model $roles_model * @property Roles_Model $roles_model
* @property Secretaries_Model $secretaries_model * @property Secretaries_Model $secretaries_model
* @property Admins_Model $admins_model * @property Admins_Model $admins_model
@ -59,6 +61,12 @@ class Appointments extends API_V1_Controller {
{ {
parent::__construct(); parent::__construct();
$this->load->model('appointments_model'); $this->load->model('appointments_model');
$this->load->model('services_model');
$this->load->model('providers_model');
$this->load->model('customers_model');
$this->load->model('settings_model');
$this->load->library('synchronization');
$this->load->library('notifications');
$this->parser = new \EA\Engine\Api\V1\Parsers\Appointments; $this->parser = new \EA\Engine\Api\V1\Parsers\Appointments;
} }
@ -109,8 +117,6 @@ class Appointments extends API_V1_Controller {
*/ */
public function post() public function post()
{ {
$this->load->model('services_model');
try try
{ {
// Insert the appointment to the database. // Insert the appointment to the database.
@ -138,6 +144,20 @@ class Appointments extends API_V1_Controller {
$id = $this->appointments_model->add($appointment); $id = $this->appointments_model->add($appointment);
$service = $this->services_model->get_row($appointment['id_services']);
$provider = $this->providers_model->get_row($appointment['id_users_provider']);
$customer = $this->customers_model->get_row($appointment['id_users_customer']);
$settings = [
'company_name' => $this->settings_model->get_setting('company_name'),
'company_email' => $this->settings_model->get_setting('company_email'),
'company_link' => $this->settings_model->get_setting('company_link'),
'date_format' => $this->settings_model->get_setting('date_format'),
'time_format' => $this->settings_model->get_setting('time_format')
];
$this->synchronization->sync_appointment_saved($appointment, $service, $provider, $customer, $settings, FALSE);
$this->notifications->notify_appointment_saved($appointment, $service, $provider, $customer, $settings, FALSE);
// Fetch the new object from the database and return it to the client. // Fetch the new object from the database and return it to the client.
$batch = $this->appointments_model->get_batch('id = ' . $id); $batch = $this->appointments_model->get_batch('id = ' . $id);
$response = new Response($batch); $response = new Response($batch);
@ -174,6 +194,21 @@ class Appointments extends API_V1_Controller {
$updatedAppointment['id'] = $id; $updatedAppointment['id'] = $id;
$id = $this->appointments_model->add($updatedAppointment); $id = $this->appointments_model->add($updatedAppointment);
$service = $this->services_model->get_row($updatedAppointment['id_services']);
$provider = $this->providers_model->get_row($updatedAppointment['id_users_provider']);
$customer = $this->customers_model->get_row($updatedAppointment['id_users_customer']);
$settings = [
'company_name' => $this->settings_model->get_setting('company_name'),
'company_email' => $this->settings_model->get_setting('company_email'),
'company_link' => $this->settings_model->get_setting('company_link'),
'date_format' => $this->settings_model->get_setting('date_format'),
'time_format' => $this->settings_model->get_setting('time_format')
];
$this->synchronization->sync_appointment_saved($updatedAppointment, $service, $provider, $customer, $settings, TRUE);
$this->notifications->notify_appointment_saved($updatedAppointment, $service, $provider, $customer, $settings, TRUE);
// Fetch the updated object from the database and return it to the client. // Fetch the updated object from the database and return it to the client.
$batch = $this->appointments_model->get_batch('id = ' . $id); $batch = $this->appointments_model->get_batch('id = ' . $id);
$response = new Response($batch); $response = new Response($batch);
@ -194,8 +229,23 @@ class Appointments extends API_V1_Controller {
{ {
try try
{ {
$appointment = $this->appointments_model->get_row($id);
$service = $this->services_model->get_row($appointment['id_services']);
$provider = $this->providers_model->get_row($appointment['id_users_provider']);
$customer = $this->customers_model->get_row($appointment['id_users_customer']);
$settings = [
'company_name' => $this->settings_model->get_setting('company_name'),
'company_email' => $this->settings_model->get_setting('company_email'),
'company_link' => $this->settings_model->get_setting('company_link'),
'date_format' => $this->settings_model->get_setting('date_format'),
'time_format' => $this->settings_model->get_setting('time_format')
];
$this->appointments_model->delete($id); $this->appointments_model->delete($id);
$this->synchronization->sync_appointment_deleted($appointment, $provider);
$this->notifications->notify_appointment_deleted($appointment, $service, $provider, $customer, $settings);
$response = new Response([ $response = new Response([
'code' => 200, 'code' => 200,
'message' => 'Record was deleted successfully!' 'message' => 'Record was deleted successfully!'

View file

@ -0,0 +1,552 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Open Source Web Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @copyright Copyright (c) 2013 - 2020, Alex Tselegidis
* @license http://opensource.org/licenses/GPL-3.0 - GPLv3
* @link http://easyappointments.org
* @since v1.4.0
* ---------------------------------------------------------------------------- */
/**
* Class Availability
*
* Handles the availability generation of providers, based on their working plan and their schedule.
*/
class Availability {
/**
* @var CI_Controller
*/
protected $CI;
/**
* Availability constructor.
*/
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');
}
/**
* Get the available hours of a provider.
*
* @param string $date Selected date (Y-m-d).
* @param array $service Service record.
* @param array $provider Provider record.
* @param int|null $exclude_appointment_id Exclude an appointment from the availability generation.
*
* @return array
*
* @throws Exception
*/
public function get_available_hours($date, $service, $provider, $exclude_appointment_id = NULL)
{
$available_periods = $this->get_available_periods($date, $provider, $exclude_appointment_id);
$available_hours = $this->generate_available_hours($date, $service, $available_periods);
if ($service['attendants_number'] > 1)
{
$available_hours = $this->consider_multiple_attendants($date, $service, $provider);
}
return $this->consider_book_advance_timeout($date, $available_hours, $provider);
}
/**
* Get an array containing the free time periods (start - end) of a selected date.
*
* This method is very important because there are many cases where the system needs to know when a provider is
* available for an appointment. This method will return an array that belongs to the selected date and contains
* values that have the start and the end time of an available time period.
*
* @param string $date Select date string.
* @param array $provider Provider record.
* @param int|null $exclude_appointment_id Exclude an appointment from the availability generation.
*
* @return array Returns an array with the available time periods of the provider.
*
* @throws Exception
*/
public function get_available_periods(
$date,
$provider,
$exclude_appointment_id = NULL
)
{
// Get the service, provider's working plan and provider appointments.
$working_plan = json_decode($provider['settings']['working_plan'], TRUE);
// Get the provider's working plan exceptions.
$working_plan_exceptions = json_decode($provider['settings']['working_plan_exceptions'], TRUE);
$conditions = [
'id_users_provider' => $provider['id'],
];
// Sometimes it might be necessary to exclude an appointment from the calculation (e.g. when editing an
// existing appointment).
if ($exclude_appointment_id)
{
$conditions['id !='] = $exclude_appointment_id;
}
$appointments = $this->CI->appointments_model->get_batch($conditions);
// Find the empty spaces on the plan. The first split between the plan is due to a break (if any). After that
// every reserved appointment is considered to be a taken space in the plan.
$date_working_plan = $working_plan[strtolower(date('l', strtotime($date)))];
// Search if the $date is an custom availability period added outside the normal working plan.
if (isset($working_plan_exceptions[$date]))
{
$date_working_plan = $working_plan_exceptions[$date];
}
$periods = [];
if (isset($date_working_plan['breaks']))
{
$periods[] = [
'start' => $date_working_plan['start'],
'end' => $date_working_plan['end']
];
$day_start = new DateTime($date_working_plan['start']);
$day_end = new DateTime($date_working_plan['end']);
// Split the working plan to available time periods that do not contain the breaks in them.
foreach ($date_working_plan['breaks'] as $index => $break)
{
$break_start = new DateTime($break['start']);
$break_end = new DateTime($break['end']);
if ($break_start < $day_start)
{
$break_start = $day_start;
}
if ($break_end > $day_end)
{
$break_end = $day_end;
}
if ($break_start >= $break_end)
{
continue;
}
foreach ($periods as $key => $period)
{
$period_start = new DateTime($period['start']);
$period_end = new DateTime($period['end']);
$remove_current_period = FALSE;
if ($break_start > $period_start && $break_start < $period_end && $break_end > $period_start)
{
$periods[] = [
'start' => $period_start->format('H:i'),
'end' => $break_start->format('H:i')
];
$remove_current_period = TRUE;
}
if ($break_start < $period_end && $break_end > $period_start && $break_end < $period_end)
{
$periods[] = [
'start' => $break_end->format('H:i'),
'end' => $period_end->format('H:i')
];
$remove_current_period = TRUE;
}
if ($break_start == $period_start && $break_end == $period_end)
{
$remove_current_period = TRUE;
}
if ($remove_current_period)
{
unset($periods[$key]);
}
}
}
}
// Break the empty periods with the reserved appointments.
foreach ($appointments as $appointment)
{
foreach ($periods as $index => &$period)
{
$appointment_start = new DateTime($appointment['start_datetime']);
$appointment_end = new DateTime($appointment['end_datetime']);
if ($appointment_start >= $appointment_end)
{
continue;
}
$period_start = new DateTime($date . ' ' . $period['start']);
$period_end = new DateTime($date . ' ' . $period['end']);
if ($appointment_start <= $period_start && $appointment_end <= $period_end && $appointment_end <= $period_start)
{
// The appointment does not belong in this time period, so we will not change anything.
continue;
}
else
{
if ($appointment_start <= $period_start && $appointment_end <= $period_end && $appointment_end >= $period_start)
{
// The appointment starts before the period and finishes somewhere inside. We will need to break
// this period and leave the available part.
$period['start'] = $appointment_end->format('H:i');
}
else
{
if ($appointment_start >= $period_start && $appointment_end < $period_end)
{
// The appointment is inside the time period, so we will split the period into two new
// others.
unset($periods[$index]);
$periods[] = [
'start' => $period_start->format('H:i'),
'end' => $appointment_start->format('H:i')
];
$periods[] = [
'start' => $appointment_end->format('H:i'),
'end' => $period_end->format('H:i')
];
}
else if ($appointment_start == $period_start && $appointment_end == $period_end)
{
unset($periods[$index]); // The whole period is blocked so remove it from the available periods array.
}
else
{
if ($appointment_start >= $period_start && $appointment_end >= $period_start && $appointment_start <= $period_end)
{
// The appointment starts in the period and finishes out of it. We will need to remove
// the time that is taken from the appointment.
$period['end'] = $appointment_start->format('H:i');
}
else
{
if ($appointment_start >= $period_start && $appointment_end >= $period_end && $appointment_start >= $period_end)
{
// The appointment does not belong in the period so do not change anything.
continue;
}
else
{
if ($appointment_start <= $period_start && $appointment_end >= $period_end && $appointment_start <= $period_end)
{
// The appointment is bigger than the period, so this period needs to be removed.
unset($periods[$index]);
}
}
}
}
}
}
}
}
return array_values($periods);
}
/**
* Calculate the available appointment hours.
*
* Calculate the available appointment hours for the given date. The empty spaces
* are broken down to 15 min and if the service fit in each quarter then a new
* available hour is added to the "$available_hours" array.
*
* @param string $date Selected date (Y-m-d).
* @param array $service Service record.
* @param array $empty_periods Empty periods as generated by the "get_provider_available_time_periods"
* method.
*
* @return array Returns an array with the available hours for the appointment.
*
* @throws Exception
*/
protected function generate_available_hours(
$date,
$service,
$empty_periods
)
{
$available_hours = [];
foreach ($empty_periods as $period)
{
$start_hour = new DateTime($date . ' ' . $period['start']);
$end_hour = new DateTime($date . ' ' . $period['end']);
$interval = $service['availabilities_type'] === AVAILABILITIES_TYPE_FIXED ? (int)$service['duration'] : 15;
$current_hour = $start_hour;
$diff = $current_hour->diff($end_hour);
while (($diff->h * 60 + $diff->i) >= (int)$service['duration'])
{
$available_hours[] = $current_hour->format('H:i');
$current_hour->add(new DateInterval('PT' . $interval . 'M'));
$diff = $current_hour->diff($end_hour);
}
}
return $available_hours;
}
/**
* Get multiple attendants hours.
*
* This method will add the additional appointment hours whenever a service accepts multiple attendants.
*
* @param string $date Selected date (Y-m-d).
* @param array $service Service record.
* @param array $provider Provider record.
*
* @return array Returns the available hours array.
*
* @throws Exception
*/
protected function consider_multiple_attendants(
$date,
$service,
$provider
)
{
$unavailability_events = $this->CI->appointments_model->get_batch([
'is_unavailable' => TRUE,
'DATE(start_datetime)' => $date,
'id_users_provider' => $provider['id']
]);
$working_plan = json_decode($provider['settings']['working_plan'], TRUE);
$working_day = strtolower(date('l', strtotime($date)));
$working_hours = $working_plan[$working_day];
$periods = [
[
'start' => new DateTime($date . ' ' . $working_hours['start']),
'end' => new DateTime($date . ' ' . $working_hours['end'])
]
];
$periods = $this->remove_breaks($date, $periods, $working_hours['breaks']);
$periods = $this->remove_unavailability_events($periods, $unavailability_events);
$hours = [];
$interval_value = $service['availabilities_type'] == AVAILABILITIES_TYPE_FIXED ? $service['duration'] : '15';
$interval = new DateInterval('PT' . (int)$interval_value . 'M');
$duration = new DateInterval('PT' . (int)$service['duration'] . 'M');
foreach ($periods as $period)
{
$slot_start = clone $period['start'];
$slot_end = clone $slot_start;
$slot_end->add($duration);
while ($slot_end <= $period['end'])
{
// Check reserved attendants for this time slot and see if current attendants fit.
$appointment_attendants_number = $this->CI->appointments_model->get_attendants_number_for_period(
$slot_start,
$slot_end,
$service['id']
);
if ($appointment_attendants_number < $service['attendants_number'])
{
$hours[] = $slot_start->format('H:i');
}
$slot_start->add($interval);
$slot_end->add($interval);
}
}
return $hours;
}
/**
* Remove breaks from available time periods.
*
* @param string $selected_date Selected data (Y-m-d format).
* @param array $periods Time periods of the current date.
* @param array $breaks Breaks array for the current date.
*
* @return array Returns the available time periods without the breaks.
* @throws Exception
*/
public function remove_breaks($selected_date, $periods, $breaks)
{
if ( ! $breaks)
{
return $periods;
}
foreach ($breaks as $break)
{
$break_start = new DateTime($selected_date . ' ' . $break['start']);
$break_end = new DateTime($selected_date . ' ' . $break['end']);
foreach ($periods as &$period)
{
$period_start = $period['start'];
$period_end = $period['end'];
if ($break_start <= $period_start && $break_end >= $period_start && $break_end <= $period_end)
{
// left
$period['start'] = $break_end;
continue;
}
if ($break_start >= $period_start && $break_start <= $period_end && $break_end >= $period_start && $break_end <= $period_end)
{
// middle
$period['end'] = $break_start;
$periods[] = [
'start' => $break_end,
'end' => $period_end
];
continue;
}
if ($break_start >= $period_start && $break_start <= $period_end && $break_end >= $period_end)
{
// right
$period['end'] = $break_start;
continue;
}
if ($break_start <= $period_start && $break_end >= $period_end)
{
// break contains period
$period['start'] = $break_end;
continue;
}
}
}
return $periods;
}
/**
* Remove the unavailability entries from the available time periods of the selected date.
*
* @param array $periods Available time periods.
* @param array $unavailability_events Unavailability events of the current date.
*
* @return array Returns the available time periods without the unavailability events.
*
* @throws Exception
*/
public function remove_unavailability_events($periods, $unavailability_events)
{
foreach ($unavailability_events as $unavailability_event)
{
$unavailability_start = new DateTime($unavailability_event['start_datetime']);
$unavailability_end = new DateTime($unavailability_event['end_datetime']);
foreach ($periods as &$period)
{
$period_start = $period['start'];
$period_end = $period['end'];
if ($unavailability_start <= $period_start && $unavailability_end >= $period_start && $unavailability_end <= $period_end)
{
// left
$period['start'] = $unavailability_end;
continue;
}
if ($unavailability_start >= $period_start && $unavailability_start <= $period_end && $unavailability_end >= $period_start && $unavailability_end <= $period_end)
{
// middle
$period['end'] = $unavailability_start;
$periods[] = [
'start' => $unavailability_end,
'end' => $period_end
];
continue;
}
if ($unavailability_start >= $period_start && $unavailability_start <= $period_end && $unavailability_end >= $period_end)
{
// right
$period['end'] = $unavailability_start;
continue;
}
if ($unavailability_start <= $period_start && $unavailability_end >= $period_end)
{
// Unavailability contains period
$period['start'] = $unavailability_end;
continue;
}
}
}
return $periods;
}
/**
* Consider the book advance timeout and remove available hours that have passed the threshold.
*
* If the selected date is today, remove past hours. It is important include the timeout before booking
* that is set in the back-office the system. Normally we might want the customer to book an appointment
* that is at least half or one hour from now. The setting is stored in minutes.
*
* @param string $selected_date The selected date.
* @param array $available_hours Already generated available hours.
* @param array $provider Provider information.
*
* @return array Returns the updated available hours.
*
* @throws Exception
*/
protected function consider_book_advance_timeout($selected_date, $available_hours, $provider)
{
$provider_timezone = new DateTimeZone($provider['timezone']);
$book_advance_timeout = $this->CI->settings_model->get_setting('book_advance_timeout');
$threshold = new DateTime('+' . $book_advance_timeout . ' minutes', $provider_timezone);
foreach ($available_hours as $index => $value)
{
$available_hour = new DateTime($selected_date . ' ' . $value, $provider_timezone);
if ($available_hour->getTimestamp() <= $threshold->getTimestamp())
{
unset($available_hours[$index]);
}
}
$available_hours = array_values($available_hours);
sort($available_hours, SORT_STRING);
return array_values($available_hours);
}
}

View file

@ -0,0 +1,227 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Open Source Web Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @copyright Copyright (c) 2013 - 2020, Alex Tselegidis
* @license http://opensource.org/licenses/GPL-3.0 - GPLv3
* @link http://easyappointments.org
* @since v1.4.0
* ---------------------------------------------------------------------------- */
use EA\Engine\Notifications\Email as EmailClient;
use EA\Engine\Types\Email;
use EA\Engine\Types\Text;
use EA\Engine\Types\Url;
/**
* Class Notifications
*
* Handles the system notifications (mostly related to scheduling changes).
*/
class Notifications {
/**
* @var CI_Controller
*/
protected $CI;
/**
* Notifications constructor.
*/
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');
}
/**
* Send the required notifications, related to an appointment creation/modification.
*
* @param array $appointment Appointment record.
* @param array $service Service record.
* @param array $provider Provider record.
* @param array $customer Customer record.
* @param array $settings Required settings for the notification content.
* @param bool|false $manage_mode
*/
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
{
$customer_title = new Text(lang('appointment_changes_saved'));
$customer_message = new Text('');
$provider_title = new Text(lang('appointment_details_changed'));
$provider_message = new Text('');
}
$customer_link = new Url(site_url('appointments/index/' . $appointment['hash']));
$provider_link = new Url(site_url('backend/index/' . $appointment['hash']));
$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));
}
$send_provider = filter_var(
$this->CI->providers_model->get_setting('notifications', $provider['id']),
FILTER_VALIDATE_BOOLEAN);
if ($send_provider === TRUE)
{
$email->sendAppointmentDetails($appointment, $provider,
$service, $customer, $settings, $provider_title,
$provider_message, $provider_link, new Email($provider['email']), new Text($ics_stream));
}
// Notify admins
$admins = $this->CI->admins_model->get_batch();
foreach ($admins as $admin)
{
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));
}
// Notify secretaries
$secretaries = $this->CI->secretaries_model->get_batch();
foreach ($secretaries as $secretary)
{
if ( ! $secretary['settings']['notifications'] === '0')
{
continue;
}
if (in_array($provider['id'], $secretary['providers']))
{
continue;
}
$email->sendAppointmentDetails($appointment, $provider,
$service, $customer, $settings, $provider_title,
$provider_message, $provider_link, new Email($secretary['email']), new Text($ics_stream));
}
}
catch (Exception $exception)
{
log_message('error', $exception->getMessage());
log_message('error', $exception->getTraceAsString());
}
}
/**
* Send the required notifications, related to an appointment removal.
*
* @param array $appointment Appointment record.
* @param array $service Service record.
* @param array $provider Provider record.
* @param array $customer Customer record.
* @param array $settings Required settings for the notification content.
*/
public function notify_appointment_deleted($appointment, $service, $provider, $customer, $settings)
{
// Send email notification to customer and provider.
try
{
$email = new EmailClient($this->CI, $this->CI->config->config);
$send_provider = filter_var($this->CI->providers_model->get_setting('notifications', $provider['id']),
FILTER_VALIDATE_BOOLEAN);
if ($send_provider === TRUE)
{
$email->sendDeleteAppointment($appointment, $provider,
$service, $customer, $settings, new Email($provider['email']),
new Text($this->CI->input->post('cancel_reason')));
}
$send_customer = filter_var(
$this->CI->settings_model->get_setting('customer_notifications'),
FILTER_VALIDATE_BOOLEAN);
if ($send_customer === TRUE)
{
$email->sendDeleteAppointment($appointment, $provider,
$service, $customer, $settings, new Email($customer['email']),
new Text($this->CI->input->post('cancel_reason')));
}
// Notify admins
$admins = $this->CI->admins_model->get_batch();
foreach ($admins as $admin)
{
if ( ! $admin['settings']['notifications'] === '0')
{
continue;
}
$email->sendDeleteAppointment($appointment, $provider,
$service, $customer, $settings, new Email($admin['email']),
new Text($this->CI->input->post('cancel_reason')));
}
// Notify secretaries
$secretaries = $this->CI->secretaries_model->get_batch();
foreach ($secretaries as $secretary)
{
if ( ! $secretary['settings']['notifications'] === '0')
{
continue;
}
if (in_array($provider['id'], $secretary['providers']))
{
continue;
}
$email->sendDeleteAppointment($appointment, $provider,
$service, $customer, $settings, new Email($secretary['email']),
new Text($this->CI->input->post('cancel_reason')));
}
}
catch (Exception $exception)
{
$exceptions[] = $exception;
}
}
}

View file

@ -0,0 +1,120 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Open Source Web Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @copyright Copyright (c) 2013 - 2020, Alex Tselegidis
* @license http://opensource.org/licenses/GPL-3.0 - GPLv3
* @link http://easyappointments.org
* @since v1.4.0
* ---------------------------------------------------------------------------- */
/**
* Class Synchronization
*
* Handles the external calendar synchronization.
*/
class Synchronization {
/**
* @var CI_Controller
*/
protected $CI;
/**
* Synchronization constructor.
*/
public function __construct()
{
$this->CI =& get_instance();
$this->CI->load->model('providers_model');
$this->CI->load->model('appointments_model');
$this->CI->load->library('google_sync');
}
/**
* Synchronize changes made to the appointment with external calendars.
*
* @param array $appointment Appointment record.
* @param array $service Service record.
* @param array $provider Provider record.
* @param array $customer Customer record.
* @param array $settings Required settings for the notification content.
* @param bool|false $manage_mode True if the appointment is being edited.
*/
public function sync_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode = FALSE)
{
try
{
$google_sync = filter_var(
$this->CI->providers_model->get_setting('google_sync', $appointment['id_users_provider']),
FILTER_VALIDATE_BOOLEAN
);
if ($google_sync === TRUE)
{
$google_token = json_decode(
$this->CI->providers_model->get_setting('google_token', $appointment['id_users_provider']));
$this->CI->load->library('google_sync');
$this->CI->google_sync->refresh_token($google_token->refresh_token);
if ($manage_mode === FALSE)
{
// Add appointment to Google Calendar.
$google_event = $this->CI->google_sync->add_appointment($appointment, $provider,
$service, $customer, $settings);
$appointment['id_google_calendar'] = $google_event->id;
$this->CI->appointments_model->add($appointment);
}
else
{
// Update appointment to Google Calendar.
$appointment['id_google_calendar'] = $this->CI->appointments_model
->get_value('id_google_calendar', $appointment['id']);
$this->CI->google_sync->update_appointment($appointment, $provider,
$service, $customer, $settings);
}
}
}
catch (Exception $exception)
{
log_message('error', $exception->getMessage());
log_message('error', $exception->getTraceAsString());
}
}
/**
* Synchronize removal of an appointment with external calendars.
*
* @param array $appointment Appointment record.
* @param array $provider Provider record.
*/
public function sync_appointment_deleted($appointment, $provider)
{
if ($appointment['id_google_calendar'] != NULL)
{
try
{
$google_sync = filter_var(
$this->CI->providers_model->get_setting('google_sync', $appointment['id_users_provider']),
FILTER_VALIDATE_BOOLEAN);
if ($google_sync === TRUE)
{
$google_token = json_decode($this->CI->providers_model->get_setting('google_token', $provider['id']));
$this->CI->load->library('Google_sync');
$this->CI->google_sync->refresh_token($google_token->refresh_token);
$this->CI->google_sync->delete_appointment($provider, $appointment['id_google_calendar']);
}
}
catch (Exception $exception)
{
$exceptions[] = $exception;
}
}
}
}