diff --git a/application/config/constants.php b/application/config/constants.php index 16c275c0..400a0144 100644 --- a/application/config/constants.php +++ b/application/config/constants.php @@ -71,6 +71,7 @@ define('PRIV_SERVICES', 'services'); define('PRIV_USERS', 'users'); define('PRIV_SYSTEM_SETTINGS', 'system_settings'); define('PRIV_USER_SETTINGS', 'user_settings'); +define('PRIV_WEBHOOKS', 'webhooks'); define('DATE_FORMAT_DMY', 'DMY'); define('DATE_FORMAT_MDY', 'MDY'); @@ -91,5 +92,33 @@ define('AVAILABILITIES_TYPE_FIXED', 'fixed'); define('EVENT_MINIMUM_DURATION', 5); // Minutes +define('DEFAULT_COMPANY_COLOR', '#ffffff'); + +/* +|-------------------------------------------------------------------------- +| Webhook Actions +|-------------------------------------------------------------------------- +| +| External application endpoints can subscribe to these webhook actions. +| +*/ + +define('WEBHOOK_APPOINTMENT_SAVE', 'appointment_save'); +define('WEBHOOK_APPOINTMENT_DELETE', 'appointment_delete'); +define('WEBHOOK_UNAVAILABILITY_SAVE', 'unavailability_save'); +define('WEBHOOK_UNAVAILABILITY_DELETE', 'unavailability_delete'); +define('WEBHOOK_CUSTOMER_SAVE', 'customer_save'); +define('WEBHOOK_CUSTOMER_DELETE', 'customer_delete'); +define('WEBHOOK_SERVICE_SAVE', 'service_save'); +define('WEBHOOK_SERVICE_DELETE', 'service_delete'); +define('WEBHOOK_CATEGORY_SAVE', 'category_save'); +define('WEBHOOK_CATEGORY_DELETE', 'category_delete'); +define('WEBHOOK_PROVIDER_SAVE', 'provider_save'); +define('WEBHOOK_PROVIDER_DELETE', 'provider_delete'); +define('WEBHOOK_SECRETARY_SAVE', 'secretary_save'); +define('WEBHOOK_SECRETARY_DELETE', 'secretary_delete'); +define('WEBHOOK_ADMIN_SAVE', 'admin_save'); +define('WEBHOOK_ADMIN_DELETE', 'admin_delete'); + /* End of file constants.php */ /* Location: ./application/config/constants.php */ diff --git a/application/config/routes.php b/application/config/routes.php index a8c3a4f6..5249eee4 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -122,6 +122,8 @@ route_api_resource($route, 'services', 'api/v1/'); route_api_resource($route, 'unavailabilities', 'api/v1/'); +route_api_resource($route, 'webhooks', 'api/v1/'); + $route['api/v1/settings']['get'] = 'api/v1/settings_api_v1/index'; $route['api/v1/settings/(:any)']['get'] = 'api/v1/settings_api_v1/show/$1'; diff --git a/application/controllers/Admins.php b/application/controllers/Admins.php index 4032d47d..e482574f 100644 --- a/application/controllers/Admins.php +++ b/application/controllers/Admins.php @@ -31,6 +31,7 @@ class Admins extends EA_Controller { $this->load->library('accounts'); $this->load->library('timezones'); + $this->load->library('webhooks_client'); } /** @@ -145,6 +146,10 @@ class Admins extends EA_Controller { ]); $admin_id = $this->admins_model->save($admin); + + $admin = $this->admins_model->find($admin_id); + + $this->webhooks_client->trigger(WEBHOOK_ADMIN_SAVE, $admin); json_response([ 'success' => TRUE, @@ -197,6 +202,10 @@ class Admins extends EA_Controller { $admin_id = $this->admins_model->save($admin); + $admin = $this->admins_model->find($admin_id); + + $this->webhooks_client->trigger(WEBHOOK_ADMIN_SAVE, $admin); + json_response([ 'success' => TRUE, 'id' => $admin_id @@ -222,8 +231,12 @@ class Admins extends EA_Controller { $admin_id = request('admin_id'); + $admin = $this->admins_model->find($admin_id); + $this->admins_model->delete($admin_id); + $this->webhooks_client->trigger(WEBHOOK_ADMIN_DELETE, $admin); + json_response([ 'success' => TRUE, ]); diff --git a/application/controllers/Appointments.php b/application/controllers/Appointments.php index 0630e6f9..852e6be5 100644 --- a/application/controllers/Appointments.php +++ b/application/controllers/Appointments.php @@ -15,7 +15,7 @@ * Appointments controller. * * Handles the appointments related operations. - * + * * Notice: This file used to have the booking page related code which since v1.5 has now moved to the Booking.php * controller for improved consistency. * @@ -34,13 +34,14 @@ class Appointments extends EA_Controller { $this->load->library('accounts'); $this->load->library('timezones'); + $this->load->library('webhooks_client'); } /** - * Support backwards compatibility for appointment links that still point to this URL. - * + * Support backwards compatibility for appointment links that still point to this URL. + * * @param string $appointment_hash - * + * * @deprecated Since 1.5 */ public function index(string $appointment_hash = '') @@ -65,7 +66,7 @@ class Appointments extends EA_Controller { $order_by = 'name ASC'; $limit = request('limit', 1000); - + $offset = 0; $appointments = $this->appointments_model->search($keyword, $limit, $offset, $order_by); @@ -106,6 +107,10 @@ class Appointments extends EA_Controller { $appointment_id = $this->appointments_model->save($appointment); + $appointment = $this->appointments_model->find($appointment); + + $this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_SAVE, $appointment); + json_response([ 'success' => TRUE, 'id' => $appointment_id @@ -170,9 +175,13 @@ class Appointments extends EA_Controller { } $appointment_id = request('appointment_id'); + + $appointment = $this->appointments_model->find($appointment_id); $this->appointments_model->delete($appointment_id); + $this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_DELETE, $appointment); + json_response([ 'success' => TRUE, ]); diff --git a/application/controllers/Booking.php b/application/controllers/Booking.php index 185fa15d..fdf1caa8 100755 --- a/application/controllers/Booking.php +++ b/application/controllers/Booking.php @@ -43,6 +43,7 @@ class Booking extends EA_Controller { $this->load->library('synchronization'); $this->load->library('notifications'); $this->load->library('availability'); + $this->load->library('webhooks_client'); $this->load->driver('cache', ['adapter' => 'file']); } @@ -555,6 +556,8 @@ class Booking extends EA_Controller { $this->notifications->notify_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode); + $this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_SAVE, $appointment); + $response = [ 'appointment_id' => $appointment['id'], 'appointment_hash' => $appointment['hash'] diff --git a/application/controllers/Booking_cancellation.php b/application/controllers/Booking_cancellation.php index 33e1aeee..c68c51d0 100755 --- a/application/controllers/Booking_cancellation.php +++ b/application/controllers/Booking_cancellation.php @@ -33,6 +33,7 @@ class Booking_cancellation extends EA_Controller { $this->load->library('synchronization'); $this->load->library('notifications'); + $this->load->library('webhooks_client'); } /** @@ -95,6 +96,9 @@ class Booking_cancellation extends EA_Controller { $this->synchronization->sync_appointment_deleted($appointment, $provider); $this->notifications->notify_appointment_deleted($appointment, $service, $provider, $customer, $settings); + + $this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_DELETE, $appointment); + } catch (Throwable $e) { diff --git a/application/controllers/Calendar.php b/application/controllers/Calendar.php index 7847758c..bfa79d60 100644 --- a/application/controllers/Calendar.php +++ b/application/controllers/Calendar.php @@ -38,6 +38,7 @@ class Calendar extends EA_Controller { $this->load->library('notifications'); $this->load->library('synchronization'); $this->load->library('timezones'); + $this->load->library('webhooks_client'); } /** @@ -272,6 +273,8 @@ class Calendar extends EA_Controller { $this->notifications->notify_appointment_saved($appointment, $service, $provider, $customer, $settings, $manage_mode); + $this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_SAVE, $appointment); + json_response([ 'success' => TRUE, ]); @@ -326,6 +329,8 @@ class Calendar extends EA_Controller { $this->synchronization->sync_appointment_deleted($appointment, $provider); + $this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_DELETE, $appointment); + json_response([ 'success' => TRUE, ]); @@ -357,50 +362,13 @@ class Calendar extends EA_Controller { $provider = $this->providers_model->find($unavailability['id_users_provider']); - // Add appointment - - - - $unavailability['id'] = $this->unavailabilities_model->save($unavailability); + $unavailability_id = $this->unavailabilities_model->save($unavailability); - $unavailability = $this->unavailabilities_model->find($unavailability['id']); // fetch all inserted data + $unavailability = $this->unavailabilities_model->find($unavailability_id); - // Google Sync - try - { - $google_sync = $this->providers_model->get_setting($unavailability['id_users_provider'], 'google_sync'); + $this->synchronization->sync_unavailability_saved($unavailability, $provider); - if ($google_sync) - { - $google_token = json_decode($this->providers_model->get_setting($unavailability['id_users_provider'], 'google_token')); - - $this->google_sync->refresh_token($google_token->refresh_token); - - if ($unavailability['id_google_calendar'] == NULL) - { - $google_event = $this->google_sync->add_unavailability($provider, $unavailability); - $unavailability['id_google_calendar'] = $google_event->id; - - $this->unavailabilities_model->only($unavailability, [ - 'start_datetime', - 'end_datetime', - 'is_unavailability', - 'notes', - 'id_users_provider' - ]); - - $this->unavailabilities_model->save($unavailability); - } - else - { - $this->google_sync->update_unavailability($provider, $unavailability); - } - } - } - catch (Throwable $e) - { - $warnings[] = $e; - } + $this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_SAVE, $unavailability); json_response([ 'success' => TRUE, @@ -431,30 +399,14 @@ class Calendar extends EA_Controller { $provider = $this->providers_model->find($unavailability['id_users_provider']); - $this->appointments_model->delete($unavailability['id']); + $this->unavailabilities_model->delete($unavailability_id); - // Google Sync - try - { - $google_sync = $this->providers_model->get_setting($provider['id'], 'google_sync'); + $this->synchronization->sync_appointment_deleted($unavailability, $provider); - if ($google_sync == TRUE) - { - $google_token = json_decode($this->providers_model->get_setting($provider['id'], 'google_token')); - - $this->google_sync->refresh_token($google_token->refresh_token); - - $this->google_sync->delete_unavailability($provider, $unavailability['id_google_calendar']); - } - } - catch (Throwable $e) - { - $warnings[] = $e; - } + $this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_DELETE, $unavailability); json_response([ 'success' => TRUE, - 'warnings' => $warnings ?? [] ]); } catch (Throwable $e) @@ -626,7 +578,7 @@ class Calendar extends EA_Controller { } $record_id = request('record_id'); - + $filter_type = request('filter_type'); if ( ! $filter_type && $record_id !== FILTER_TYPE_ALL) @@ -648,7 +600,9 @@ class Calendar extends EA_Controller { elseif ($filter_type === FILTER_TYPE_SERVICE) { $where_id = 'id_services'; - } else { + } + else + { $where_id = $record_id; } diff --git a/application/controllers/Categories.php b/application/controllers/Categories.php index f098a1c7..1bc4cc76 100644 --- a/application/controllers/Categories.php +++ b/application/controllers/Categories.php @@ -31,6 +31,7 @@ class Categories extends EA_Controller { $this->load->library('accounts'); $this->load->library('timezones'); + $this->load->library('webhooks_client'); } /** @@ -44,7 +45,7 @@ class Categories extends EA_Controller { session(['dest_url' => site_url('categories')]); $user_id = session('user_id'); - + if (cannot('view', PRIV_SERVICES)) { if ($user_id) @@ -116,7 +117,7 @@ class Categories extends EA_Controller { { abort(403, 'Forbidden'); } - + $category = request('category'); $this->categories_model->only($category, [ @@ -126,6 +127,10 @@ class Categories extends EA_Controller { $category_id = $this->categories_model->save($category); + $category = $this->categories_model->find($category_id); + + $this->webhooks_client->trigger(WEBHOOK_CATEGORY_SAVE, $category); + json_response([ 'success' => TRUE, 'id' => $category_id @@ -159,6 +164,10 @@ class Categories extends EA_Controller { $category_id = $this->categories_model->save($category); + $category = $this->categories_model->find($category_id); + + $this->webhooks_client->trigger(WEBHOOK_CATEGORY_SAVE, $category); + json_response([ 'success' => TRUE, 'id' => $category_id @@ -184,8 +193,12 @@ class Categories extends EA_Controller { $category_id = request('category_id'); + $category = $this->categories_model->find($category_id); + $this->categories_model->delete($category_id); + $this->webhooks_client->trigger(WEBHOOK_CATEGORY_DELETE, $category); + json_response([ 'success' => TRUE, ]); diff --git a/application/controllers/Console.php b/application/controllers/Console.php index c7934133..705702e4 100644 --- a/application/controllers/Console.php +++ b/application/controllers/Console.php @@ -139,7 +139,7 @@ class Console extends EA_Controller { continue; } - Google::sync($provider['id']); + Google::sync((string)$provider['id']); } } diff --git a/application/controllers/Customers.php b/application/controllers/Customers.php index 232c0f9d..a3c75b8d 100644 --- a/application/controllers/Customers.php +++ b/application/controllers/Customers.php @@ -34,6 +34,7 @@ class Customers extends EA_Controller { $this->load->library('accounts'); $this->load->library('permissions'); $this->load->library('timezones'); + $this->load->library('webhook_client'); } /** @@ -185,6 +186,10 @@ class Customers extends EA_Controller { $customer_id = $this->customers_model->save($customer); + $customer = $this->customers_model->find($customer_id); + + $this->webhooks_client->trigger(WEBHOOK_CUSTOMER_SAVE, $customer); + json_response([ 'success' => TRUE, 'id' => $customer_id @@ -219,6 +224,10 @@ class Customers extends EA_Controller { $customer_id = $this->customers_model->save($customer); + $customer = $this->customers_model->find($customer_id); + + $this->webhooks_client->trigger(WEBHOOK_CUSTOMER_SAVE, $customer); + json_response([ 'success' => TRUE, 'id' => $customer_id @@ -251,8 +260,12 @@ class Customers extends EA_Controller { abort(403, 'Forbidden'); } + $customer = $this->customers_model->find($customer_id); + $this->customers_model->delete($customer_id); + $this->webhooks_client->trigger(WEBHOOK_CUSTOMER_DELETE, $customer); + json_response([ 'success' => TRUE, ]); diff --git a/application/controllers/Providers.php b/application/controllers/Providers.php index d9e7dfbb..21cc8960 100644 --- a/application/controllers/Providers.php +++ b/application/controllers/Providers.php @@ -32,6 +32,7 @@ class Providers extends EA_Controller { $this->load->library('accounts'); $this->load->library('timezones'); + $this->load->library('webhooks_client'); } /** @@ -137,6 +138,10 @@ class Providers extends EA_Controller { $provider_id = $this->providers_model->save($provider); + $provider = $this->providers_model->find($provider_id); + + $this->webhooks_client->trigger(WEBHOOK_PROVIDER_SAVE, $provider); + json_response([ 'success' => TRUE, 'id' => $provider_id @@ -164,6 +169,10 @@ class Providers extends EA_Controller { $provider_id = $this->providers_model->save($provider); + $provider = $this->providers_model->find($provider_id); + + $this->webhooks_client->trigger(WEBHOOK_PROVIDER_SAVE, $provider); + json_response([ 'success' => TRUE, 'id' => $provider_id @@ -189,8 +198,12 @@ class Providers extends EA_Controller { $provider_id = request('provider_id'); + $provider = $this->providers_model->find($provider_id); + $this->providers_model->delete($provider_id); + $this->webhooks_client->trigger(WEBHOOK_PROVIDER_DELETE, $provider); + json_response([ 'success' => TRUE, ]); diff --git a/application/controllers/Secretaries.php b/application/controllers/Secretaries.php index 7b43cd5d..9c367ba8 100644 --- a/application/controllers/Secretaries.php +++ b/application/controllers/Secretaries.php @@ -32,6 +32,7 @@ class Secretaries extends EA_Controller { $this->load->library('accounts'); $this->load->library('timezones'); + $this->load->library('webhooks_client'); } /** @@ -137,6 +138,10 @@ class Secretaries extends EA_Controller { $secretary_id = $this->secretaries_model->save($secretary); + $secretary = $this->secretaries_model->find($secretary_id); + + $this->webhooks_client->trigger(WEBHOOK_SECRETARY_SAVE, $secretary); + json_response([ 'success' => TRUE, 'id' => $secretary_id @@ -164,6 +169,10 @@ class Secretaries extends EA_Controller { $secretary_id = $this->secretaries_model->save($secretary); + $secretary = $this->secretaries_model->find($secretary_id); + + $this->webhooks_client->trigger(WEBHOOK_SECRETARY_SAVE, $secretary); + json_response([ 'success' => TRUE, 'id' => $secretary_id @@ -189,8 +198,12 @@ class Secretaries extends EA_Controller { $secretary_id = request('secretary_id'); + $secretary = $this->secretaries_model->find($secretary_id); + $this->secretaries_model->delete($secretary_id); + $this->webhooks_client->trigger(WEBHOOK_SECRETARY_DELETE, $secretary); + json_response([ 'success' => TRUE, ]); diff --git a/application/controllers/Services.php b/application/controllers/Services.php index 0abacf07..e2dd21d1 100644 --- a/application/controllers/Services.php +++ b/application/controllers/Services.php @@ -31,6 +31,7 @@ class Services extends EA_Controller { $this->load->library('accounts'); $this->load->library('timezones'); + $this->load->library('webhooks_client'); } /** @@ -120,9 +121,13 @@ class Services extends EA_Controller { $service = request('service'); - $service['id_categories'] = $service['id_categories'] ?: null; + $service['id_categories'] = $service['id_categories'] ?: NULL; $service_id = $this->services_model->save($service); + + $service = $this->services_model->find($service_id); + + $this->webhooks_client->trigger(WEBHOOK_SERVICE_SAVE, $service); json_response([ 'success' => TRUE, @@ -149,10 +154,14 @@ class Services extends EA_Controller { $service = request('service'); - $service['id_categories'] = $service['id_categories'] ?: null; + $service['id_categories'] = $service['id_categories'] ?: NULL; $service_id = $this->services_model->save($service); + $service = $this->services_model->find($service_id); + + $this->webhooks_client->trigger(WEBHOOK_SERVICE_SAVE, $service); + json_response([ 'success' => TRUE, 'id' => $service_id @@ -178,8 +187,12 @@ class Services extends EA_Controller { $service_id = request('service_id'); + $service = $this->services_model->find($service_id); + $this->services_model->delete($service_id); + $this->webhooks_client->trigger(WEBHOOK_SERVICE_DELETE, $service); + json_response([ 'success' => TRUE, ]); diff --git a/application/controllers/Unavailabilities.php b/application/controllers/Unavailabilities.php index 078c415b..5ea43484 100644 --- a/application/controllers/Unavailabilities.php +++ b/application/controllers/Unavailabilities.php @@ -31,6 +31,7 @@ class Unavailabilities extends EA_Controller { $this->load->library('accounts'); $this->load->library('timezones'); + $this->load->library('webhooks_client'); } /** @@ -50,7 +51,7 @@ class Unavailabilities extends EA_Controller { $order_by = 'name ASC'; $limit = request('limit', 1000); - + $offset = 0; $unavailabilities = $this->unavailabilities_model->search($keyword, $limit, $offset, $order_by); @@ -75,10 +76,18 @@ class Unavailabilities extends EA_Controller { abort(403, 'Forbidden'); } - $unavailability = json_decode(request('unavailability'), TRUE); + $unavailability = request('unavailability'); $unavailability_id = $this->unavailabilities_model->save($unavailability); + $unavailability = $this->unavailabilities_model->find($unavailability_id); + + $provider = $this->providers_model->find($unavailability['id_users_provider']); + + $this->synchronization->sync_unavailability_saved($unavailability, $provider); + + $this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_SAVE, $unavailability); + json_response([ 'success' => TRUE, 'id' => $unavailability_id @@ -102,10 +111,18 @@ class Unavailabilities extends EA_Controller { abort(403, 'Forbidden'); } - $unavailability = json_decode(request('unavailability'), TRUE); + $unavailability = request('unavailability'); $unavailability_id = $this->unavailabilities_model->save($unavailability); + $unavailability = $this->unavailabilities_model->find($unavailability_id); + + $provider = $this->providers_model->find($unavailability['id_users_provider']); + + $this->synchronization->sync_unavailability_saved($unavailability, $provider); + + $this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_SAVE, $unavailability); + json_response([ 'success' => TRUE, 'id' => $unavailability_id @@ -131,8 +148,12 @@ class Unavailabilities extends EA_Controller { $unavailability_id = request('unavailability_id'); + $unavailability = $this->unavailabilities_model->find($unavailability_id); + $this->unavailabilities_model->delete($unavailability_id); + $this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_DELETE, $unavailability); + json_response([ 'success' => TRUE, ]); diff --git a/application/controllers/Webhooks.php b/application/controllers/Webhooks.php new file mode 100644 index 00000000..d2d37e2e --- /dev/null +++ b/application/controllers/Webhooks.php @@ -0,0 +1,248 @@ + + * @copyright Copyright (c) Alex Tselegidis + * @license https://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link https://easyappointments.org + * @since v1.0.0 + * ---------------------------------------------------------------------------- */ + +/** + * Webhooks controller. + * + * Handles the webhooks related operations. + * + * @package Controllers + */ +class Webhooks extends EA_Controller { + /** + * Webhooks constructor. + */ + public function __construct() + { + parent::__construct(); + + $this->load->model('webhooks_model'); + $this->load->model('roles_model'); + + $this->load->library('accounts'); + $this->load->library('timezones'); + } + + /** + * Render the backend webhooks page. + * + * On this page admin users will be able to manage webhooks, which are eventually selected by customers during the + * booking process. + */ + public function index() + { + session(['dest_url' => site_url('webhooks')]); + + $user_id = session('user_id'); + + if (cannot('view', PRIV_WEBHOOKS)) + { + if ($user_id) + { + abort(403, 'Forbidden'); + } + + redirect('login'); + + return; + } + + $role_slug = session('role_slug'); + + script_vars([ + 'user_id' => $user_id, + 'role_slug' => $role_slug, + ]); + + html_vars([ + 'page_title' => lang('webhooks'), + 'active_menu' => PRIV_SYSTEM_SETTINGS, + 'user_display_name' => $this->accounts->get_user_display_name($user_id), + 'timezones' => $this->timezones->to_array(), + 'privileges' => $this->roles_model->get_permissions_by_slug($role_slug), + 'available_actions' => [ + WEBHOOK_APPOINTMENT_SAVE, + WEBHOOK_APPOINTMENT_DELETE, + WEBHOOK_UNAVAILABILITY_SAVE, + WEBHOOK_UNAVAILABILITY_DELETE, + WEBHOOK_CUSTOMER_SAVE, + WEBHOOK_CUSTOMER_DELETE, + WEBHOOK_SERVICE_SAVE, + WEBHOOK_SERVICE_DELETE, + WEBHOOK_CATEGORY_SAVE, + WEBHOOK_CATEGORY_DELETE, + WEBHOOK_PROVIDER_SAVE, + WEBHOOK_PROVIDER_DELETE, + WEBHOOK_SECRETARY_SAVE, + WEBHOOK_SECRETARY_DELETE, + WEBHOOK_ADMIN_SAVE, + WEBHOOK_ADMIN_DELETE + ] + ]); + + $this->load->view('pages/webhooks'); + } + + /** + * Filter webhooks by the provided keyword. + */ + public function search() + { + try + { + if (cannot('view', PRIV_WEBHOOKS)) + { + abort(403, 'Forbidden'); + } + + $keyword = request('keyword', ''); + + $order_by = 'name ASC'; + + $limit = request('limit', 1000); + + $offset = 0; + + $webhooks = $this->webhooks_model->search($keyword, $limit, $offset, $order_by); + + json_response($webhooks); + } + catch (Throwable $e) + { + json_exception($e); + } + } + + /** + * Create a webhook. + */ + public function create() + { + try + { + if (cannot('add', PRIV_WEBHOOKS)) + { + abort(403, 'Forbidden'); + } + + $webhook = request('webhook'); + + $this->webhooks_model->only($webhook, [ + 'name', + 'url', + 'actions', + 'secret_token', + 'is_ssl_verified', + 'notes', + ]); + + $webhook_id = $this->webhooks_model->save($webhook); + + json_response([ + 'success' => TRUE, + 'id' => $webhook_id + ]); + } + catch (Throwable $e) + { + json_exception($e); + } + } + + /** + * Update a webhook. + */ + public function update() + { + try + { + if (cannot('edit', PRIV_WEBHOOKS)) + { + abort(403, 'Forbidden'); + } + + $webhook = request('webhook'); + + $this->webhooks_model->only($webhook, [ + 'id', + 'name', + 'url', + 'actions', + 'secret_token', + 'is_ssl_verified', + 'notes', + ]); + + $webhook_id = $this->webhooks_model->save($webhook); + + json_response([ + 'success' => TRUE, + 'id' => $webhook_id + ]); + } + catch (Throwable $e) + { + json_exception($e); + } + } + + /** + * Remove a webhook. + */ + public function destroy() + { + try + { + if (cannot('delete', PRIV_WEBHOOKS)) + { + abort(403, 'Forbidden'); + } + + $webhook_id = request('webhook_id'); + + $this->webhooks_model->delete($webhook_id); + + json_response([ + 'success' => TRUE, + ]); + } + catch (Throwable $e) + { + json_exception($e); + } + } + + /** + * Find a webhook. + */ + public function find() + { + try + { + if (cannot('view', PRIV_WEBHOOKS)) + { + abort(403, 'Forbidden'); + } + + $webhook_id = request('webhook_id'); + + $webhook = $this->webhooks_model->find($webhook_id); + + json_response($webhook); + } + catch (Throwable $e) + { + json_exception($e); + } + } +} diff --git a/application/controllers/api/v1/Webhooks_api_v1.php b/application/controllers/api/v1/Webhooks_api_v1.php new file mode 100644 index 00000000..d89ee9e5 --- /dev/null +++ b/application/controllers/api/v1/Webhooks_api_v1.php @@ -0,0 +1,219 @@ + + * @copyright Copyright (c) Alex Tselegidis + * @license https://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link https://easyappointments.org + * @since v1.5.0 + * ---------------------------------------------------------------------------- */ + +/** + * Webhooks API v1 controller. + * + * @package Controllers + */ +class Webhooks_api_v1 extends EA_Controller { + /** + * Webhooks_api_v1 constructor. + */ + public function __construct() + { + parent::__construct(); + + $this->load->model('webhooks_model'); + + $this->load->library('api'); + + $this->api->auth(); + + $this->api->model('webhooks_model'); + } + + /** + * Get a webhook collection. + */ + public function index() + { + try + { + $keyword = $this->api->request_keyword(); + + $limit = $this->api->request_limit(); + + $offset = $this->api->request_offset(); + + $order_by = $this->api->request_order_by(); + + $fields = $this->api->request_fields(); + + $with = $this->api->request_with(); + + $webhooks = empty($keyword) + ? $this->webhooks_model->get(NULL, $limit, $offset, $order_by) + : $this->webhooks_model->search($keyword, $limit, $offset, $order_by); + + foreach ($webhooks as &$webhook) + { + $this->webhooks_model->api_encode($webhook); + + if ( ! empty($fields)) + { + $this->webhooks_model->only($webhook, $fields); + } + + if ( ! empty($with)) + { + $this->webhooks_model->load($webhook, $with); + } + } + + json_response($webhooks); + } + catch (Throwable $e) + { + json_exception($e); + } + } + + /** + * Get a single webhook. + * + * @param int|null $id Webhook ID. + */ + public function show(int $id = NULL) + { + try + { + $fields = $this->api->request_fields(); + + $with = $this->api->request_with(); + + $webhook = $this->webhooks_model->find($id); + + $this->webhooks_model->api_encode($webhook); + + if ( ! empty($fields)) + { + $this->webhooks_model->only($webhook, $fields); + } + + if ( ! empty($with)) + { + $this->webhooks_model->load($webhook, $with); + } + + if ( ! $webhook) + { + response('', 404); + + return; + } + + json_response($webhook); + } + catch (Throwable $e) + { + json_exception($e); + } + } + + /** + * Create a webhook. + */ + public function store() + { + try + { + $webhook = request(); + + $this->webhooks_model->api_decode($webhook); + + if (array_key_exists('id', $webhook)) + { + unset($webhook['id']); + } + + $webhook_id = $this->webhooks_model->save($webhook); + + $created_webhook = $this->webhooks_model->find($webhook_id); + + $this->webhooks_model->api_encode($created_webhook); + + json_response($created_webhook, 201); + } + catch (Throwable $e) + { + json_exception($e); + } + } + + /** + * Update a webhook. + * + * @param int $id Webhook ID. + */ + public function update(int $id) + { + try + { + $occurrences = $this->webhooks_model->get(['id' => $id]); + + if (empty($occurrences)) + { + response('', 404); + + return; + } + + $original_webhook = $occurrences[0]; + + $webhook = request(); + + $this->webhooks_model->api_decode($webhook, $original_webhook); + + $webhook_id = $this->webhooks_model->save($webhook); + + $updated_webhook = $this->webhooks_model->find($webhook_id); + + $this->webhooks_model->api_encode($updated_webhook); + + json_response($updated_webhook); + } + catch (Throwable $e) + { + json_exception($e); + } + } + + /** + * Delete a webhook. + * + * @param int $id Webhook ID. + */ + public function destroy(int $id) + { + try + { + $occurrences = $this->webhooks_model->get(['id' => $id]); + + if (empty($occurrences)) + { + response('', 404); + + return; + } + + $this->webhooks_model->delete($id); + + response('', 204); + } + catch (Throwable $e) + { + json_exception($e); + } + } +} diff --git a/application/core/EA_Controller.php b/application/core/EA_Controller.php index e061094b..28d7796e 100644 --- a/application/core/EA_Controller.php +++ b/application/core/EA_Controller.php @@ -51,6 +51,7 @@ * @property Settings_model $settings_model * @property Unavailabilities_model $unavailabilities_model * @property Users_model $users_model + * @property Webhooks_model $webhooks_model * * @property Accounts $accounts * @property Api $api @@ -64,6 +65,7 @@ * @property Permissions $permissions * @property Synchronization $synchronization * @property Timezones $timezones + * @property Webhooks_client $webhooks_client */ class EA_Controller extends CI_Controller { /** diff --git a/application/language/arabic/translations_lang.php b/application/language/arabic/translations_lang.php index c00f11a1..f66109b5 100755 --- a/application/language/arabic/translations_lang.php +++ b/application/language/arabic/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/bulgarian/translations_lang.php b/application/language/bulgarian/translations_lang.php index 4f16ce51..e3feee64 100755 --- a/application/language/bulgarian/translations_lang.php +++ b/application/language/bulgarian/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/catalan/translations_lang.php b/application/language/catalan/translations_lang.php index 9d86e849..1eb27f6e 100644 --- a/application/language/catalan/translations_lang.php +++ b/application/language/catalan/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'Quin tipus d\'esdeveniment voleu afegir?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/chinese/translations_lang.php b/application/language/chinese/translations_lang.php index 912a07b7..83a68ad5 100755 --- a/application/language/chinese/translations_lang.php +++ b/application/language/chinese/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/czech/translations_lang.php b/application/language/czech/translations_lang.php index 6469625e..8ded3016 100644 --- a/application/language/czech/translations_lang.php +++ b/application/language/czech/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/danish/translations_lang.php b/application/language/danish/translations_lang.php index e2aeb982..1b5bb8cb 100755 --- a/application/language/danish/translations_lang.php +++ b/application/language/danish/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/dutch/translations_lang.php b/application/language/dutch/translations_lang.php index b5e8c076..76d93c47 100755 --- a/application/language/dutch/translations_lang.php +++ b/application/language/dutch/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/english/translations_lang.php b/application/language/english/translations_lang.php index eadf98d3..a46f833b 100755 --- a/application/language/english/translations_lang.php +++ b/application/language/english/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/estonian/translations_lang.php b/application/language/estonian/translations_lang.php index 7caf57b4..45e0b29c 100644 --- a/application/language/estonian/translations_lang.php +++ b/application/language/estonian/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'Mis laadi sündmust soovid lisada?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/finnish/translations_lang.php b/application/language/finnish/translations_lang.php index 1ebee6a8..6db704ff 100755 --- a/application/language/finnish/translations_lang.php +++ b/application/language/finnish/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/french/translations_lang.php b/application/language/french/translations_lang.php index 3d5e78da..9d10d1b7 100755 --- a/application/language/french/translations_lang.php +++ b/application/language/french/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/greek/translations_lang.php b/application/language/greek/translations_lang.php index a0dd302a..4161442b 100755 --- a/application/language/greek/translations_lang.php +++ b/application/language/greek/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/hebrew/translations_lang.php b/application/language/hebrew/translations_lang.php index 83a0c213..2cbefade 100644 --- a/application/language/hebrew/translations_lang.php +++ b/application/language/hebrew/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/hindi/translations_lang.php b/application/language/hindi/translations_lang.php index 43d7186b..407acf13 100755 --- a/application/language/hindi/translations_lang.php +++ b/application/language/hindi/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/hungarian/translations_lang.php b/application/language/hungarian/translations_lang.php index 21ccbd06..743b0e23 100755 --- a/application/language/hungarian/translations_lang.php +++ b/application/language/hungarian/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/italian/translations_lang.php b/application/language/italian/translations_lang.php index 38db88d3..73e6f1c4 100755 --- a/application/language/italian/translations_lang.php +++ b/application/language/italian/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/japanese/translations_lang.php b/application/language/japanese/translations_lang.php index cfa66ade..01bb5274 100755 --- a/application/language/japanese/translations_lang.php +++ b/application/language/japanese/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/luxembourgish/translations_lang.php b/application/language/luxembourgish/translations_lang.php index 7d224707..36d75c55 100755 --- a/application/language/luxembourgish/translations_lang.php +++ b/application/language/luxembourgish/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/marathi/translations_lang.php b/application/language/marathi/translations_lang.php index a3177ad7..0b921019 100644 --- a/application/language/marathi/translations_lang.php +++ b/application/language/marathi/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/polish/translations_lang.php b/application/language/polish/translations_lang.php index 50fbcb31..4340acb6 100755 --- a/application/language/polish/translations_lang.php +++ b/application/language/polish/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/portuguese-br/translations_lang.php b/application/language/portuguese-br/translations_lang.php index 1bdcfb0a..da4f0789 100755 --- a/application/language/portuguese-br/translations_lang.php +++ b/application/language/portuguese-br/translations_lang.php @@ -366,4 +366,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/portuguese/translations_lang.php b/application/language/portuguese/translations_lang.php index 6b8a613b..6bc6da8e 100755 --- a/application/language/portuguese/translations_lang.php +++ b/application/language/portuguese/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/romanian/translations_lang.php b/application/language/romanian/translations_lang.php index 126a1ead..c9c06e95 100755 --- a/application/language/romanian/translations_lang.php +++ b/application/language/romanian/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/russian/translations_lang.php b/application/language/russian/translations_lang.php index 8bca10c4..f5556431 100755 --- a/application/language/russian/translations_lang.php +++ b/application/language/russian/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/serbian/translations_lang.php b/application/language/serbian/translations_lang.php index 6826c434..6b238a5d 100644 --- a/application/language/serbian/translations_lang.php +++ b/application/language/serbian/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/slovak/translations_lang.php b/application/language/slovak/translations_lang.php index 63c3a25a..2f197bb1 100755 --- a/application/language/slovak/translations_lang.php +++ b/application/language/slovak/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/spanish/translations_lang.php b/application/language/spanish/translations_lang.php index 21fa8b78..a2cb44e3 100755 --- a/application/language/spanish/translations_lang.php +++ b/application/language/spanish/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/swedish/translations_lang.php b/application/language/swedish/translations_lang.php index fab5dcae..ab1ac07f 100644 --- a/application/language/swedish/translations_lang.php +++ b/application/language/swedish/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/language/turkish/translations_lang.php b/application/language/turkish/translations_lang.php index 1098b579..22ba2595 100755 --- a/application/language/turkish/translations_lang.php +++ b/application/language/turkish/translations_lang.php @@ -367,4 +367,24 @@ $lang['what_kind_of_event'] = 'What kind of event would you like to add?'; $lang['theme'] = 'Theme'; $lang['limit_customer_access'] = 'Limit Customer Access'; $lang['limit_customer_access_hint'] = 'If enabled, providers and secretaries will only be able to access customers they have an appointment with.'; +$lang['url'] = 'URL'; +$lang['secret_token'] = 'Secret Token'; +$lang['verify_ssl'] = 'Verify SSL'; +$lang['appointment_save'] = 'Appointment Save'; +$lang['appointment_delete'] = 'Appointment Delete'; +$lang['unavailability_save'] = 'Unavailability Save'; +$lang['unavailability_delete'] = 'Unavailability Delete'; +$lang['customer_save'] = 'Customer Save'; +$lang['customer_delete'] = 'Customer Delete'; +$lang['service_save'] = 'Service Save'; +$lang['service_delete'] = 'Service Delete'; +$lang['category_save'] = 'Category Save'; +$lang['category_delete'] = 'Category Delete'; +$lang['provider_save'] = 'Provider Save'; +$lang['provider_delete'] = 'Provider Delete'; +$lang['secretary_save'] = 'Secretary Save'; +$lang['secretary_delete'] = 'Secretary Delete'; +$lang['admin_save'] = 'Admin Save'; +$lang['admin_delete'] = 'Admin Delete'; +$lang['options'] = 'Options'; // End diff --git a/application/libraries/Synchronization.php b/application/libraries/Synchronization.php index 9d790225..3dfdaba5 100644 --- a/application/libraries/Synchronization.php +++ b/application/libraries/Synchronization.php @@ -51,74 +51,160 @@ class Synchronization { { try { - $google_sync = filter_var( - $this->CI->providers_model->get_setting($appointment['id_users_provider'], 'google_sync'), - FILTER_VALIDATE_BOOLEAN - ); - - if ($google_sync === TRUE) + if ( ! $provider['settings']['google_sync']) { - $google_token = json_decode( - $this->CI->providers_model->get_setting($appointment['id_users_provider'], 'google_token')); + return; + } - $this->CI->load->library('google_sync'); + if (empty($provider['settings']['google_token'])) + { + throw new RuntimeException('No google token available for the provider: ' . $provider['id']); + } - $this->CI->google_sync->refresh_token($google_token->refresh_token); + $google_token = json_decode($provider['settings']['google_token'], TRUE); - 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->value($appointment['id'], 'id_google_calendar'); + $this->CI->google_sync->refresh_token($google_token['refresh_token']); - $this->CI->google_sync->update_appointment($appointment, $provider, - $service, $customer, $settings); - } + if (empty($appointment['id_google_calendar'])) + { + $google_event = $this->CI->google_sync->add_appointment( + $appointment, + $provider, + $webhook, + $customer, + $settings + ); + + $appointment['id_google_calendar'] = $google_event->getId(); + + $this->CI->appointments_model->save($appointment); + } + else + { + $this->CI->google_sync->update_appointment( + $appointment, + $provider, + $webhook, + $customer, + $settings + ); } } - catch (Exception $exception) + catch (Throwable $e) { - log_message('error', $exception->getMessage()); - log_message('error', $exception->getTraceAsString()); + log_message('error', $e->getMessage()); + log_message('error', $e->getTraceAsString()); } } /** * Synchronize removal of an appointment with external calendars. * - * @param array $appointment Appointment data. - * @param array $provider Provider data. + * @param array $appointment Appointment record. + * @param array $provider Provider record. */ public function sync_appointment_deleted(array $appointment, array $provider) { - if ( ! empty($appointment['id_google_calendar'])) + try { - try + if ( ! $provider['settings']['google_sync'] || empty($appointment['id_google_calendar'])) { - $google_sync = filter_var( - $this->CI->providers_model->get_setting($appointment['id_users_provider'], 'google_sync'), - FILTER_VALIDATE_BOOLEAN - ); + return; + } - if ($google_sync === TRUE) - { - $google_token = json_decode($this->CI->providers_model->get_setting($provider['id'], 'google_token')); - $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) + if (empty($provider['settings']['google_token'])) { - $exceptions[] = $exception; + throw new RuntimeException('No google token available for the provider: ' . $provider['id']); } + + $google_token = json_decode($provider['settings']['google_token'], TRUE); + + $this->CI->google_sync->refresh_token($google_token['refresh_token']); + + $this->CI->google_sync->delete_appointment($provider, $appointment['id_google_calendar']); + + } + catch (Throwable $e) + { + log_message('error', $e->getMessage()); + log_message('error', $e->getTraceAsString()); + } + } + + /** + * Synchronize changes made to the unavailability with external calendars. + * + * @param array $unavailability Unavailability record. + * @param array $provider Provider record. + */ + public function sync_unavailability_saved(array $unavailability, array $provider) + { + try + { + if ( ! $provider['settings']['google_sync']) + { + return; + } + + if (empty($provider['settings']['google_token'])) + { + throw new RuntimeException('No google token available for the provider: ' . $provider['id']); + } + + $google_token = json_decode($provider['settings']['google_token'], TRUE); + + $this->CI->google_sync->refresh_token($google_token['refresh_token']); + + if (empty($unavailability['id_google_calendar'])) + { + $google_event = $this->CI->google_sync->add_unavailability($provider, $unavailability); + + $unavailability['id_google_calendar'] = $google_event->getId(); + + $this->CI->unavailabilities_model->save($unavailability); + } + else + { + $this->CI->google_sync->update_unavailability($provider, $unavailability); + } + } + catch (Throwable $e) + { + log_message('error', $e->getMessage()); + log_message('error', $e->getTraceAsString()); + } + } + + /** + * Synchronize removal of an unavailability with external calendars. + * + * @param array $unavailability Unavailability record. + * @param array $provider Provider record. + */ + public function sync_unavailability_deleted(array $unavailability, array $provider) + { + try + { + if ( ! $provider['settings']['google_sync'] || empty($unavailability['id_google_calendar'])) + { + return; + } + + if (empty($provider['settings']['google_token'])) + { + throw new RuntimeException('No google token available for the provider: ' . $provider['id']); + } + + $google_token = json_decode($provider['settings']['google_token'], TRUE); + + $this->CI->google_sync->refresh_token($google_token['refresh_token']); + + $this->CI->google_sync->delete_unavailability($provider, $unavailability['id_google_calendar']); + } + catch (Throwable $e) + { + log_message('error', $e->getMessage()); + log_message('error', $e->getTraceAsString()); } } } diff --git a/application/libraries/Webhooks_client.php b/application/libraries/Webhooks_client.php new file mode 100644 index 00000000..89ae3501 --- /dev/null +++ b/application/libraries/Webhooks_client.php @@ -0,0 +1,92 @@ + + * @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 GuzzleHttp\Client; + +/** + * Webhooks client library. + * + * Handles the webhook HTTP related functionality. + * + * @package Libraries + */ +class Webhooks_client { + /** + * @var EA_Controller + */ + protected $CI; + + /** + * Webhook client 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->model('webhooks_model'); + } + + /** + * Trigger the registered webhooks for the provided action. + * + * @param string $action Webhook action. + * @param array $payload Payload data. + * + * @return void|null + */ + public function trigger(string $action, array $payload) + { + $webhooks = $this->CI->webhooks_model->get(); + + foreach ($webhooks as $webhook) + { + if (strpos($webhook['actions'], $action) !== FALSE) + { + $this->call($webhook, $action, $payload); + } + } + } + + /** + * Call the provided webhook. + * + * @param array $webhook + * @param string $action + * @param array $payload + */ + private function call(array $webhook, string $action, array $payload) + { + try + { + $client = new Client(); + + $client->post($webhook['url'], [ + 'verify' => $webhook['is_ssl_verified'], + 'json' => [ + 'action' => $action, + 'payload' => $payload + ] + ]); + } + catch (Throwable $e) + { + log_message('error', 'Webhooks Client - The webhook (' . ($webhook['id'] ?? NULL) . ') request received an unexpected exception: ' . $e->getMessage()); + } + } +} diff --git a/application/migrations/040_create_webhooks_table.php b/application/migrations/040_create_webhooks_table.php new file mode 100644 index 00000000..8e839351 --- /dev/null +++ b/application/migrations/040_create_webhooks_table.php @@ -0,0 +1,85 @@ + + * @copyright Copyright (c) Alex Tselegidis + * @license https://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link https://easyappointments.org + * @since v1.4.0 + * ---------------------------------------------------------------------------- */ + +class Migration_Create_webhooks_table extends EA_Migration { + /** + * Upgrade method. + */ + public function up() + { + if ( ! $this->db->table_exists('webhooks')) + { + $this->dbforge->add_field([ + 'id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'auto_increment' => TRUE + ], + 'create_datetime' => [ + 'type' => 'DATETIME', + 'null' => TRUE + ], + 'update_datetime' => [ + 'type' => 'DATETIME', + 'null' => TRUE + ], + 'delete_datetime' => [ + 'type' => 'DATETIME', + 'null' => TRUE + ], + 'name' => [ + 'type' => 'VARCHAR', + 'constraint' => '256', + 'null' => TRUE, + ], + 'url' => [ + 'type' => 'TEXT', + 'null' => TRUE, + ], + 'actions' => [ + 'type' => 'TEXT', + 'null' => TRUE, + ], + 'secret_token' => [ + 'type' => 'VARCHAR', + 'constraint' => '512', + 'null' => TRUE, + ], + 'is_ssl_verified' => [ + 'type' => 'TINYINT', + 'constraint' => '4', + 'default' => TRUE, + ], + 'notes' => [ + 'type' => 'TEXT', + 'null' => TRUE, + ], + ]); + + $this->dbforge->add_key('id', TRUE); + + $this->dbforge->create_table('webhooks', TRUE, ['engine' => 'InnoDB']); + } + } + + /** + * Downgrade method. + */ + public function down() + { + if ($this->db->table_exists('webhooks')) + { + $this->dbforge->drop_table('webhooks'); + } + } +} diff --git a/application/migrations/041_add_webhooks_column_to_roles_table.php b/application/migrations/041_add_webhooks_column_to_roles_table.php new file mode 100644 index 00000000..6ea39a5e --- /dev/null +++ b/application/migrations/041_add_webhooks_column_to_roles_table.php @@ -0,0 +1,48 @@ + + * @copyright Copyright (c) Alex Tselegidis + * @license https://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link https://easyappointments.org + * @since v1.4.0 + * ---------------------------------------------------------------------------- */ + +class Migration_Add_webhooks_column_to_roles_table extends EA_Migration { + /** + * Upgrade method. + */ + public function up() + { + if ( ! $this->db->field_exists('webhooks', 'roles')) + { + $fields = [ + 'webhooks' => [ + 'type' => 'INT', + 'constraint' => '11', + 'null' => TRUE + ] + ]; + + $this->dbforge->add_column('roles', $fields); + + $this->db->update('roles', ['webhooks' => '15'], ['slug' => 'admin']); + + $this->db->update('roles', ['webhooks' => '0'], ['slug !=' => 'admin']); + } + } + + /** + * Downgrade method. + */ + public function down() + { + if ($this->db->field_exists('webhooks', 'roles')) + { + $this->dbforge->drop_column('roles', 'webhooks'); + } + } +} diff --git a/application/models/Webhooks_model.php b/application/models/Webhooks_model.php new file mode 100644 index 00000000..d360b57a --- /dev/null +++ b/application/models/Webhooks_model.php @@ -0,0 +1,323 @@ + + * @copyright Copyright (c) Alex Tselegidis + * @license https://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link https://easyappointments.org + * @since v1.3.2 + * ---------------------------------------------------------------------------- */ + +/** + * Webhooks model. + * + * Handles all the database operations of the webhook resource. + * + * @package Models + */ +class Webhooks_model extends EA_Model { + /** + * @var array + */ + protected $casts = [ + 'id' => 'integer', + 'is_active' => 'boolean', + 'is_ssl_verified' => 'boolean', + ]; + + /** + * @var array + */ + protected $api_resource = [ + 'id' => 'id', + 'name' => 'name', + 'url' => 'url', + 'action' => 'action', + 'secretToken' => 'secret_token', + 'isActive' => 'is_active', + 'isSslVerified' => 'is_ssl_verified', + 'notes' => 'notes', + ]; + + + /** + * Save (insert or update) a webhook. + * + * @param array $webhook Associative array with the webhook data. + * + * @return int Returns the webhook ID. + * + * @throws InvalidArgumentException + */ + public function save(array $webhook): int + { + $this->validate($webhook); + + if (empty($webhook['id'])) + { + return $this->insert($webhook); + } + else + { + return $this->update($webhook); + } + } + + + /** + * Validate the webhook data. + * + * @param array $webhook Associative array with the webhook data. + * + * @throws InvalidArgumentException + */ + public function validate(array $webhook) + { + if ( + empty($webhook['name']) + || empty($webhook['url']) + || empty($webhook['actions']) + ) + { + throw new InvalidArgumentException('Not all required fields are provided: ' . print_r($webhook, TRUE)); + } + } + + /** + * Insert a new webhook into the database. + * + * @param array $webhook Associative array with the webhook data. + * + * @return int Returns the webhook ID. + * + * @throws RuntimeException + */ + protected function insert(array $webhook): int + { + $webhook['create_datetime'] = date('Y-m-d H:i:s'); + $webhook['update_datetime'] = date('Y-m-d H:i:s'); + + if ( ! $this->db->insert('webhooks', $webhook)) + { + throw new RuntimeException('Could not insert webhook.'); + } + + return $this->db->insert_id(); + } + + /** + * Update an existing webhook. + * + * @param array $webhook Associative array with the webhook data. + * + * @return int Returns the webhook ID. + * + * @throws RuntimeException + */ + protected function update(array $webhook): int + { + $webhook['update_datetime'] = date('Y-m-d H:i:s'); + + if ( ! $this->db->update('webhooks', $webhook, ['id' => $webhook['id']])) + { + throw new RuntimeException('Could not update webhook.'); + } + + return $webhook['id']; + } + + /** + * Remove an existing webhook from the database. + * + * @param int $webhook_id Webhook ID. + * @param bool $force_delete Override soft delete. + * + * @throws RuntimeException + */ + public function delete(int $webhook_id, bool $force_delete = FALSE) + { + if ($force_delete) + { + $this->db->delete('webhooks', ['id' => $webhook_id]); + } + else + { + $this->db->update('webhooks', ['delete_datetime' => date('Y-m-d H:i:s')], ['id' => $webhook_id]); + } + } + + /** + * Get a specific webhook from the database. + * + * @param int $webhook_id The ID of the record to be returned. + * @param bool $with_trashed + * + * @return array Returns an array with the webhook data. + */ + public function find(int $webhook_id, bool $with_trashed = FALSE): array + { + if ( ! $with_trashed) + { + $this->db->where('delete_datetime IS NULL'); + } + + $webhook = $this->db->get_where('webhooks', ['id' => $webhook_id])->row_array(); + + if ( ! $webhook) + { + throw new InvalidArgumentException('The provided webhook ID was not found in the database: ' . $webhook_id); + } + + $this->cast($webhook); + + return $webhook; + } + + /** + * Get a specific field value from the database. + * + * @param int $webhook_id Webhook ID. + * @param string $field Name of the value to be returned. + * + * @return string Returns the selected webhook value from the database. + * + * @throws InvalidArgumentException + */ + public function value(int $webhook_id, string $field): string + { + if (empty($field)) + { + throw new InvalidArgumentException('The field argument is cannot be empty.'); + } + + if (empty($webhook_id)) + { + throw new InvalidArgumentException('The webhook ID argument cannot be empty.'); + } + + // Check whether the webhook exists. + $query = $this->db->get_where('webhooks', ['id' => $webhook_id]); + + if ( ! $query->num_rows()) + { + throw new InvalidArgumentException('The provided webhook ID was not found in the database: ' . $webhook_id); + } + + // Check if the required field is part of the webhook data. + $webhook = $query->row_array(); + + $this->cast($webhook); + + if ( ! array_key_exists($field, $webhook)) + { + throw new InvalidArgumentException('The requested field was not found in the webhook data: ' . $field); + } + + return $webhook[$field]; + } + + /** + * Get all webhooks that match the provided criteria. + * + * @param array|string $where Where conditions. + * @param int|null $limit Record limit. + * @param int|null $offset Record offset. + * @param string|null $order_by Order by. + * @param bool $with_trashed + * + * @return array Returns an array of webhooks. + */ + public function get($where = NULL, int $limit = NULL, int $offset = NULL, string $order_by = NULL, bool $with_trashed = FALSE): array + { + if ($where !== NULL) + { + $this->db->where($where); + } + + if ($order_by !== NULL) + { + $this->db->order_by($order_by); + } + + if ( ! $with_trashed) + { + $this->db->where('delete_datetime IS NULL'); + } + + $webhooks = $this->db->get('webhooks', $limit, $offset)->result_array(); + + foreach ($webhooks as &$webhook) + { + $this->cast($webhook); + } + + return $webhooks; + } + + /** + * Get the query builder interface, configured for use with the webhooks table. + * + * @return CI_DB_query_builder + */ + public function query(): CI_DB_query_builder + { + return $this->db->from('webhooks'); + } + + /** + * Search webhooks by the provided keyword. + * + * @param string $keyword Search keyword. + * @param int|null $limit Record limit. + * @param int|null $offset Record offset. + * @param string|null $order_by Order by. + * @param bool $with_trashed + * + * @return array Returns an array of webhooks. + */ + public function search(string $keyword, int $limit = NULL, int $offset = NULL, string $order_by = NULL, bool $with_trashed = FALSE): array + { + if ( ! $with_trashed) + { + $this->db->where('delete_datetime IS NULL'); + } + + $webhooks = $this + ->db + ->select() + ->from('webhooks') + ->group_start() + ->like('name', $keyword) + ->or_like('url', $keyword) + ->or_like('actions', $keyword) + ->group_end() + ->limit($limit) + ->offset($offset) + ->order_by($order_by) + ->get() + ->result_array(); + + foreach ($webhooks as &$webhook) + { + $this->cast($webhook); + } + + return $webhooks; + } + + /** + * Load related resources to a webhook. + * + * @param array $webhook Associative array with the webhook data. + * @param array $resources Resource names to be attached. + * + * @throws InvalidArgumentException + */ + public function load(array &$webhook, array $resources) + { + // Webhooks do not currently have any related resources. + } +} diff --git a/application/views/pages/webhooks.php b/application/views/pages/webhooks.php new file mode 100755 index 00000000..3516069a --- /dev/null +++ b/application/views/pages/webhooks.php @@ -0,0 +1,140 @@ + + + + +
+
+
+
+
+ + + +
+
+ +

+
+
+ +
+
+
+ + + +
+ + +
+ +

+ + + + + +
+ + +
+ +
+ + +
+ +
+ + + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + + +
+
+ +
+
+ +
+ + +
+
+ + + +
+
+
+
+
+
+ + + + + + + + + + + + diff --git a/assets/js/http/webhooks_http_client.js b/assets/js/http/webhooks_http_client.js new file mode 100644 index 00000000..65a9ff9f --- /dev/null +++ b/assets/js/http/webhooks_http_client.js @@ -0,0 +1,133 @@ +/* ---------------------------------------------------------------------------- + * Easy!Appointments - Online Appointment Scheduler + * + * @package EasyAppointments + * @author A.Tselegidis + * @copyright Copyright (c) Alex Tselegidis + * @license https://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link https://easyappointments.org + * @since v1.5.0 + * ---------------------------------------------------------------------------- */ + +/** + * Webhooks HTTP client. + * + * This module implements the webhooks related HTTP requests. + */ +App.Http.Webhooks = (function () { + /** + * Save (create or update) a webhook. + * + * @param {Object} webhook + * + * @return {Object} + */ + function save(webhook) { + return webhook.id ? update(webhook) : create(webhook); + } + + /** + * Create an webhook. + * + * @param {Object} webhook + * + * @return {Object} + */ + function create(webhook) { + const url = App.Utils.Url.siteUrl('webhooks/create'); + + const data = { + csrf_token: vars('csrf_token'), + webhook: webhook + }; + + return $.post(url, data); + } + + /** + * Update an webhook. + * + * @param {Object} webhook + * + * @return {Object} + */ + function update(webhook) { + const url = App.Utils.Url.siteUrl('webhooks/update'); + + const data = { + csrf_token: vars('csrf_token'), + webhook: webhook + }; + + return $.post(url, data); + } + + /** + * Delete an webhook. + * + * @param {Number} webhookId + * + * @return {Object} + */ + function destroy(webhookId) { + const url = App.Utils.Url.siteUrl('webhooks/destroy'); + + const data = { + csrf_token: vars('csrf_token'), + webhook_id: webhookId + }; + + return $.post(url, data); + } + + /** + * Search webhooks by keyword. + * + * @param {String} keyword + * @param {Number} limit + * @param {Number} offset + * @param {String} orderBy + * + * @return {Object} + */ + function search(keyword, limit, offset, orderBy) { + const url = App.Utils.Url.siteUrl('webhooks/search'); + + const data = { + csrf_token: vars('csrf_token'), + keyword, + limit, + offset, + order_by: orderBy + }; + + return $.post(url, data); + } + + /** + * Find an webhook. + * + * @param {Number} webhookId + * + * @return {Object} + */ + function find(webhookId) { + const url = App.Utils.Url.siteUrl('webhooks/find'); + + const data = { + csrf_token: vars('csrf_token'), + webhook_id: webhookId + }; + + return $.post(url, data); + } + + return { + save, + create, + update, + destroy, + search, + find + }; +})(); diff --git a/assets/js/pages/webhooks.js b/assets/js/pages/webhooks.js new file mode 100644 index 00000000..dc168c2c --- /dev/null +++ b/assets/js/pages/webhooks.js @@ -0,0 +1,383 @@ +/* ---------------------------------------------------------------------------- + * Easy!Appointments - Online Appointment Scheduler + * + * @package EasyAppointments + * @author A.Tselegidis + * @copyright Copyright (c) Alex Tselegidis + * @license https://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link https://easyappointments.org + * @since v1.5.0 + * ---------------------------------------------------------------------------- */ + +/** + * Webhooks page. + * + * This module implements the functionality of the webhooks page. + */ +App.Pages.Webhooks = (function () { + const $webhooks = $('#webhooks'); + const $id = $('#id'); + const $name = $('#name'); + const $url = $('#url'); + const $actions = $('#actions'); + const $secretToken = $('#secret-token'); + const $isSslVerified = $('#is-ssl-verified'); + const $notes = $('#notes'); + const $filterWebhooks = $('#filter-webhooks'); + let filterResults = {}; + let filterLimit = 20; + + /** + * Add page event listeners. + */ + function addEventListeners() { + /** + * Event: Filter Webhooks Form "Submit" + * + * @param {jQuery.Event} event + */ + $webhooks.on('submit', '#filter-webhooks form', (event) => { + event.preventDefault(); + const key = $filterWebhooks.find('.key').val(); + $filterWebhooks.find('.selected').removeClass('selected'); + resetForm(); + filter(key); + }); + + /** + * Event: Filter Webhook Row "Click" + * + * Display the selected webhook data to the user. + */ + $webhooks.on('click', '.webhook-row', (event) => { + if ($filterWebhooks.find('.filter').prop('disabled')) { + $filterWebhooks.find('.results').css('color', '#AAA'); + return; // exit because we are on edit mode + } + + const webhookId = $(event.currentTarget).attr('data-id'); + + const webhook = filterResults.find((filterResult) => Number(filterResult.id) === Number(webhookId)); + + display(webhook); + + $filterWebhooks.find('.selected').removeClass('selected'); + $(event.currentTarget).addClass('selected'); + $('#edit-webhook, #delete-webhook').prop('disabled', false); + }); + + /** + * Event: Add New Webhook Button "Click" + */ + $webhooks.on('click', '#add-webhook', () => { + resetForm(); + $webhooks.find('.add-edit-delete-group').hide(); + $webhooks.find('.save-cancel-group').show(); + $webhooks.find('.record-details').find('input, select, textarea').prop('disabled', false); + $webhooks.find('.record-details .form-label span').prop('hidden', false); + $filterWebhooks.find('button').prop('disabled', true); + $filterWebhooks.find('.results').css('color', '#AAA'); + }); + + /** + * Event: Cancel Webhook Button "Click" + * + * Cancel add or edit of a webhook record. + */ + $webhooks.on('click', '#cancel-webhook', () => { + const id = $id.val(); + + resetForm(); + + if (id !== '') { + select(id, true); + } + }); + + /** + * Event: Save Webhook Button "Click" + */ + $webhooks.on('click', '#save-webhook', () => { + const webhook = { + name: $name.val(), + url: $url.val(), + actions: '', + secret_token: $secretToken.val(), + is_ssl_verified: Number($isSslVerified.prop('checked')), + notes: $notes.val(), + }; + + const actions = []; + + $actions.find('input:checked').each((index, checkbox) => { + var action = $(checkbox).data('action'); + actions.push(action); + }); + + webhook.actions = actions.join(','); + + if ($id.val() !== '') { + webhook.id = $id.val(); + } + + if (!validate()) { + return; + } + + save(webhook); + }); + + /** + * Event: Edit Webhook Button "Click" + */ + $webhooks.on('click', '#edit-webhook', () => { + $webhooks.find('.add-edit-delete-group').hide(); + $webhooks.find('.save-cancel-group').show(); + $webhooks.find('.record-details').find('input, select, textarea').prop('disabled', false); + $webhooks.find('.record-details .form-label span').prop('hidden', false); + $filterWebhooks.find('button').prop('disabled', true); + $filterWebhooks.find('.results').css('color', '#AAA'); + }); + + /** + * Event: Delete Webhook Button "Click" + */ + $webhooks.on('click', '#delete-webhook', () => { + const webhookId = $id.val(); + const buttons = [ + { + text: lang('cancel'), + click: () => { + $('#message-box').dialog('close'); + } + }, + { + text: lang('delete'), + click: () => { + remove(webhookId); + $('#message-box').dialog('close'); + } + } + ]; + + App.Utils.Message.show(lang('delete_webhook'), lang('delete_record_prompt'), buttons); + }); + } + + /** + * Save webhook record to database. + * + * @param {Object} webhook Contains the webhook record data. If an 'id' value is provided + * then the update operation is going to be executed. + */ + function save(webhook) { + App.Http.Webhooks.save(webhook).then((response) => { + App.Layouts.Backend.displayNotification(lang('webhook_saved')); + resetForm(); + $filterWebhooks.find('.key').val(''); + filter('', response.id, true); + }); + } + + /** + * Delete a webhook record from database. + * + * @param {Number} id Record ID to be deleted. + */ + function remove(id) { + App.Http.Webhooks.destroy(id).then(() => { + App.Layouts.Backend.displayNotification(lang('webhook_deleted')); + resetForm(); + filter($filterWebhooks.find('.key').val()); + }); + } + + /** + * Validates a webhook record. + * + * @return {Boolean} Returns the validation result. + */ + function validate() { + $webhooks.find('.is-invalid').removeClass('is-invalid'); + $webhooks.find('.form-message').removeClass('alert-danger').hide(); + + try { + // Validate required fields. + let missingRequired = false; + + $webhooks.find('.required').each((index, requiredField) => { + if (!$(requiredField).val()) { + $(requiredField).addClass('is-invalid'); + missingRequired = true; + } + }); + + if (missingRequired) { + throw new Error(lang('fields_are_required')); + } + + return true; + } catch (error) { + $webhooks.find('.form-message').addClass('alert-danger').text(error.message).show(); + return false; + } + } + + /** + * Resets the webhook tab form back to its initial state. + */ + function resetForm() { + $filterWebhooks.find('.selected').removeClass('selected'); + $filterWebhooks.find('button').prop('disabled', false); + $filterWebhooks.find('.results').css('color', ''); + + $webhooks.find('.record-details').find('input, select, textarea').val('').prop('disabled', true); + $webhooks.find('.record-details .form-label span').prop('hidden', true); + $webhooks.find('.record-details h3 a').remove(); + + $webhooks.find('.add-edit-delete-group').show(); + $webhooks.find('.save-cancel-group').hide(); + $('#edit-webhook, #delete-webhook').prop('disabled', true); + + $webhooks.find('.record-details .is-invalid').removeClass('is-invalid'); + $webhooks.find('.record-details .form-message').hide(); + + $actions.find('input:checkbox').prop('checked', false); + } + + /** + * Display a webhook record into the webhook form. + * + * @param {Object} webhook Contains the webhook record data. + */ + function display(webhook) { + $id.val(webhook.id); + $name.val(webhook.name); + $url.val(webhook.url); + $secretToken.val(webhook.secret_token); + $isSslVerified.prop('checked', Boolean(Number(webhook.is_ssl_verified))); + + $actions.find('input:checkbox').prop('checked', false); + + if (webhook.actions && webhook.actions.length) { + const actions = webhook.actions.split(','); + actions.forEach((action) => $(`[data-action="${action}"]`).prop('checked', true)); + } + } + + /** + * Filters webhook records depending a string keyword. + * + * @param {String} keyword This is used to filter the webhook records of the database. + * @param {Number} selectId Optional, if set then after the filter operation the record with this + * ID will be selected (but not displayed). + * @param {Boolean} show Optional (false), if true then the selected record will be displayed on the form. + */ + function filter(keyword, selectId = null, show = false) { + App.Http.Webhooks.search(keyword, filterLimit).then((response) => { + filterResults = response; + + $filterWebhooks.find('.results').empty(); + + response.forEach((webhook) => { + $filterWebhooks.find('.results').append(getFilterHtml(webhook)).append($('
')); + }); + + if (response.length === 0) { + $filterWebhooks.find('.results').append( + $('', { + 'text': lang('no_records_found') + }) + ); + } else if (response.length === filterLimit) { + $('