From dc586ecefbef8377292f9c82ff5c26af669d278d Mon Sep 17 00:00:00 2001 From: "alextselegidis@gmail.com" Date: Wed, 19 Jun 2013 19:29:00 +0000 Subject: [PATCH] =?UTF-8?q?=CE=9F=CE=BB=CE=BF=CE=BA=CE=BB=CE=AE=CF=81?= =?UTF-8?q?=CF=89=CF=83=CE=B7=20=CF=84=CE=B7=CF=82=20=CE=B4=CE=B9=CE=B1?= =?UTF-8?q?=CE=B4=CE=B9=CE=BA=CE=B1=CF=83=CE=AF=CE=B1=CF=82=20OAuth=20?= =?UTF-8?q?=CF=84=CE=B7=CF=82=20Google.=20=CE=A3=CF=85=CF=87=CF=81=CE=BF?= =?UTF-8?q?=CE=BD=CE=B9=CF=83=CE=BC=CF=8C=CF=82=20=CF=84=CF=89=CE=BD=20?= =?UTF-8?q?=CF=81=CE=B1=CE=BD=CF=84=CE=B5=CE=B2=CE=BF=CF=8D=20=CF=80=CE=BF?= =?UTF-8?q?=CF=85=20=CF=80=CF=81=CE=BF=CF=83=CF=84=CE=AF=CE=B8=CE=BF=CE=BD?= =?UTF-8?q?=CF=84=CE=B1=CE=B9=20=CE=B1=CF=80=CF=8C=20=CF=84=CE=BF=CF=85?= =?UTF-8?q?=CF=82=20=CF=80=CE=B5=CE=BB=CE=AC=CF=84=CE=B5=CF=82=20=CF=83?= =?UTF-8?q?=CF=84=CE=BF=20=CE=B7=CE=BC=CE=B5=CF=81=CE=BF=CE=BB=CF=8C=CE=B3?= =?UTF-8?q?=CE=B9=CE=BF=20=CF=84=CE=BF=CF=85=20=CE=B1=CE=BD=CF=84=CE=AF?= =?UTF-8?q?=CF=83=CF=84=CE=BF=CE=B9=CF=87=CE=BF=CF=85=20=CF=80=CE=AC=CF=81?= =?UTF-8?q?=CE=BF=CF=87=CE=BF=CF=85.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Release Notes.txt | 3 +- db/easy_appointments.sql | 33 ++-- src/application/config/config.php | 2 +- src/application/controllers/appointments.php | 27 +-- src/application/controllers/backend.php | 111 ++++++++++++- src/application/controllers/google.php | 37 ++++- src/application/libraries/google_sync.php | 99 ++++++++--- src/application/libraries/notifications.php | 13 ++ src/application/models/providers_model.php | 13 +- src/application/views/appointments/book.php | 1 + src/application/views/backend/calendar.php | 50 +++--- src/assets/js/backend_calendar.js | 163 ++++++++++++++++--- 12 files changed, 442 insertions(+), 110 deletions(-) diff --git a/Release Notes.txt b/Release Notes.txt index 090bc6d2..c5dccf95 100644 --- a/Release Notes.txt +++ b/Release Notes.txt @@ -6,10 +6,11 @@ Main - Javascript Google API usage from the customer's perspective. - Backend google calendar authentication Process. - Sync every appointment change made from E!A to Google Calendar. -- Display user friendly error messages. + Minor +- Display user friendly error messages. - Added sync exception to Google Sync library. \ No newline at end of file diff --git a/db/easy_appointments.sql b/db/easy_appointments.sql index a346be48..5dd8ad07 100644 --- a/db/easy_appointments.sql +++ b/db/easy_appointments.sql @@ -3,7 +3,7 @@ -- http://www.phpmyadmin.net -- -- Φιλοξενητής: localhost --- Χρόνος δημιουργίας: 08 Ιουν 2013 στις 12:49:21 +-- Χρόνος δημιουργίας: 19 Ιουν 2013 στις 22:27:32 -- Έκδοση διακομιστή: 5.5.24-log -- Έκδοση PHP: 5.4.3 @@ -36,18 +36,12 @@ CREATE TABLE IF NOT EXISTS `ea_appointments` ( `id_users_provider` bigint(20) unsigned NOT NULL, `id_users_customer` bigint(20) unsigned NOT NULL, `id_services` bigint(20) unsigned NOT NULL, + `id_google_calendar` text, PRIMARY KEY (`id`), KEY `id_users_customer` (`id_users_customer`), KEY `id_services` (`id_services`), KEY `id_users_provider` (`id_users_provider`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=43 ; - --- --- Άδειασμα δεδομένων του πίνακα `ea_appointments` --- - -INSERT INTO `ea_appointments` (`id`, `book_datetime`, `start_datetime`, `end_datetime`, `notes`, `hash`, `id_users_provider`, `id_users_customer`, `id_services`) VALUES -(10, NULL, '2013-06-07 15:30:00', '2013-06-07 15:50:00', '', 'c4baf9ea27dcd0fdc5449eb91b0ee2c5', 2, 20, 1); +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=88 ; -- -------------------------------------------------------- @@ -93,7 +87,7 @@ CREATE TABLE IF NOT EXISTS `ea_services` ( `id_service_categories` bigint(20) unsigned DEFAULT NULL, PRIMARY KEY (`id`), KEY `id_service_categories` (`id_service_categories`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=22 ; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=10 ; -- -- Άδειασμα δεδομένων του πίνακα `ea_services` @@ -156,7 +150,7 @@ CREATE TABLE IF NOT EXISTS `ea_settings` ( `name` varchar(512) DEFAULT NULL, `value` longtext, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=22 ; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=10 ; -- -- Άδειασμα δεδομένων του πίνακα `ea_settings` @@ -190,7 +184,7 @@ CREATE TABLE IF NOT EXISTS `ea_users` ( `id_roles` bigint(20) unsigned NOT NULL, PRIMARY KEY (`id`), KEY `id_roles` (`id_roles`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=116 ; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=154 ; -- -- Άδειασμα δεδομένων του πίνακα `ea_users` @@ -200,9 +194,7 @@ INSERT INTO `ea_users` (`id`, `first_name`, `last_name`, `email`, `mobile_number (1, '', '1', 'alextselegidis@gmail.com', '123456789', '1', '', '', NULL, '', 'This is me making Easy!Appointments', 1), (2, 'Γεώργιος', 'Παπαδόπουλος', 'alextselegidis@gmail.com', '1212121212', '1', '', '', NULL, '', 'This is a test provider', 2), (3, 'Νίκος', 'Αναστασίου', 'prov2@test.gr', '1313133113131', '32132165146', 'Some Street 3', NULL, NULL, NULL, NULL, 2), -(4, 'Ηρώ', 'Καριοφύλη', 'prov3@test.gr', '239203490', '029340923', 'John Doe 3 ', NULL, NULL, NULL, NULL, 2), -(20, 'Alex', 'Tselegidis', 'alextselegidis@yahoo.gr', NULL, '123456789', 'Some Str', 'Some City', NULL, '12345', NULL, 3), -(76, '', 'a', 'alextselegidis@yahoo.gr', NULL, 'a', '', '', NULL, '', NULL, 3); +(4, 'Ηρώ', 'Καριοφύλη', 'prov3@test.gr', '239203490', '029340923', 'John Doe 3 ', NULL, NULL, NULL, NULL, 2); -- -------------------------------------------------------- @@ -216,7 +208,8 @@ CREATE TABLE IF NOT EXISTS `ea_user_settings` ( `password` varchar(512) DEFAULT NULL, `working_plan` text, `notifications` text, - `google_sync` tinyint(4) DEFAULT NULL, + `google_sync` tinyint(4) DEFAULT '0', + `google_token` text, PRIMARY KEY (`id_users`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -224,10 +217,10 @@ CREATE TABLE IF NOT EXISTS `ea_user_settings` ( -- Άδειασμα δεδομένων του πίνακα `ea_user_settings` -- -INSERT INTO `ea_user_settings` (`id_users`, `username`, `password`, `working_plan`, `notifications`, `google_sync`) VALUES -(2, 'provider_1', 'provider_1', '{"monday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"tuesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"wednesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"thursday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"friday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"saturday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"sunday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]}}', NULL, 0), -(3, 'provider_2', 'provider_2', '{"monday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"tuesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"wednesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"thursday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"friday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"saturday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"sunday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]}}', NULL, 0), -(4, 'provider_3', 'provider_3', '{"monday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"tuesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"wednesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"thursday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"friday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"saturday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"sunday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]}}', NULL, 0); +INSERT INTO `ea_user_settings` (`id_users`, `username`, `password`, `working_plan`, `notifications`, `google_sync`, `google_token`) VALUES +(2, 'provider_1', 'provider_1', '{"monday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"tuesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"wednesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"thursday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"friday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"saturday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"sunday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]}}', NULL, 1, '{"access_token":"ya29.AHES6ZRsDBInIFSW1vdMEUt9N_teDoKPk6IVLS-mM41J7P0","token_type":"Bearer","expires_in":3600,"refresh_token":"1\\/9KusWyDci21Fv-PpgeZr3Yik56WnNQ7LDTcmeUhNTN8","created":1371639646}'), +(3, 'provider_2', 'provider_2', '{"monday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"tuesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"wednesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"thursday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"friday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"saturday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"sunday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]}}', NULL, 0, NULL), +(4, 'provider_3', 'provider_3', '{"monday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"tuesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"wednesday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"thursday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"friday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"saturday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]},"sunday":{"start":"09:00","end":"18:00","breaks":[{"start":"11:20","end":"11:30"},{"start":"14:30","end":"15:00"}]}}', NULL, 1, '{"access_token":"ya29.AHES6ZQLXwNinpRgyZ30VP4aNy2MctNkj3fc6oJid8-Gc-TEifJ6WA","token_type":"Bearer","expires_in":3600,"refresh_token":"1\\/bBPokd195S2UX2so9-jclC3E3gpzxgyDjGhJkJxmkHU","created":1371639504}'); -- -- Περιορισμοί για άχρηστους πίνακες diff --git a/src/application/config/config.php b/src/application/config/config.php index 90b302d6..641d7f3e 100644 --- a/src/application/config/config.php +++ b/src/application/config/config.php @@ -15,7 +15,7 @@ | */ require_once dirname(dirname(dirname(__FILE__))) . '/configuration.php'; -$config['base_url'] = SystemConfiguration::$base_url; //'http://localhost/dev/external/Easy!Appointments/trunk/src/WRONG'; +$config['base_url'] = SystemConfiguration::$base_url; /* |-------------------------------------------------------------------------- diff --git a/src/application/controllers/appointments.php b/src/application/controllers/appointments.php index 903e4945..d7524817 100644 --- a/src/application/controllers/appointments.php +++ b/src/application/controllers/appointments.php @@ -30,8 +30,11 @@ class Appointments extends CI_Controller { $manage_mode = TRUE; - $appointment_data = $this->Appointments_Model - ->get_batch(array('hash' => $appointment_hash))[0]; + $results = $this->Appointments_Model + ->get_batch(array('hash' => $appointment_hash)); + + $appointment_data = $results[0]; // Php 5.3 does not support treating a function as an array. + $provider_data = $this->Providers_Model ->get_row($appointment_data['id_users_provider']); $customer_data = $this->Customers_Model @@ -106,22 +109,22 @@ class Appointments extends CI_Controller { $appointment_data['id_users_provider']); if ($google_sync == TRUE) { - $google_token = $this->Providers_Model->get_setting('google_token', - $appointment_data['id_users_provider']); + $google_token = json_decode($this->Providers_Model->get_setting('google_token', + $appointment_data['id_users_provider'])); // Authenticate the token. If it isn't valid, the sync operation cannot // be completed. $this->load->library('google_sync'); + $this->google_sync->refresh_token($google_token->refresh_token); - if ($this->google_sync->authenticate($google_token) === TRUE) { - if ($manage_mode === FALSE) { - // Add appointment to Google Calendar. - $this->google_sync->add_appointment($appointment_data['id']); - } else { - // Update appointment to Google Calendar. - $this->google_sync->update_appointment($appointment_data['id']); - } + if ($post_data['manage_mode'] === FALSE) { + // Add appointment to Google Calendar. + $this->google_sync->add_appointment($appointment_data['id']); + } else { + // Update appointment to Google Calendar. + $this->google_sync->update_appointment($appointment_data['id']); } + } // Load the book success view. diff --git a/src/application/controllers/backend.php b/src/application/controllers/backend.php index ac5ecdca..9d0d93d2 100644 --- a/src/application/controllers/backend.php +++ b/src/application/controllers/backend.php @@ -95,6 +95,10 @@ class Backend extends CI_Controller { * appointment data. * @param array $_POST['customer_data'] (OPTIONAL) Array with the customer * data. + * + * @task Send email notifications to both provider and customer that changes + * have been made to the appointment. + * @task Sync changes with google calendar. */ public function ajax_save_appointment_changes() { try { @@ -102,6 +106,18 @@ class Backend extends CI_Controller { $appointment_data = json_decode(stripcslashes($_POST['appointment_data']), true); $this->load->model('Appointments_Model'); $this->Appointments_Model->add($appointment_data); + + if ($appointment_data['id_google_calendar'] != NULL) { + $this->load->model('Providers_Model'); + $provider_settings = json_decode($this->Providers_Model + ->get_setting('google_token', $appointment_data['id_users_provider'])); + + if ($provider_settings->google_sync == TRUE) { + $this->load->library('Google_Sync'); + $this->google_sync->refresh_token($provider_settings->refresh_token); + $this->google_sync->update_appointment($appointment_data['id']); + } + } } if (isset($_POST['customer_data'])) { @@ -113,11 +129,100 @@ class Backend extends CI_Controller { echo json_encode('SUCCESS'); } catch(Exception $exc) { - $js_error = array( + echo json_encode(array( 'error' => $exc->getMessage() - ); + )); + } + } + + /** + * [AJAX] Delete appointment from the database. + * + * This method deletes an existing appointment from the database. Once this + * action is finished it cannot be undone. Notification emails are send to both + * provider and customer and the delete action is executed to the Google Calendar + * account of the provider, if the "google_sync" setting is enabled. + * + * @param int $_POST['appointment_id'] The appointment id to be deleted. + * + * @task Sync action with GCal. + * @task Send email notifications to provider and customer. + */ + public function ajax_delete_appointment() { + try { + if (!isset($_POST['appointment_id'])) { + throw new Exception('No appointment id provided.'); + } - echo json_encode($js_error); + // :: STORE APPOINTMENT DATA FOR LATER USE IN THIS METHOD + $this->load->model('Appointments_Model'); + $this->load->model('Providers_Model'); + $this->load->model('Customers_Model'); + $this->load->model('Services_Model'); + + $appointment_data = $this->Appointments_Model->get_row($_POST['appointment_id']); + $provider_data = $this->Providers_Model->get_row($appointment_data['id_users_provider']); + $customer_data = $this->Customers_Model->get_row($appointment_data['id_users_customer']); + $service_data = $this->services_Model->get_row($appointment_data['id_services']); + + // :: DELETE APPOINTMENT RECORD FROM DATABASE. + $this->Appointments_Model->delete($_POST['appointment_id']); + + // :: SYNC CHANGE TO GOOGLE CALENDAR + $google_sync = $this->Providers_Model->get_setting('google_sync', + $provider_data['id']); + + if ($google_sync == TRUE) { + $google_token = json_decode($this->Providers_Model->get_setting('google_token', + $provider_data['id'])); + + $this->load->library('Google_Sync'); + $this->google_sync->refresh_token($google_token->refresh_token); + $this->google_sync->delete_appointment($appointment_data['id_google_calendar']); + } + + // :: SEND NOTIFICATION EMAILS TO PROVIDER AND CUSTOMER. + $this->load->library('Notifications'); + $this->notification->send_delete_appointment($appointment_data, $provider_data, + $service_data, $customer_data); + + echo json_encode('SUCCESS'); + + } catch(Exception $exc) { + echo json_encode(array( + 'error' => $exc->getMessage() + )); + } + } + + /** + * [AJAX] Disable a providers sync setting. + * + * This method deletes the "google_sync" and "google_token" settings from the + * database. After that the provider's appointments will be no longer synced + * with google calendar. + * + * @param string $_POST['provider_id'] The selected provider record id. + */ + public function ajax_disable_provider_sync() { + try { + if (!isset($_POST['provider_id'])) { + throw new Exception('Provider id not specified.'); + } + + $this->load->model('Providers_Model'); + + $this->Providers_Model->set_setting('google_sync', FALSE, + $_POST['provider_id']); + $this->Providers_Model->set_setting('google_token', NULL, + $_POST['provider_id']); + + echo json_encode('SUCCESS'); + + } catch(Exception $exc) { + echo json_encode(array( + 'error' => $exc->getMessage() + )); } } } diff --git a/src/application/controllers/google.php b/src/application/controllers/google.php index c0abd7e3..abec252b 100644 --- a/src/application/controllers/google.php +++ b/src/application/controllers/google.php @@ -12,9 +12,15 @@ class Google extends CI_Controller { * made. */ public function oauth($provider_id) { + // Store the provider id for use on the callback function. + if (!isset($_SESSION)) { + @session_start(); + } + $_SESSION['oauth_provider_id'] = $provider_id; + + // Redirect browser to google user content page. $this->load->library('Google_Sync'); - - // @task Create auth link and redirect browser window. + header('Location: ' . $this->google_sync->get_auth_url()); } /** @@ -29,10 +35,33 @@ class Google extends CI_Controller { * 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-e!a/google/oauth_callback" should be - * included in the allowed redirect urls. + * included in an allowed redirect url. */ public function oauth_callback() { - // @task Store refresh token. + if (isset($_GET['code'])) { + $this->load->library('Google_Sync'); + $token = $this->google_sync->authenticate($_GET['code']); + + // Store the token into the database for future reference. + if (!isset($_SESSION)) { + @session_start(); + } + + if (isset($_SESSION['oauth_provider_id'])) { + $this->load->model('Providers_Model'); + + $this->Providers_Model->set_setting('google_sync', TRUE, + $_SESSION['oauth_provider_id']); + $this->Providers_Model->set_setting('google_token', $token, + $_SESSION['oauth_provider_id']); + + } else { + echo '

Sync provider id not specified!

'; + } + + } else { + echo '

Authorization Failed!

'; + } } } diff --git a/src/application/libraries/google_sync.php b/src/application/libraries/google_sync.php index a5f3b364..58cfe04c 100644 --- a/src/application/libraries/google_sync.php +++ b/src/application/libraries/google_sync.php @@ -12,6 +12,7 @@ require_once dirname(dirname(dirname(__FILE__))) . '/configuration.php'; * and the Easy!Appointments system. */ class Google_Sync { + private $CI; private $client; private $service; @@ -21,10 +22,12 @@ class Google_Sync { * This method initializes the Google client class and the Calendar service * class so that they can be used by the other methods. */ - public function __construct() { - session_start(); + public function __construct() { + $this->CI =& get_instance(); - $CI =& get_instance(); + if (!isset($_SESSION)) { + @session_start(); + } // Initialize google client and calendar service. $this->client = new Google_Client(); @@ -32,13 +35,40 @@ class Google_Sync { $this->client->setClientId(SystemConfiguration::$google_client_id); $this->client->setClientSecret(SystemConfiguration::$google_client_secret); $this->client->setDeveloperKey(SystemConfiguration::$google_api_key); - $this->client->setRedirectUri('http://localhost/oauth_callback'); + $this->client->setRedirectUri($this->CI->config->item('base_url') . 'google/oauth_callback'); $this->service = new Google_CalendarService($this->client); } + /** + * Get Google OAuth authorization url. + * + * This url must be used to redirect the user to the Google user consent page, + * where the user grants access to his data for the Easy!Appointments app. + */ + public function get_auth_url() { + // "max_auth_age" is needed because the user needs to always log in + // and not use an existing session. + return $this->client->createAuthUrl() . '&max_auth_age=0'; + } + /** * Authenticate the Google API usage. * + * When the user grants consent for his data usage, google is going to redirect + * the browser back to the given redirect url. There a authentication code is + * provided. Using this code, we can authenticate the API usage and store the + * token information to the database. + * + * @see Google Controller + */ + public function authenticate($auth_code) { + $this->client->authenticate($auth_code); + return $this->client->getAccessToken(); + } + + /** + * Refresh the Google Client access token. + * * This method must be executed every time we need to make actions on a * provider's Google Calendar account. A new token is necessary and the * only way to get it is to use the stored refresh token that was provided @@ -48,10 +78,9 @@ class Google_Sync { * @param string $refresh_token The provider's refresh token. This value is * stored in the database and used every time we need to make actions to his * Google Caledar account. - * @return bool Returns the authenticate operation result. */ - public function authenticate($refresh_token) { - + public function refresh_token($refresh_token) { + $this->client->refreshToken($refresh_token); } /** @@ -70,23 +99,24 @@ class Google_Sync { * @param int $appointment_id The record id of the appointment that is going to * be added to the database. * @return Google_Event Returns the Google_Event class object. + * + * @task This library should not use the models. The data must be provided from + * the controllers (same for notification library). */ public function add_appointment($appointment_id) { - $CI =& get_instance(); + $this->CI->load->model('Appointments_Model'); + $this->CI->load->model('Providers_Model'); + $this->CI->load->model('Services_Model'); + $this->CI->load->model('Customers_Model'); + $this->CI->load->model('Settings_Model'); - $CI->load->model('Appointments_Model'); - $CI->load->model('Service_Model'); - $CI->load->model('Provider_Model'); - $CI->load->model('Customers_Model'); - $CI->load->model('Settings_Model'); + $appointment_data = $this->CI->Appointments_Model->get_row($appointment_id); + $provider_data = $this->CI->Providers_Model->get_row($appointment_data['id_users_provider']); + $customer_data = $this->CI->Customers_Model->get_row($appointment_data['id_users_customer']); + $service_data = $this->CI->Services_Model->get_row($appointment_data['id_services']); + $company_name = $this->CI->Settings_Model->get_setting('company_name'); - $appointment_data = $CI->Appointments_Model->get_row($appointment_id); - $provider_data = $CI->Providers_Model->get_row($appointment_data['id_users_provider']); - $customer_data = $CI->Customers_Model->get_row($appointment_data['id_users_customer']); - $service_data = $CI->Services_Model->get_row($appointment_data['id_services']); - $company_name = $CI->Settings_Model->get_setting('company_name'); - - $CI->load->helper('general'); + $this->CI->load->helper('general'); $event = new Google_Event(); $event->setSummary($service_data['name']); @@ -118,25 +148,44 @@ class Google_Sync { // Add the new event to the "primary" calendar. $created_event = $this->service->events->insert('primary', $event); + // Set the Google Calendar event id to the E!A database record. + $appointment_data['id_google_calendar'] = $created_event['id']; + $this->CI->Appointments_Model->add($appointment_data); + return $created_event; } /** * Update an existing appointment that is already synced with Google Calendar. * - * @param int $appointment_id + * @param int $appointment_id The appointment record id. */ - public function update_appointment($appointment_id) { + public function update_appointment($appointment_data, $provider_data, + $service_data, $customer_data) { + $this->CI->load->model('Appointments_Model'); + $this->CI->load->model('Services_Model'); + $this->CI->load->model('Providers_Model'); + $this->CI->load->model('Customers_Model'); + $this->CI->load->model('Settings_Model'); + + $appointment_data = $this->CI->Appointments_Model->get_row($appointment_id); + $provider_data = $this->CI->Providers_Model->get_row($appointment_data['id_users_provider']); + $customer_data = $this->CI->Customers_Model->get_row($appointment_data['id_users_customer']); + $service_data = $this->CI->Services_Model->get_row($appointment_data['id_services']); + $company_name = $this->CI->Settings_Model->get_setting('company_name'); + + $this->CI->load->helper('general'); } /** * Delete an existing appointment from Google Calendar. * - * @param type $appointment_id + * @param string $google_calendar_id The Google Calendar event id to + * be deleted. */ - public function delete_appointment($appointment_id) { - + public function delete_appointment($google_calendar_id) { + $this->service->events->delete('primary', $google_calendar_id); } } diff --git a/src/application/libraries/notifications.php b/src/application/libraries/notifications.php index 2e636dc8..1d558cda 100644 --- a/src/application/libraries/notifications.php +++ b/src/application/libraries/notifications.php @@ -201,6 +201,19 @@ class Notifications { return TRUE; } + + /** + * Send delete appointment notification. + * + * This method should be called after the appointment has been deleted. + * + * IMPORTANT! This method's arguments should be taken + * from database before the appointment record is deleted. + */ + public function send_delete_appointment($appointment_data, $provider_data, + $service_data, $customer_data) { + // @task Implement sending delete appointment notification. + } } diff --git a/src/application/models/providers_model.php b/src/application/models/providers_model.php index 38c4232e..0086bca7 100644 --- a/src/application/models/providers_model.php +++ b/src/application/models/providers_model.php @@ -1,4 +1,4 @@ -db->get()->result_array(); + // :: GET PROVIDER SERVICES // Return also an array with the services that each provider can provide // to the customers. foreach($providers as &$provider) { @@ -115,6 +116,16 @@ class Providers_Model extends CI_Model { } } + // :: GET PROVIDER SETTINGS + foreach($providers as &$provider) { + $this->db + ->select('*') + ->from('ea_user_settings') + ->where('id_users', $provider['id']); + $provider['settings'] = $this->db->get()->row_array(); + unset($provider['settings']['id_users']); // Do not need it. + } + return $providers; } diff --git a/src/application/views/appointments/book.php b/src/application/views/appointments/book.php index 05956e81..3fb72a04 100644 --- a/src/application/views/appointments/book.php +++ b/src/application/views/appointments/book.php @@ -1,6 +1,7 @@ + Book Appointment | <?php echo $company_name; ?> -
- +
+
+ + + +
- - - - - +
+ + + +
diff --git a/src/assets/js/backend_calendar.js b/src/assets/js/backend_calendar.js index 093591b6..25273f6b 100644 --- a/src/assets/js/backend_calendar.js +++ b/src/assets/js/backend_calendar.js @@ -60,8 +60,12 @@ var BackendCalendar = { // :: FILL THE SELECT ELEMENTS OF THE PAGE var optgroupHtml = ''; $.each(GlobalVariables.availableProviders, function(index, provider) { + var hasGoogleSync = (provider['settings']['google_sync'] === '1') + ? 'true' : 'false'; + optgroupHtml += ''; }); optgroupHtml += ''; @@ -112,6 +116,20 @@ var BackendCalendar = { } else { $('#google-sync, #enable-sync, #insert-unavailable-period') .prop('disabled', false); + // If the user has already the sync enabled then apply the proper + // style changes. + if ($('#select-filter-item option:selected').attr('google-sync') + === 'true') { + $('#enable-sync').addClass('btn-success enabled'); + $('#enable-sync i').addClass('icon-white'); + $('#enable-sync span').text('Disable Sync'); + $('#google-sync').prop('disabled', false); + } else { + $('#enable-sync').removeClass('btn-success enabled'); + $('#enable-sync i').removeClass('icon-white'); + $('#enable-sync span').text('Enable Sync'); + $('#google-sync').prop('disabled', true); + } } }); @@ -201,6 +219,52 @@ var BackendCalendar = { $('#manage-appointment').modal('show'); }); + /** + * Event: Delete Popover Button "Click" + * + * Displays a prompt on whether the user wants the appoinmtent to be + * deleted. If he confirms the deletion then an ajax call is made to + * the server and deletes the appointment from the database. + */ + $(document).on('click', '.delete-popover', function() { + $(this).parents().eq(2).remove(); // Hide the popover + + var messageButtons = { + 'Delete' : function() { + var postUrl = GlobalVariables.baseUrl + 'backend/ajax_delete_appointment'; + + var postData = { + 'appointment_id' : BackendCalendar.lastFocusedEventData.data['id'] + }; + + $.post(postUrl, postData, function(response) { + ///////////////////////////////////////////////////////// + console.log('Delete Appointment Response :', response); + ///////////////////////////////////////////////////////// + + if (response.error) { + GeneralFunctions.displayMessageBox('Delete Appointment Error', + 'An unexpected error occured during the deletion of the ' + + 'appointment. Please try again.'); + return; + } + + // Close dialog and refresh calendar events. + $('#message_box').dialog('close'); + $('#select-filter-item').trigger('change'); + + }, 'json'); + }, + 'Cancel' : function() { + $('#message_box').dialog('close'); + } + }; + + GeneralFunctions.displayMessageBox('Delete Appointment', 'Are you sure ' + + 'that you want to delete this appointment? This action cannot ' + + 'be undone.', messageButtons); + }); + /** * Event: Manage Appointments Dialog Cancel Button "Click" * @@ -289,30 +353,60 @@ var BackendCalendar = { }); /** - * Event: Enable Synchronization Button "Click" + * Event: Enable - Disable Synchronization Button "Click" * * When the user clicks on the "Enable Sync" button, a popup should appear * that is going to follow the web server authorization flow of OAuth. - * - * @task Check whether the selected provider has already enabled the sync - * or not. */ $('#enable-sync').click(function() { - var authUrl = GlobalVariables.baseUrl + 'google/oauth/' - + $('#select-filter-item').val(); - var redirectUrl = GlobalVariables.baseUrl + 'google/oauth_callback'; - - var windowHandle = window.open(authUrl, 'Authorize Easy!Appointments', - 'width=800, height=600'); - - var authInterval = window.setInterval(function() { - if (windowHandle.document.URL.indexOf(redirectUrl) !== -1) { - // The user has granted access to his data. - windowHandle.close(); - window.clearInterval(authInterval); - $('#enable-sync').addClass('btn-success'); - } - }, 100); + if ($('#enable-sync').hasClass('enabled') === false) { + // :: ENABLE SYNCHRONIZATION FOR SELECTED PROVIDER + var authUrl = GlobalVariables.baseUrl + 'google/oauth/' + + $('#select-filter-item').val(); + + var redirectUrl = GlobalVariables.baseUrl + 'google/oauth_callback'; + + var windowHandle = window.open(authUrl, 'Authorize Easy!Appointments', + 'width=800, height=600'); + + var authInterval = window.setInterval(function() { + // When the browser redirects to the google user consent page the + // "window.document" variable becomes "undefined" and when it comes + // back to the redirect url it changes back. So check whether the + // variable is undefined to avoid javascript errors. + if (windowHandle.document !== undefined) { + if (windowHandle.document.URL.indexOf(redirectUrl) !== -1) { + // The user has granted access to his data. + windowHandle.close(); + window.clearInterval(authInterval); + $('#enable-sync').addClass('btn-success enabled'); + $('#enable-sync i').addClass('icon-white'); + $('#enable-sync span').text('Disable Sync'); + $('#google-sync').prop('disabled', false); + } + } + }, 100); + + } else { + // :: DISABLE SYNCHRONIZATION FOR SELECTED PROVIDER + // Update page elements and make an ajax call to remove the google + // sync setting of the selected provider. + $.each(GlobalVariables.availableProviders, function(index, provider) { + if (provider['id'] == $('#select-filter-item').val()) { + provider['settings']['google_sync'] = '0'; + provider['settings']['google_token'] = null; + + BackendCalendar.disableProviderSync(provider['id']); + + $('#enable-sync').removeClass('btn-success enabled'); + $('#enable-sync i').removeClass('icon-white'); + $('#enable-sync span').text('Enable Sync'); + $('#google-sync').prop('disabled', true); + + return; + } + }); + } }); }, @@ -556,6 +650,7 @@ var BackendCalendar = { + '
' + '
' + '' + + '' + '' + '
'; @@ -674,5 +769,33 @@ var BackendCalendar = { $('.fv-events').each(function(index, eventHandle) { $(eventHandle).popover(); }); + }, + + /** + * This method disables the google synchronization for a specific provider. + * + * @param {int} providerId The selected provider record id. + */ + disableProviderSync: function(providerId) { + // Make an ajax call to the server in order to disable the setting + // from the database. + var postUrl = GlobalVariables.baseUrl + 'backend/ajax_disable_provider_sync'; + + var postData = { + 'provider_id' : providerId + }; + + $.post(postUrl, postData, function(response) { + //////////////////////////////////////////////////////////// + //console.log('Disable Provider Sync Response :', response); + //////////////////////////////////////////////////////////// + + if (response.error) { + GeneralFunctions.displayMessageBox('Disable Sync Error', 'An unexpected ' + + 'error occured during the disable provider sync operation : ' + + response.error); + } + + }, 'json'); } }; \ No newline at end of file