easyappointments/application/controllers/Google.php
2020-09-24 10:26:29 +03:00

320 lines
13 KiB
PHP

<?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.0.0
* ---------------------------------------------------------------------------- */
/**
* Google Controller
*
* This controller handles the Google Calendar synchronization operations.
*
* @property CI_Session $session
* @property CI_Loader $load
* @property CI_Input $input
* @property CI_Output $output
* @property CI_Config $config
* @property CI_Lang $lang
* @property CI_Cache $cache
* @property CI_DB_query_builder $db
* @property CI_Security $security
* @property Google_Sync $google_sync
* @property Ics_file $ics_file
* @property Appointments_Model $appointments_model
* @property Providers_Model $providers_model
* @property Services_Model $services_model
* @property Customers_Model $customers_model
* @property Settings_Model $settings_model
* @property Timezones $timezones
* @property Roles_Model $roles_model
* @property Secretaries_Model $secretaries_model
* @property Admins_Model $admins_model
* @property User_Model $user_model
*
* @package Controllers
*/
class Google extends CI_Controller {
/**
* Class Constructor
*/
public function __construct()
{
parent::__construct();
$this->load->library('session');
}
/**
* Authorize Google Calendar API usage for a specific provider.
*
* Since it is required to follow the web application flow, in order to retrieve a refresh token from the Google API
* service, this method is going to authorize the given provider.
*
* @param int $provider_id The provider id, for whom the sync authorization is made.
*/
public function oauth($provider_id)
{
// Store the provider id for use on the callback function.
$this->session->set_userdata('oauth_provider_id', $provider_id);
// Redirect browser to google user content page.
$this->load->library('google_sync');
header('Location: ' . $this->google_sync->get_auth_url());
}
/**
* Callback method for the Google Calendar API authorization process.
*
* Once the user grants consent with his Google Calendar data usage, the Google OAuth service will redirect him back
* in this page. Here we are going to store the refresh token, because this is what will be used to generate access
* tokens in the future.
*
* IMPORTANT: Because it is necessary to authorize the application using the web server flow (see official
* documentation of OAuth), every Easy!Appointments installation should use its own calendar api key. So in every
* api console account, the "http://path-to-Easy!Appointments/google/oauth_callback" should be included in an allowed redirect URL.
*/
public function oauth_callback()
{
$code = $this->input->get('code');
if (empty($code))
{
$this->output->set_output('Code authorization failed.');
return;
}
$this->load->library('Google_sync');
$token = $this->google_sync->authenticate($code);
if (empty($token))
{
$this->output->set_output('Token authorization failed.');
return;
}
// Store the token into the database for future reference.
$oauth_provider_id = $this->session->userdata('oauth_provider_id');
if ($oauth_provider_id)
{
$this->load->model('providers_model');
$this->providers_model->set_setting('google_sync', TRUE, $oauth_provider_id);
$this->providers_model->set_setting('google_token', json_encode($token), $oauth_provider_id);
$this->providers_model->set_setting('google_calendar', 'primary', $oauth_provider_id);
}
else
{
$this->output->set_output('Sync provider id not specified.');
}
}
/**
* Complete synchronization of appointments between Google Calendar and Easy!Appointments.
*
* This method will completely sync the appointments of a provider with his Google Calendar account. The sync period
* needs to be relatively small, because a lot of API calls might be necessary and this will lead to consuming the
* Google limit for the Calendar API usage.
*
* @param int $provider_id Provider record to be synced.
*/
public static function sync($provider_id = NULL)
{
try
{
$framework = get_instance();
// The user must be logged in.
$framework->load->library('session');
if ($framework->session->userdata('user_id') == FALSE && is_cli() === FALSE)
{
return;
}
if ($provider_id === NULL)
{
throw new Exception('Provider id not specified.');
}
$framework->load->model('appointments_model');
$framework->load->model('providers_model');
$framework->load->model('services_model');
$framework->load->model('customers_model');
$framework->load->model('settings_model');
$provider = $framework->providers_model->get_row($provider_id);
// Check whether the selected provider has google sync enabled.
$google_sync = $framework->providers_model->get_setting('google_sync', $provider['id']);
if ( ! $google_sync)
{
throw new Exception('The selected provider has not the google synchronization setting enabled.');
}
$google_token = json_decode($framework->providers_model->get_setting('google_token', $provider['id']));
$framework->load->library('google_sync');
$framework->google_sync->refresh_token($google_token->refresh_token);
// Fetch provider's appointments that belong to the sync time period.
$sync_past_days = $framework->providers_model->get_setting('sync_past_days', $provider['id']);
$sync_future_days = $framework->providers_model->get_setting('sync_future_days', $provider['id']);
$start = strtotime('-' . $sync_past_days . ' days', strtotime(date('Y-m-d')));
$end = strtotime('+' . $sync_future_days . ' days', strtotime(date('Y-m-d')));
$where_clause = [
'start_datetime >=' => date('Y-m-d H:i:s', $start),
'end_datetime <=' => date('Y-m-d H:i:s', $end),
'id_users_provider' => $provider['id']
];
$appointments = $framework->appointments_model->get_batch($where_clause);
$company_settings = [
'company_name' => $framework->settings_model->get_setting('company_name'),
'company_link' => $framework->settings_model->get_setting('company_link'),
'company_email' => $framework->settings_model->get_setting('company_email')
];
$provider_timezone = new DateTimeZone($provider['timezone']);
// Sync each appointment with Google Calendar by following the project's sync protocol (see documentation).
foreach ($appointments as $appointment)
{
if ($appointment['is_unavailable'] == FALSE)
{
$service = $framework->services_model->get_row($appointment['id_services']);
$customer = $framework->customers_model->get_row($appointment['id_users_customer']);
}
else
{
$service = NULL;
$customer = NULL;
}
// If current appointment not synced yet, add to Google Calendar.
if ($appointment['id_google_calendar'] == NULL)
{
$google_event = $framework->google_sync->add_appointment($appointment, $provider,
$service, $customer, $company_settings);
$appointment['id_google_calendar'] = $google_event->id;
$framework->appointments_model->add($appointment); // Save the Google Calendar ID.
}
else
{
// Appointment is synced with google calendar.
try
{
$google_event = $framework->google_sync->get_event($provider, $appointment['id_google_calendar']);
if ($google_event->status == 'cancelled')
{
throw new Exception('Event is cancelled, remove the record from Easy!Appointments.');
}
// If Google Calendar event is different from Easy!Appointments appointment then update
// Easy!Appointments record.
$is_different = FALSE;
$appt_start = strtotime($appointment['start_datetime']);
$appt_end = strtotime($appointment['end_datetime']);
$event_start = new DateTime($google_event->getStart()->getDateTime());
$event_start->setTimezone($provider_timezone);
$event_end = new DateTime($google_event->getEnd()->getDateTime());
$event_end->setTimezone($provider_timezone);
if ($appt_start != $event_start->getTimestamp() || $appt_end != $event_end->getTimestamp()
|| $appointment['notes'] !== $google_event->getDescription())
{
$is_different = TRUE;
}
if ($is_different)
{
$appointment['start_datetime'] = $event_start->format('Y-m-d H:i:s');
$appointment['end_datetime'] = $event_end->format('Y-m-d H:i:s');
$appointment['notes'] = $google_event->getDescription();
$framework->appointments_model->add($appointment);
}
}
catch (Exception $exception)
{
// Appointment not found on Google Calendar, delete from Easy!Appoinmtents.
$framework->appointments_model->delete($appointment['id']);
$appointment['id_google_calendar'] = NULL;
}
}
}
// Add Google Calendar events that do not exist in Easy!Appointments.
$google_calendar = $provider['settings']['google_calendar'];
$google_events = $framework->google_sync->get_sync_events($google_calendar, $start, $end);
foreach ($google_events->getItems() as $google_event)
{
if ($google_event->getStatus() === 'cancelled')
{
continue;
}
if ($google_event->getStart() === NULL || $google_event->getEnd() === NULL)
{
continue;
}
$results = $framework->appointments_model->get_batch(['id_google_calendar' => $google_event->getId()]);
if ( ! empty($results))
{
continue;
}
$event_start = new DateTime($google_event->getStart()->getDateTime());
$event_start->setTimezone($provider_timezone);
$event_end = new DateTime($google_event->getEnd()->getDateTime());
$event_end->setTimezone($provider_timezone);
// Record doesn't exist in the Easy!Appointments, so add the event now.
$appointment = [
'start_datetime' => $event_start->format('Y-m-d H:i:s'),
'end_datetime' => $event_end->format('Y-m-d H:i:s'),
'is_unavailable' => TRUE,
'location' => $google_event->getLocation(),
'notes' => $google_event->getSummary() . ' ' . $google_event->getDescription(),
'id_users_provider' => $provider_id,
'id_google_calendar' => $google_event->getId(),
'id_users_customer' => NULL,
'id_services' => NULL,
];
$framework->appointments_model->add($appointment);
}
$response = AJAX_SUCCESS;
}
catch (Exception $exception)
{
$framework->output->set_status_header(500);
$response = [
'message' => $exception->getMessage(),
'trace' => config('debug') ? $exception->getTrace() : []
];
}
$framework->output
->set_content_type('application/json')
->set_output(json_encode($response));
}
}