Add support for dynamic webhook definition in the settings page (#581)

This commit is contained in:
Alex Tselegidis 2022-06-19 18:05:45 +01:00
parent 80cc4f9d5d
commit 0d5e60cdb7
55 changed files with 2883 additions and 221 deletions

View File

@ -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 */

View File

@ -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';

View File

@ -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,
]);

View File

@ -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,
]);

View File

@ -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']

View File

@ -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)
{

View File

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

View File

@ -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,
]);

View File

@ -139,7 +139,7 @@ class Console extends EA_Controller {
continue;
}
Google::sync($provider['id']);
Google::sync((string)$provider['id']);
}
}

View File

@ -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,
]);

View File

@ -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,
]);

View File

@ -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,
]);

View File

@ -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,
]);

View File

@ -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,
]);

View File

@ -0,0 +1,248 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Online Appointment Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @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);
}
}
}

View File

@ -0,0 +1,219 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Online Appointment Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @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);
}
}
}

View File

@ -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 {
/**

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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());
}
}
}

View File

@ -0,0 +1,92 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Open Source Web Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @copyright Copyright (c) 2013 - 2020, Alex Tselegidis
* @license http://opensource.org/licenses/GPL-3.0 - GPLv3
* @link http://easyappointments.org
* @since v1.4.0
* ---------------------------------------------------------------------------- */
use 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());
}
}
}

View File

@ -0,0 +1,85 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Online Appointment Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @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');
}
}
}

View File

@ -0,0 +1,48 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Online Appointment Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @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');
}
}
}

View File

@ -0,0 +1,323 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/* ----------------------------------------------------------------------------
* Easy!Appointments - Online Appointment Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @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.
}
}

View File

@ -0,0 +1,140 @@
<?php extend('layouts/backend_layout') ?>
<?php section('content') ?>
<div class="container-fluid backend-page" id="webhooks-page">
<div class="row" id="webhooks">
<div id="filter-webhooks" class="filter-records col col-12 col-md-5">
<form class="mb-4">
<div class="input-group">
<input type="text" class="key form-control">
<button class="filter btn btn-outline-secondary" type="submit"
data-tippy-content="<?= lang('filter') ?>">
<i class="fas fa-search"></i>
</button>
</div>
</form>
<h3><?= lang('webhooks') ?></h3>
<div class="results"></div>
</div>
<div class="record-details column col-12 col-md-5">
<div class="btn-toolbar mb-4">
<div class="add-edit-delete-group btn-group">
<button id="add-webhook" class="btn btn-primary">
<i class="fas fa-plus-square me-2"></i>
<?= lang('add') ?>
</button>
<button id="edit-webhook" class="btn btn-outline-secondary" disabled="disabled">
<i class="fas fa-edit me-2"></i>
<?= lang('edit') ?>
</button>
<button id="delete-webhook" class="btn btn-outline-secondary" disabled="disabled">
<i class="fas fa-trash-alt me-2"></i>
<?= lang('delete') ?>
</button>
</div>
<div class="save-cancel-group" style="display:none;">
<button id="save-webhook" class="btn btn-primary">
<i class="fas fa-check-square me-2"></i>
<?= lang('save') ?>
</button>
<button id="cancel-webhook" class="btn btn-secondary">
<?= lang('cancel') ?>
</button>
</div>
</div>
<h3><?= lang('details') ?></h3>
<div class="form-message alert" style="display:none;"></div>
<input type="hidden" id="id">
<div class="mb-3">
<label class="form-label" for="name">
<?= lang('name') ?>
<span class="text-danger" hidden>*</span>
</label>
<input id="name" class="form-control required" maxlength="128" disabled>
</div>
<div class="mb-3">
<label class="form-label" for="duration">
<?= lang('url') ?>
<span class="text-danger" hidden>*</span>
</label>
<input id="url" class="form-control required" disabled>
</div>
<div class="mb-3">
<label class="form-label" for="secret-token">
<?= lang('secret_token') ?>
</label>
<input id="secret-token" class="form-control" disabled>
</div>
<div class="mb-3">
<label class="form-label" for="notes">
<?= lang('notes') ?>
</label>
<textarea id="notes" rows="4" class="form-control" disabled></textarea>
</div>
<div class="border raounded mb-3 p-3">
<label class="form-label mb-3" for="actions">
<?= lang('actions') ?>
</label>
<div id="actions">
<?php foreach (vars('available_actions') as $available_action): ?>
<div class="mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox"
id="include-<?= str_replace('_', '-', $available_action) ?>"
data-action="<?= $available_action ?>">
<label class="form-check-label"
for="include-<?= str_replace('_', '-', $available_action) ?>">
<?= lang($available_action) ?>
</label>
</div>
</div>
<?php endforeach ?>
</div>
</div>
<div class="border rounded mb-3 p-3">
<label class="form-label mb-3">
<?= lang('options') ?>
</label>
<div class="">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="is-ssl-verified">
<label class="form-check-label" for="is-ssl-verified">
<?= lang('verify_ssl') ?>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<?php section('content') ?>
<?php section('scripts') ?>
<script src="<?= asset_url('assets/js/utils/message.js') ?>"></script>
<script src="<?= asset_url('assets/js/utils/validation.js') ?>"></script>
<script src="<?= asset_url('assets/js/utils/url.js') ?>"></script>
<script src="<?= asset_url('assets/js/http/webhooks_http_client.js') ?>"></script>
<script src="<?= asset_url('assets/js/pages/webhooks.js') ?>"></script>
<?php section('scripts') ?>

View File

@ -0,0 +1,133 @@
/* ----------------------------------------------------------------------------
* Easy!Appointments - Online Appointment Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @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
};
})();

383
assets/js/pages/webhooks.js Normal file
View File

@ -0,0 +1,383 @@
/* ----------------------------------------------------------------------------
* Easy!Appointments - Online Appointment Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @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($('<hr/>'));
});
if (response.length === 0) {
$filterWebhooks.find('.results').append(
$('<em/>', {
'text': lang('no_records_found')
})
);
} else if (response.length === filterLimit) {
$('<button/>', {
'type': 'button',
'class': 'btn btn-outline-secondary w-100 load-more text-center',
'text': lang('load_more'),
'click': () => {
filterLimit += 20;
filter(keyword, selectId, show);
}
}).appendTo('#filter-webhooks .results');
}
if (selectId) {
select(selectId, show);
}
});
}
/**
* Get Filter HTML
*
* Get a webhook row HTML code that is going to be displayed on the filter results list.
*
* @param {Object} webhook Contains the webhook record data.
*
* @return {String} The HTML code that represents the record on the filter results list.
*/
function getFilterHtml(webhook) {
const name = webhook.name;
const actionCount = webhook.actions && webhook.actions.length ? webhook.actions.split(',').length : 0;
const info = `${actionCount} ${lang('actions')}`;
return $('<div/>', {
'class': 'webhook-row entry',
'data-id': webhook.id,
'html': [
$('<strong/>', {
'text': name
}),
$('<br/>'),
$('<small/>', {
'class': 'text-muted',
'text': info
}),
$('<br/>')
]
});
}
/**
* Select a specific record from the current filter results. If the webhook id does not exist
* in the list then no record will be selected.
*
* @param {Number} id The record id to be selected from the filter results.
* @param {Boolean} show Optional (false), if true then the method will display the record on the form.
*/
function select(id, show = false) {
$filterWebhooks.find('.selected').removeClass('selected');
$filterWebhooks.find('.webhook-row[data-id="' + id + '"]').addClass('selected');
if (show) {
const webhook = filterResults.find((filterResult) => Number(filterResult.id) === Number(id));
display(webhook);
$('#edit-webhook, #delete-webhook').prop('disabled', false);
}
}
/**
* Initialize the module.
*/
function initialize() {
resetForm();
filter('');
addEventListeners();
}
document.addEventListener('DOMContentLoaded', initialize);
return {
filter,
save,
remove,
getFilterHtml,
resetForm,
select
};
})();

View File

@ -27,6 +27,7 @@ tags:
- name: services
- name: settings
- name: unavailabilities
- name: webhooks
paths:
/availabilities:
get:
@ -62,8 +63,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/appointments:
get:
tags:
@ -115,8 +116,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
post:
tags:
- appointments
@ -144,8 +145,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/appointments/{appointmentId}:
get:
tags:
@ -175,8 +176,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
put:
tags:
- appointments
@ -212,8 +213,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
delete:
tags:
- appointments
@ -238,8 +239,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/unavailabilities:
get:
tags:
@ -312,8 +313,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/unavailabilities/{unavailabilityId}:
get:
tags:
@ -343,8 +344,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
put:
tags:
- unavailabilities
@ -380,8 +381,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
delete:
tags:
- unavailabilities
@ -406,8 +407,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/customers:
get:
tags:
@ -454,8 +455,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
post:
tags:
- customers
@ -482,8 +483,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
x-codegen-request-body-name: body
/customers/{customerId}:
get:
@ -514,8 +515,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
put:
tags:
- customers
@ -551,8 +552,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
delete:
tags:
- customers
@ -577,8 +578,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/services:
get:
tags:
@ -625,8 +626,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
post:
tags:
- services
@ -654,8 +655,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/services/{serviceId}:
get:
tags:
@ -685,8 +686,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
put:
tags:
- services
@ -722,8 +723,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
delete:
tags:
- services
@ -748,8 +749,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/categories:
get:
tags:
@ -796,8 +797,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
post:
tags:
- categories
@ -825,8 +826,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/categories/{categoryId}:
get:
tags:
@ -856,8 +857,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
put:
tags:
- categories
@ -893,8 +894,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
delete:
tags:
- categories
@ -919,8 +920,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/admins:
get:
tags:
@ -967,8 +968,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
post:
tags:
- admins
@ -996,8 +997,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/admins/{adminId}:
get:
tags:
@ -1027,8 +1028,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
put:
tags:
- admins
@ -1064,8 +1065,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
delete:
tags:
- admins
@ -1090,8 +1091,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/providers:
get:
tags:
@ -1138,8 +1139,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
post:
tags:
- providers
@ -1167,8 +1168,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/providers/{providerId}:
get:
tags:
@ -1198,8 +1199,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
put:
tags:
- providers
@ -1235,8 +1236,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
delete:
tags:
- providers
@ -1261,8 +1262,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/secretaries:
get:
tags:
@ -1309,8 +1310,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
post:
tags:
- secretaries
@ -1338,8 +1339,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/secretaries/{secretaryId}:
get:
tags:
@ -1369,8 +1370,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
put:
tags:
- secretaries
@ -1406,8 +1407,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
delete:
tags:
- secretaries
@ -1426,8 +1427,8 @@ paths:
'404':
description: Not Found
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/settings:
get:
tags:
@ -1474,8 +1475,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/settings/{settingName}:
get:
tags:
@ -1505,8 +1506,8 @@ paths:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
put:
tags:
- settings
@ -1542,8 +1543,179 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: []
- BasicAuth: []
- BearerToken: [ ]
- BasicAuth: [ ]
/webhooks:
get:
tags:
- webhooks
summary: Get all webhooks
parameters:
- name: page
in: query
schema:
type: integer
- name: length
in: query
schema:
type: integer
- name: sort
in: query
schema:
type: string
- name: q
in: query
schema:
type: string
- name: fields
in: query
schema:
type: string
- name: with
in: query
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookCollection'
'401':
description: Unauthorized
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: [ ]
- BasicAuth: [ ]
post:
tags:
- webhooks
summary: Create a webhook
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookPayload'
required: true
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookRecord'
'401':
description: Unauthorized
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: [ ]
- BasicAuth: [ ]
/webhooks/{webhookId}:
get:
tags:
- webhooks
summary: Get a webhook
parameters:
- name: webhookId
in: path
required: true
schema:
type: integer
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookRecord'
'401':
description: Unauthorized
'404':
description: Not Found
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: [ ]
- BasicAuth: [ ]
put:
tags:
- webhooks
summary: Update a webhook
parameters:
- name: webhookId
in: path
required: true
schema:
type: integer
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookPayload'
required: true
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookRecord'
'401':
description: Unauthorized
'404':
description: Not Found
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
x-codegen-request-body-name: body
security:
- BearerToken: [ ]
- BasicAuth: [ ]
delete:
tags:
- webhooks
summary: Delete a webhook
parameters:
- name: webhookId
in: path
required: true
schema:
type: integer
responses:
'204':
description: No Content
'401':
description: Unauthorized
'404':
description: Not Found
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- BearerToken: [ ]
- BasicAuth: [ ]
components:
schemas:
Availabilities:
@ -2019,7 +2191,7 @@ components:
notes: This is a test provider.
timezone: UTC
language: english
services: []
services: [ ]
settings:
username: chrisdoe
password: Password@123
@ -2035,23 +2207,23 @@ components:
monday:
start: '09:00'
end: '17:00'
breaks: []
breaks: [ ]
tuesday:
start: '09:00'
end: '17:00'
breaks: []
breaks: [ ]
wednesday:
start: '09:00'
end: '17:00'
breaks: []
breaks: [ ]
thursday:
start: '09:00'
end: '17:00'
breaks: []
breaks: [ ]
friday:
start: '09:00'
end: '17:00'
breaks: []
breaks: [ ]
saturday: null
ProviderPayload:
type: object
@ -2118,7 +2290,7 @@ components:
notes: This is a test provider.
timezone: UTC
language: english
services: []
services: [ ]
settings:
username: chrisdoe
password: Password@123
@ -2134,23 +2306,23 @@ components:
monday:
start: '09:00'
end: '17:00'
breaks: []
breaks: [ ]
tuesday:
start: '09:00'
end: '17:00'
breaks: []
breaks: [ ]
wednesday:
start: '09:00'
end: '17:00'
breaks: []
breaks: [ ]
thursday:
start: '09:00'
end: '17:00'
breaks: []
breaks: [ ]
friday:
start: '09:00'
end: '17:00'
breaks: []
breaks: [ ]
saturday: null
ProviderCollection:
type: array
@ -2212,7 +2384,7 @@ components:
notes: This is a test service.
timezone: UTC
language: english
providers: []
providers: [ ]
settings:
username: jessydoe
password: Password@123
@ -2271,7 +2443,7 @@ components:
notes: This is a test service.
timezone: UTC
language: english
providers: []
providers: [ ]
settings:
username: jessydoe
password: Password@123
@ -2302,6 +2474,57 @@ components:
type: array
items:
$ref: '#/components/schemas/SettingRecord'
WebhookRecord:
type: object
properties:
id:
type: integer
name:
type: string
url:
type: string
actions:
type: string
secretToken:
type: string
isSslVerified:
type: boolean
notes:
type: string
example:
id: 1
name: Test Webhook
url: https://example.org/webhook?withTestQueryParam=Value
actions: appointment_create,appointment_update,customer_delete,category_create
secretToken: SecureSecretTokenHere
isSslVerified: true
notes: This is a webhook.
WebhookPayload:
type: object
properties:
name:
type: string
url:
type: string
actions:
type: string
secretToken:
type: string
isSslVerified:
type: boolean
notes:
type: string
example:
name: Test Webhook
url: https://example.org/webhook?withTestQueryParam=Value
actions: appointment_create,appointment_update,customer_delete,category_create
secretToken: SecureSecretTokenHere
isSslVerified: true
notes: This is a webhook.
WebhookCollection:
type: array
items:
$ref: '#/components/schemas/WebhookRecord'
ErrorResponse:
type: object
properties: