Merge branch 'develop'

This commit is contained in:
Alex Tselegidis 2020-10-22 12:52:47 +03:00
commit 6bf4e8b4ea
49 changed files with 10866 additions and 1330 deletions

View file

@ -9,7 +9,7 @@
|
*/
$config['version'] = '1.4.0'; // This must be changed manually.
$config['release_label'] = 'Dev'; // Leave empty for no title or add Alpha, Beta etc ...
$config['release_label'] = 'Beta.1'; // Leave empty for no title or add Alpha, Beta etc ...
$config['debug'] = Config::DEBUG_MODE;
/*
@ -302,7 +302,7 @@ $config['cache_path'] = __DIR__ . '/../../storage/cache/';
| new release.
|
*/
$config['cache_busting_token'] = '93GX4';
$config['cache_busting_token'] = '824HX';
/*
|--------------------------------------------------------------------------

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 Settings_Model $settings_model
* @property Timezones $timezones
* @property Synchronization $synchronization
* @property Notifications $notifications
* @property Roles_Model $roles_model
* @property Secretaries_Model $secretaries_model
* @property Admins_Model $admins_model
@ -102,7 +104,7 @@ class Backend_api extends CI_Controller {
'start_datetime >=' => $startDate,
'end_datetime <=' => $endDate
]),
'unavailabilities' => $this->appointments_model->get_batch([
'unavailability_events' => $this->appointments_model->get_batch([
'is_unavailable' => TRUE,
'start_datetime >=' => $startDate,
'end_datetime <=' => $endDate
@ -130,11 +132,11 @@ class Backend_api extends CI_Controller {
}
}
foreach ($response['unavailabilities'] as $index => $unavailability)
foreach ($response['unavailability_events'] as $index => $unavailability_event)
{
if ((int)$unavailability['id_users_provider'] !== (int)$userId)
if ((int)$unavailability_event['id_users_provider'] !== (int)$userId)
{
unset($response['unavailabilities'][$index]);
unset($response['unavailability_events'][$index]);
}
}
}
@ -152,11 +154,11 @@ class Backend_api extends CI_Controller {
}
}
foreach ($response['unavailabilities'] as $index => $unavailability)
foreach ($response['unavailability_events'] as $index => $unavailability_event)
{
if ( ! in_array((int)$unavailability['id_users_provider'], $providers))
if ( ! in_array((int)$unavailability_event['id_users_provider'], $providers))
{
unset($response['unavailabilities'][$index]);
unset($response['unavailability_events'][$index]);
}
}
}
@ -283,6 +285,8 @@ class Backend_api extends CI_Controller {
$this->load->model('customers_model');
$this->load->model('settings_model');
$this->load->library('timezones');
$this->load->library('synchronization');
$this->load->library('notifications');
$this->load->model('user_model');
// 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);
$required_privilegesileges = ( ! isset($customer['id']))
$required_privileges = ( ! isset($customer['id']))
? $this->privileges[PRIV_CUSTOMERS]['add']
: $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.');
}
@ -306,17 +310,17 @@ class Backend_api extends CI_Controller {
{
$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]['edit'];
if ($required_privilegesileges == FALSE)
if ($required_privileges == FALSE)
{
throw new Exception('You do not have the required privileges for this task.');
}
$manage_mode = isset($appointment['id']);
// If the appointment does not contain the customer record id, then it
// means that is is going to be inserted. Get the customer's record id.
// If the appointment does not contain the customer record id, then it means that is is going to be
// inserted. Get the customer's record ID.
if ( ! isset($appointment['id_users_customer']))
{
$appointment['id_users_customer'] = $customer['id'];
@ -348,139 +352,10 @@ class Backend_api extends CI_Controller {
'time_format' => $this->settings_model->get_setting('time_format')
];
// Sync appointment changes with Google Calendar.
try
{
$google_sync = $this->providers_model->get_setting('google_sync',
$appointment['id_users_provider']);
$this->synchronization->sync_appointment_deleted($appointment, $provider);
$this->notifications->notify_appointment_deleted($appointment, $service, $provider, $customer, $settings);
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;
}
else
{
$response = ['warnings' => $warnings];
}
$response = AJAX_SUCCESS;
}
catch (Exception $exception)
{
@ -1032,10 +907,10 @@ class Backend_api extends CI_Controller {
$this->load->model('customers_model');
$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]['edit'];
if ($required_privilegesileges == FALSE)
if ($required_privileges == FALSE)
{
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');
$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]['edit'];
if ($required_privilegesileges == FALSE)
if ($required_privileges == FALSE)
{
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');
$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]['edit'];
if ($required_privilegesileges == FALSE)
if ($required_privileges == FALSE)
{
throw new Exception('You do not have the required privileges for this task.');
}

View file

@ -62,7 +62,7 @@ class API_V1_Controller extends CI_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
@ -142,7 +142,7 @@ class API_V1_Controller extends CI_Controller {
*
* @param Exception $exception Thrown exception to be outputted.
*/
protected function _handleException(Exception $exception)
protected function handle_exception(Exception $exception)
{
$error = [
'code' => $exception->getCode() ?: 500,
@ -166,7 +166,7 @@ class API_V1_Controller extends CI_Controller {
*
* @throws \EA\Engine\Api\V1\Exception
*/
protected function _throwRecordNotFound()
protected function throw_record_not_found()
{
throw new \EA\Engine\Api\V1\Exception('The requested record was not found!', 404, 'Not Found');
}

View file

@ -76,7 +76,7 @@ class Admins extends API_V1_Controller {
if ($id !== NULL && count($admins) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$response = new Response($admins);
@ -92,7 +92,7 @@ class Admins extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -123,7 +123,7 @@ class Admins extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -141,7 +141,7 @@ class Admins extends API_V1_Controller {
if ($id !== NULL && count($batch) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$request = new Request();
@ -158,7 +158,7 @@ class Admins extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -182,7 +182,7 @@ class Admins extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
}

View file

@ -37,6 +37,8 @@ use EA\Engine\Types\NonEmptyText;
* @property Customers_Model $customers_model
* @property Settings_Model $settings_model
* @property Timezones $timezones
* @property Notifications $notifications
* @property Synchronization $synchronization
* @property Roles_Model $roles_model
* @property Secretaries_Model $secretaries_model
* @property Admins_Model $admins_model
@ -59,6 +61,12 @@ class Appointments extends API_V1_Controller {
{
parent::__construct();
$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;
}
@ -84,7 +92,7 @@ class Appointments extends API_V1_Controller {
if ($id !== NULL && count($appointments) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$response = new Response($appointments);
@ -100,7 +108,7 @@ class Appointments extends API_V1_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
@ -109,8 +117,6 @@ class Appointments extends API_V1_Controller {
*/
public function post()
{
$this->load->model('services_model');
try
{
// Insert the appointment to the database.
@ -138,6 +144,20 @@ class Appointments extends API_V1_Controller {
$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.
$batch = $this->appointments_model->get_batch('id = ' . $id);
$response = new Response($batch);
@ -146,7 +166,7 @@ class Appointments extends API_V1_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
@ -164,15 +184,30 @@ class Appointments extends API_V1_Controller {
if ($id !== NULL && count($batch) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$request = new Request();
$updatedAppointment = $request->getBody();
$baseAppointment = $batch[0];
$this->parser->decode($updatedAppointment, $baseAppointment);
$updatedAppointment['id'] = $id;
$id = $this->appointments_model->add($updatedAppointment);
$updated_appointment = $request->getBody();
$base_appointment = $batch[0];
$this->parser->decode($updated_appointment, $base_appointment);
$updated_appointment['id'] = $id;
$id = $this->appointments_model->add($updated_appointment);
$service = $this->services_model->get_row($updated_appointment['id_services']);
$provider = $this->providers_model->get_row($updated_appointment['id_users_provider']);
$customer = $this->customers_model->get_row($updated_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($updated_appointment, $service, $provider, $customer, $settings, TRUE);
$this->notifications->notify_appointment_saved($updated_appointment, $service, $provider, $customer, $settings, TRUE);
// Fetch the updated object from the database and return it to the client.
$batch = $this->appointments_model->get_batch('id = ' . $id);
@ -181,7 +216,7 @@ class Appointments extends API_V1_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
@ -194,8 +229,23 @@ class Appointments extends API_V1_Controller {
{
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->synchronization->sync_appointment_deleted($appointment, $provider);
$this->notifications->notify_appointment_deleted($appointment, $service, $provider, $customer, $settings);
$response = new Response([
'code' => 200,
'message' => 'Record was deleted successfully!'
@ -205,7 +255,7 @@ class Appointments extends API_V1_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
}

View file

@ -66,8 +66,8 @@ class Availabilities extends API_V1_Controller {
{
try
{
$providerId = new UnsignedInteger($this->input->get('providerId'));
$serviceId = new UnsignedInteger($this->input->get('serviceId'));
$provider_id = new UnsignedInteger($this->input->get('providerId'));
$service_id = new UnsignedInteger($this->input->get('serviceId'));
if ($this->input->get('date'))
{
@ -78,49 +78,49 @@ class Availabilities extends API_V1_Controller {
$date = new DateTime();
}
$provider = $this->providers_model->get_row($providerId->get());
$service = $this->services_model->get_row($serviceId->get());
$provider = $this->providers_model->get_row($provider_id->get());
$service = $this->services_model->get_row($service_id->get());
$emptyPeriods = $this->_getProviderAvailableTimePeriods($providerId->get(),
$empty_periods = $this->get_provider_available_time_periods($provider_id->get(),
$date->format('Y-m-d'), []);
$availableHours = $this->_calculateAvailableHours($emptyPeriods,
$available_hours = $this->calculate_available_hours($empty_periods,
$date->format('Y-m-d'), $service['duration'], FALSE, $service['availabilities_type']);
if ($service['attendants_number'] > 1)
{
$availableHours = $this->_getMultipleAttendantsHours($date->format('Y-m-d'), $service, $provider);
$available_hours = $this->get_multiple_attendants_hours($date->format('Y-m-d'), $service, $provider);
}
// 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.
// 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.
if ($date->format('Y-m-d') === date('Y-m-d'))
{
$bookAdvanceTimeout = $this->settings_model->get_setting('book_advance_timeout');
$book_advance_timeout = $this->settings_model->get_setting('book_advance_timeout');
foreach ($availableHours as $index => $value)
foreach ($available_hours as $index => $value)
{
$availableHour = strtotime($value);
$currentHour = strtotime('+' . $bookAdvanceTimeout . ' minutes', strtotime('now'));
if ($availableHour <= $currentHour)
$available_hour = strtotime($value);
$currentHour = strtotime('+' . $book_advance_timeout . ' minutes', strtotime('now'));
if ($available_hour <= $currentHour)
{
unset($availableHours[$index]);
unset($available_hours[$index]);
}
}
}
$availableHours = array_values($availableHours);
sort($availableHours, SORT_STRING);
$availableHours = array_values($availableHours);
$available_hours = array_values($available_hours);
sort($available_hours, SORT_STRING);
$available_hours = array_values($available_hours);
$this->output
->set_content_type('application/json')
->set_output(json_encode($availableHours));
->set_output(json_encode($available_hours));
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
@ -139,7 +139,7 @@ class Availabilities extends API_V1_Controller {
*
* @return array Returns an array with the available time periods of the provider.
*/
protected function _getProviderAvailableTimePeriods(
protected function get_provider_available_time_periods(
$provider_id,
$selected_date,
$exclude_appointments = []
@ -342,7 +342,7 @@ class Availabilities extends API_V1_Controller {
*
* @return array Returns an array with the available hours for the appointment.
*/
protected function _calculateAvailableHours(
protected function calculate_available_hours(
array $empty_periods,
$selected_date,
$service_duration,
@ -385,7 +385,7 @@ class Availabilities extends API_V1_Controller {
*
* @return array Returns the available hours array.
*/
protected function _getMultipleAttendantsHours(
protected function get_multiple_attendants_hours(
$selected_date,
$service,
$provider
@ -412,8 +412,8 @@ class Availabilities extends API_V1_Controller {
]
];
$periods = $this->_removeBreaks($selected_date, $periods, $working_hours['breaks']);
$periods = $this->_removeUnavailabilities($periods, $unavailabilities);
$periods = $this->remove_breaks($selected_date, $periods, $working_hours['breaks']);
$periods = $this->remove_unavailabilities($periods, $unavailabilities);
$hours = [];
@ -455,7 +455,7 @@ class Availabilities extends API_V1_Controller {
*
* @return array Returns the available time periods without the breaks.
*/
public function _removeBreaks($selected_date, $periods, $breaks)
public function remove_breaks($selected_date, $periods, $breaks)
{
if ( ! $breaks)
{
@ -517,7 +517,7 @@ class Availabilities extends API_V1_Controller {
*
* @return array Returns the available time periods without the unavailabilities.
*/
public function _removeUnavailabilities($periods, $unavailabilities)
public function remove_unavailabilities($periods, $unavailabilities)
{
foreach ($unavailabilities as $unavailability)
{

View file

@ -76,7 +76,7 @@ class Categories extends API_V1_Controller {
if ($id !== NULL && count($categories) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$response = new Response($categories);
@ -92,7 +92,7 @@ class Categories extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -123,7 +123,7 @@ class Categories extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -141,15 +141,15 @@ class Categories extends API_V1_Controller {
if ($id !== NULL && count($batch) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$request = new Request();
$updatedCategory = $request->getBody();
$baseCategory = $batch[0];
$this->parser->decode($updatedCategory, $baseCategory);
$updatedCategory['id'] = $id;
$id = $this->services_model->add_category($updatedCategory);
$updated_category = $request->getBody();
$base_category = $batch[0];
$this->parser->decode($updated_category, $base_category);
$updated_category['id'] = $id;
$id = $this->services_model->add_category($updated_category);
// Fetch the updated object from the database and return it to the client.
$batch = $this->services_model->get_all_categories('id = ' . $id);
@ -158,7 +158,7 @@ class Categories extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -182,7 +182,7 @@ class Categories extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
}

View file

@ -76,7 +76,7 @@ class Customers extends API_V1_Controller {
if ($id !== NULL && count($customers) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$response = new Response($customers);
@ -92,7 +92,7 @@ class Customers extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -123,7 +123,7 @@ class Customers extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -141,15 +141,15 @@ class Customers extends API_V1_Controller {
if ($id !== NULL && count($batch) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$request = new Request();
$updatedCustomer = $request->getBody();
$baseCustomer = $batch[0];
$this->parser->decode($updatedCustomer, $baseCustomer);
$updatedCustomer['id'] = $id;
$id = $this->customers_model->add($updatedCustomer);
$updated_customer = $request->getBody();
$base_customer = $batch[0];
$this->parser->decode($updated_customer, $base_customer);
$updated_customer['id'] = $id;
$id = $this->customers_model->add($updated_customer);
// Fetch the updated object from the database and return it to the client.
$batch = $this->customers_model->get_batch('id = ' . $id);
@ -158,7 +158,7 @@ class Customers extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -182,7 +182,7 @@ class Customers extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
}

View file

@ -76,7 +76,7 @@ class Providers extends API_V1_Controller {
if ($id !== NULL && count($providers) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$response = new Response($providers);
@ -92,7 +92,7 @@ class Providers extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -123,7 +123,7 @@ class Providers extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -141,15 +141,15 @@ class Providers extends API_V1_Controller {
if ($id !== NULL && count($batch) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$request = new Request();
$updatedProvider = $request->getBody();
$baseProvider = $batch[0];
$this->parser->decode($updatedProvider, $baseProvider);
$updatedProvider['id'] = $id;
$id = $this->providers_model->add($updatedProvider);
$updated_provider = $request->getBody();
$base_provider = $batch[0];
$this->parser->decode($updated_provider, $base_provider);
$updated_provider['id'] = $id;
$id = $this->providers_model->add($updated_provider);
// Fetch the updated object from the database and return it to the client.
$batch = $this->providers_model->get_batch('id = ' . $id);
@ -158,7 +158,7 @@ class Providers extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -182,7 +182,7 @@ class Providers extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
}

View file

@ -76,7 +76,7 @@ class Secretaries extends API_V1_Controller {
if ($id !== NULL && count($secretaries) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$response = new Response($secretaries);
@ -92,7 +92,7 @@ class Secretaries extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -123,7 +123,7 @@ class Secretaries extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -141,15 +141,15 @@ class Secretaries extends API_V1_Controller {
if ($id !== NULL && count($batch) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$request = new Request();
$updatedSecretary = $request->getBody();
$baseSecretary = $batch[0];
$this->parser->decode($updatedSecretary, $baseSecretary);
$updatedSecretary['id'] = $id;
$id = $this->secretaries_model->add($updatedSecretary);
$updated_secretary = $request->getBody();
$base_secretary = $batch[0];
$this->parser->decode($updated_secretary, $base_secretary);
$updated_secretary['id'] = $id;
$id = $this->secretaries_model->add($updated_secretary);
// Fetch the updated object from the database and return it to the client.
$batch = $this->secretaries_model->get_batch('id = ' . $id);
@ -158,7 +158,7 @@ class Secretaries extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -182,7 +182,7 @@ class Secretaries extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
}

View file

@ -76,7 +76,7 @@ class Services extends API_V1_Controller {
if ($id !== NULL && count($services) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$response = new Response($services);
@ -92,7 +92,7 @@ class Services extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -123,7 +123,7 @@ class Services extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -141,15 +141,15 @@ class Services extends API_V1_Controller {
if ($id !== NULL && count($batch) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$request = new Request();
$updatedService = $request->getBody();
$baseService = $batch[0];
$this->parser->decode($updatedService, $baseService);
$updatedService['id'] = $id;
$id = $this->services_model->add($updatedService);
$updated_service = $request->getBody();
$base_service = $batch[0];
$this->parser->decode($updated_service, $base_service);
$updated_service['id'] = $id;
$id = $this->services_model->add($updated_service);
// Fetch the updated object from the database and return it to the client.
$batch = $this->services_model->get_batch('id = ' . $id);
@ -158,7 +158,7 @@ class Services extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
@ -182,7 +182,7 @@ class Services extends API_V1_Controller {
}
catch (Exception $exception)
{
$this->_handleException($exception);
$this->handle_exception($exception);
}
}
}

View file

@ -87,7 +87,7 @@ class Settings extends API_V1_Controller {
if (empty($setting))
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
unset($setting['id']);
@ -110,7 +110,7 @@ class Settings extends API_V1_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
@ -138,7 +138,7 @@ class Settings extends API_V1_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
@ -162,7 +162,7 @@ class Settings extends API_V1_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
}

View file

@ -76,7 +76,7 @@ class Unavailabilities extends API_V1_Controller {
if ($id !== NULL && count($unavailabilities) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$response = new Response($unavailabilities);
@ -92,7 +92,7 @@ class Unavailabilities extends API_V1_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
@ -123,7 +123,7 @@ class Unavailabilities extends API_V1_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
@ -141,7 +141,7 @@ class Unavailabilities extends API_V1_Controller {
if ($id !== NULL && count($batch) === 0)
{
$this->_throwRecordNotFound();
$this->throw_record_not_found();
}
$request = new Request();
@ -158,7 +158,7 @@ class Unavailabilities extends API_V1_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
@ -182,7 +182,7 @@ class Unavailabilities extends API_V1_Controller {
}
catch (Exception $exception)
{
exit($this->_handleException($exception));
exit($this->handle_exception($exception));
}
}
}

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;
}
}
}
}

View file

@ -10,7 +10,6 @@
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/bootstrap/css/bootstrap.min.css') ?>">
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/jquery-ui/jquery-ui.min.css') ?>">
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/jquery-qtip/jquery.qtip.min.css') ?>">
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/cookieconsent/cookieconsent.min.css') ?>">
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/css/frontend.css') ?>">
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/css/general.css') ?>">
@ -32,17 +31,17 @@
<span id="company-name"><?= $company_name ?></span>
<div id="steps">
<div id="step-1" class="book-step active-step" title="<?= lang('service_and_provider') ?>">
<div id="step-1" class="book-step active-step" data-tippy-content="<?= lang('service_and_provider') ?>">
<strong>1</strong>
</div>
<div id="step-2" class="book-step" title="<?= lang('appointment_date_and_time') ?>">
<div id="step-2" class="book-step" data-toggle="tooltip" data-tippy-content="<?= lang('appointment_date_and_time') ?>">
<strong>2</strong>
</div>
<div id="step-3" class="book-step" title="<?= lang('customer_information') ?>">
<div id="step-3" class="book-step" data-toggle="tooltip" data-tippy-content="<?= lang('customer_information') ?>">
<strong>3</strong>
</div>
<div id="step-4" class="book-step" title="<?= lang('appointment_confirmation') ?>">
<div id="step-4" class="book-step" data-toggle="tooltip" data-tippy-content="<?= lang('appointment_confirmation') ?>">
<strong>4</strong>
</div>
</div>
@ -450,9 +449,10 @@
<script src="<?= asset_url('assets/js/general_functions.js') ?>"></script>
<script src="<?= asset_url('assets/ext/jquery/jquery.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/jquery-ui/jquery-ui.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/jquery-qtip/jquery.qtip.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/cookieconsent/cookieconsent.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/bootstrap/js/bootstrap.bundle.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/popper/popper.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/tippy/tippy-bundle.umd.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/datejs/date.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/moment/moment.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/moment/moment-timezone-with-data.min.js') ?>"></script>

View file

@ -9,7 +9,7 @@
<script src="<?= asset_url('assets/js/backend_calendar_table_view.js') ?>"></script>
<script src="<?= asset_url('assets/js/backend_calendar_google_sync.js') ?>"></script>
<script src="<?= asset_url('assets/js/backend_calendar_appointments_modal.js') ?>"></script>
<script src="<?= asset_url('assets/js/backend_calendar_unavailabilities_modal.js') ?>"></script>
<script src="<?= asset_url('assets/js/backend_calendar_unavailability_events_modal.js') ?>"></script>
<script src="<?= asset_url('assets/js/backend_calendar_api.js') ?>"></script>
<script>
var GlobalVariables = {
@ -44,7 +44,7 @@
<div id="calendar-filter" class="col-12 col-sm-5">
<div class="form-group calendar-filter-items">
<select id="select-filter-item" class="form-control col"
title="<?= lang('select_filter_item_hint') ?>">
data-tippy-content="<?= lang('select_filter_item_hint') ?>">
</select>
</div>
</div>
@ -53,13 +53,13 @@
<?php if (($role_slug == DB_SLUG_ADMIN || $role_slug == DB_SLUG_PROVIDER)
&& config('google_sync_feature') == TRUE): ?>
<button id="google-sync" class="btn btn-primary"
title="<?= lang('trigger_google_sync_hint') ?>">
data-tippy-content="<?= lang('trigger_google_sync_hint') ?>">
<i class="fas fa-sync-alt"></i>
<span><?= lang('synchronize') ?></span>
</button>
<button id="enable-sync" class="btn btn-light" data-toggle="button"
title="<?= lang('enable_appointment_sync_hint') ?>">
data-tippy-content="<?= lang('enable_appointment_sync_hint') ?>">
<i class="far fa-calendar-alt mr-2"></i>
<span><?= lang('enable_sync') ?></span>
</button>
@ -90,20 +90,20 @@
</div>
<?php endif ?>
<button id="reload-appointments" class="btn btn-light" title="<?= lang('reload_appointments_hint') ?>">
<button id="reload-appointments" class="btn btn-light" data-tippy-content="<?= lang('reload_appointments_hint') ?>">
<i class="fas fa-sync-alt"></i>
</button>
<?php if($calendar_view === 'default'): ?>
<a class="btn btn-light" href="<?= site_url('backend?view=table') ?>"
title="<?= lang('table') ?>">
data-tippy-content="<?= lang('table') ?>">
<i class="fas fa-table"></i>
</a>
<?php endif ?>
<?php if($calendar_view === 'table'): ?>
<a class="btn btn-light" href="<?= site_url('backend?view=default') ?>"
title="<?= lang('default') ?>">
data-tippy-content="<?= lang('default') ?>">
<i class="fas fa-calendar-alt"></i>
</a>
<?php endif ?>
@ -272,12 +272,12 @@
<legend>
<?= lang('customer_details_title') ?>
<button id="new-customer" class="btn btn-outline-secondary btn-sm" type="button"
title="<?= lang('clear_fields_add_existing_customer_hint') ?>">
data-tippy-content="<?= lang('clear_fields_add_existing_customer_hint') ?>">
<i class="far fa-plus-square mr-2"></i>
<?= lang('new') ?>
</button>
<button id="select-customer" class="btn btn-outline-secondary btn-sm" type="button"
title="<?= lang('pick_existing_customer_hint') ?>">
data-tippy-content="<?= lang('pick_existing_customer_hint') ?>">
<i class="far fa-hand-pointer mr-2"></i>
<span>
<?= lang('select') ?>

View file

@ -35,10 +35,10 @@
<div class="input-group-addon">
<div>
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
<i class="fas fa-search"></i>
</button>
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
<i class="fas fa-redo-alt"></i>
</button>
</div>

View file

@ -16,6 +16,7 @@
<?= lang('licensed_under') ?> GPLv3 |
<span id="select-language" class="badge badge-secondary">
<i class="fas fa-language mr-2"></i>
<?= ucfirst(config('language')) ?>
</span>

View file

@ -8,7 +8,6 @@
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/bootstrap/css/bootstrap.min.css') ?>">
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/jquery-ui/jquery-ui.min.css') ?>">
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/jquery-qtip/jquery.qtip.min.css') ?>">
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/trumbowyg/ui/trumbowyg.min.css') ?>">
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/ext/select2/select2.min.css') ?>">
<link rel="stylesheet" type="text/css" href="<?= asset_url('assets/css/backend.css') ?>">
@ -25,8 +24,9 @@
<script src="<?= asset_url('assets/ext/jquery/jquery.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/bootstrap/js/bootstrap.bundle.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/popper/popper.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/tippy/tippy-bundle.umd.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/jquery-ui/jquery-ui.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/jquery-qtip/jquery.qtip.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/moment/moment.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/datejs/date.min.js') ?>"></script>
<script src="<?= asset_url('assets/ext/trumbowyg/trumbowyg.min.js') ?>"></script>
@ -38,7 +38,8 @@
<nav id="header" class="navbar navbar-expand-md navbar-dark">
<div id="header-logo" class="navbar-brand">
<img src="<?= base_url('assets/img/logo.png') ?>">
<span>EASY!APPOINTMENTS</span>
<h6>EASY!APPOINTMENTS</h6>
<small>Open Source Appointment Scheduler</small>
</div>
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#header-menu">
@ -52,7 +53,7 @@
<?php $active = ($active_menu == PRIV_APPOINTMENTS) ? 'active' : '' ?>
<li class="nav-item <?= $active . $hidden ?>">
<a href="<?= site_url('backend') ?>" class="nav-link"
title="<?= lang('manage_appointment_record_hint') ?>">
data-tippy-content="<?= lang('manage_appointment_record_hint') ?>">
<i class="far fa-calendar-alt mr-2"></i>
<?= lang('calendar') ?>
</a>
@ -62,7 +63,7 @@
<?php $active = ($active_menu == PRIV_CUSTOMERS) ? 'active' : '' ?>
<li class="nav-item <?= $active . $hidden ?>">
<a href="<?= site_url('backend/customers') ?>" class="nav-link"
title="<?= lang('manage_customers_hint') ?>">
data-tippy-content="<?= lang('manage_customers_hint') ?>">
<i class="fas fa-user-friends mr-2"></i>
<?= lang('customers') ?>
</a>
@ -72,7 +73,7 @@
<?php $active = ($active_menu == PRIV_SERVICES) ? 'active' : '' ?>
<li class="nav-item <?= $active . $hidden ?>">
<a href="<?= site_url('backend/services') ?>" class="nav-link"
title="<?= lang('manage_services_hint') ?>">
data-tippy-content="<?= lang('manage_services_hint') ?>">
<i class="fas fa-business-time mr-2"></i>
<?= lang('services') ?>
</a>
@ -82,7 +83,7 @@
<?php $active = ($active_menu == PRIV_USERS) ? 'active' : '' ?>
<li class="nav-item <?= $active . $hidden ?>">
<a href="<?= site_url('backend/users') ?>" class="nav-link"
title="<?= lang('manage_users_hint') ?>">
data-tippy-content="<?= lang('manage_users_hint') ?>">
<i class="fas fa-users-cog mr-2"></i>
<?= lang('users') ?>
</a>
@ -93,7 +94,7 @@
<?php $active = ($active_menu == PRIV_SYSTEM_SETTINGS) ? 'active' : '' ?>
<li class="nav-item <?= $active . $hidden ?>">
<a href="<?= site_url('backend/settings') ?>" class="nav-link"
title="<?= lang('settings_hint') ?>">
data-tippy-content="<?= lang('settings_hint') ?>">
<i class="fas fa-cogs mr-2"></i>
<?= lang('settings') ?>
</a>
@ -101,7 +102,7 @@
<li class="nav-item">
<a href="<?= site_url('user/logout') ?>" class="nav-link"
title="<?= lang('log_out_hint') ?>">
data-tippy-content="<?= lang('log_out_hint') ?>">
<i class="fas fa-sign-out-alt mr-2"></i>
<?= lang('log_out') ?>
</a>

View file

@ -51,10 +51,10 @@
<div class="input-group-addon">
<div>
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
<i class="fas fa-search"></i>
</button>
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
<i class="fas fa-redo-alt"></i>
</button>
</div>
@ -192,10 +192,10 @@
<div class="input-group-addon">
<div>
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
<i class="fas fa-search"></i>
</button>
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
<i class="fas fa-redo-alt"></i>
</button>
</div>

View file

@ -70,7 +70,7 @@
<?= lang('general_settings') ?>
<?php if ($privileges[PRIV_SYSTEM_SETTINGS]['edit'] == TRUE): ?>
<button type="button" class="save-settings btn btn-primary btn-sm mb-2"
title="<?= lang('save') ?>">
data-tippy-content="<?= lang('save') ?>">
<i class="far fa-check-square mr-2"></i>
<?= lang('save') ?>
</button>
@ -223,7 +223,7 @@
<?= lang('business_logic') ?>
<?php if ($privileges[PRIV_SYSTEM_SETTINGS]['edit'] == TRUE): ?>
<button type="button" class="save-settings btn btn-primary btn-sm mb-2"
title="<?= lang('save') ?>">
data-tippy-content="<?= lang('save') ?>">
<i class="far fa-check-square mr-2"></i>
<?= lang('save') ?>
</button>
@ -311,7 +311,7 @@
<?= lang('legal_contents') ?>
<?php if ($privileges[PRIV_SYSTEM_SETTINGS]['edit'] == TRUE): ?>
<button type="button" class="save-settings btn btn-primary btn-sm mb-2"
title="<?= lang('save') ?>">
data-tippy-content="<?= lang('save') ?>">
<i class="far fa-check-square mr-2"></i>
<?= lang('save') ?>
</button>
@ -392,7 +392,7 @@
<?= lang('personal_information') ?>
<?php if ($privileges[PRIV_USER_SETTINGS]['edit'] == TRUE): ?>
<button type="button" class="save-settings btn btn-primary btn-sm mb-2"
title="<?= lang('save') ?>">
data-tippy-content="<?= lang('save') ?>">
<i class="far fa-check-square mr-2"></i>
<?= lang('save') ?>
</button>

View file

@ -68,10 +68,10 @@
<span class="input-group-addon">
<div>
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
<i class="fas fa-search"></i>
</button>
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
<i class="fas fa-redo-alt"></i>
</button>
</div>
@ -284,7 +284,7 @@
<div class="working-plan-view tab-pane fade clearfix" id="working-plan">
<h3><?= lang('working_plan') ?></h3>
<button id="reset-working-plan" class="btn btn-primary"
title="<?= lang('reset_working_plan') ?>">
data-tippy-content="<?= lang('reset_working_plan') ?>">
<i class="fas fa-redo-alt mr-2"></i>
<?= lang('reset_plan') ?></button>
<table class="working-plan table table-striped mt-2">
@ -374,10 +374,10 @@
<span class="input-group-addon">
<div>
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
<i class="fas fa-search"></i>
</button>
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
<i class="fas fa-redo-alt"></i>
</button>
</div>
@ -580,10 +580,10 @@
<span class="input-group-addon">
<div>
<button class="filter btn btn-outline-secondary" type="submit" title="<?= lang('filter') ?>">
<button class="filter btn btn-outline-secondary" type="submit" data-tippy-content="<?= lang('filter') ?>">
<i class="fas fa-search"></i>
</button>
<button class="clear btn btn-outline-secondary" type="button" title="<?= lang('clear') ?>">
<button class="clear btn btn-outline-secondary" type="button" data-tippy-content="<?= lang('clear') ?>">
<i class="fas fa-redo-alt"></i>
</button>
</div>

View file

@ -17,10 +17,9 @@
</div>
<header>
<a href="https://easyappointments.org" target="_blank">
<img src="<?= base_url('assets/img/installation-banner.png') ?>"
alt="Easy!Appointments Installation Banner">
</a>
<div class="container">
<h1 class="page-title">Easy!Appointments Installation</h1>
</div>
</header>
<div class="content container-fluid">

View file

@ -29,6 +29,12 @@ root {
#header #header-logo {
padding: 5px;
line-height: 0;
}
#header #header-logo small {
font-size: 9px;
color: #2e6a5b;
}
#header #header-logo img {
@ -38,12 +44,11 @@ root {
margin-right: 10px;
}
#header #header-logo span {
float: left;
font-size: 14px;
#header #header-logo h6 {
margin-top: 6px;
font-size: 15px;
font-weight: bold;
color: white;
margin-top: 12px;
}
#header .navbar-toggler {

View file

@ -147,6 +147,7 @@ body {
text-align: center;
color: #0bb98d;
transition: all .3s linear;
cursor: default;
}
#book-appointment-wizard .active-step {
@ -171,10 +172,6 @@ body {
border-top: 1px solid #ebeef1;
}
#book-appointment-wizard #steps .custom-qtip {
border-width: 2px;
}
#book-appointment-wizard #available-hours {
overflow: auto;
margin: 15px 0;

View file

@ -15,17 +15,6 @@
}
}
body .custom-qtip {
border: none;
border-radius: 0;
padding: 10px;
box-shadow: 1px 1px 3px #767676;
background: #EFFDF6;
font-size: 12px;
line-height: 20px;
color: #258D53;
}
body .ui-widget {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1.1em;

View file

@ -3,11 +3,21 @@ header {
margin-bottom: 25px;
}
header .page-title {
font-weight: lighter;
padding: 40px 0;
color: #429a82;
}
.content {
margin: 32px;
max-width: 980px;
}
.content p {
word-break: break-all;
}
.alert {
margin: 25px 0 10px 0;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6
assets/ext/popper/popper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View file

@ -25,7 +25,7 @@ window.Backend = window.Backend || {};
/**
* Main javascript code for the backend of Easy!Appointments.
*/
$(document).ready(function () {
$(function () {
$(window)
.on('resize', function () {
Backend.placeFooterToBottom();
@ -40,15 +40,7 @@ window.Backend = window.Backend || {};
$('#loading').hide();
});
$('.menu-item').qtip({
position: {
my: 'top center',
at: 'bottom center'
},
style: {
classes: 'qtip-green qtip-shadow custom-qtip'
}
});
tippy('[data-tippy-content]');
GeneralFunctions.enableLanguageSelection($('#select-language'));
});

View file

@ -114,7 +114,7 @@ window.BackendCalendar = window.BackendCalendar || {};
exports.initialize = function (view) {
BackendCalendarGoogleSync.initialize();
BackendCalendarAppointmentsModal.initialize();
BackendCalendarUnavailabilitiesModal.initialize();
BackendCalendarUnavailabilityEventsModal.initialize();
// Load and initialize the calendar view.
if (view === 'table') {

View file

@ -142,7 +142,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
endDatetime = Date.parseExact(unavailable.end_datetime, 'yyyy-MM-dd HH:mm:ss');
$dialog = $('#manage-unavailable');
BackendCalendarUnavailabilitiesModal.resetUnavailableDialog();
BackendCalendarUnavailabilityEventsModal.resetUnavailableDialog();
// Apply unavailable data to dialog.
$dialog.find('.modal-header h3').text('Edit Unavailable Period');
@ -1066,7 +1066,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
// Add custom unavailable periods (they are always displayed on the calendar, even if the provider won't
// work on that day).
var unavailableEvents = [];
var unavailabilityEvents = [];
response.unavailables.forEach(function (unavailable) {
var notes = unavailable.notes ? ' - ' + unavailable.notes : '';
@ -1074,7 +1074,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
notes = unavailable.notes.substring(0, 30) + '...'
}
var unavailableEvent = {
var unavailabilityEvent = {
title: EALang.unavailable + notes,
start: moment(unavailable.start_datetime),
end: moment(unavailable.end_datetime),
@ -1085,10 +1085,10 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
data: unavailable
};
unavailableEvents.push(unavailableEvent);
unavailabilityEvents.push(unavailabilityEvent);
});
$calendar.fullCalendar('addEventSource', unavailableEvents);
$calendar.fullCalendar('addEventSource', unavailabilityEvents);
var calendarView = $('#calendar').fullCalendar('getView');
@ -1103,7 +1103,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
var workingPlan = jQuery.parseJSON(provider.settings.working_plan);
var workingPlanExceptions = jQuery.parseJSON(provider.settings.working_plan_exceptions);
var unavailableEvent;
var unavailabilityEvent;
var viewStart;
var viewEnd;
var breakStart;
@ -1157,7 +1157,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
// Non-working day.
if (sortedWorkingPlan[weekdayName] === null) {
// Working plan exception.
unavailableEvent = {
unavailabilityEvent = {
title: EALang.not_working,
start: calendarView.intervalStart.clone(),
end: calendarView.intervalEnd.clone(),
@ -1167,7 +1167,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
className: 'fc-unavailable'
};
$calendar.fullCalendar('renderEvent', unavailableEvent, false);
$calendar.fullCalendar('renderEvent', unavailabilityEvent, false);
return; // Go to next loop.
}
@ -1276,7 +1276,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
// Non-working day.
if (sortedWorkingPlan[weekdayName] === null) {
// Add a full day unavailable event.
unavailableEvent = {
unavailabilityEvent = {
title: EALang.not_working,
start: calendarDate.clone(),
end: calendarDate.clone().add(1, 'day'),
@ -1286,7 +1286,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
className: 'fc-unavailable'
};
$calendar.fullCalendar('renderEvent', unavailableEvent, true);
$calendar.fullCalendar('renderEvent', unavailabilityEvent, true);
calendarDate.add(1, 'day');
@ -1300,7 +1300,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
workDateStart.minute(parseInt(startHour[1]));
if (calendarDate < workDateStart) {
unavailableEvent = {
unavailabilityEvent = {
title: EALang.not_working,
start: calendarDate.clone(),
end: moment(calendarDate.format('YYYY-MM-DD') + ' ' + sortedWorkingPlan[weekdayName].start + ':00'),
@ -1310,7 +1310,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
className: 'fc-unavailable'
};
$calendar.fullCalendar('renderEvent', unavailableEvent, true);
$calendar.fullCalendar('renderEvent', unavailabilityEvent, true);
}
// Add unavailable period after work ends.
@ -1320,7 +1320,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
workDateEnd.minute(parseInt(endHour[1]));
if (calendarView.end > workDateEnd) {
unavailableEvent = {
unavailabilityEvent = {
title: EALang.not_working,
start: moment(calendarDate.format('YYYY-MM-DD') + ' ' + sortedWorkingPlan[weekdayName].end + ':00'),
end: calendarDate.clone().add(1, 'day'),
@ -1330,7 +1330,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
className: 'fc-unavailable'
};
$calendar.fullCalendar('renderEvent', unavailableEvent, false);
$calendar.fullCalendar('renderEvent', unavailabilityEvent, false);
}
// Add unavailable periods during day breaks.
@ -1345,7 +1345,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
breakEnd.hour(parseInt(breakEndString[0]));
breakEnd.minute(parseInt(breakEndString[1]));
var unavailableEvent = {
var unavailabilityEvent = {
title: EALang.break,
start: moment(calendarDate.format('YYYY-MM-DD') + ' ' + breakPeriod.start),
end: moment(calendarDate.format('YYYY-MM-DD') + ' ' + breakPeriod.end),
@ -1355,7 +1355,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
className: 'fc-unavailable fc-break'
};
$calendar.fullCalendar('renderEvent', unavailableEvent, false);
$calendar.fullCalendar('renderEvent', unavailabilityEvent, false);
});
calendarDate.add(1, 'day');
@ -1612,27 +1612,6 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {};
$dialog.modal('show');
}
// Apply qtip to control tooltips.
$('#calendar-toolbar button').qtip({
position: {
my: 'top center',
at: 'bottom center'
},
style: {
classes: 'qtip-green qtip-shadow custom-qtip'
}
});
$('#select-filter-item').qtip({
position: {
my: 'middle left',
at: 'middle right'
},
style: {
classes: 'qtip-green qtip-shadow custom-qtip'
}
});
if (!$('#select-filter-item option').length) {
$('#calendar-actions button').prop('disabled', true);
}

View file

@ -91,13 +91,16 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
$providerColumn.find('.calendar-wrapper').fullCalendar('removeEvents');
createNonWorkingHours($providerColumn.find('.calendar-wrapper'), JSON.parse($providerColumn.data('provider').settings.working_plan));
createNonWorkingHours(
$providerColumn.find('.calendar-wrapper'),
$providerColumn.data('provider')
);
// Add the appointments to the column.
createAppointments($providerColumn, response.appointments);
// Add the unavailabilities to the column.
createUnavailabilities($providerColumn, response.unavailabilities);
// Add the unavailability events to the column.
createUnavailabilityEvents($providerColumn, response.unavailability_events);
// Add the provider breaks to the column.
var workingPlan = JSON.parse(provider.settings.working_plan);
@ -145,7 +148,36 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
var $dialog;
if (lastFocusedEventData.data.is_unavailable === '0') {
if (lastFocusedEventData.data.workingPlanException) {
var date = lastFocusedEventData.data.date;
var workingPlanException = lastFocusedEventData.data.workingPlanException;
var provider = lastFocusedEventData.data.provider;
WorkingPlanExceptionsModal
.edit(date, workingPlanException)
.done(function (date, workingPlanException) {
var successCallback = function () {
Backend.displayNotification(EALang.working_plan_exception_saved);
var workingPlanExceptions = jQuery.parseJSON(provider.settings.working_plan_exceptions) || {};
workingPlanExceptions[date] = workingPlanException;
for (var index in GlobalVariables.availableProviders) {
var availableProvider = GlobalVariables.availableProviders[index];
if (Number(availableProvider.id) === Number(provider.id)) {
availableProvider.settings.working_plan_exceptions = JSON.stringify(workingPlanExceptions);
break;
}
}
$('#select-filter-item').trigger('change'); // Update the calendar.
};
BackendCalendarApi.saveWorkingPlanException(date, workingPlanException, provider.id, successCallback, null);
});
} else if (lastFocusedEventData.data.is_unavailable === '0') {
var appointment = lastFocusedEventData.data;
$dialog = $('#manage-appointment');
@ -186,7 +218,7 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
var endDatetime = Date.parseExact(unavailable.end_datetime, 'yyyy-MM-dd HH:mm:ss');
$dialog = $('#manage-unavailable');
BackendCalendarUnavailabilitiesModal.resetUnavailableDialog();
BackendCalendarUnavailabilityEventsModal.resetUnavailableDialog();
// Apply unavailable data to dialog.
$dialog.find('.modal-header h3').text('Edit Unavailable Period');
@ -592,13 +624,16 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
createCalendar($providerColumn, date, provider);
// Create non working hours.
createNonWorkingHours($providerColumn.find('.calendar-wrapper'), JSON.parse(provider.settings.working_plan))
createNonWorkingHours(
$providerColumn.find('.calendar-wrapper'),
provider
);
// Add the appointments to the column.
createAppointments($providerColumn, events.appointments);
// Add the unavailabilities to the column.
createUnavailabilities($providerColumn, events.unavailabilities);
// Add the unavailability events to the column.
createUnavailabilityEvents($providerColumn, events.unavailability_events);
Backend.placeFooterToBottom();
}
@ -606,8 +641,8 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
/**
* Get Calendar Component Height
*
* This method calculates the proper calendar height, in order to be displayed correctly, even when the
* browser window is resizing.
* This method calculates the proper calendar height, in order to be displayed correctly, even when the browser
* window is resizing.
*
* @return {Number} Returns the calendar element height in pixels.
*/
@ -793,11 +828,38 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
$(element).fullCalendar('option', 'height', getCalendarHeight());
}
function createNonWorkingHours($calendar, workingPlan) {
function createNonWorkingHours($calendar, provider) {
var workingPlan = JSON.parse(provider.settings.working_plan);
var workingPlanExceptions = JSON.parse(provider.settings.working_plan_exceptions);
var view = $calendar.fullCalendar('getView');
var start = view.start.clone();
var end = view.end.clone();
var selDayName = start.toDate().toString('dddd').toLowerCase();
var selDayDate = start.format('YYYY-MM-DD');
if (workingPlanExceptions[selDayDate]) {
workingPlan[selDayName] = workingPlanExceptions[selDayDate];
var workingPlanExceptionStart = selDayDate + ' ' + workingPlan[selDayName].start;
var workingPlanExceptionEnd = selDayDate + ' ' + workingPlan[selDayName].end;
var workingPlanExceptionEvent = {
title: EALang.working_plan_exception,
start: moment(workingPlanExceptionStart, 'YYYY-MM-DD HH:mm', true),
end: moment(workingPlanExceptionEnd, 'YYYY-MM-DD HH:mm', true).add(1, 'day'),
allDay: true,
color: '#879DB4',
editable: false,
className: 'fc-working-plan-exception fc-custom',
data: {
date: selDayDate,
workingPlanException: workingPlanExceptions[selDayDate],
provider: provider
}
};
$calendar.fullCalendar('renderEvent', workingPlanExceptionEvent, false);
}
if (workingPlan[selDayName] === null) {
var nonWorkingDay = {
@ -912,20 +974,20 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
}
/**
* Create Unavailabilities Events
* Create Unavailability Events
*
* This method will add the unavailability events on the table view.
*
* @param {jQuery} $providerColumn The provider column container.
* @param {Object[]} unavailabilities Contains the unavailability events data.
* @param {Object[]} unavailabilityEvents Contains the unavailability events data.
*/
function createUnavailabilities($providerColumn, unavailabilities) {
if (unavailabilities.length === 0) {
function createUnavailabilityEvents($providerColumn, unavailabilityEvents) {
if (unavailabilityEvents.length === 0) {
return;
}
for (var index in unavailabilities) {
var unavailability = unavailabilities[index];
for (var index in unavailabilityEvents) {
var unavailability = unavailabilityEvents[index];
if (unavailability.id_users_provider !== $providerColumn.data('provider').id) {
continue;
@ -1036,10 +1098,10 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
if ($(this).hasClass('fc-unavailable') || $parent.hasClass('fc-unavailable') || $altParent.hasClass('fc-unavailable')) {
displayEdit = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
&& GlobalVariables.user.privileges.appointments.edit === true)
? 'mr-2' : 'd-none';
? '' : 'd-none';
displayDelete = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
&& GlobalVariables.user.privileges.appointments.delete === true)
? 'mr-2' : 'd-none'; // Same value at the time.
? '' : 'd-none'; // Same value at the time.
$html = $('<div/>', {
'html': [
@ -1070,8 +1132,30 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
$('<hr/>'),
$('<div/>', {
'class': 'd-flex justify-content-between',
'class': 'd-flex justify-content-center',
'html': [
$('<button/>', {
'class': 'close-popover btn btn-outline-secondary mr-2',
'html': [
$('<i/>', {
'class': 'fas fa-ban mr-2'
}),
$('<span/>', {
'text': EALang.close
})
]
}),
$('<button/>', {
'class': 'delete-popover btn btn-outline-secondary mr-2 ' + displayDelete,
'html': [
$('<i/>', {
'class': 'far fa-trash-alt mr-2'
}),
$('<span/>', {
'text': EALang.delete
})
]
}),
$('<button/>', {
'class': 'edit-popover btn btn-primary ' + displayEdit,
'html': [
@ -1083,28 +1167,6 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
})
]
}),
$('<button/>', {
'class': 'delete-popover btn btn-outline-secondary ' + displayDelete,
'html': [
$('<i/>', {
'class': 'far fa-trash-alt mr-2'
}),
$('<span/>', {
'text': EALang.delete
})
]
}),
$('<button/>', {
'class': 'close-popover btn btn-outline-secondary',
'html': [
$('<i/>', {
'class': 'fas fa-ban mr-2'
}),
$('<span/>', {
'text': EALang.close
})
]
})
]
})
]
@ -1112,11 +1174,11 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
} else if ($(this).hasClass('fc-working-plan-exception') || $parent.hasClass('fc-working-plan-exception') || $altParent.hasClass('fc-working-plan-exception')) {
displayEdit = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
&& GlobalVariables.user.privileges.appointments.edit === true)
? 'mr-2' : 'd-none'; // Same value at the time.
? '' : 'd-none'; // Same value at the time.
displayDelete = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
&& GlobalVariables.user.privileges.appointments.delete === true)
? 'mr-2' : 'd-none'; // Same value at the time.
? '' : 'd-none'; // Same value at the time.
$html = $('<div/>', {
'html': [
@ -1124,7 +1186,7 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
'text': EALang.provider
}),
$('<span/>', {
'text': event.data ? event.data.first_name + ' ' + event.data.last_name : '-'
'text': event.data ? event.data.provider.first_name + ' ' + event.data.provider.last_name : '-'
}),
$('<br/>'),
@ -1158,18 +1220,18 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
'class': 'd-flex justify-content-center',
'html': [
$('<button/>', {
'class': 'edit-popover btn btn-danger ' + displayEdit,
'class': 'close-popover btn btn-outline-secondary mr-2',
'html': [
$('<i/>', {
'class': 'far fa-edit mr-2'
'class': 'fas fa-ban mr-2'
}),
$('<span/>', {
'text': EALang.edit
'text': EALang.close
})
]
}),
$('<button/>', {
'class': 'delete-popover btn btn-outline-secondary ' + displayDelete,
'class': 'delete-popover btn btn-outline-secondary mr-2 ' + displayDelete,
'html': [
$('<i/>', {
'class': 'far fa-trash-alt mr-2'
@ -1180,13 +1242,13 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
]
}),
$('<button/>', {
'class': 'close-popover btn btn-outline-secondary',
'class': 'edit-popover btn btn-primary ' + displayEdit,
'html': [
$('<i/>', {
'class': 'fas fa-ban mr-2'
'class': 'far fa-edit mr-2'
}),
$('<span/>', {
'text': EALang.close
'text': EALang.edit
})
]
})
@ -1196,9 +1258,9 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
});
} else {
displayEdit = (GlobalVariables.user.privileges.appointments.edit === true)
? 'mr-2' : 'd-none';
? '' : 'd-none';
displayDelete = (GlobalVariables.user.privileges.appointments.delete === true)
? 'mr-2' : 'd-none';
? '' : 'd-none';
$html = $('<div/>', {
'html': [
@ -1284,18 +1346,18 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
'class': 'd-flex justify-content-center',
'html': [
$('<button/>', {
'class': 'edit-popover btn btn-primary ' + displayEdit,
'class': 'close-popover btn btn-outline-secondary mr-2',
'html': [
$('<i/>', {
'class': 'far fa-edit mr-2'
'class': 'fas fa-ban mr-2'
}),
$('<span/>', {
'text': EALang.edit
'text': EALang.close
})
]
}),
$('<button/>', {
'class': 'delete-popover btn btn-outline-secondary ' + displayDelete,
'class': 'delete-popover btn btn-outline-secondary mr-2' + displayDelete,
'html': [
$('<i/>', {
'class': 'far fa-trash-alt mr-2'
@ -1306,13 +1368,13 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
]
}),
$('<button/>', {
'class': 'close-popover btn btn-outline-secondary',
'class': 'edit-popover btn btn-primary ' + displayEdit,
'html': [
$('<i/>', {
'class': 'fas fa-ban mr-2'
'class': 'far fa-edit mr-2'
}),
$('<span/>', {
'text': EALang.close
'text': EALang.edit
})
]
})
@ -1679,6 +1741,8 @@ window.BackendCalendarTableView = window.BackendCalendarTableView || {};
createView(startDate, endDate);
$('#insert-working-plan-exception').hide();
bindEventHandlers();
// Hide Google Calendar Sync buttons cause they can not be used within this view.

View file

@ -10,13 +10,13 @@
* ---------------------------------------------------------------------------- */
/**
* Backend Calendar Unavailabilities Modal
* Backend Calendar Unavailability Events Modal
*
* This module implements the unavailabilities modal functionality.
* This module implements the unavailability events modal functionality.
*
* @module BackendCalendarUnavailabilitiesModal
* @module BackendCalendarUnavailabilityEventsModal
*/
window.BackendCalendarUnavailabilitiesModal = window.BackendCalendarUnavailabilitiesModal || {};
window.BackendCalendarUnavailabilityEventsModal = window.BackendCalendarUnavailabilityEventsModal || {};
(function (exports) {
@ -91,7 +91,7 @@ window.BackendCalendarUnavailabilitiesModal = window.BackendCalendarUnavailabili
* he cannot accept any appointments.
*/
$('#insert-unavailable').on('click', function () {
BackendCalendarUnavailabilitiesModal.resetUnavailableDialog();
BackendCalendarUnavailabilityEventsModal.resetUnavailableDialog();
var $dialog = $('#manage-unavailable');
// Set the default datetime values.
@ -229,4 +229,4 @@ window.BackendCalendarUnavailabilitiesModal = window.BackendCalendarUnavailabili
bindEventHandlers();
};
})(window.BackendCalendarUnavailabilitiesModal);
})(window.BackendCalendarUnavailabilityEventsModal);

View file

@ -113,16 +113,6 @@ window.BackendUsers = window.BackendUsers || {};
.appendTo('#secretary-providers');
});
$('#reset-working-plan').qtip({
position: {
my: 'top center',
at: 'bottom center'
},
style: {
classes: 'qtip-green qtip-shadow custom-qtip'
}
});
// Bind event handlers.
if (defaultEventHandlers) {
bindEventHandlers();

View file

@ -89,15 +89,7 @@ window.FrontendBook = window.FrontendBook || {};
FrontendBook.manageMode = manageMode;
// Initialize page's components (tooltips, datepickers etc).
$('.book-step').qtip({
position: {
my: 'top center',
at: 'bottom center'
},
style: {
classes: 'qtip-green qtip-shadow custom-qtip'
}
});
tippy('[data-tippy-content]');
var weekDayId = GeneralFunctions.getWeekDayId(GlobalVariables.firstWeekday);
@ -632,7 +624,7 @@ window.FrontendBook = window.FrontendBook || {};
data.appointment = {
start_datetime: $('#select-date').datepicker('getDate').toString('yyyy-MM-dd')
+ ' ' + Date.parse($('.selected-hour').text()).toString('HH:mm') + ':00',
+ ' ' + Date.parse($('.selected-hour').data('value') || '').toString('HH:mm') + ':00',
end_datetime: calculateEndDatetime(),
notes: $('#notes').val(),
is_unavailable: false,
@ -666,7 +658,7 @@ window.FrontendBook = window.FrontendBook || {};
// Add the duration to the start datetime.
var startDatetime = $('#select-date').datepicker('getDate').toString('dd-MM-yyyy')
+ ' ' + Date.parse($('.selected-hour').text()).toString('HH:mm');
+ ' ' + Date.parse($('.selected-hour').data('value') || '').toString('HH:mm');
startDatetime = Date.parseExact(startDatetime, 'dd-MM-yyyy HH:mm');
var endDatetime;

View file

@ -90,7 +90,7 @@ window.FrontendBookApi = window.FrontendBookApi || {};
var selectedTimezone = $('#select-timezone').val();
var timeFormat = GlobalVariables.timeFormat === 'regular' ? 'h:mm a' : 'HH:mm';
response.forEach(function (availableHour, index) {
response.forEach(function (availableHour) {
var availableHourMoment = moment
.tz(selectedDate + ' ' + availableHour + ':00', providerTimezone)
.tz(selectedTimezone);
@ -98,6 +98,9 @@ window.FrontendBookApi = window.FrontendBookApi || {};
$('#available-hours').append(
$('<button/>', {
'class': 'btn btn-outline-secondary btn-block shadow-none available-hour',
'data': {
'value': availableHour
},
'text': availableHourMoment.format(timeFormat)
})
);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

9510
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -24,16 +24,17 @@
"node": ">=12"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.14.0",
"bootstrap": "^4.5.0",
"@fortawesome/fontawesome-free": "^5.15.1",
"bootstrap": "^4.5.3",
"cookieconsent": "^3.1.1",
"datejs": "0.0.2",
"fullcalendar": "^3.10.1",
"fullcalendar": "^3.10.2",
"jquery": "^3.5.1",
"jquery-ui": "^1.12.0",
"moment": "^2.25.3",
"moment-timezone": "^0.5.28",
"moment": "^2.29.1",
"moment-timezone": "^0.5.31",
"qtip2": "^3.0.3",
"tippy.js": "^6.2.7",
"trumbowyg": "^2.21.0"
},
"devDependencies": {
@ -47,7 +48,7 @@
"gulp-plumber": "^1.2.1",
"gulp-rename": "^1.4.0",
"gulp-uglify": "^3.0.2",
"jsdoc": "^3.6.4",
"jsdoc": "^3.6.6",
"node-notifier": "^5.4.3",
"plato": "^1.7.0",
"zip-dir": "^1.0.2"