项目初始化

This commit is contained in:
Jay Huang 2024-08-01 10:38:28 +08:00
parent 5d1d6b9cd5
commit 201a6f2a9e
27488 changed files with 3013290 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
#缓存文件
/storage/
#配置文件
/config.php
# misc
.DS_Store

1
application/.htaccess Executable file
View File

@ -0,0 +1 @@
Deny from all

18
application/config/app.php Executable file
View File

@ -0,0 +1,18 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/*
|--------------------------------------------------------------------------
| App Configuration
|--------------------------------------------------------------------------
|
| Declare some of the global config values of Easy!Appointments.
|
*/
$config['version'] = '1.5.0'; // This must be changed manually.
$config['url'] = Config::BASE_URL;
$config['debug'] = Config::DEBUG_MODE;
$config['cache_busting_token'] = 'ZV954';

133
application/config/autoload.php Executable file
View File

@ -0,0 +1,133 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/*
| -------------------------------------------------------------------
| AUTO-LOADER
| -------------------------------------------------------------------
| This file specifies which systems should be loaded by default.
|
| In order to keep the framework as light-weight as possible only the
| absolute minimal resources are loaded by default. For example,
| the database is not connected to automatically since no assumption
| is made regarding whether you intend to use it. This file lets
| you globally define which systems you would like loaded with every
| request.
|
| -------------------------------------------------------------------
| Instructions
| -------------------------------------------------------------------
|
| These are the things you can load automatically:
|
| 1. Packages
| 2. Libraries
| 3. Helper files
| 4. Custom config files
| 5. Language files
| 6. Models
|
*/
/*
| -------------------------------------------------------------------
| Auto-load Packages
| -------------------------------------------------------------------
| Prototype:
|
| $autoload['packages'] = array(APPPATH.'third_party', '/usr/local/shared');
|
*/
$autoload['packages'] = [];
/*
| -------------------------------------------------------------------
| Auto-load Libraries
| -------------------------------------------------------------------
| These are the classes located in the system/libraries folder
| or in your application/libraries folder.
|
| Prototype:
|
| $autoload['libraries'] = array('database', 'session', 'xmlrpc');
*/
$autoload['libraries'] = ['database', 'session'];
/*
| -------------------------------------------------------------------
| Auto-load Helper Files
| -------------------------------------------------------------------
| Prototype:
|
| $autoload['helper'] = array('url', 'file');
*/
$autoload['helper'] = [
'array',
'asset',
'config',
'date',
'debug',
'env',
'file',
'html',
'http',
'installation',
'language',
'password',
'path',
'permission',
'rate_limit',
'routes',
'session',
'setting',
'string',
'url',
'validation'
];
/*
| -------------------------------------------------------------------
| Auto-load Config files
| -------------------------------------------------------------------
| Prototype:
|
| $autoload['config'] = array('config1', 'config2');
|
| NOTE: This item is intended for use ONLY if you have created custom
| config files. Otherwise, leave it blank.
|
*/
$autoload['config'] = ['app', 'google', 'email'];
/*
| -------------------------------------------------------------------
| Auto-load Language files
| -------------------------------------------------------------------
| Prototype:
|
| $autoload['language'] = array('lang1', 'lang2');
|
| NOTE: Do not include the "_lang" part of your file. For example
| "codeigniter_lang.php" would be referenced as array('codeigniter');
|
*/
$autoload['language'] = [];
/*
| -------------------------------------------------------------------
| Auto-load Models
| -------------------------------------------------------------------
| Prototype:
|
| $autoload['model'] = array('model1', 'model2');
|
*/
$autoload['model'] = [];
/* End of file autoload.php */
/* Location: ./application/config/autoload.php */

402
application/config/config.php Executable file
View File

@ -0,0 +1,402 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/*
|--------------------------------------------------------------------------
| Base Site URL
|--------------------------------------------------------------------------
|
| URL to your CodeIgniter root. Typically this will be your base URL,
| WITH a trailing slash:
|
| http://example.com/
|
| If this is not set then CodeIgniter will guess the protocol, domain and
| path to your installation.
|
*/
$protocol =
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ||
(isset($_SERVER['SERVER_PORT']) && (int) $_SERVER['SERVER_PORT'] === 443) ||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
? 'https://'
: 'http://';
$domain = $_SERVER['HTTP_HOST'] ?? 'localhost';
$request_uri = dirname($_SERVER['SCRIPT_NAME']);
if ($request_uri === '.') {
$request_uri = '';
}
$config['base_url'] = trim($protocol . $domain . $request_uri, '/');
/*
|--------------------------------------------------------------------------
| Index File
|--------------------------------------------------------------------------
|
| Typically this will be your index.php file, unless you've renamed it to
| something else. If you are using mod_rewrite to remove the page set this
| variable so that it is blank.
|
*/
$config['index_page'] = 'index.php';
/*
|--------------------------------------------------------------------------
| URI PROTOCOL
|--------------------------------------------------------------------------
|
| This item determines which server global should be used to retrieve the
| URI string. The default setting of 'AUTO' works for most servers.
| If your links do not seem to work, try one of the other delicious flavors:
|
| 'AUTO' Default - auto detects
| 'PATH_INFO' Uses the PATH_INFO
| 'QUERY_STRING' Uses the QUERY_STRING
| 'REQUEST_URI' Uses the REQUEST_URI
| 'ORIG_PATH_INFO' Uses the ORIG_PATH_INFO
|
*/
$config['uri_protocol'] = 'AUTO';
/*
|--------------------------------------------------------------------------
| URL suffix
|--------------------------------------------------------------------------
|
| This option allows you to add a suffix to all URLs generated by CodeIgniter.
| For more information please see the user guide:
|
| http://codeigniter.com/user_guide/general/urls.html
*/
$config['url_suffix'] = '';
/*
|--------------------------------------------------------------------------
| Default Language
|--------------------------------------------------------------------------
|
| This determines which set of language files should be used. Make sure
| there is an available translation if you intend to use something other
| than english.
|
*/
$languages = [
'zh' => 'Chinese(Simplified)',
'hk' => 'Chinese(Traditional)',
'en' => 'English',
];
$config['language_codes'] = $languages;
$language_code = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2) : 'en';
$config['language'] = isset($_SERVER['HTTP_ACCEPT_LANGUAGE'], $languages[$language_code])
? $languages[$language_code]
: Config::LANGUAGE;
$config['language_code'] = array_search($config['language'], $languages) ?: 'en';
/*
|--------------------------------------------------------------------------
| Available Languages
|--------------------------------------------------------------------------
|
| Each item of this array must be a directory with the translation files in
| the /application/language directory. The users will be able to select one
| of these languages.
|
*/
$config['available_languages'] = [
'Chinese(Simplified)',
'Chinese(Traditional)',
'English',
];
/*
|--------------------------------------------------------------------------
| Default Character Set
|--------------------------------------------------------------------------
|
| This determines which character set is used by default in various methods
| that require a character set to be provided.
|
*/
$config['charset'] = 'UTF-8';
/*
|--------------------------------------------------------------------------
| Enable/Disable System Hooks
|--------------------------------------------------------------------------
|
| If you would like to use the 'hooks' feature you must enable it by
| setting this variable to TRUE (boolean). See the user guide for details.
|
*/
$config['enable_hooks'] = true;
/*
|--------------------------------------------------------------------------
| Class Extension Prefix
|--------------------------------------------------------------------------
|
| This item allows you to set the filename/classname prefix when extending
| native libraries. For more information please see the user guide:
|
| http://codeigniter.com/user_guide/general/core_classes.html
| http://codeigniter.com/user_guide/general/creating_libraries.html
|
*/
$config['subclass_prefix'] = 'EA_';
/*
|--------------------------------------------------------------------------
| Allowed URL Characters
|--------------------------------------------------------------------------
|
| This lets you specify with a regular expression which characters are permitted
| within your URLs. When someone tries to submit a URL with disallowed
| characters they will get a warning message.
|
| As a security measure you are STRONGLY encouraged to restrict URLs to
| as few characters as possible. By default only these are allowed: a-z 0-9~%.:_-
|
| Leave blank to allow all characters -- but only if you are insane.
|
| DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
|
*/
$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-';
/*
|--------------------------------------------------------------------------
| Enable Query Strings
|--------------------------------------------------------------------------
|
| By default CodeIgniter uses search-engine friendly segment based URLs:
| example.com/who/what/where/
|
| By default CodeIgniter enables access to the $_GET array. If for some
| reason you would like to disable it, set 'allow_get_array' to FALSE.
|
| You can optionally enable standard query string based URLs:
| example.com?who=me&what=something&where=here
|
| Options are: TRUE or FALSE (boolean)
|
| The other items let you set the query string 'words' that will
| invoke your controllers and its functions:
| example.com/index.php?c=controller&m=function
|
| Please note that some of the helpers won't work as expected when
| this feature is enabled, since CodeIgniter is designed primarily to
| use segment based URLs.
|
*/
$config['allow_get_array'] = true;
$config['enable_query_strings'] = false;
$config['controller_trigger'] = 'c';
$config['function_trigger'] = 'm';
$config['directory_trigger'] = 'd'; // experimental not currently in use
/*
|--------------------------------------------------------------------------
| Error Logging Threshold
|--------------------------------------------------------------------------
|
| If you have enabled error logging, you can set an error threshold to
| determine what gets logged. Threshold options are:
| You can enable error logging by setting a threshold over zero. The
| threshold determines what gets logged. Threshold options are:
|
| 0 = Disables logging, Error logging TURNED OFF
| 1 = Error Messages (including PHP errors)
| 2 = Debug Messages
| 3 = Informational Messages
| 4 = All Messages
|
| For a live site you'll usually only enable Errors (1) to be logged otherwise
| your log files will fill up very fast.
|
*/
$config['log_threshold'] = 1;
/*
|--------------------------------------------------------------------------
| Error Logging Directory Path
|--------------------------------------------------------------------------
|
| Leave this BLANK unless you would like to set something other than the default
| application/logs/ folder. Use a full server path with trailing slash.
|
*/
$config['log_path'] = __DIR__ . '/../../storage/logs/';
/*
|--------------------------------------------------------------------------
| Date Format for Logs
|--------------------------------------------------------------------------
|
| Each item that is logged has an associated date. You can use PHP date
| codes to set your own date formatting
|
*/
$config['log_date_format'] = 'Y-m-d H:i:s';
/*
|--------------------------------------------------------------------------
| Cache Directory Path
|--------------------------------------------------------------------------
|
| Leave this BLANK unless you would like to set something other than the default
| system/cache/ folder. Use a full server path with trailing slash.
|
*/
$config['cache_path'] = __DIR__ . '/../../storage/cache/';
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| If you use the Encryption class or the Session class you
| MUST set an encryption key. See the user guide for info.
|
*/
$config['encryption_key'] = base64_encode(APPPATH);
/*
|--------------------------------------------------------------------------
| Session Variables
|--------------------------------------------------------------------------
|
| 'sess_cookie_name' = the name you want for the cookie
| 'sess_expiration' = the number of SECONDS you want the session to last.
| by default sessions last 7200 seconds (two hours). Set to zero for no expiration.
| 'sess_expire_on_close' = Whether to cause the session to expire automatically
| when the browser window is closed
| 'sess_encrypt_cookie' = Whether to encrypt the cookie
| 'sess_use_database' = Whether to save the session data to a database
| 'sess_table_name' = The name of the session database table
| 'sess_match_ip' = Whether to match the user's IP address when reading the session data
| 'sess_match_useragent' = Whether to match the User Agent when reading the session data
| 'sess_time_to_update' = how many seconds between CI refreshing Session Information
|
*/
$config['sess_driver'] = 'files';
$config['sess_cookie_name'] = 'ea_session';
$config['sess_expiration'] = 7200;
$config['sess_save_path'] = __DIR__ . '/../../storage/sessions';
$config['sess_match_ip'] = false;
$config['sess_time_to_update'] = 300;
$config['sess_regenerate_destroy'] = true;
/*
|--------------------------------------------------------------------------
| Cookie Related Variables
|--------------------------------------------------------------------------
|
| 'cookie_prefix' = Set a prefix if you need to avoid collisions
| 'cookie_domain' = Set to .your-domain.com for site-wide cookies
| 'cookie_path' = Typically will be a forward slash
| 'cookie_secure' = Cookies will only be set if a secure HTTPS connection exists.
|
*/
$config['cookie_prefix'] = '';
$config['cookie_domain'] = '';
$config['cookie_path'] = '/';
$config['cookie_secure'] = strpos($config['base_url'], 'https') !== false;
/*
|--------------------------------------------------------------------------
| Cross Site Request Forgery
|--------------------------------------------------------------------------
| Enables a CSRF cookie token to be set. When set to TRUE, token will be
| checked on a submitted form. If you are accepting user data, it is strongly
| recommended CSRF protection be enabled.
|
| 'csrf_token_name' = The token name
| 'csrf_cookie_name' = The cookie name
| 'csrf_expire' = The number in seconds the token should expire.
*/
$config['csrf_protection'] = true;
$config['csrf_token_name'] = 'csrf_token';
$config['csrf_cookie_name'] = 'csrf_cookie';
$config['csrf_expire'] = 7200;
$config['csrf_exclude_uris'] = ['api/v1/.*', 'booking/.*', 'booking_cancellation/.*', 'booking_confirmation/.*'];
/*
|--------------------------------------------------------------------------
| Output Compression
|--------------------------------------------------------------------------
|
| Enables Gzip output compression for faster page loads. When enabled,
| the output class will test whether your server supports Gzip.
| Even if it does, however, not all browsers support compression
| so enable only if you are reasonably sure your visitors can handle it.
|
| VERY IMPORTANT: If you are getting a blank page when compression is enabled it
| means you are prematurely outputting something to your browser. It could
| even be a line of whitespace at the end of one of your scripts. For
| compression to work, nothing can be sent before the output buffer is called
| by the output class. Do not 'echo' any values with compression enabled.
|
*/
$config['compress_output'] = false;
/*
|--------------------------------------------------------------------------
| Master Time Reference
|--------------------------------------------------------------------------
|
| Options are 'local' or 'gmt'. This pref tells the system whether to use
| your server's local time as the master 'now' reference, or convert it to
| GMT. See the 'date helper' page of the user guide for information
| regarding date handling.
|
*/
$config['time_reference'] = 'local';
/*
|--------------------------------------------------------------------------
| Rewrite PHP Short Tags
|--------------------------------------------------------------------------
|
| If your PHP installation does not have short tag support enabled CI
| can rewrite the tags on-the-fly, enabling you to utilize that syntax
| in your view files. Options are TRUE or FALSE (boolean)
|
*/
$config['rewrite_short_tags'] = false;
/*
|--------------------------------------------------------------------------
| Reverse Proxy IPs
|--------------------------------------------------------------------------
|
| If your server is behind a reverse proxy, you must whitelist the proxy IP
| addresses from which CodeIgniter should trust the HTTP_X_FORWARDED_FOR
| header in order to properly identify the visitor's IP address.
| Comma-delimited, e.g. '10.0.1.200,10.0.1.201'
|
*/
$config['proxy_ips'] = '';
/*
|--------------------------------------------------------------------------
| Rate Limiting
|--------------------------------------------------------------------------
|
| Toggle the rate limiting feature in your application. Using rate limiting
| will control the number of requests a client can sent to the app.
|
*/
$config['rate_limiting'] = true;
/* End of file config.php */
/* Location: ./application/config/config.php */

157
application/config/constants.php Executable file
View File

@ -0,0 +1,157 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/*
|--------------------------------------------------------------------------
| File and Directory Modes
|--------------------------------------------------------------------------
|
| These prefs are used when checking and setting modes when working
| with the file system. The defaults are fine on servers with proper
| security, but you may wish (or even need) to change the values in
| certain environments (Apache running a separate process for each
| user, PHP under CGI with Apache suEXEC, etc.). Octal values should
| always be used to set the mode correctly.
|
*/
const FILE_READ_MODE = 0644;
const FILE_WRITE_MODE = 0666;
const DIR_READ_MODE = 0755;
const DIR_WRITE_MODE = 0777;
/*
|--------------------------------------------------------------------------
| File Stream Modes
|--------------------------------------------------------------------------
|
| These modes are used when working with fopen()/popen()
|
*/
const FOPEN_READ = 'rb';
const FOPEN_READ_WRITE = 'r+b';
const FOPEN_WRITE_CREATE_DESTRUCTIVE = 'wb'; // truncates existing file data, use with care
const FOPEN_READ_WRITE_CREATE_DESTRUCTIVE = 'w+b'; // truncates existing file data, use with care
const FOPEN_WRITE_CREATE = 'ab';
const FOPEN_READ_WRITE_CREATE = 'a+b';
const FOPEN_WRITE_CREATE_STRICT = 'xb';
const FOPEN_READ_WRITE_CREATE_STRICT = 'x+b';
/*
|--------------------------------------------------------------------------
| Application Data
|--------------------------------------------------------------------------
|
| These constants are used globally from the application when handling data.
|
*/
const DB_SLUG_CUSTOMER = 'customer';
const DB_SLUG_PROVIDER = 'provider';
const DB_SLUG_ADMIN = 'admin';
const DB_SLUG_SECRETARY = 'secretary';
const FILTER_TYPE_ALL = 'all';
const FILTER_TYPE_PROVIDER = 'provider';
const FILTER_TYPE_SERVICE = 'service';
const AJAX_SUCCESS = 'SUCCESS';
const AJAX_FAILURE = 'FAILURE';
const SETTINGS_SYSTEM = 'SETTINGS_SYSTEM';
const SETTINGS_USER = 'SETTINGS_USER';
const PRIV_VIEW = 1;
const PRIV_ADD = 2;
const PRIV_EDIT = 4;
const PRIV_DELETE = 8;
const PRIV_APPOINTMENTS = 'appointments';
const PRIV_CUSTOMERS = 'customers';
const PRIV_SERVICES = 'services';
const PRIV_USERS = 'users';
const PRIV_SYSTEM_SETTINGS = 'system_settings';
const PRIV_USER_SETTINGS = 'user_settings';
const PRIV_WEBHOOKS = 'webhooks';
const PRIV_BLOCKED_PERIODS = 'blocked_periods';
const DATE_FORMAT_DMY = 'DMY';
const DATE_FORMAT_MDY = 'MDY';
const DATE_FORMAT_YMD = 'YMD';
const TIME_FORMAT_REGULAR = 'regular';
const TIME_FORMAT_MILITARY = 'military';
const MIN_PASSWORD_LENGTH = 7;
const MAX_PASSWORD_LENGTH = 100;
const ANY_PROVIDER = 'any-provider';
const CALENDAR_VIEW_DEFAULT = 'default';
const CALENDAR_VIEW_TABLE = 'table';
const AVAILABILITIES_TYPE_FLEXIBLE = 'flexible';
const AVAILABILITIES_TYPE_FIXED = 'fixed';
const EVENT_MINIMUM_DURATION = 5; // Minutes
const DEFAULT_COMPANY_COLOR = '#ffffff';
const LDAP_DEFAULT_FILTER = '(&(objectClass=*)(|(cn={{KEYWORD}})(sn={{KEYWORD}})(mail={{KEYWORD}})(givenName={{KEYWORD}})(uid={{KEYWORD}})))';
const LDAP_WHITELISTED_ATTRIBUTES = [
'givenname',
'cn',
'dn',
'sn',
'mail',
'telephonenumber',
'description',
'member',
'objectclass',
'objectcategory',
'instancetype',
'whencreated',
'name',
'samaccountname',
'samaccounttype',
'objectcategory',
'memberof',
'distinguishedname',
];
const LDAP_DEFAULT_FIELD_MAPPING = [
'first_name' => 'givenname',
'last_name' => 'sn',
'email' => 'mail',
'phone_number' => 'telephonenumber',
'username' => 'cn',
];
/*
|--------------------------------------------------------------------------
| Webhook Actions
|--------------------------------------------------------------------------
|
| External application endpoints can subscribe to these webhook actions.
|
*/
const WEBHOOK_APPOINTMENT_SAVE = 'appointment_save';
const WEBHOOK_APPOINTMENT_DELETE = 'appointment_delete';
const WEBHOOK_UNAVAILABILITY_SAVE = 'unavailability_save';
const WEBHOOK_UNAVAILABILITY_DELETE = 'unavailability_delete';
const WEBHOOK_CUSTOMER_SAVE = 'customer_save';
const WEBHOOK_CUSTOMER_DELETE = 'customer_delete';
const WEBHOOK_SERVICE_SAVE = 'service_save';
const WEBHOOK_SERVICE_DELETE = 'service_delete';
const WEBHOOK_SERVICE_CATEGORY_SAVE = 'service_category_save';
const WEBHOOK_SERVICE_CATEGORY_DELETE = 'service_category_delete';
const WEBHOOK_PROVIDER_SAVE = 'provider_save';
const WEBHOOK_PROVIDER_DELETE = 'provider_delete';
const WEBHOOK_SECRETARY_SAVE = 'secretary_save';
const WEBHOOK_SECRETARY_DELETE = 'secretary_delete';
const WEBHOOK_ADMIN_SAVE = 'admin_save';
const WEBHOOK_ADMIN_DELETE = 'admin_delete';
const WEBHOOK_BLOCKED_PERIOD_SAVE = 'blocked_period_save';
const WEBHOOK_BLOCKED_PERIOD_DELETE = 'blocked_period_delete';
/* End of file constants.php */
/* Location: ./application/config/constants.php */

69
application/config/database.php Executable file
View File

@ -0,0 +1,69 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/*
| -------------------------------------------------------------------
| DATABASE CONNECTIVITY SETTINGS
| -------------------------------------------------------------------
| This file will contain the settings needed to access your database.
|
| For complete instructions please consult the 'Database Connection'
| page of the User Guide.
|
| -------------------------------------------------------------------
| EXPLANATION OF VARIABLES
| -------------------------------------------------------------------
|
| ['hostname'] The hostname of your database server.
| ['username'] The username used to connect to the database
| ['password'] The password used to connect to the database
| ['database'] The name of the database you want to connect to
| ['dbdriver'] The database type. ie: mysql. Currently supported:
mysql, mysqli, postgre, odbc, mssql, sqlite, oci8
| ['dbprefix'] You can add an optional prefix, which will be added
| to the table name when using the Active Record class
| ['pconnect'] TRUE/FALSE - Whether to use a persistent connection
| ['db_debug'] TRUE/FALSE - Whether database errors should be displayed.
| ['cache_on'] TRUE/FALSE - Enables/disables query caching
| ['cachedir'] The path to the folder where cache files should be stored
| ['char_set'] The character set used in communicating with the database
| ['dbcollat'] The character collation used in communicating with the database
| NOTE: For MySQL and MySQLi databases, this setting is only used
| as a backup if your server is running PHP < 5.2.3 or MySQL < 5.0.7
| (and in table creation queries made with DB Forge).
| There is an incompatibility in PHP with mysql_real_escape_string() which
| can make your site vulnerable to SQL injection if you are using a
| multi-byte character set and are running versions lower than these.
| Sites using Latin-1 or UTF-8 database character set and collation are unaffected.
| ['swap_pre'] A default table prefix that should be swapped with the dbprefix
| ['autoinit'] Whether or not to automatically initialize the database.
| ['stricton'] TRUE/FALSE - forces 'Strict Mode' connections
| - good for ensuring strict SQL while developing
|
| The $active_group variable lets you choose which connection group to
| make active. By default there is only one group (the 'default' group).
|
| The $active_record variables lets you determine whether or not to load
| the active record class
*/
$active_group = 'default';
$query_builder = TRUE;
$db['default']['hostname'] = Config::DB_HOST;
$db['default']['username'] = Config::DB_USERNAME;
$db['default']['password'] = Config::DB_PASSWORD;
$db['default']['database'] = Config::DB_NAME;
$db['default']['dbdriver'] = 'mysqli';
$db['default']['dbprefix'] = 'ea_';
$db['default']['pconnect'] = TRUE;
$db['default']['db_debug'] = TRUE;
$db['default']['cache_on'] = FALSE;
$db['default']['cachedir'] = '';
$db['default']['char_set'] = 'utf8mb4';
$db['default']['dbcollat'] = 'utf8mb4_unicode_ci';
$db['default']['swap_pre'] = '';
$db['default']['autoinit'] = TRUE;
$db['default']['stricton'] = FALSE;
/* End of file database.php */
/* Location: ./application/config/database.php */

21
application/config/email.php Executable file
View File

@ -0,0 +1,21 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
// Add custom values by settings them to the $config array.
// Example: $config['smtp_host'] = 'smtp.gmail.com';
// @link https://codeigniter.com/user_guide/libraries/email.html
$config['useragent'] = 'Easy!Appointments';
$config['protocol'] = 'mail'; // or 'smtp'
$config['mailtype'] = 'html'; // or 'text'
// $config['smtp_debug'] = '0'; // or '1'
// $config['smtp_auth'] = TRUE; //or FALSE for anonymous relay.
// $config['smtp_host'] = '';
// $config['smtp_user'] = '';
// $config['smtp_pass'] = '';
// $config['smtp_crypto'] = 'ssl'; // or 'tls'
// $config['smtp_port'] = 25;
// $config['from_name'] = '';
// $config['from_address'] = '';
// $config['reply_to'] = '';
$config['crlf'] = "\r\n";
$config['newline'] = "\r\n";

17
application/config/google.php Executable file
View File

@ -0,0 +1,17 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/*
|--------------------------------------------------------------------------
| Google Calendar - Internal Configuration
|--------------------------------------------------------------------------
|
| Declare some of the global config values of the Google Calendar
| synchronization feature.
|
*/
$config['google_sync_feature'] = Config::GOOGLE_SYNC_FEATURE;
$config['google_client_id'] = Config::GOOGLE_CLIENT_ID;
$config['google_client_secret'] = Config::GOOGLE_CLIENT_SECRET;

15
application/config/hooks.php Executable file
View File

@ -0,0 +1,15 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/*
| -------------------------------------------------------------------------
| Hooks
| -------------------------------------------------------------------------
| This file lets you define "hooks" to extend CI without hacking the core
| files. Please see the user guide for info:
|
| http://codeigniter.com/user_guide/general/hooks.html
|
*/
/* End of file hooks.php */
/* Location: ./application/config/hooks.php */

10
application/config/index.html Executable file
View File

@ -0,0 +1,10 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

View File

@ -0,0 +1,39 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/*
|--------------------------------------------------------------------------
| Enable/Disable Migrations
|--------------------------------------------------------------------------
|
| Migrations are disabled by default but should be enabled
| whenever you intend to do a schema migration.
|
*/
$config['migration_enabled'] = TRUE;
/*
|--------------------------------------------------------------------------
| Migrations version
|--------------------------------------------------------------------------
|
| This is used to set migration version that the file system should be on.
| If you run $this->migration->latest() this is the version that schema will
| be upgraded / downgraded to.
|
*/
$config['migration_version'] = 0;
/*
|--------------------------------------------------------------------------
| Migrations Path
|--------------------------------------------------------------------------
|
| Path to your migrations folder.
| Typically, it will be within your application path.
| Also, writing permission is required within the migrations path.
|
*/
$config['migration_path'] = APPPATH . 'migrations/';
/* End of file migration.php */
/* Location: ./application/config/migration.php */

294
application/config/mimes.php Executable file
View File

@ -0,0 +1,294 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
/*
| -------------------------------------------------------------------
| MIME TYPES
| -------------------------------------------------------------------
| This file contains an array of mime types. It is used by the
| Upload class to help identify allowed file types.
|
*/
return [
'hqx' => [
'application/mac-binhex40',
'application/mac-binhex',
'application/x-binhex40',
'application/x-mac-binhex40',
],
'cpt' => 'application/mac-compactpro',
'csv' => [
'text/x-comma-separated-values',
'text/comma-separated-values',
'application/octet-stream',
'application/vnd.ms-excel',
'application/x-csv',
'text/x-csv',
'text/csv',
'application/csv',
'application/excel',
'application/vnd.msexcel',
'text/plain',
],
'bin' => [
'application/macbinary',
'application/mac-binary',
'application/octet-stream',
'application/x-binary',
'application/x-macbinary',
],
'dms' => 'application/octet-stream',
'lha' => 'application/octet-stream',
'lzh' => 'application/octet-stream',
'exe' => ['application/octet-stream', 'application/x-msdownload'],
'class' => 'application/octet-stream',
'psd' => ['application/x-photoshop', 'image/vnd.adobe.photoshop'],
'so' => 'application/octet-stream',
'sea' => 'application/octet-stream',
'dll' => 'application/octet-stream',
'oda' => 'application/oda',
'pdf' => ['application/pdf', 'application/force-download', 'application/x-download', 'binary/octet-stream'],
'ai' => ['application/pdf', 'application/postscript'],
'eps' => 'application/postscript',
'ps' => 'application/postscript',
'smi' => 'application/smil',
'smil' => 'application/smil',
'mif' => 'application/vnd.mif',
'xls' => [
'application/vnd.ms-excel',
'application/msexcel',
'application/x-msexcel',
'application/x-ms-excel',
'application/x-excel',
'application/x-dos_ms_excel',
'application/xls',
'application/x-xls',
'application/excel',
'application/download',
'application/vnd.ms-office',
'application/msword',
],
'ppt' => [
'application/powerpoint',
'application/vnd.ms-powerpoint',
'application/vnd.ms-office',
'application/msword',
],
'pptx' => [
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/x-zip',
'application/zip',
],
'wbxml' => 'application/wbxml',
'wmlc' => 'application/wmlc',
'dcr' => 'application/x-director',
'dir' => 'application/x-director',
'dxr' => 'application/x-director',
'dvi' => 'application/x-dvi',
'gtar' => 'application/x-gtar',
'gz' => 'application/x-gzip',
'gzip' => 'application/x-gzip',
'php' => [
'application/x-httpd-php',
'application/php',
'application/x-php',
'text/php',
'text/x-php',
'application/x-httpd-php-source',
],
'php4' => 'application/x-httpd-php',
'php3' => 'application/x-httpd-php',
'phtml' => 'application/x-httpd-php',
'phps' => 'application/x-httpd-php-source',
'js' => ['application/x-javascript', 'text/plain'],
'swf' => 'application/x-shockwave-flash',
'sit' => 'application/x-stuffit',
'tar' => 'application/x-tar',
'tgz' => ['application/x-tar', 'application/x-gzip-compressed'],
'z' => 'application/x-compress',
'xhtml' => 'application/xhtml+xml',
'xht' => 'application/xhtml+xml',
'zip' => [
'application/x-zip',
'application/zip',
'application/x-zip-compressed',
'application/s-compressed',
'multipart/x-zip',
],
'rar' => ['application/x-rar', 'application/rar', 'application/x-rar-compressed'],
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'mpga' => 'audio/mpeg',
'mp2' => 'audio/mpeg',
'mp3' => ['audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'],
'aif' => ['audio/x-aiff', 'audio/aiff'],
'aiff' => ['audio/x-aiff', 'audio/aiff'],
'aifc' => 'audio/x-aiff',
'ram' => 'audio/x-pn-realaudio',
'rm' => 'audio/x-pn-realaudio',
'rpm' => 'audio/x-pn-realaudio-plugin',
'ra' => 'audio/x-realaudio',
'rv' => 'video/vnd.rn-realvideo',
'wav' => ['audio/x-wav', 'audio/wave', 'audio/wav'],
'bmp' => [
'image/bmp',
'image/x-bmp',
'image/x-bitmap',
'image/x-xbitmap',
'image/x-win-bitmap',
'image/x-windows-bmp',
'image/ms-bmp',
'image/x-ms-bmp',
'application/bmp',
'application/x-bmp',
'application/x-win-bitmap',
],
'gif' => 'image/gif',
'jpeg' => ['image/jpeg', 'image/pjpeg'],
'jpg' => ['image/jpeg', 'image/pjpeg'],
'jpe' => ['image/jpeg', 'image/pjpeg'],
'jp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
'j2k' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
'jpf' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
'jpg2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
'jpx' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
'jpm' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
'mj2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
'mjp2' => ['image/jp2', 'video/mj2', 'image/jpx', 'image/jpm'],
'png' => ['image/png', 'image/x-png'],
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'css' => ['text/css', 'text/plain'],
'html' => ['text/html', 'text/plain'],
'htm' => ['text/html', 'text/plain'],
'shtml' => ['text/html', 'text/plain'],
'txt' => 'text/plain',
'text' => 'text/plain',
'log' => ['text/plain', 'text/x-log'],
'rtx' => 'text/richtext',
'rtf' => 'text/rtf',
'xml' => ['application/xml', 'text/xml', 'text/plain'],
'xsl' => ['application/xml', 'text/xsl', 'text/xml'],
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'qt' => 'video/quicktime',
'mov' => 'video/quicktime',
'avi' => ['video/x-msvideo', 'video/msvideo', 'video/avi', 'application/x-troff-msvideo'],
'movie' => 'video/x-sgi-movie',
'doc' => ['application/msword', 'application/vnd.ms-office'],
'docx' => [
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/zip',
'application/msword',
'application/x-zip',
],
'dot' => ['application/msword', 'application/vnd.ms-office'],
'dotx' => [
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/zip',
'application/msword',
],
'xlsx' => [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/zip',
'application/vnd.ms-excel',
'application/msword',
'application/x-zip',
],
'word' => ['application/msword', 'application/octet-stream'],
'xl' => 'application/excel',
'eml' => 'message/rfc822',
'json' => ['application/json', 'text/json'],
'pem' => ['application/x-x509-user-cert', 'application/x-pem-file', 'application/octet-stream'],
'p10' => ['application/x-pkcs10', 'application/pkcs10'],
'p12' => 'application/x-pkcs12',
'p7a' => 'application/x-pkcs7-signature',
'p7c' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
'p7m' => ['application/pkcs7-mime', 'application/x-pkcs7-mime'],
'p7r' => 'application/x-pkcs7-certreqresp',
'p7s' => 'application/pkcs7-signature',
'crt' => ['application/x-x509-ca-cert', 'application/x-x509-user-cert', 'application/pkix-cert'],
'crl' => ['application/pkix-crl', 'application/pkcs-crl'],
'der' => 'application/x-x509-ca-cert',
'kdb' => 'application/octet-stream',
'pgp' => 'application/pgp',
'gpg' => 'application/gpg-keys',
'sst' => 'application/octet-stream',
'csr' => 'application/octet-stream',
'rsa' => 'application/x-pkcs7',
'cer' => ['application/pkix-cert', 'application/x-x509-ca-cert'],
'3g2' => 'video/3gpp2',
'3gp' => ['video/3gp', 'video/3gpp'],
'mp4' => 'video/mp4',
'm4a' => 'audio/x-m4a',
'f4v' => ['video/mp4', 'video/x-f4v'],
'flv' => 'video/x-flv',
'webm' => 'video/webm',
'aac' => 'audio/x-acc',
'm4u' => 'application/vnd.mpegurl',
'm3u' => 'text/plain',
'xspf' => 'application/xspf+xml',
'vlc' => 'application/videolan',
'wmv' => ['video/x-ms-wmv', 'video/x-ms-asf'],
'au' => 'audio/x-au',
'ac3' => 'audio/ac3',
'flac' => 'audio/x-flac',
'ogg' => ['audio/ogg', 'video/ogg', 'application/ogg'],
'kmz' => ['application/vnd.google-earth.kmz', 'application/zip', 'application/x-zip'],
'kml' => ['application/vnd.google-earth.kml+xml', 'application/xml', 'text/xml'],
'ics' => 'text/calendar',
'ical' => 'text/calendar',
'zsh' => 'text/x-scriptzsh',
'7z' => [
'application/x-7z-compressed',
'application/x-compressed',
'application/x-zip-compressed',
'application/zip',
'multipart/x-zip',
],
'7zip' => [
'application/x-7z-compressed',
'application/x-compressed',
'application/x-zip-compressed',
'application/zip',
'multipart/x-zip',
],
'cdr' => [
'application/cdr',
'application/coreldraw',
'application/x-cdr',
'application/x-coreldraw',
'image/cdr',
'image/x-cdr',
'zz-application/zz-winassoc-cdr',
],
'wma' => ['audio/x-ms-wma', 'video/x-ms-asf'],
'jar' => [
'application/java-archive',
'application/x-java-application',
'application/x-jar',
'application/x-compressed',
],
'svg' => ['image/svg+xml', 'application/xml', 'text/xml'],
'vcf' => 'text/x-vcard',
'srt' => ['text/srt', 'text/plain'],
'vtt' => ['text/vtt', 'text/plain'],
'ico' => ['image/x-icon', 'image/x-ico', 'image/vnd.microsoft.icon'],
'odc' => 'application/vnd.oasis.opendocument.chart',
'otc' => 'application/vnd.oasis.opendocument.chart-template',
'odf' => 'application/vnd.oasis.opendocument.formula',
'otf' => 'application/vnd.oasis.opendocument.formula-template',
'odg' => 'application/vnd.oasis.opendocument.graphics',
'otg' => 'application/vnd.oasis.opendocument.graphics-template',
'odi' => 'application/vnd.oasis.opendocument.image',
'oti' => 'application/vnd.oasis.opendocument.image-template',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'otp' => 'application/vnd.oasis.opendocument.presentation-template',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
'odt' => 'application/vnd.oasis.opendocument.text',
'odm' => 'application/vnd.oasis.opendocument.text-master',
'ott' => 'application/vnd.oasis.opendocument.text-template',
'oth' => 'application/vnd.oasis.opendocument.text-web',
];

16
application/config/profiler.php Executable file
View File

@ -0,0 +1,16 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/*
| -------------------------------------------------------------------------
| Profiler Sections
| -------------------------------------------------------------------------
| This file lets you determine whether or not various sections of Profiler
| data are displayed when the Profiler is enabled.
| Please see the user guide for info:
|
| http://codeigniter.com/user_guide/general/profiling.html
|
*/
/* End of file profiler.php */
/* Location: ./application/config/profiler.php */

163
application/config/routes.php Executable file
View File

@ -0,0 +1,163 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/*
| -------------------------------------------------------------------------
| URI ROUTING
| -------------------------------------------------------------------------
| This file lets you re-map URI requests to specific controller functions.
|
| Typically there is a one-to-one relationship between a URL string
| and its corresponding controller class/method. The segments in a
| URL normally follow this pattern:
|
| example.com/class/method/id/
|
| In some instances, however, you may want to remap this relationship
| so that a different class/function is called than the one
| corresponding to the URL.
|
| Please see the user guide for complete details:
|
| https://codeigniter.com/userguide3/general/routing.html
|
| -------------------------------------------------------------------------
| RESERVED ROUTES
| -------------------------------------------------------------------------
|
| There are three reserved routes:
|
| $route['default_controller'] = 'welcome';
|
| This route indicates which controller class should be loaded if the
| URI contains no data. In the above example, the "welcome" class
| would be loaded.
|
| $route['404_override'] = 'errors/page_missing';
|
| This route will tell the Router which controller/method to use if those
| provided in the URL cannot be matched to a valid route.
|
| $route['translate_uri_dashes'] = FALSE;
|
| This is not exactly a route, but allows you to automatically route
| controller and method names that contain dashes. '-' isn't a valid
| class or method name character, so it requires translation.
| When you set this option to TRUE, it will replace ALL dashes with
| underscores in the controller and method URI segments.
|
| Examples: my-controller/index -> my_controller/index
| my-controller/my-method -> my_controller/my_method
*/
require_once __DIR__ . '/../helpers/routes_helper.php';
$route['default_controller'] = 'booking';
$route['404_override'] = '';
$route['translate_uri_dashes'] = false;
/*
| -------------------------------------------------------------------------
| FRAME OPTIONS HEADERS
| -------------------------------------------------------------------------
| Set the appropriate headers so that iframe control and permissions are
| properly configured.
|
| Enable this if you want to disable use of Easy!Appointments within an
| iframe.
|
| Options:
|
| - DENY
| - SAMEORIGIN
|
*/
// header('X-Frame-Options: SAMEORIGIN');
/*
| -------------------------------------------------------------------------
| CORS HEADERS
| -------------------------------------------------------------------------
| Set the appropriate headers so that CORS requirements are met and any
| incoming preflight options request succeeds.
|
*/
header('Access-Control-Allow-Origin: ' . ($_SERVER['HTTP_ORIGIN'] ?? '*')); // NOTICE: Change this header to restrict CORS access.
header('Access-Control-Allow-Credentials: "true"');
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
// May also be using PUT, PATCH, HEAD etc
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD');
}
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
header('Access-Control-Allow-Headers: ' . $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
}
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit(0);
}
/*
| -------------------------------------------------------------------------
| REST API ROUTING
| -------------------------------------------------------------------------
| Define the API resource routes using the routing helper function. By
| default, each resource will have by default the following actions:
|
| - index [GET]
|
| - show/:id [GET]
|
| - store [POST]
|
| - update [PUT]
|
| - destroy [DELETE]
|
| Some resources like the availabilities and the settings do not follow this
| pattern and are explicitly defined.
|
*/
route_api_resource($route, 'appointments', 'api/v1/');
route_api_resource($route, 'admins', 'api/v1/');
route_api_resource($route, 'categories', 'api/v1/');
route_api_resource($route, 'customers', 'api/v1/');
route_api_resource($route, 'providers', 'api/v1/');
route_api_resource($route, 'secretaries', 'api/v1/');
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';
$route['api/v1/settings/(:any)']['put'] = 'api/v1/settings_api_v1/update/$1';
$route['api/v1/availabilities']['get'] = 'api/v1/availabilities_api_v1/get';
/*
| -------------------------------------------------------------------------
| CUSTOM ROUTING
| -------------------------------------------------------------------------
| You can add custom routes to the following section to define URL patterns
| that are later mapped to the available controllers in the filesystem.
|
*/
/* End of file routes.php */
/* Location: ./application/config/routes.php */

View File

@ -0,0 +1,19 @@
<?php defined('BASEPATH') or exit('No direct script access allowed');
/*
| -------------------------------------------------------------------------
| TESTING ROUTES
| -------------------------------------------------------------------------
| The following routes are defined in order for CI to be able to process
| test execution requests via the CLI.
|
| The Test controller class is used as a placeholder for this purpose.
|
*/
$route['default_controller'] = 'test/index';
$route['404_override'] = 'test/index'; // when in doubt, use the hammer
$route['translate_uri_dashes'] = FALSE;
/* End of file routes.php */
/* Location: ./application/config/testing/routes.php */

View File

@ -0,0 +1,79 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* About controller.
*
* Handles about settings related operations.
*
* @package Controllers
*/
class About extends EA_Controller
{
/**
* About constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('customers_model');
$this->load->model('services_model');
$this->load->model('providers_model');
$this->load->model('roles_model');
$this->load->model('settings_model');
$this->load->library('accounts');
$this->load->library('google_sync');
$this->load->library('notifications');
$this->load->library('synchronization');
$this->load->library('timezones');
}
/**
* Render the settings page.
*/
public function index(): void
{
session(['dest_url' => site_url('about')]);
$user_id = session('user_id');
if (cannot('view', PRIV_USER_SETTINGS)) {
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('settings'),
'active_menu' => PRIV_SYSTEM_SETTINGS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
]);
$this->load->view('pages/about');
}
}

View File

@ -0,0 +1,153 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Account controller.
*
* Handles current account related operations.
*
* @package Controllers
*/
class Account extends EA_Controller
{
public array $allowed_user_fields = [
'id',
'first_name',
'last_name',
'email',
'mobile_number',
'phone_number',
'address',
'city',
'state',
'zip_code',
'notes',
'timezone',
'language',
'settings',
];
public array $allowed_user_setting_fields = ['username', 'password', 'notifications', 'calendar_view'];
/**
* Account constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('customers_model');
$this->load->model('services_model');
$this->load->model('providers_model');
$this->load->model('roles_model');
$this->load->model('settings_model');
$this->load->library('accounts');
$this->load->library('google_sync');
$this->load->library('notifications');
$this->load->library('synchronization');
$this->load->library('timezones');
}
/**
* Render the settings page.
*/
public function index(): void
{
session(['dest_url' => site_url('account')]);
$user_id = session('user_id');
if (cannot('view', PRIV_USER_SETTINGS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$account = $this->users_model->find($user_id);
script_vars([
'account' => $account,
]);
html_vars([
'page_title' => lang('settings'),
'active_menu' => PRIV_SYSTEM_SETTINGS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
'grouped_timezones' => $this->timezones->to_grouped_array(),
]);
$this->load->view('pages/account');
}
/**
* Save general settings.
*/
public function save(): void
{
try {
if (cannot('edit', PRIV_USER_SETTINGS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$account = request('account');
$account['id'] = session('user_id');
$this->users_model->only($account, $this->allowed_user_fields);
$this->users_model->only($account['settings'], $this->allowed_user_setting_fields);
if (empty($account['password'])) {
unset($account['password']);
}
$this->users_model->save($account);
session([
'user_email' => $account['email'],
'username' => $account['settings']['username'],
'timezone' => $account['timezone'],
'language' => $account['language'],
]);
response();
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Make sure the username is valid and unique in the database.
*/
public function validate_username(): void
{
try {
$username = request('username');
$user_id = request('user_id');
$is_valid = $this->users_model->validate_username($username, $user_id);
json_response([
'is_valid' => $is_valid,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,235 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Admins controller.
*
* Handles the admins related operations.
*
* @package Controllers
*/
class Admins extends EA_Controller
{
public array $allowed_admin_fields = [
'id',
'first_name',
'last_name',
'email',
'mobile_number',
'phone_number',
'address',
'city',
'state',
'zip_code',
'notes',
'timezone',
'language',
'ldap_dn',
'settings',
];
public array $allowed_admin_setting_fields = ['username', 'password', 'notifications', 'calendar_view'];
/**
* Admins constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('admins_model');
$this->load->model('roles_model');
$this->load->library('accounts');
$this->load->library('timezones');
$this->load->library('webhooks_client');
}
/**
* Render the backend admins page.
*
* On this page admin users will be able to manage admins, which are eventually selected by customers during the
* booking process.
*/
public function index(): void
{
session(['dest_url' => site_url('admins')]);
$user_id = session('user_id');
if (cannot('view', PRIV_USERS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'timezones' => $this->timezones->to_array(),
'min_password_length' => MIN_PASSWORD_LENGTH,
'default_language' => setting('default_language'),
'default_timezone' => setting('default_timezone'),
]);
html_vars([
'page_title' => lang('admins'),
'active_menu' => PRIV_USERS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
'grouped_timezones' => $this->timezones->to_grouped_array(),
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
]);
$this->load->view('pages/admins');
}
/**
* Filter admins by the provided keyword.
*/
public function search(): void
{
try {
if (cannot('view', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$keyword = request('keyword', '');
$order_by = request('order_by', 'update_datetime DESC');
$limit = request('limit', 1000);
$offset = (int) request('offset', '0');
$admins = $this->admins_model->search($keyword, $limit, $offset, $order_by);
json_response($admins);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new admin.
*/
public function store(): void
{
try {
if (cannot('add', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$admin = request('admin');
$this->admins_model->only($admin, $this->allowed_admin_fields);
$this->admins_model->only($admin['settings'], $this->allowed_admin_setting_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Find an admin.
*/
public function find(): void
{
try {
if (cannot('view', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$admin_id = request('admin_id');
$admin = $this->admins_model->find($admin_id);
json_response($admin);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update an admin.
*/
public function update(): void
{
try {
if (cannot('edit', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$admin = request('admin');
$this->admins_model->only($admin, $this->allowed_admin_fields);
$this->admins_model->only($admin['settings'], $this->allowed_admin_setting_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Remove an admin.
*/
public function destroy(): void
{
try {
if (cannot('delete', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,102 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* API settings controller.
*
* Handles API settings related operations.
*
* @package Controllers
*/
class Api_settings extends EA_Controller
{
/**
* Api_settings constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('settings_model');
$this->load->library('accounts');
}
/**
* Render the settings page.
*/
public function index(): void
{
session(['dest_url' => site_url('api_settings')]);
$user_id = session('user_id');
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'api_settings' => $this->settings_model->get('name like "api_%"'),
]);
html_vars([
'page_title' => lang('api'),
'active_menu' => PRIV_SYSTEM_SETTINGS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
]);
$this->load->view('pages/api_settings');
}
/**
* Save general settings.
*/
public function save(): void
{
try {
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$settings = request('api_settings', []);
foreach ($settings as $setting) {
$existing_setting = $this->settings_model
->query()
->where('name', $setting['name'])
->get()
->row_array();
if (!empty($existing_setting)) {
$setting['id'] = $existing_setting['id'];
}
$this->settings_model->save($setting);
}
response();
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,191 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* 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.
*
* @package Controllers
*/
class Appointments extends EA_Controller
{
public array $allowed_appointment_fields = [
'id',
'start_datetime',
'end_datetime',
'location',
'notes',
'color',
'is_unavailability',
'id_users_provider',
'id_users_customer',
'id_services',
];
/**
* Appointments constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('roles_model');
$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.
*
* @param string $appointment_hash
*
* @deprecated Since 1.5
*/
public function index(string $appointment_hash = '')
{
redirect('booking/' . $appointment_hash);
}
/**
* Filter appointments by the provided keyword.
*/
public function search(): void
{
try {
if (cannot('view', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$keyword = request('keyword', '');
$order_by = request('order_by', 'update_datetime DESC');
$limit = request('limit', 1000);
$offset = (int) request('offset', '0');
$appointments = $this->appointments_model->search($keyword, $limit, $offset, $order_by);
json_response($appointments);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new appointment.
*/
public function store(): void
{
try {
if (cannot('add', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$appointment = json_decode(request('appointment'), true);
$this->appointments_model->only($appointment, $this->allowed_appointment_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Find an appointment.
*/
public function find(): void
{
try {
if (cannot('view', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$appointment_id = request('appointment_id');
$appointment = $this->appointments_model->find($appointment_id);
json_response($appointment);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a appointment.
*/
public function update(): void
{
try {
if (cannot('edit', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$appointment = json_decode(request('appointment'), true);
$this->appointments_model->only($appointment, $this->allowed_appointment_fields);
$appointment_id = $this->appointments_model->save($appointment);
json_response([
'success' => true,
'id' => $appointment_id,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Remove a appointment.
*/
public function destroy(): void
{
try {
if (cannot('delete', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,103 @@
<?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
* ---------------------------------------------------------------------------- */
/*
|------------------------------------------------------------------------------
| Deprecation Notice
|------------------------------------------------------------------------------
|
| This file is still in the project for backwards compatibility reasons and for
| providing additional information on how to migrate your code to the latest
| codebase state.
|
| Visit the Easy!Appointments Developers website for more information:
|
| https://developers.easyappointments.org
|
| Since v1.5, the methods of this controller were ported to standalone controller
| classes, that can both handle the page rendering and all asynchronous HTTP
| requests.
|
*/
/**
* Backend controller.
*
* Handles the backend related operations.
*
* @package Controllers
*
* @deprecated Since 1.5
*/
class Backend extends EA_Controller
{
/**
* Display the calendar page.
*
* @param string $appointment_hash Appointment edit dialog will appear when the page loads (default '').
*/
public function index(string $appointment_hash = ''): void
{
if (empty($appointment_hash)) {
redirect('calendar');
} else {
redirect('calendar/reschedule/' . $appointment_hash);
}
}
/**
* Display the customers page.
*/
public function customers(): void
{
redirect('customers');
}
/**
* Display the services page.
*/
public function services(): void
{
redirect('services');
}
/**
* Display the users page.
*
* Notice: Since the "users" page is split into multiple pages (providers, secretaries, admins), this method will
* redirect to "providers" page by default
*/
public function users(): void
{
redirect('providers');
}
/**
* Display settings page.
*
* Notice: Since the "settings" page is split into multiple pages (general, business, booking etc), this method will
* redirect to "general" page by default.
*/
public function settings(): void
{
redirect('general_settings');
}
/**
* Display the update page.
*/
public function update(): void
{
redirect('update');
}
}

View File

@ -0,0 +1,307 @@
<?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
* ---------------------------------------------------------------------------- */
/*
|------------------------------------------------------------------------------
| Deprecation Notice
|------------------------------------------------------------------------------
|
| This file is still in the project for backwards compatibility reasons and for
| providing additional information on how to migrate your code to the latest
| codebase state.
|
| Visit the Easy!Appointments Developers website for more information:
|
| https://developers.easyappointments.org
|
| Since v1.5, the methods of this controller were ported to standalone controller
| classes, that can both handle the page rendering and all asynchronous HTTP
| requests.
|
*/
/**
* Backend API controller.
*
* Handles the backend API related operations.
*
* @package Controllers
*
* @deprecated Since 1.5
*/
class Backend_api extends EA_Controller
{
/**
* Get Calendar Events
*/
public function ajax_get_calendar_events(): void
{
redirect('calendar/get_calendar_appointments_for_table_view');
}
/**
* Get the registered appointments for the given date period and record.
*/
public function ajax_get_calendar_appointments(): void
{
redirect('calendar/get_calendar_appointments');
}
/**
* Save appointment changes that are made from the backend calendar page.
*/
public function ajax_save_appointment(): void
{
redirect('calendar/save_appointment');
}
/**
* Delete appointment from the database.
*/
public function ajax_delete_appointment(): void
{
redirect('calendar/delete_appointment');
}
/**
* Disable a providers sync setting.
*/
public function ajax_disable_provider_sync(): void
{
redirect('google/disable_provider_sync');
}
/**
* Filter the customer records with the given key string.
*/
public function ajax_filter_customers(): void
{
redirect('customers/search');
}
/**
* Insert or update an unavailability.
*/
public function ajax_save_unavailability(): void
{
redirect('calendar/save_unavailability');
}
/**
* Delete an unavailability time period from database.
*/
public function ajax_delete_unavailability(): void
{
redirect('calendar/delete_unavailability');
}
/**
* Insert of update working plan exceptions to database.
*/
public function ajax_save_working_plan_exception(): void
{
redirect('calendar/save_working_plan_exception');
}
/**
* Delete a working plan exceptions time period to database.
*/
public function ajax_delete_working_plan_exception(): void
{
redirect('calendar/delete_working_plan_exception');
}
/**
* Save (insert or update) a customer record.
*/
public function ajax_save_customer(): void
{
redirect('customers/create'); // or "customers/update"
}
/**
* Delete customer from database.
*/
public function ajax_delete_customer(): void
{
redirect('customers/destroy');
}
/**
* Save (insert or update) service record.
*/
public function ajax_save_service(): void
{
redirect('services/create'); // or "services/update"
}
/**
* Delete service record from database.
*/
public function ajax_delete_service(): void
{
redirect('services/destroy');
}
/**
* Filter service records by given key string.
*/
public function ajax_filter_services(): void
{
redirect('services/search');
}
/**
* Save (insert or update) category record.
*/
public function ajax_save_service_category(): void
{
redirect('categories/create'); // or "categories/update"
}
/**
* Delete category record from database.
*/
public function ajax_delete_service_category(): void
{
redirect('categories/destroy');
}
/**
* Filter services categories with key string.
*/
public function ajax_filter_service_categories(): void
{
redirect('categories/search');
}
/**
* Filter admin records with string key.
*/
public function ajax_filter_admins(): void
{
redirect('admins/search');
}
/**
* Save (insert or update) admin record into database.
*/
public function ajax_save_admin(): void
{
redirect('admins/create'); // or "admins/update"
}
/**
* Delete an admin record from the database.
*/
public function ajax_delete_admin(): void
{
redirect('admins/destroy');
}
/**
* Filter provider records with string key.
*/
public function ajax_filter_providers(): void
{
redirect('providers/search');
}
/**
* Save (insert or update) a provider record into database.
*/
public function ajax_save_provider(): void
{
redirect('providers/create'); // or "providers/update"
}
/**
* Delete a provider record from the database.
*/
public function ajax_delete_provider(): void
{
redirect('providers/destroy');
}
/**
* Filter secretary records with string key.
*/
public function ajax_filter_secretaries(): void
{
redirect('secretaries/search');
}
/**
* Save (insert or update) a secretary record into database.
*/
public function ajax_save_secretary(): void
{
redirect('secretaries/create'); // or "secretaries/update"
}
/**
* Delete a secretary record from the database.
*/
public function ajax_delete_secretary(): void
{
redirect('secretaries/destroy');
}
/**
* Save a setting or multiple settings in the database.
*/
public function ajax_save_settings(): void
{
redirect('general_settings/save'); // or "business_settings/save", "booking_settings/save", "legal_settings/save"
}
/**
* This method checks whether the username already exists in the database.
*/
public function ajax_validate_username(): void
{
redirect('account/validate_username');
}
/**
* Change system language for current user.
*/
public function ajax_change_language(): void
{
redirect('account/change_language');
}
/**
* This method will return a list of the available Google Calendars.
*/
public function ajax_get_google_calendars(): void
{
redirect('google/get_google_calendars');
}
/**
* Select a specific google calendar for a provider.
*/
public function ajax_select_google_calendar(): void
{
redirect('google/select_google_calendar');
}
/**
* Apply global working plan to all providers.
*/
public function ajax_apply_global_working_plan(): void
{
redirect('business_settings/apply_global_working_plan');
}
}

View File

@ -0,0 +1,212 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Blocked_periods controller.
*
* Handles the blocked-periods related operations.
*
* @package Controllers
*/
class Blocked_periods extends EA_Controller
{
public array $allowed_blocked_period_fields = ['id', 'name', 'start_datetime', 'end_datetime', 'notes'];
/**
* Blocked_periods constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('blocked_periods_model');
$this->load->model('roles_model');
$this->load->library('accounts');
$this->load->library('timezones');
$this->load->library('webhooks_client');
}
/**
* Render the backend blocked-periods page.
*
* On this page admin users will be able to manage blocked-periods, which are eventually selected by customers during the
* booking process.
*/
public function index(): void
{
session(['dest_url' => site_url('blocked_periods')]);
$user_id = session('user_id');
if (cannot('view', PRIV_BLOCKED_PERIODS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'date_format' => setting('date_format'),
'time_format' => setting('time_format'),
'first_weekday' => setting('first_weekday'),
]);
html_vars([
'page_title' => lang('blocked_periods'),
'active_menu' => PRIV_BLOCKED_PERIODS,
'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),
]);
$this->load->view('pages/blocked_periods');
}
/**
* Filter blocked-periods by the provided keyword.
*/
public function search(): void
{
try {
if (cannot('view', PRIV_BLOCKED_PERIODS)) {
abort(403, 'Forbidden');
}
$keyword = request('keyword', '');
$order_by = request('order_by', 'update_datetime DESC');
$limit = request('limit', 1000);
$offset = (int) request('offset', '0');
$blocked_periods = $this->blocked_periods_model->search($keyword, $limit, $offset, $order_by);
json_response($blocked_periods);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new service-category.
*/
public function store(): void
{
try {
if (cannot('add', PRIV_BLOCKED_PERIODS)) {
abort(403, 'Forbidden');
}
$blocked_period = request('blocked_period');
$this->blocked_periods_model->only($blocked_period, $this->allowed_blocked_period_fields);
$blocked_period_id = $this->blocked_periods_model->save($blocked_period);
$blocked_period = $this->blocked_periods_model->find($blocked_period_id);
$this->webhooks_client->trigger(WEBHOOK_BLOCKED_PERIOD_SAVE, $blocked_period);
json_response([
'success' => true,
'id' => $blocked_period_id,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Find a service-category.
*/
public function find(): void
{
try {
if (cannot('view', PRIV_BLOCKED_PERIODS)) {
abort(403, 'Forbidden');
}
$blocked_period_id = request('blocked_period_id');
$blocked_period = $this->blocked_periods_model->find($blocked_period_id);
json_response($blocked_period);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a service-category.
*/
public function update(): void
{
try {
if (cannot('edit', PRIV_BLOCKED_PERIODS)) {
abort(403, 'Forbidden');
}
$blocked_period = request('blocked_period');
$this->blocked_periods_model->only($blocked_period, $this->allowed_blocked_period_fields);
$blocked_period_id = $this->blocked_periods_model->save($blocked_period);
$blocked_period = $this->blocked_periods_model->find($blocked_period_id);
$this->webhooks_client->trigger(WEBHOOK_BLOCKED_PERIOD_SAVE, $blocked_period);
json_response([
'success' => true,
'id' => $blocked_period_id,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Remove a service-category.
*/
public function destroy(): void
{
try {
if (cannot('delete', PRIV_BLOCKED_PERIODS)) {
abort(403, 'Forbidden');
}
$blocked_period_id = request('blocked_period_id');
$blocked_period = $this->blocked_periods_model->find($blocked_period_id);
$this->blocked_periods_model->delete($blocked_period_id);
$this->webhooks_client->trigger(WEBHOOK_BLOCKED_PERIOD_DELETE, $blocked_period);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,769 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Booking controller.
*
* Handles the booking 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.
*
* @package Controllers
*/
class Booking extends EA_Controller
{
public array $allowed_customer_fields = [
'id',
'first_name',
'last_name',
'email',
'phone_number',
'address',
'city',
'state',
'zip_code',
'timezone',
'language',
'custom_field_1',
'custom_field_2',
'custom_field_3',
'custom_field_4',
'custom_field_5',
];
public mixed $allowed_provider_fields = ['id', 'first_name', 'last_name', 'services', 'timezone'];
public array $allowed_appointment_fields = [
'id',
'start_datetime',
'end_datetime',
'location',
'notes',
'color',
'status',
'is_unavailability',
'id_users_provider',
'id_users_customer',
'id_services',
];
/**
* Booking constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('providers_model');
$this->load->model('admins_model');
$this->load->model('secretaries_model');
$this->load->model('service_categories_model');
$this->load->model('services_model');
$this->load->model('customers_model');
$this->load->model('settings_model');
$this->load->model('consents_model');
$this->load->library('timezones');
$this->load->library('synchronization');
$this->load->library('notifications');
$this->load->library('availability');
$this->load->library('webhooks_client');
}
/**
* Render the booking page and display the selected appointment.
*
* This method will call the "index" callback to handle the page rendering.
*
* @param string $appointment_hash
*/
public function reschedule(string $appointment_hash): void
{
html_vars(['appointment_hash' => $appointment_hash]);
$this->index();
}
/**
* Render the booking page.
*
* This method creates the appointment book wizard.
*/
public function index(): void
{
if (!is_app_installed()) {
redirect('installation');
return;
}
$company_name = setting('company_name');
$company_logo = setting('company_logo');
$company_color = setting('company_color');
$disable_booking = setting('disable_booking');
$google_analytics_code = setting('google_analytics_code');
$matomo_analytics_url = setting('matomo_analytics_url');
$matomo_analytics_site_id = setting('matomo_analytics_site_id');
if ($disable_booking) {
$disable_booking_message = setting('disable_booking_message');
html_vars([
'show_message' => true,
'page_title' => lang('page_title') . ' ' . $company_name,
'message_title' => lang('booking_is_disabled'),
'message_text' => $disable_booking_message,
'message_icon' => base_url('assets/img/error.png'),
'google_analytics_code' => $google_analytics_code,
'matomo_analytics_url' => $matomo_analytics_url,
'matomo_analytics_site_id' => $matomo_analytics_site_id,
]);
$this->load->view('pages/booking_message');
return;
}
$available_services = $this->services_model->get_available_services(true);
$available_providers = $this->providers_model->get_available_providers(true);
foreach ($available_providers as &$available_provider) {
// Only expose the required provider data.
$this->providers_model->only($available_provider, $this->allowed_provider_fields);
}
$date_format = setting('date_format');
$time_format = setting('time_format');
$first_weekday = setting('first_weekday');
$display_first_name = setting('display_first_name');
$require_first_name = setting('require_first_name');
$display_last_name = setting('display_last_name');
$require_last_name = setting('require_last_name');
$display_email = setting('display_email');
$require_email = setting('require_email');
$display_phone_number = setting('display_phone_number');
$require_phone_number = setting('require_phone_number');
$display_address = setting('display_address');
$require_address = setting('require_address');
$display_city = setting('display_city');
$require_city = setting('require_city');
$display_zip_code = setting('display_zip_code');
$require_zip_code = setting('require_zip_code');
$display_notes = setting('display_notes');
$require_notes = setting('require_notes');
$display_cookie_notice = setting('display_cookie_notice');
$cookie_notice_content = setting('cookie_notice_content');
$display_terms_and_conditions = setting('display_terms_and_conditions');
$terms_and_conditions_content = setting('terms_and_conditions_content');
$display_privacy_policy = setting('display_privacy_policy');
$privacy_policy_content = setting('privacy_policy_content');
$display_any_provider = setting('display_any_provider');
$display_login_button = setting('display_login_button');
$display_delete_personal_information = setting('display_delete_personal_information');
$book_advance_timeout = setting('book_advance_timeout');
$theme = request('theme', setting('theme', 'default'));
if (empty($theme) || !file_exists(__DIR__ . '/../../assets/css/themes/' . $theme . '.min.css')) {
$theme = 'default';
}
$timezones = $this->timezones->to_array();
$grouped_timezones = $this->timezones->to_grouped_array();
$appointment_hash = html_vars('appointment_hash');
if (!empty($appointment_hash)) {
// Load the appointments data and enable the manage mode of the booking page.
$manage_mode = true;
$results = $this->appointments_model->get(['hash' => $appointment_hash]);
if (empty($results)) {
html_vars([
'show_message' => true,
'page_title' => lang('page_title') . ' ' . $company_name,
'message_title' => lang('appointment_not_found'),
'message_text' => lang('appointment_does_not_exist_in_db'),
'message_icon' => base_url('assets/img/error.png'),
'google_analytics_code' => $google_analytics_code,
'matomo_analytics_url' => $matomo_analytics_url,
'matomo_analytics_site_id' => $matomo_analytics_site_id,
]);
$this->load->view('pages/booking_message');
return;
}
// Make sure the appointment can still be rescheduled.
$start_datetime = strtotime($results[0]['start_datetime']);
$limit = strtotime('+' . $book_advance_timeout . ' minutes', strtotime('now'));
if ($start_datetime < $limit) {
$hours = floor($book_advance_timeout / 60);
$minutes = $book_advance_timeout % 60;
html_vars([
'show_message' => true,
'page_title' => lang('page_title') . ' ' . $company_name,
'message_title' => lang('appointment_locked'),
'message_text' => strtr(lang('appointment_locked_message'), [
'{$limit}' => sprintf('%02d:%02d', $hours, $minutes),
]),
'message_icon' => base_url('assets/img/error.png'),
'google_analytics_code' => $google_analytics_code,
'matomo_analytics_url' => $matomo_analytics_url,
'matomo_analytics_site_id' => $matomo_analytics_site_id,
]);
$this->load->view('pages/booking_message');
return;
}
$appointment = $results[0];
$provider = $this->providers_model->find($appointment['id_users_provider']);
$customer = $this->customers_model->find($appointment['id_users_customer']);
$customer_token = md5(uniqid(mt_rand(), true));
// Cache the token for 10 minutes.
$this->cache->save('customer-token-' . $customer_token, $customer['id'], 600);
} else {
$manage_mode = false;
$customer_token = false;
$appointment = null;
$provider = null;
$customer = null;
}
script_vars([
'manage_mode' => $manage_mode,
'available_services' => $available_services,
'available_providers' => $available_providers,
'date_format' => $date_format,
'time_format' => $time_format,
'first_weekday' => $first_weekday,
'display_cookie_notice' => $display_cookie_notice,
'display_any_provider' => setting('display_any_provider'),
'future_booking_limit' => setting('future_booking_limit'),
'appointment_data' => $appointment,
'provider_data' => $provider,
'customer_data' => $customer,
'default_language' => setting('default_language'),
'default_timezone' => setting('default_timezone'),
]);
html_vars([
'available_services' => $available_services,
'available_providers' => $available_providers,
'theme' => $theme,
'company_name' => $company_name,
'company_logo' => $company_logo,
'company_color' => $company_color === '#ffffff' ? '' : $company_color,
'date_format' => $date_format,
'time_format' => $time_format,
'first_weekday' => $first_weekday,
'display_first_name' => $display_first_name,
'require_first_name' => $require_first_name,
'display_last_name' => $display_last_name,
'require_last_name' => $require_last_name,
'display_email' => $display_email,
'require_email' => $require_email,
'display_phone_number' => $display_phone_number,
'require_phone_number' => $require_phone_number,
'display_address' => $display_address,
'require_address' => $require_address,
'display_city' => $display_city,
'require_city' => $require_city,
'display_zip_code' => $display_zip_code,
'require_zip_code' => $require_zip_code,
'display_notes' => $display_notes,
'require_notes' => $require_notes,
'display_cookie_notice' => $display_cookie_notice,
'cookie_notice_content' => $cookie_notice_content,
'display_terms_and_conditions' => $display_terms_and_conditions,
'terms_and_conditions_content' => $terms_and_conditions_content,
'display_privacy_policy' => $display_privacy_policy,
'privacy_policy_content' => $privacy_policy_content,
'display_any_provider' => $display_any_provider,
'display_login_button' => $display_login_button,
'display_delete_personal_information' => $display_delete_personal_information,
'google_analytics_code' => $google_analytics_code,
'matomo_analytics_url' => $matomo_analytics_url,
'matomo_analytics_site_id' => $matomo_analytics_site_id,
'timezones' => $timezones,
'grouped_timezones' => $grouped_timezones,
'manage_mode' => $manage_mode,
'customer_token' => $customer_token,
'appointment_data' => $appointment,
'provider_data' => $provider,
'customer_data' => $customer,
]);
$this->load->view('pages/booking');
}
/**
* Register the appointment to the database.
*/
public function register(): void
{
try {
$disable_booking = setting('disable_booking');
if ($disable_booking) {
abort(403);
}
$post_data = request('post_data');
$captcha = request('captcha');
$appointment = $post_data['appointment'];
$customer = $post_data['customer'];
$manage_mode = filter_var($post_data['manage_mode'], FILTER_VALIDATE_BOOLEAN);
if (!array_key_exists('address', $customer)) {
$customer['address'] = '';
}
if (!array_key_exists('city', $customer)) {
$customer['city'] = '';
}
if (!array_key_exists('zip_code', $customer)) {
$customer['zip_code'] = '';
}
if (!array_key_exists('notes', $customer)) {
$customer['notes'] = '';
}
if (!array_key_exists('phone_number', $customer)) {
$customer['phone_number'] = '';
}
// Check appointment availability before registering it to the database.
$appointment['id_users_provider'] = $this->check_datetime_availability();
if (!$appointment['id_users_provider']) {
throw new RuntimeException(lang('requested_hour_is_unavailable'));
}
$provider = $this->providers_model->find($appointment['id_users_provider']);
$service = $this->services_model->find($appointment['id_services']);
$require_captcha = (bool) setting('require_captcha');
$captcha_phrase = session('captcha_phrase');
// Validate the CAPTCHA string.
if ($require_captcha && strtoupper($captcha_phrase) !== strtoupper($captcha)) {
json_response([
'captcha_verification' => false,
]);
return;
}
if ($this->customers_model->exists($customer)) {
$customer['id'] = $this->customers_model->find_record_id($customer);
$existing_appointments = $this->appointments_model->get([
'id !=' => $manage_mode ? $appointment['id'] : null,
'id_users_customer' => $customer['id'],
'start_datetime <=' => $appointment['start_datetime'],
'end_datetime >=' => $appointment['end_datetime'],
]);
if (count($existing_appointments)) {
throw new RuntimeException(lang('customer_is_already_booked'));
}
}
if (empty($appointment['location']) && !empty($service['location'])) {
$appointment['location'] = $service['location'];
}
if (empty($appointment['color']) && !empty($service['color'])) {
$appointment['color'] = $service['color'];
}
$customer_ip = $this->input->ip_address();
// Create the consents (if needed).
$consent = [
'first_name' => $customer['first_name'] ?? '-',
'last_name' => $customer['last_name'] ?? '-',
'email' => $customer['email'] ?? '-',
'ip' => $customer_ip,
];
if (setting('display_terms_and_conditions')) {
$consent['type'] = 'terms-and-conditions';
$this->consents_model->save($consent);
}
if (setting('display_privacy_policy')) {
$consent['type'] = 'privacy-policy';
$this->consents_model->save($consent);
}
// Save customer language (the language which is used to render the booking page).
$customer['language'] = session('language') ?? config('language');
$this->customers_model->only($customer, $this->allowed_customer_fields);
$customer_id = $this->customers_model->save($customer);
$customer = $this->customers_model->find($customer_id);
$appointment['id_users_customer'] = $customer_id;
$appointment['is_unavailability'] = false;
$appointment['color'] = $service['color'];
$appointment_status_options_json = setting('appointment_status_options', '[]');
$appointment_status_options = json_decode($appointment_status_options_json, true) ?? [];
$appointment['status'] = $appointment_status_options[0] ?? null;
$this->appointments_model->only($appointment, $this->allowed_appointment_fields);
$appointment_id = $this->appointments_model->save($appointment);
$appointment = $this->appointments_model->find($appointment_id);
$settings = [
'company_name' => setting('company_name'),
'company_link' => setting('company_link'),
'company_email' => setting('company_email'),
'date_format' => setting('date_format'),
'time_format' => setting('time_format'),
];
$this->synchronization->sync_appointment_saved($appointment, $service, $provider, $customer, $settings);
$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'],
];
json_response($response);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Check whether the provider is still available in the selected appointment date.
*
* It is possible that two or more customers select the same appointment date and time concurrently. The app won't
* allow this to happen, so one of the two will eventually get the selected date and the other one will have
* to choose for another one.
*
* Use this method just before the customer confirms the appointment registration. If the selected date was reserved
* in the meanwhile, the customer must be prompted to select another time.
*
* @return int|null Returns the ID of the provider that is available for the appointment.
*
* @throws Exception
*/
protected function check_datetime_availability(): ?int
{
$post_data = request('post_data');
$appointment = $post_data['appointment'];
$appointment_start = new DateTime($appointment['start_datetime']);
$date = $appointment_start->format('Y-m-d');
$hour = $appointment_start->format('H:i');
if ($appointment['id_users_provider'] === ANY_PROVIDER) {
$appointment['id_users_provider'] = $this->search_any_provider($appointment['id_services'], $date, $hour);
return $appointment['id_users_provider'];
}
$service = $this->services_model->find($appointment['id_services']);
$exclude_appointment_id = $appointment['id'] ?? null;
$provider = $this->providers_model->find($appointment['id_users_provider']);
$available_hours = $this->availability->get_available_hours(
$date,
$service,
$provider,
$exclude_appointment_id,
);
$is_still_available = false;
$appointment_hour = date('H:i', strtotime($appointment['start_datetime']));
foreach ($available_hours as $available_hour) {
if ($appointment_hour === $available_hour) {
$is_still_available = true;
break;
}
}
return $is_still_available ? $appointment['id_users_provider'] : null;
}
/**
* Search for any provider that can handle the requested service.
*
* This method will return the database ID of the provider with the most available periods.
*
* @param int $service_id Service ID
* @param string $date Selected date (Y-m-d).
* @param string|null $hour Selected hour (H:i).
*
* @return int|null Returns the ID of the provider that can provide the service at the selected date.
*
* @throws Exception
*/
protected function search_any_provider(int $service_id, string $date, string $hour = null): ?int
{
$available_providers = $this->providers_model->get_available_providers(true);
$service = $this->services_model->find($service_id);
$provider_id = null;
$max_hours_count = 0;
foreach ($available_providers as $provider) {
foreach ($provider['services'] as $provider_service_id) {
if ($provider_service_id == $service_id) {
// Check if the provider is available for the requested date.
$available_hours = $this->availability->get_available_hours($date, $service, $provider);
if (
count($available_hours) > $max_hours_count &&
(empty($hour) || in_array($hour, $available_hours))
) {
$provider_id = $provider['id'];
$max_hours_count = count($available_hours);
}
}
}
}
return $provider_id;
}
/**
* Get the available appointment hours for the selected date.
*
* This method answers to an AJAX request. It calculates the available hours for the given service, provider and
* date.
*/
public function get_available_hours(): void
{
try {
$disable_booking = setting('disable_booking');
if ($disable_booking) {
abort(403);
}
$provider_id = request('provider_id');
$service_id = request('service_id');
$selected_date = request('selected_date');
// Do not continue if there was no provider selected (more likely there is no provider in the system).
if (empty($provider_id)) {
json_response();
return;
}
// If manage mode is TRUE then the following we should not consider the selected appointment when
// calculating the available time periods of the provider.
$exclude_appointment_id = request('manage_mode') ? request('appointment_id') : null;
// If the user has selected the "any-provider" option then we will need to search for an available provider
// that will provide the requested service.
$service = $this->services_model->find($service_id);
if ($provider_id === ANY_PROVIDER) {
$providers = $this->providers_model->get();
$available_hours = [];
foreach ($providers as $provider) {
if (!in_array($service_id, $provider['services'])) {
continue;
}
$provider_available_hours = $this->availability->get_available_hours(
$selected_date,
$service,
$provider,
$exclude_appointment_id,
);
$available_hours = array_merge($available_hours, $provider_available_hours);
}
$available_hours = array_unique(array_values($available_hours));
sort($available_hours);
$response = $available_hours;
} else {
$provider = $this->providers_model->find($provider_id);
$response = $this->availability->get_available_hours(
$selected_date,
$service,
$provider,
$exclude_appointment_id,
);
}
json_response($response);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Get Unavailable Dates
*
* Get an array with the available dates of a specific provider, service and month of the year. Provide the
* "provider_id", "service_id" and "selected_date" as GET parameters to the request. The "selected_date" parameter
* must have the "Y-m-d" format.
*
* Outputs a JSON string with the unavailability dates. that are unavailability.
*/
public function get_unavailable_dates(): void
{
try {
$disable_booking = setting('disable_booking');
if ($disable_booking) {
abort(403);
}
$provider_id = request('provider_id');
$service_id = request('service_id');
$appointment_id = request('appointment_id');
$manage_mode = filter_var(request('manage_mode'), FILTER_VALIDATE_BOOLEAN);
$selected_date_string = request('selected_date');
$selected_date = new DateTime($selected_date_string);
$number_of_days_in_month = (int) $selected_date->format('t');
$unavailable_dates = [];
$provider_ids =
$provider_id === ANY_PROVIDER ? $this->search_providers_by_service($service_id) : [$provider_id];
$exclude_appointment_id = $manage_mode ? $appointment_id : null;
// Get the service record.
$service = $this->services_model->find($service_id);
for ($i = 1; $i <= $number_of_days_in_month; $i++) {
$current_date = new DateTime($selected_date->format('Y-m') . '-' . $i);
if ($current_date < new DateTime(date('Y-m-d 00:00:00'))) {
// Past dates become immediately unavailability.
$unavailable_dates[] = $current_date->format('Y-m-d');
continue;
}
// Finding at least one slot of availability.
foreach ($provider_ids as $current_provider_id) {
$provider = $this->providers_model->find($current_provider_id);
$available_hours = $this->availability->get_available_hours(
$current_date->format('Y-m-d'),
$service,
$provider,
$exclude_appointment_id,
);
if (!empty($available_hours)) {
break;
}
}
// No availability amongst all the provider.
if (empty($available_hours)) {
$unavailable_dates[] = $current_date->format('Y-m-d');
}
}
if (count($unavailable_dates) === $number_of_days_in_month) {
json_response([
'is_month_unavailable' => true,
]);
return;
}
json_response($unavailable_dates);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Search for any provider that can handle the requested service.
*
* This method will return the database ID of the providers affected to the requested service.
*
* @param int $service_id The requested service ID.
*
* @return array Returns the ID of the provider that can provide the requested service.
*/
protected function search_providers_by_service(int $service_id): array
{
$available_providers = $this->providers_model->get_available_providers(true);
$provider_list = [];
foreach ($available_providers as $provider) {
foreach ($provider['services'] as $provider_service_id) {
if ($provider_service_id === $service_id) {
// Check if the provider is affected to the selected service.
$provider_list[] = $provider['id'];
}
}
}
return $provider_list;
}
}

View File

@ -0,0 +1,127 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Booking cancellation controller.
*
* Handles the booking cancellation related operations.
*
* @package Controllers
*/
class Booking_cancellation extends EA_Controller
{
/**
* Booking_cancellation constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('providers_model');
$this->load->model('services_model');
$this->load->model('customers_model');
$this->load->library('synchronization');
$this->load->library('notifications');
$this->load->library('webhooks_client');
}
/**
* Cancel an existing appointment.
*
* This method removes an appointment from the company's schedule. In order for the appointment to be deleted, the
* hash string must be provided. The customer can only cancel the appointment if the edit time period is not over
* yet.
*
* @param string $appointment_hash This appointment hash identifier.
*/
public function of(string $appointment_hash): void
{
try {
$disable_booking = setting('disable_booking');
if ($disable_booking) {
abort(403);
}
$cancellation_reason = request('cancellation_reason');
if ($this->input->method() !== 'post' || empty($cancellation_reason)) {
abort(403, 'Forbidden');
}
$occurrences = $this->appointments_model->get(['hash' => $appointment_hash]);
if (empty($occurrences)) {
html_vars([
'page_title' => lang('appointment_not_found'),
'company_color' => setting('company_color'),
'message_title' => lang('appointment_not_found'),
'message_text' => lang('appointment_does_not_exist_in_db'),
'message_icon' => base_url('assets/img/error.png'),
'google_analytics_code' => setting('google_analytics_code'),
'matomo_analytics_url' => setting('matomo_analytics_url'),
'matomo_analytics_site_id' => setting('matomo_analytics_site_id'),
]);
$this->load->view('pages/booking_message');
return;
}
$appointment = $occurrences[0];
$provider = $this->providers_model->find($appointment['id_users_provider']);
$customer = $this->customers_model->find($appointment['id_users_customer']);
$service = $this->services_model->find($appointment['id_services']);
$settings = [
'company_name' => setting('company_name'),
'company_email' => setting('company_email'),
'company_link' => setting('company_link'),
'date_format' => setting('date_format'),
'time_format' => setting('time_format'),
];
$this->appointments_model->delete($appointment['id']);
$this->synchronization->sync_appointment_deleted($appointment, $provider);
$this->notifications->notify_appointment_deleted(
$appointment,
$service,
$provider,
$customer,
$settings,
$cancellation_reason,
);
$this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_DELETE, $appointment);
} catch (Throwable $e) {
log_message('error', 'Booking Cancellation Exception: ' . $e->getMessage());
}
html_vars([
'page_title' => lang('appointment_cancelled_title'),
'company_color' => setting('company_color'),
'google_analytics_code' => setting('google_analytics_code'),
'matomo_analytics_url' => setting('matomo_analytics_url'),
'matomo_analytics_site_id' => setting('matomo_analytics_site_id'),
]);
$this->load->view('pages/booking_cancellation');
}
}

View File

@ -0,0 +1,70 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Booking confirmation controller.
*
* Handles the booking confirmation related operations.
*
* @package Controllers
*/
class Booking_confirmation extends EA_Controller
{
/**
* Booking_confirmation constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('providers_model');
$this->load->model('services_model');
$this->load->model('customers_model');
$this->load->library('google_sync');
}
/**
* Display the appointment registration success page.
*
* @throws Exception
*/
public function of(): void
{
$appointment_hash = $this->uri->segment(3);
$occurrences = $this->appointments_model->get(['hash' => $appointment_hash]);
if (empty($occurrences)) {
redirect('appointments'); // The appointment does not exist.
return;
}
$appointment = $occurrences[0];
$add_to_google_url = $this->google_sync->get_add_to_google_url($appointment['id']);
html_vars([
'page_title' => lang('success'),
'company_color' => setting('company_color'),
'google_analytics_code' => setting('google_analytics_code'),
'matomo_analytics_url' => setting('matomo_analytics_url'),
'matomo_analytics_site_id' => setting('matomo_analytics_site_id'),
'add_to_google_url' => $add_to_google_url,
]);
$this->load->view('pages/booking_confirmation');
}
}

View File

@ -0,0 +1,115 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Booking settings controller.
*
* Handles booking settings related operations.
*
* @package Controllers
*/
class Booking_settings extends EA_Controller
{
public array $allowed_setting_fields = ['id', 'name', 'value'];
/**
* Booking_settings constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('customers_model');
$this->load->model('services_model');
$this->load->model('providers_model');
$this->load->model('roles_model');
$this->load->model('settings_model');
$this->load->library('accounts');
$this->load->library('google_sync');
$this->load->library('notifications');
$this->load->library('synchronization');
$this->load->library('timezones');
}
/**
* Render the settings page.
*/
public function index(): void
{
session(['dest_url' => site_url('booking_settings')]);
$user_id = session('user_id');
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'booking_settings' => $this->settings_model->get_batch(),
]);
html_vars([
'page_title' => lang('settings'),
'active_menu' => PRIV_SYSTEM_SETTINGS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
]);
$this->load->view('pages/booking_settings');
}
/**
* Save booking settings.
*/
public function save(): void
{
try {
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$settings = request('booking_settings', []);
foreach ($settings as $setting) {
$existing_setting = $this->settings_model
->query()
->where('name', $setting['name'])
->get()
->row_array();
if (!empty($existing_setting)) {
$setting['id'] = $existing_setting['id'];
}
$this->settings_model->only($setting, $this->allowed_setting_fields);
$this->settings_model->save($setting);
}
response();
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,139 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Business logic controller.
*
* Handles general settings related operations.
*
* @package Controllers
*/
class Business_settings extends EA_Controller
{
/**
* Business_logic constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('customers_model');
$this->load->model('services_model');
$this->load->model('providers_model');
$this->load->model('roles_model');
$this->load->model('settings_model');
$this->load->library('accounts');
$this->load->library('google_sync');
$this->load->library('notifications');
$this->load->library('synchronization');
$this->load->library('timezones');
}
/**
* Render the settings page.
*/
public function index(): void
{
session(['dest_url' => site_url('business_settings')]);
$user_id = session('user_id');
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'business_settings' => $this->settings_model->get(),
'first_weekday' => setting('first_weekday'),
'time_format' => setting('time_format'),
]);
html_vars([
'page_title' => lang('settings'),
'active_menu' => PRIV_SYSTEM_SETTINGS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
]);
$this->load->view('pages/business_settings');
}
/**
* Save general settings.
*/
public function save(): void
{
try {
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$settings = request('business_settings', []);
foreach ($settings as $setting) {
$existing_setting = $this->settings_model
->query()
->where('name', $setting['name'])
->get()
->row_array();
if (!empty($existing_setting)) {
$setting['id'] = $existing_setting['id'];
}
$this->settings_model->only($setting, ['id', 'name', 'value']);
$this->settings_model->save($setting);
}
response();
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Apply global working plan to all providers.
*/
public function apply_global_working_plan()
{
try {
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$working_plan = request('working_plan');
$providers = $this->providers_model->get();
foreach ($providers as $provider) {
$this->providers_model->set_setting($provider['id'], 'working_plan', $working_plan);
}
response();
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,314 @@
<?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
* ---------------------------------------------------------------------------- */
use GuzzleHttp\Exception\GuzzleException;
/**
* Caldav controller.
*
* Handles the Caldav Calendar synchronization related operations.
*
* @package Controllers
*/
class Caldav extends EA_Controller
{
/**
* Caldav constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('caldav_sync');
$this->load->model('appointments_model');
$this->load->model('unavailabilities_model');
$this->load->model('providers_model');
}
/**
* Connect to the target CalDAV server
*
* @return void
*/
public function connect_to_server(): void
{
try {
$provider_id = request('provider_id');
$user_id = session('user_id');
if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$caldav_url = request('caldav_url');
$caldav_username = request('caldav_username');
$caldav_password = request('caldav_password');
$this->caldav_sync->test_connection($caldav_url, $caldav_username, $caldav_password);
$provider = $this->providers_model->find($provider_id);
$provider['settings']['caldav_sync'] = true;
$provider['settings']['caldav_url'] = $caldav_url;
$provider['settings']['caldav_username'] = $caldav_username;
$provider['settings']['caldav_password'] = $caldav_password;
$this->providers_model->save($provider);
json_response([
'success' => true,
]);
} catch (GuzzleException | InvalidArgumentException $e) {
json_response([
'success' => false,
'message' => $e->getMessage(),
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Sync the provider events with the remote CalDAV calendar.
*
* @param string $provider_id Provider ID (String because this is used with HTTP and CLI)
*
* @return void
*
* @throws \Jsvrcek\ICS\Exception\CalendarEventException
* @throws Exception
* @throws Throwable
*/
public static function sync(string $provider_id): void
{
/** @var EA_Controller $CI */
$CI = get_instance();
$CI->load->library('caldav_sync');
// Load the libraries as this method is called statically from the CLI command
$CI->load->model('appointments_model');
$CI->load->model('unavailabilities_model');
$CI->load->model('providers_model');
$CI->load->model('services_model');
$CI->load->model('customers_model');
$CI->load->model('settings_model');
$user_id = session('user_id');
if (!$user_id && !is_cli()) {
return;
}
if (empty($provider_id)) {
throw new InvalidArgumentException('No provider ID provided.');
}
$provider = $CI->providers_model->find($provider_id);
// Check whether the selected provider has the CalDAV Sync enabled.
if (!$provider['settings']['caldav_sync']) {
return; // The selected provider does not have the CalDAV Sync enabled.
}
// Fetch provider's appointments that belong to the sync time period.
$sync_past_days = $provider['settings']['sync_past_days'];
$sync_future_days = $provider['settings']['sync_future_days'];
$start_date_time = date('Y-m-d H:i:s', strtotime('-' . $sync_past_days . ' days'));
$end_date_time = date('Y-m-d H:i:s', strtotime('+' . $sync_future_days . ' days'));
$where = [
'start_datetime >=' => $start_date_time,
'end_datetime <=' => $end_date_time,
'id_users_provider' => $provider['id'],
];
$appointments = $CI->appointments_model->get($where);
$unavailabilities = $CI->unavailabilities_model->get($where);
$local_events = [...$appointments, ...$unavailabilities];
// Sync each appointment with CalDAV Calendar by following the project's sync protocol (see documentation).
foreach ($local_events as $local_event) {
if (!$local_event['is_unavailability']) {
$service = $CI->services_model->find($local_event['id_services']);
$customer = $CI->customers_model->find($local_event['id_users_customer']);
$events_model = $CI->appointments_model;
} else {
$service = null;
$customer = null;
$events_model = $CI->unavailabilities_model;
}
// If current appointment not synced yet, add to CalDAV Calendar.
if (!$local_event['id_caldav_calendar']) {
if (!$local_event['is_unavailability']) {
$caldav_event_id = $CI->caldav_sync->save_appointment($local_event, $service, $provider, $customer);
} else {
$caldav_event_id = $CI->caldav_sync->save_unavailability($local_event, $provider);
}
$local_event['id_caldav_calendar'] = $caldav_event_id;
$events_model->save($local_event); // Save the CalDAV Calendar ID.
continue;
}
// Appointment is synced with CalDAV Calendar.
try {
$caldav_event = $CI->caldav_sync->get_event($provider, $local_event['id_caldav_calendar']);
if ($caldav_event['status'] === 'CANCELLED') {
throw new Exception('Event is cancelled, remove the record from Easy!Appointments.');
}
// If CalDAV Calendar event is different from Easy!Appointments appointment then update Easy!Appointments record.
$local_event_start = strtotime($local_event['start_datetime']);
$local_event_end = strtotime($local_event['end_datetime']);
$caldav_event_start = new DateTime($caldav_event['start_datetime']);
$caldav_event_end = new DateTime($caldav_event['end_datetime']);
$caldav_event_notes = $local_event['is_unavailability']
? $caldav_event['summary'] . ' ' . $caldav_event['description']
: $caldav_event['description'];
$is_different =
$local_event_start !== $caldav_event_start->getTimestamp() ||
$local_event_end !== $caldav_event_end->getTimestamp() ||
$local_event['notes'] !== $caldav_event_notes;
if ($is_different) {
$local_event['start_datetime'] = $caldav_event_start->format('Y-m-d H:i:s');
$local_event['end_datetime'] = $caldav_event_end->format('Y-m-d H:i:s');
$local_event['notes'] = $caldav_event_notes;
$events_model->save($local_event);
}
} catch (Throwable) {
// Appointment not found on CalDAV Calendar, delete from Easy!Appointments.
$events_model->delete($local_event['id']);
$local_event['id_caldav_calendar'] = null;
}
}
// Add CalDAV Calendar events that do not exist in Easy!Appointments.
try {
$caldav_events = $CI->caldav_sync->get_sync_events($provider, $start_date_time, $end_date_time);
} catch (Throwable $e) {
if ($e->getCode() === 404) {
log_message('error', 'CalDAV - Remote Calendar not found for provider ID: ' . $provider_id);
return; // The remote calendar was not found.
} else {
throw $e;
}
}
foreach ($caldav_events as $caldav_event) {
if ($caldav_event['status'] === 'CANCELLED') {
continue;
}
if ($caldav_event['start_datetime'] === $caldav_event['end_datetime']) {
continue; // Cannot sync events with the same start and end date time value
}
$appointment_results = $CI->appointments_model->get(['id_caldav_calendar' => $caldav_event['id']]);
if (!empty($appointment_results)) {
continue;
}
$unavailability_results = $CI->unavailabilities_model->get([
'id_caldav_calendar' => $caldav_event['id'],
]);
if (!empty($unavailability_results)) {
continue;
}
// Record doesn't exist in the Easy!Appointments, so add the event now.
$local_event = [
'start_datetime' => $caldav_event['start_datetime'],
'end_datetime' => $caldav_event['end_datetime'],
'location' => $caldav_event['location'],
'notes' => $caldav_event['summary'] . ' ' . $caldav_event['description'],
'id_users_provider' => $provider_id,
'id_caldav_calendar' => $caldav_event['id'],
];
$CI->unavailabilities_model->save($local_event);
}
json_response([
'success' => true,
]);
}
/**
* Disable a providers sync setting.
*
* This method resets the CalDAV related settings from the user_settings DB table.
*
* @return void
*/
public function disable_provider_sync(): void
{
try {
$provider_id = request('provider_id');
if (!$provider_id) {
throw new Exception('Provider id not specified.');
}
$user_id = session('user_id');
if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$provider = $this->providers_model->find($provider_id);
$provider['settings']['caldav_sync'] = false;
$provider['settings']['caldav_url'] = null;
$provider['settings']['caldav_username'] = null;
$provider['settings']['caldav_password'] = null;
$this->providers_model->save($provider);
$this->appointments_model->clear_caldav_sync_ids($provider_id);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,788 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Calendar controller.
*
* Handles calendar related operations.
*
* @package Controllers
*/
class Calendar extends EA_Controller
{
public array $allowed_customer_fields = [
'id',
'first_name',
'last_name',
'email',
'phone_number',
'address',
'city',
'state',
'zip_code',
'timezone',
'language',
'notes',
'custom_field_1',
'custom_field_2',
'custom_field_3',
'custom_field_4',
'custom_field_5',
];
public array $allowed_appointment_fields = [
'id',
'start_datetime',
'end_datetime',
'location',
'notes',
'color',
'status',
'is_unavailability',
'id_users_provider',
'id_users_customer',
'id_services',
];
/**
* Calendar constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('unavailabilities_model');
$this->load->model('blocked_periods_model');
$this->load->model('customers_model');
$this->load->model('services_model');
$this->load->model('providers_model');
$this->load->model('roles_model');
$this->load->library('accounts');
$this->load->library('google_sync');
$this->load->library('notifications');
$this->load->library('synchronization');
$this->load->library('timezones');
$this->load->library('webhooks_client');
}
/**
* Render the calendar page and display the selected appointment.
*
* This method will call the "index" callback to handle the page rendering.
*
* @param string $appointment_hash Appointment hash.
*/
public function reschedule(string $appointment_hash): void
{
$this->index($appointment_hash);
}
/**
* Display the main backend page.
*
* This method displays the main backend page. All login permission can view this page which displays a calendar
* with the events of the selected provider or service. If a user has more privileges he will see more menus at the
* top of the page.
*
* @param string $appointment_hash Appointment hash.
*/
public function index(string $appointment_hash = ''): void
{
session([
'dest_url' => site_url('calendar/index' . (!empty($appointment_hash) ? '/' . $appointment_hash : '')),
]);
$user_id = session('user_id');
if (cannot('view', PRIV_APPOINTMENTS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
$user = $this->users_model->find($user_id);
$secretary_providers = [];
if ($role_slug === DB_SLUG_SECRETARY) {
$secretary = $this->secretaries_model->find(session('user_id'));
$secretary_providers = $secretary['providers'];
}
$edit_appointment = null;
if (!empty($appointment_hash)) {
$occurrences = $this->appointments_model->get(['hash' => $appointment_hash]);
if ($appointment_hash !== '' && !empty($occurrences)) {
$edit_appointment = $occurrences[0];
$this->appointments_model->load($edit_appointment, ['customer']);
}
}
$privileges = $this->roles_model->get_permissions_by_slug($role_slug);
$available_providers = $this->providers_model->get_available_providers();
if ($role_slug === DB_SLUG_PROVIDER) {
$available_providers = array_values(
array_filter($available_providers, function ($available_provider) use ($user_id) {
return (int) $available_provider['id'] === (int) $user_id;
}),
);
}
if ($role_slug === DB_SLUG_SECRETARY) {
$available_providers = array_values(
array_filter($available_providers, function ($available_provider) use ($secretary_providers) {
return in_array($available_provider['id'], $secretary_providers);
}),
);
}
$available_services = $this->services_model->get_available_services();
$calendar_view = request('view', $user['settings']['calendar_view']);
$appointment_status_options = setting('appointment_status_options');
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'date_format' => setting('date_format'),
'time_format' => setting('time_format'),
'first_weekday' => setting('first_weekday'),
'company_working_plan' => setting('company_working_plan'),
'timezones' => $this->timezones->to_array(),
'privileges' => $privileges,
'calendar_view' => $calendar_view,
'available_providers' => $available_providers,
'available_services' => $available_services,
'secretary_providers' => $secretary_providers,
'edit_appointment' => $edit_appointment,
'google_sync_feature' => config('google_sync_feature'),
'customers' => $this->customers_model->get(null, 50, null, 'update_datetime DESC'),
'default_language' => setting('default_language'),
'default_timezone' => setting('default_timezone'),
]);
html_vars([
'page_title' => lang('calendar'),
'active_menu' => PRIV_APPOINTMENTS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
'timezone' => session('timezone'),
'timezones' => $this->timezones->to_array(),
'grouped_timezones' => $this->timezones->to_grouped_array(),
'privileges' => $privileges,
'calendar_view' => $calendar_view,
'available_providers' => $available_providers,
'available_services' => $available_services,
'secretary_providers' => $secretary_providers,
'appointment_status_options' => json_decode($appointment_status_options, true) ?? [],
'require_first_name' => setting('require_first_name'),
'require_last_name' => setting('require_last_name'),
'require_email' => setting('require_email'),
'require_phone_number' => setting('require_phone_number'),
'require_address' => setting('require_address'),
'require_city' => setting('require_city'),
'require_zip_code' => setting('require_zip_code'),
'require_notes' => setting('require_notes'),
]);
$this->load->view('pages/calendar');
}
/**
* Save appointment changes that are made from the backend calendar page.
*/
public function save_appointment(): void
{
try {
$customer_data = request('customer_data');
$appointment_data = request('appointment_data');
$this->check_event_permissions((int) $appointment_data['id_users_provider']);
// Save customer changes to the database.
if ($customer_data) {
$customer = $customer_data;
$required_permissions = !empty($customer['id'])
? can('add', PRIV_CUSTOMERS)
: can('edit', PRIV_CUSTOMERS);
if (!$required_permissions) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$this->customers_model->only($customer, $this->allowed_customer_fields);
$customer['id'] = $this->customers_model->save($customer);
}
// Save appointment changes to the database.
$manage_mode = !empty($appointment_data['id']);
if ($appointment_data) {
$appointment = $appointment_data;
$required_permissions = !empty($appointment['id'])
? can('add', PRIV_APPOINTMENTS)
: can('edit', PRIV_APPOINTMENTS);
if (!$required_permissions) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
// If the appointment does not contain the customer record id, then it means that is going to be inserted.
if (!isset($appointment['id_users_customer'])) {
$appointment['id_users_customer'] = $customer['id'] ?? $customer_data['id'];
}
if ($manage_mode && !empty($appointment['id'])) {
$this->synchronization->remove_appointment_on_provider_change($appointment['id']);
}
$this->appointments_model->only($appointment, $this->allowed_appointment_fields);
$appointment['id'] = $this->appointments_model->save($appointment);
}
if (empty($appointment['id'])) {
throw new RuntimeException('The appointment ID is not available.');
}
$appointment = $this->appointments_model->find($appointment['id']);
$provider = $this->providers_model->find($appointment['id_users_provider']);
$customer = $this->customers_model->find($appointment['id_users_customer']);
$service = $this->services_model->find($appointment['id_services']);
$settings = [
'company_name' => setting('company_name'),
'company_link' => setting('company_link'),
'company_email' => setting('company_email'),
'date_format' => setting('date_format'),
'time_format' => setting('time_format'),
];
$this->synchronization->sync_appointment_saved($appointment, $service, $provider, $customer, $settings);
$this->notifications->notify_appointment_saved(
$appointment,
$service,
$provider,
$customer,
$settings,
$manage_mode,
);
$this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_SAVE, $appointment);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
private function check_event_permissions(int $provider_id): void
{
$user_id = (int) session('user_id');
$role_slug = session('role_slug');
if (
$role_slug === DB_SLUG_SECRETARY &&
!$this->secretaries_model->is_provider_supported($user_id, $provider_id)
) {
abort(403);
}
if ($role_slug === DB_SLUG_PROVIDER && $user_id !== $provider_id) {
abort(403);
}
}
/**
* Delete appointment from the database.
*
* This method deletes an existing appointment from the database. Once this action is finished it cannot be undone.
* Notification emails are send to both provider and customer and the delete action is executed to the Google
* Calendar account of the provider, if the "google_sync" setting is enabled.
*/
public function delete_appointment(): void
{
try {
if (cannot('delete', 'appointments')) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$appointment_id = request('appointment_id');
$cancellation_reason = (string) request('cancellation_reason');
if (empty($appointment_id)) {
throw new InvalidArgumentException('No appointment id provided.');
}
// Store appointment data for later use in this method.
$appointment = $this->appointments_model->find($appointment_id);
$this->check_event_permissions((int) $appointment['id_users_provider']);
$provider = $this->providers_model->find($appointment['id_users_provider']);
$customer = $this->customers_model->find($appointment['id_users_customer']);
$service = $this->services_model->find($appointment['id_services']);
$settings = [
'company_name' => setting('company_name'),
'company_email' => setting('company_email'),
'company_link' => setting('company_link'),
'date_format' => setting('date_format'),
'time_format' => setting('time_format'),
];
// Delete appointment record from the database.
$this->appointments_model->delete($appointment_id);
$this->notifications->notify_appointment_deleted(
$appointment,
$service,
$provider,
$customer,
$settings,
$cancellation_reason,
);
$this->synchronization->sync_appointment_deleted($appointment, $provider);
$this->webhooks_client->trigger(WEBHOOK_APPOINTMENT_DELETE, $appointment);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Insert of update unavailability to database.
*/
public function save_unavailability(): void
{
try {
// Check privileges
$unavailability = request('unavailability');
$required_permissions = !isset($unavailability['id'])
? can('add', PRIV_APPOINTMENTS)
: can('edit', PRIV_APPOINTMENTS);
if (!$required_permissions) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$provider_id = (int) $unavailability['id_users_provider'];
$this->check_event_permissions($provider_id);
$provider = $this->providers_model->find($provider_id);
$unavailability_id = $this->unavailabilities_model->save($unavailability);
$unavailability = $this->unavailabilities_model->find($unavailability_id);
$this->synchronization->sync_unavailability_saved($unavailability, $provider);
$this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_SAVE, $unavailability);
json_response([
'success' => true,
'warnings' => $warnings ?? [],
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Delete an unavailability from database.
*/
public function delete_unavailability(): void
{
try {
if (cannot('delete', PRIV_APPOINTMENTS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$unavailability_id = request('unavailability_id');
$unavailability = $this->unavailabilities_model->find($unavailability_id);
$this->check_event_permissions((int) $unavailability['id_users_provider']);
$provider = $this->providers_model->find($unavailability['id_users_provider']);
$this->unavailabilities_model->delete($unavailability_id);
$this->synchronization->sync_unavailability_deleted($unavailability, $provider);
$this->webhooks_client->trigger(WEBHOOK_UNAVAILABILITY_DELETE, $unavailability);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Insert of update working plan exceptions to database.
*/
public function save_working_plan_exception(): void
{
try {
if (cannot('edit', PRIV_USERS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$date = request('date');
$original_date = request('original_date');
$working_plan_exception = request('working_plan_exception');
if (!$working_plan_exception) {
$working_plan_exception = null;
}
$provider_id = request('provider_id');
$this->providers_model->save_working_plan_exception($provider_id, $date, $working_plan_exception);
if ($original_date && $date !== $original_date) {
$this->providers_model->delete_working_plan_exception($provider_id, $original_date);
}
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Delete a working plan exceptions time period to database.
*/
public function delete_working_plan_exception(): void
{
try {
$required_permissions = can('edit', PRIV_CUSTOMERS);
if (!$required_permissions) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$date = request('date');
$provider_id = request('provider_id');
$this->providers_model->delete_working_plan_exception($provider_id, $date);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Get Calendar Events
*
* This method will return all the calendar events within a specified period.
*/
public function get_calendar_appointments_for_table_view(): void
{
try {
$required_permissions = can('view', PRIV_APPOINTMENTS);
if (!$required_permissions) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$start_date = request('start_date') . ' 00:00:00';
$end_date = request('end_date') . ' 23:59:59';
$response = [
'appointments' => $this->appointments_model->get([
'start_datetime >=' => $start_date,
'end_datetime <=' => $end_date,
]),
'unavailabilities' => $this->unavailabilities_model->get([
'start_datetime >=' => $start_date,
'end_datetime <=' => $end_date,
]),
];
foreach ($response['appointments'] as &$appointment) {
$appointment['provider'] = $this->providers_model->find($appointment['id_users_provider']);
$appointment['service'] = $this->services_model->find($appointment['id_services']);
$appointment['customer'] = $this->customers_model->find($appointment['id_users_customer']);
}
unset($appointment);
$user_id = session('user_id');
$role_slug = session('role_slug');
// If the current user is a provider he must only see his own appointments.
if ($role_slug === DB_SLUG_PROVIDER) {
foreach ($response['appointments'] as $index => $appointment) {
if ((int) $appointment['id_users_provider'] !== (int) $user_id) {
unset($response['appointments'][$index]);
}
}
$response['appointments'] = array_values($response['appointments']);
foreach ($response['unavailabilities'] as $index => $unavailability) {
if ((int) $unavailability['id_users_provider'] !== (int) $user_id) {
unset($response['unavailabilities'][$index]);
}
}
$response['unavailabilities'] = array_values($response['unavailabilities']);
}
// If the current user is a secretary he must only see the appointments of his providers.
if ($role_slug === DB_SLUG_SECRETARY) {
$providers = $this->secretaries_model->find($user_id)['providers'];
foreach ($response['appointments'] as $index => $appointment) {
if (!in_array((int) $appointment['id_users_provider'], $providers)) {
unset($response['appointments'][$index]);
}
}
$response['appointments'] = array_values($response['appointments']);
foreach ($response['unavailabilities'] as $index => $unavailability) {
if (!in_array((int) $unavailability['id_users_provider'], $providers)) {
unset($response['unavailabilities'][$index]);
}
}
$response['unavailabilities'] = array_values($response['unavailabilities']);
}
foreach ($response['unavailabilities'] as &$unavailability) {
$unavailability['provider'] = $this->providers_model->find($unavailability['id_users_provider']);
}
unset($unavailability);
// Add blocked periods to the response.
$start_date = request('start_date');
$end_date = request('end_date');
$response['blocked_periods'] = $this->blocked_periods_model->get_for_period($start_date, $end_date);
json_response($response);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Get the registered appointments for the given date period and record.
*
* This method returns the database appointments and unavailability periods for the user selected date period and
* record type (provider or service).
*/
public function get_calendar_appointments(): void
{
try {
if (cannot('view', PRIV_APPOINTMENTS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$record_id = request('record_id');
$is_all = request('record_id') === FILTER_TYPE_ALL;
$filter_type = request('filter_type');
if (!$filter_type && !$is_all) {
json_response([
'appointments' => [],
'unavailabilities' => [],
]);
return;
}
$record_id = $this->db->escape($record_id);
if ($filter_type == FILTER_TYPE_PROVIDER) {
$where_id = 'id_users_provider';
} elseif ($filter_type === FILTER_TYPE_SERVICE) {
$where_id = 'id_services';
} else {
$where_id = $record_id;
}
// Get appointments
$start_date = $this->db->escape(request('start_date'));
$end_date = $this->db->escape(date('Y-m-d', strtotime(request('end_date') . ' +1 day')));
$where_clause =
$where_id .
' = ' .
$record_id .
'
AND ((start_datetime > ' .
$start_date .
' AND start_datetime < ' .
$end_date .
')
or (end_datetime > ' .
$start_date .
' AND end_datetime < ' .
$end_date .
')
or (start_datetime <= ' .
$start_date .
' AND end_datetime >= ' .
$end_date .
'))
AND is_unavailability = 0
';
$response['appointments'] = $this->appointments_model->get($where_clause);
foreach ($response['appointments'] as &$appointment) {
$appointment['provider'] = $this->providers_model->find($appointment['id_users_provider']);
$appointment['service'] = $this->services_model->find($appointment['id_services']);
$appointment['customer'] = $this->customers_model->find($appointment['id_users_customer']);
}
unset($appointment);
// Get unavailability periods (only for provider).
$response['unavailabilities'] = [];
if ($filter_type == FILTER_TYPE_PROVIDER || $is_all) {
$where_clause =
$where_id .
' = ' .
$record_id .
'
AND ((start_datetime > ' .
$start_date .
' AND start_datetime < ' .
$end_date .
')
or (end_datetime > ' .
$start_date .
' AND end_datetime < ' .
$end_date .
')
or (start_datetime <= ' .
$start_date .
' AND end_datetime >= ' .
$end_date .
'))
AND is_unavailability = 1
';
$response['unavailabilities'] = $this->unavailabilities_model->get($where_clause);
}
$user_id = session('user_id');
$role_slug = session('role_slug');
// If the current user is a provider he must only see his own appointments.
if ($role_slug === DB_SLUG_PROVIDER) {
foreach ($response['appointments'] as $index => $appointment) {
if ((int) $appointment['id_users_provider'] !== (int) $user_id) {
unset($response['appointments'][$index]);
}
}
$response['appointments'] = array_values($response['appointments']);
foreach ($response['unavailabilities'] as $index => $unavailability) {
if ((int) $unavailability['id_users_provider'] !== (int) $user_id) {
unset($response['unavailabilities'][$index]);
}
}
unset($unavailability);
$response['unavailabilities'] = array_values($response['unavailabilities']);
}
// If the current user is a secretary he must only see the appointments of his providers.
if ($role_slug === DB_SLUG_SECRETARY) {
$providers = $this->secretaries_model->find($user_id)['providers'];
foreach ($response['appointments'] as $index => $appointment) {
if (!in_array((int) $appointment['id_users_provider'], $providers)) {
unset($response['appointments'][$index]);
}
}
$response['appointments'] = array_values($response['appointments']);
foreach ($response['unavailabilities'] as $index => $unavailability) {
if (!in_array((int) $unavailability['id_users_provider'], $providers)) {
unset($response['unavailabilities'][$index]);
}
}
$response['unavailabilities'] = array_values($response['unavailabilities']);
}
foreach ($response['unavailabilities'] as &$unavailability) {
$unavailability['provider'] = $this->providers_model->find($unavailability['id_users_provider']);
}
unset($unavailability);
// Add blocked periods to the response.
$start_date = request('start_date');
$end_date = request('end_date');
$response['blocked_periods'] = $this->blocked_periods_model->get_for_period($start_date, $end_date);
json_response($response);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,47 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Captcha controller.
*
* Handles the captcha operations.
*
* @package Controllers
*/
class Captcha extends EA_Controller
{
/**
* Class Constructor
*/
public function __construct()
{
parent::__construct();
$this->load->library('captcha_builder');
}
/**
* Make a request to this method to get a captcha image.
*/
public function index(): void
{
$this->captcha_builder->setDistortion(true);
$this->captcha_builder->setMaxBehindLines(1);
$this->captcha_builder->setMaxFrontLines(1);
$this->captcha_builder->setBackgroundColor(255, 255, 255);
$this->captcha_builder->build();
session(['captcha_phrase' => $this->captcha_builder->getPhrase()]);
header('Content-type: image/jpeg');
$this->captcha_builder->output();
}
}

View File

@ -0,0 +1,73 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Consents controller.
*
* Handles user consent related operations.
*
* @package Controllers
*/
class Consents extends EA_Controller
{
/**
* Consents constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('consents_model');
}
/**
* Save (insert or update) the consent
*/
public function save(): void
{
try {
$consent = request('consent');
$consent['ip'] = $this->input->ip_address();
$occurrences = $this->consents_model->get(['ip' => $consent['ip']], 1, 0, 'create_datetime DESC');
if (!empty($occurrences)) {
$last_consent = $occurrences[0];
$last_consent_create_datetime_instance = new DateTime($last_consent['create_datetime']);
$threshold_datetime_instance = new DateTime('-24 hours');
if ($last_consent_create_datetime_instance > $threshold_datetime_instance) {
// Do not create a new consent.
json_response([
'success' => true,
]);
return;
}
}
$consent['id'] = $this->consents_model->save($consent);
json_response([
'success' => true,
'id' => $consent['id'],
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,186 @@
<?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
* ---------------------------------------------------------------------------- */
require_once __DIR__ . '/Google.php';
/**
* Console controller.
*
* Handles all the Console related operations.
*/
class Console extends EA_Controller
{
/**
* Console constructor.
*/
public function __construct()
{
if (!is_cli()) {
exit('No direct script access allowed');
}
parent::__construct();
$this->load->dbutil();
$this->load->library('instance');
$this->load->model('admins_model');
$this->load->model('customers_model');
$this->load->model('providers_model');
$this->load->model('services_model');
$this->load->model('settings_model');
}
/**
* Perform a console installation.
*
* Use this method to install Easy!Appointments directly from the terminal.
*
* Usage:
*
* php index.php console install
*
* @throws Exception
*/
public function install(): void
{
$this->instance->migrate('fresh');
$password = $this->instance->seed();
response(
PHP_EOL . '⇾ Installation completed, login with "administrator" / "' . $password . '".' . PHP_EOL . PHP_EOL,
);
}
/**
* Migrate the database to the latest state.
*
* Use this method to upgrade an Easy!Appointments instance to the latest database state.
*
* Notice:
*
* Do not use this method to install the app as it will not seed the database with the initial entries (admin,
* provider, service, settings etc.).
*
* Usage:
*
* php index.php console migrate
*
* php index.php console migrate fresh
*
* @param string $type
*/
public function migrate(string $type = ''): void
{
$this->instance->migrate($type);
}
/**
* Seed the database with test data.
*
* Use this method to add test data to your database
*
* Usage:
*
* php index.php console seed
* @throws Exception
*/
public function seed(): void
{
$this->instance->seed();
}
/**
* Create a database backup file.
*
* Use this method to back up your Easy!Appointments data.
*
* Usage:
*
* php index.php console backup
*
* php index.php console backup /path/to/backup/folder
*
* @throws Exception
*/
public function backup(): void
{
$this->instance->backup($GLOBALS['argv'][3] ?? null);
}
/**
* Trigger the synchronization of all provider calendars with Google Calendar.
*
* Use this method in a cronjob to automatically sync events between Easy!Appointments and Google Calendar.
*
* Notice:
*
* Google syncing must first be enabled for each individual provider from inside the backend calendar page.
*
* Usage:
*
* php index.php console sync
*/
public function sync(): void
{
$providers = $this->providers_model->get();
foreach ($providers as $provider) {
if (filter_var($provider['settings']['google_sync'], FILTER_VALIDATE_BOOLEAN)) {
Google::sync((string) $provider['id']);
}
if (filter_var($provider['settings']['caldav_sync'], FILTER_VALIDATE_BOOLEAN)) {
Caldav::sync((string) $provider['id']);
}
}
}
/**
* Show help information about the console capabilities.
*
* Use this method to see the available commands.
*
* Usage:
*
* php index.php console help
*/
public function help(): void
{
$help = [
'',
'Easy!Appointments ' . config('version'),
'',
'Usage:',
'',
'⇾ php index.php console [command] [arguments]',
'',
'Commands:',
'',
'⇾ php index.php console migrate',
'⇾ php index.php console migrate fresh',
'⇾ php index.php console migrate up',
'⇾ php index.php console migrate down',
'⇾ php index.php console seed',
'⇾ php index.php console install',
'⇾ php index.php console backup',
'⇾ php index.php console sync',
'',
'',
];
response(implode(PHP_EOL, $help));
}
}

View File

@ -0,0 +1,304 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Customers controller.
*
* Handles the customers related operations.
*
* @package Controllers
*/
class Customers extends EA_Controller
{
public array $allowed_customer_fields = [
'id',
'first_name',
'last_name',
'email',
'phone_number',
'address',
'city',
'state',
'zip_code',
'notes',
'timezone',
'language',
'custom_field_1',
'custom_field_2',
'custom_field_3',
'custom_field_4',
'custom_field_5',
'ldap_dn',
];
/**
* Customers constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('customers_model');
$this->load->model('secretaries_model');
$this->load->model('roles_model');
$this->load->library('accounts');
$this->load->library('permissions');
$this->load->library('timezones');
$this->load->library('webhooks_client');
}
/**
* Render the backend customers page.
*
* On this page admin users will be able to manage customers, which are eventually selected by customers during the
* booking process.
*/
public function index(): void
{
session(['dest_url' => site_url('customers')]);
$user_id = session('user_id');
if (cannot('view', PRIV_CUSTOMERS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
$date_format = setting('date_format');
$time_format = setting('time_format');
$require_first_name = setting('require_first_name');
$require_last_name = setting('require_last_name');
$require_email = setting('require_email');
$require_phone_number = setting('require_phone_number');
$require_address = setting('require_address');
$require_city = setting('require_city');
$require_zip_code = setting('require_zip_code');
$secretary_providers = [];
if ($role_slug === DB_SLUG_SECRETARY) {
$secretary = $this->secretaries_model->find($user_id);
$secretary_providers = $secretary['providers'];
}
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'date_format' => $date_format,
'time_format' => $time_format,
'timezones' => $this->timezones->to_array(),
'secretary_providers' => $secretary_providers,
'default_language' => setting('default_language'),
'default_timezone' => setting('default_timezone'),
]);
html_vars([
'page_title' => lang('customers'),
'active_menu' => PRIV_CUSTOMERS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
'timezones' => $this->timezones->to_array(),
'grouped_timezones' => $this->timezones->to_grouped_array(),
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
'require_first_name' => $require_first_name,
'require_last_name' => $require_last_name,
'require_email' => $require_email,
'require_phone_number' => $require_phone_number,
'require_address' => $require_address,
'require_city' => $require_city,
'require_zip_code' => $require_zip_code,
'available_languages' => config('available_languages'),
]);
$this->load->view('pages/customers');
}
/**
* Find a customer.
*/
public function find(): void
{
try {
if (cannot('view', PRIV_CUSTOMERS)) {
abort(403, 'Forbidden');
}
$user_id = session('user_id');
$customer_id = request('customer_id');
if (!$this->permissions->has_customer_access($user_id, $customer_id)) {
abort(403, 'Forbidden');
}
$customer = $this->customers_model->find($customer_id);
json_response($customer);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Filter customers by the provided keyword.
*/
public function search(): void
{
try {
if (cannot('view', PRIV_CUSTOMERS)) {
abort(403, 'Forbidden');
}
$keyword = request('keyword', '');
$order_by = request('order_by', 'update_datetime DESC');
$limit = request('limit', 1000);
$offset = (int) request('offset', '0');
$customers = $this->customers_model->search($keyword, $limit, $offset, $order_by);
$user_id = session('user_id');
foreach ($customers as $index => &$customer) {
if (!$this->permissions->has_customer_access($user_id, $customer['id'])) {
unset($customers[$index]);
continue;
}
$appointments = $this->appointments_model->get(['id_users_customer' => $customer['id']]);
foreach ($appointments as &$appointment) {
$this->appointments_model->load($appointment, ['service', 'provider']);
}
$customer['appointments'] = $appointments;
}
json_response(array_values($customers));
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new customer.
*/
public function store(): void
{
try {
if (cannot('add', PRIV_CUSTOMERS)) {
abort(403, 'Forbidden');
}
if (session('role_slug') !== DB_SLUG_ADMIN && setting('limit_customer_visibility')) {
abort(403);
}
$customer = request('customer');
$this->customers_model->only($customer, $this->allowed_customer_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a customer.
*/
public function update(): void
{
try {
if (cannot('edit', PRIV_CUSTOMERS)) {
abort(403, 'Forbidden');
}
$user_id = session('user_id');
$customer = request('customer');
if (!$this->permissions->has_customer_access($user_id, $customer['id'])) {
abort(403, 'Forbidden');
}
$this->customers_model->only($customer, $this->allowed_customer_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Remove a customer.
*/
public function destroy(): void
{
try {
if (cannot('delete', PRIV_CUSTOMERS)) {
abort(403, 'Forbidden');
}
$user_id = session('user_id');
$customer_id = request('customer_id');
if (!$this->permissions->has_customer_access($user_id, $customer_id)) {
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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,112 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* General settings controller.
*
* Handles general settings related operations.
*
* @package Controllers
*/
class General_settings extends EA_Controller
{
/**
* Calendar constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('settings_model');
$this->load->library('accounts');
$this->load->library('timezones');
}
/**
* Render the settings page.
*/
public function index(): void
{
session(['dest_url' => site_url('general_settings')]);
$user_id = session('user_id');
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
$available_theme_files = glob(__DIR__ . '/../../assets/css/themes/*.min.css');
$available_themes = array_map(function ($available_theme_file) {
return str_replace('.min.css', '', basename($available_theme_file));
}, $available_theme_files);
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'timezones' => $this->timezones->to_array(),
'general_settings' => $this->settings_model->get(),
]);
html_vars([
'page_title' => lang('settings'),
'active_menu' => PRIV_SYSTEM_SETTINGS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
'grouped_timezones' => $this->timezones->to_grouped_array(),
'available_themes' => $available_themes,
]);
$this->load->view('pages/general_settings');
}
/**
* Save general settings.
*/
public function save(): void
{
try {
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$settings = request('general_settings', []);
foreach ($settings as $setting) {
$existing_setting = $this->settings_model
->query()
->where('name', $setting['name'])
->get()
->row_array();
if (!empty($existing_setting)) {
$setting['id'] = $existing_setting['id'];
}
$this->settings_model->save($setting);
}
response();
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,436 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Google controller.
*
* Handles the Google Calendar synchronization related operations.
*
* @package Controllers
*/
class Google extends EA_Controller
{
/**
* Google constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('google_sync');
$this->load->model('appointments_model');
$this->load->model('providers_model');
$this->load->model('roles_model');
}
/**
* Complete synchronization of appointments between Google Calendar and Easy!Appointments.
*
* This method will completely sync the appointments of a provider with his Google Calendar account. The sync period
* needs to be relatively small, because a lot of API calls might be necessary and this will lead to consuming the
* Google limit for the Calendar API usage.
*/
public static function sync(string $provider_id = null): void
{
try {
/** @var EA_Controller $CI */
$CI = get_instance();
$CI->load->library('google_sync');
// Load the libraries as this method is called statically from the CLI command
$CI->load->model('appointments_model');
$CI->load->model('unavailabilities_model');
$CI->load->model('providers_model');
$CI->load->model('services_model');
$CI->load->model('customers_model');
$CI->load->model('settings_model');
$user_id = session('user_id');
if (!$user_id && !is_cli()) {
return;
}
if (!$provider_id) {
throw new InvalidArgumentException('No provider ID provided.');
}
$provider = $CI->providers_model->find($provider_id);
// Check whether the selected provider has the Google Sync enabled.
$google_sync = $CI->providers_model->get_setting($provider['id'], 'google_sync');
if (!$google_sync) {
return; // The selected provider does not have the Google Sync enabled.
}
$google_token = json_decode($provider['settings']['google_token'], true);
$CI->google_sync->refresh_token($google_token['refresh_token']);
// Fetch provider's appointments that belong to the sync time period.
$sync_past_days = $provider['settings']['sync_past_days'];
$sync_future_days = $provider['settings']['sync_future_days'];
$start = strtotime('-' . $sync_past_days . ' days', strtotime(date('Y-m-d')));
$end = strtotime('+' . $sync_future_days . ' days', strtotime(date('Y-m-d')));
$where = [
'start_datetime >=' => date('Y-m-d H:i:s', $start),
'end_datetime <=' => date('Y-m-d H:i:s', $end),
'id_users_provider' => $provider['id'],
];
$appointments = $CI->appointments_model->get($where);
$unavailabilities = $CI->unavailabilities_model->get($where);
$local_events = [...$appointments, ...$unavailabilities];
$settings = [
'company_name' => setting('company_name'),
'company_link' => setting('company_link'),
'company_email' => setting('company_email'),
];
$provider_timezone = new DateTimeZone($provider['timezone']);
// Sync each appointment with Google Calendar by following the project's sync protocol (see documentation).
foreach ($local_events as $local_event) {
if (!$local_event['is_unavailability']) {
$service = $CI->services_model->find($local_event['id_services']);
$customer = $CI->customers_model->find($local_event['id_users_customer']);
$events_model = $CI->appointments_model;
} else {
$service = null;
$customer = null;
$events_model = $CI->unavailabilities_model;
}
// If current appointment not synced yet, add to Google Calendar.
if (!$local_event['id_google_calendar']) {
if (!$local_event['is_unavailability']) {
$google_event = $CI->google_sync->add_appointment(
$local_event,
$provider,
$service,
$customer,
$settings,
);
} else {
$google_event = $CI->google_sync->add_unavailability($provider, $local_event);
}
$local_event['id_google_calendar'] = $google_event->getId();
$events_model->save($local_event); // Save the Google Calendar ID.
continue;
}
// Appointment is synced with Google Calendar.
try {
$google_event = $CI->google_sync->get_event($provider, $local_event['id_google_calendar']);
if ($google_event->getStatus() == 'cancelled') {
throw new Exception('Event is cancelled, remove the record from Easy!Appointments.');
}
// If Google Calendar event is different from Easy!Appointments appointment then update Easy!Appointments record.
$local_event_start = strtotime($local_event['start_datetime']);
$local_event_end = strtotime($local_event['end_datetime']);
$google_event_start = new DateTime(
$google_event->getStart()->getDateTime() ?? $google_event->getEnd()->getDate(),
);
$google_event_start->setTimezone($provider_timezone);
$google_event_end = new DateTime(
$google_event->getEnd()->getDateTime() ?? $google_event->getEnd()->getDate(),
);
$google_event_end->setTimezone($provider_timezone);
$google_event_notes = $local_event['is_unavailability']
? $google_event->getSummary() . ' ' . $google_event->getDescription()
: $google_event->getDescription();
$is_different =
$local_event_start !== $google_event_start->getTimestamp() ||
$local_event_end !== $google_event_end->getTimestamp() ||
$local_event['notes'] !== $google_event_notes;
if ($is_different) {
$local_event['start_datetime'] = $google_event_start->format('Y-m-d H:i:s');
$local_event['end_datetime'] = $google_event_end->format('Y-m-d H:i:s');
$local_event['notes'] = $google_event_notes;
$events_model->save($local_event);
}
} catch (Throwable) {
// Appointment not found on Google Calendar, delete from Easy!Appointments.
$events_model->delete($local_event['id']);
$local_event['id_google_calendar'] = null;
}
}
// Add Google Calendar events that do not exist in Easy!Appointments.
$google_calendar = $provider['settings']['google_calendar'];
try {
$google_events = $CI->google_sync->get_sync_events($google_calendar, $start, $end);
} catch (Throwable $e) {
if ($e->getCode() === 404) {
log_message('error', 'Google - Remote Calendar not found for provider ID: ' . $provider_id);
return; // The remote calendar was not found.
} else {
throw $e;
}
}
foreach ($google_events->getItems() as $google_event) {
if ($google_event->getStatus() === 'cancelled') {
continue;
}
if ($google_event->getStart() === null || $google_event->getEnd() === null) {
continue;
}
if ($google_event->getStart()->getDateTime() === $google_event->getEnd()->getDateTime()) {
continue;
}
$google_event_start = new DateTime($google_event->getStart()->getDateTime());
$google_event_start->setTimezone($provider_timezone);
$google_event_end = new DateTime($google_event->getEnd()->getDateTime());
$google_event_end->setTimezone($provider_timezone);
$appointment_results = $CI->appointments_model->get(['id_google_calendar' => $google_event->getId()]);
if (!empty($appointment_results)) {
continue;
}
$unavailability_results = $CI->unavailabilities_model->get([
'id_google_calendar' => $google_event->getId(),
]);
if (!empty($unavailability_results)) {
continue;
}
// Record doesn't exist in the Easy!Appointments, so add the event now.
$local_event = [
'start_datetime' => $google_event_start->format('Y-m-d H:i:s'),
'end_datetime' => $google_event_end->format('Y-m-d H:i:s'),
'is_unavailability' => true,
'location' => $google_event->getLocation(),
'notes' => $google_event->getSummary() . ' ' . $google_event->getDescription(),
'id_users_provider' => $provider_id,
'id_google_calendar' => $google_event->getId(),
'id_users_customer' => null,
'id_services' => null,
];
$CI->unavailabilities_model->save($local_event);
}
json_response([
'success' => true,
]);
} catch (Throwable $e) {
log_message(
'error',
'Google - Sync completed with an error (provider ID "' . $provider_id . '"): ' . $e->getMessage(),
);
json_exception($e);
}
}
/**
* Authorize Google Calendar API usage for a specific provider.
*
* Since it is required to follow the web application flow, in order to retrieve a refresh token from the Google API
* service, this method is going to authorize the given provider.
*
* @param string $provider_id The provider id, for whom the sync authorization is made.
*/
public function oauth(string $provider_id): void
{
if (!$this->session->userdata('user_id')) {
show_error('Forbidden', 403);
}
// Store the provider id for use on the callback function.
session(['oauth_provider_id' => $provider_id]);
// Redirect browser to google user content page.
header('Location: ' . $this->google_sync->get_auth_url());
}
/**
* Callback method for the Google Calendar API authorization process.
*
* Once the user grants consent with his Google Calendar data usage, the Google OAuth service will redirect him back
* in this page. Here we are going to store the refresh token, because this is what will be used to generate access
* tokens in the future.
*
* IMPORTANT: Because it is necessary to authorize the application using the web server flow (see official
* documentation of OAuth), every Easy!Appointments installation should use its own calendar api key. So in every
* api console account, the "http://path-to-Easy!Appointments/google/oauth_callback" should be included in an
* allowed redirect URL.
*
* @throws Exception
*/
public function oauth_callback(): void
{
if (!session('user_id')) {
abort(403, 'Forbidden');
}
$code = request('code');
if (empty($code)) {
response('Code authorization failed.');
return;
}
$token = $this->google_sync->authenticate($code);
if (empty($token)) {
response('Token authorization failed.');
return;
}
// Store the token into the database for future reference.
$oauth_provider_id = session('oauth_provider_id');
if ($oauth_provider_id) {
$this->providers_model->set_setting($oauth_provider_id, 'google_sync', true);
$this->providers_model->set_setting($oauth_provider_id, 'google_token', json_encode($token));
$this->providers_model->set_setting($oauth_provider_id, 'google_calendar', 'primary');
} else {
response('Sync provider id not specified.');
}
}
/**
* This method will return a list of the available Google Calendars.
*
* The user will need to select a specific calendar from this list to sync his appointments with. Google access must
* be already granted for the specific provider.
*/
public function get_google_calendars(): void
{
try {
$provider_id = (int) request('provider_id');
if (empty($provider_id)) {
throw new Exception('Provider id is required in order to fetch the google calendars.');
}
// Check if selected provider has sync enabled.
$google_sync = $this->providers_model->get_setting($provider_id, 'google_sync');
if (!$google_sync) {
json_response([
'success' => false,
]);
return;
}
$google_token = json_decode($this->providers_model->get_setting($provider_id, 'google_token'), true);
$this->google_sync->refresh_token($google_token['refresh_token']);
$calendars = $this->google_sync->get_google_calendars();
json_response($calendars);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Select a specific google calendar for a provider.
*
* All the appointments will be synced with this particular calendar.
*/
public function select_google_calendar(): void
{
try {
$provider_id = request('provider_id');
$user_id = session('user_id');
if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$calendar_id = request('calendar_id');
$this->providers_model->set_setting($provider_id, 'google_calendar', $calendar_id);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Disable a providers sync setting.
*
* This method deletes the "google_sync" and "google_token" settings from the database.
*
* After that the provider's appointments will be no longer synced with Google Calendar.
*/
public function disable_provider_sync(): void
{
try {
$provider_id = request('provider_id');
if (!$provider_id) {
throw new Exception('Provider id not specified.');
}
$user_id = session('user_id');
if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$this->providers_model->set_setting($provider_id, 'google_sync', false);
$this->providers_model->set_setting($provider_id, 'google_token');
$this->appointments_model->clear_google_sync_ids($provider_id);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,102 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Google Analytics settings controller.
*
* Handles Google Analytics settings related operations.
*
* @package Controllers
*/
class Google_analytics_settings extends EA_Controller
{
/**
* Google_analytics_settings constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('settings_model');
$this->load->library('accounts');
}
/**
* Render the settings page.
*/
public function index(): void
{
session(['dest_url' => site_url('google_analytics_settings')]);
$user_id = session('user_id');
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'google_analytics_settings' => $this->settings_model->get('name like "google_analytics_%"'),
]);
html_vars([
'page_title' => lang('google_analytics'),
'active_menu' => PRIV_SYSTEM_SETTINGS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
]);
$this->load->view('pages/google_analytics_settings');
}
/**
* Save general settings.
*/
public function save(): void
{
try {
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$settings = request('google_analytics_settings', []);
foreach ($settings as $setting) {
$existing_setting = $this->settings_model
->query()
->where('name', $setting['name'])
->get()
->row_array();
if (!empty($existing_setting)) {
$setting['id'] = $existing_setting['id'];
}
$this->settings_model->save($setting);
}
response();
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,140 @@
<?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.1.0
* ---------------------------------------------------------------------------- */
/**
* Installation controller.
*
* Handles the installation related operations.
*
* @package Controllers
*/
class Installation extends EA_Controller
{
/**
* Installation constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('admins_model');
$this->load->model('settings_model');
$this->load->model('services_model');
$this->load->model('providers_model');
$this->load->model('customers_model');
$this->load->library('instance');
}
/**
* Display the installation page.
*/
public function index(): void
{
if (is_app_installed()) {
redirect();
return;
}
$this->load->view('pages/installation', [
'base_url' => config('base_url'),
]);
}
/**
* Installs Easy!Appointments on the server.
*/
public function perform()
{
try {
if (is_app_installed()) {
return;
}
$admin = request('admin');
$company = request('company');
$this->instance->migrate();
// Insert admin
$admin['timezone'] = date_default_timezone_get();
$admin['settings']['username'] = $admin['username'];
$admin['settings']['password'] = $admin['password'];
$admin['settings']['notifications'] = true;
$admin['settings']['calendar_view'] = CALENDAR_VIEW_DEFAULT;
unset($admin['username'], $admin['password']);
$admin['id'] = $this->admins_model->save($admin);
session([
'user_id' => $admin['id'],
'user_email' => $admin['email'],
'role_slug' => DB_SLUG_ADMIN,
'language' => $admin['language'],
'timezone' => $admin['timezone'],
'username' => $admin['settings']['username'],
]);
// Save company settings
setting([
'company_name' => $company['company_name'],
'company_email' => $company['company_email'],
'company_link' => $company['company_link'],
]);
// Service
$service_id = $this->services_model->save([
'name' => 'Service',
'duration' => '30',
'price' => '0',
'currency' => '',
'availabilities_type' => 'flexible',
'attendants_number' => '1',
]);
// Provider
$this->providers_model->save([
'first_name' => 'Jane',
'last_name' => 'Doe',
'email' => 'jane@example.org',
'phone_number' => '+1 (000) 000-0000',
'services' => [$service_id],
'language' => $admin['language'],
'timezone' => $admin['timezone'],
'settings' => [
'username' => 'janedoe',
'password' => random_string(),
'working_plan' => setting('company_working_plan'),
'notifications' => true,
'google_sync' => false,
'sync_past_days' => 30,
'sync_future_days' => 90,
'calendar_view' => CALENDAR_VIEW_DEFAULT,
],
]);
// Customer
$this->customers_model->save([
'first_name' => 'James',
'last_name' => 'Doe',
'email' => 'james@example.org',
'phone_number' => '+1 (000) 000-0000',
]);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,74 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Integrations controller.
*
* Displays the integrations page.
*
* @package Controllers
*/
class Integrations extends EA_Controller
{
/**
* Integrations constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('customers_model');
$this->load->model('services_model');
$this->load->model('providers_model');
$this->load->model('roles_model');
$this->load->model('settings_model');
$this->load->library('accounts');
$this->load->library('google_sync');
$this->load->library('notifications');
$this->load->library('synchronization');
$this->load->library('timezones');
}
/**
* Render the settings page.
*/
public function index(): void
{
session(['dest_url' => site_url('about')]);
$user_id = session('user_id');
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
html_vars([
'page_title' => lang('integrations'),
'active_menu' => PRIV_SYSTEM_SETTINGS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
]);
$this->load->view('pages/integrations');
}
}

View File

@ -0,0 +1,132 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* LDAP settings controller.
*
* Handles LDAP settings related operations.
*
* @package Controllers
*/
class Ldap_settings extends EA_Controller
{
/**
* Ldap_settings constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('settings_model');
$this->load->library('accounts');
$this->load->library('ldap_client');
}
/**
* Render the settings page.
*/
public function index(): void
{
session(['dest_url' => site_url('ldap_settings')]);
$user_id = session('user_id');
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'ldap_settings' => $this->settings_model->get('name like "ldap_%"'),
'ldap_default_filter' => LDAP_DEFAULT_FILTER,
'ldap_default_field_mapping' => LDAP_DEFAULT_FIELD_MAPPING,
]);
html_vars([
'page_title' => lang('ldap'),
'active_menu' => PRIV_SYSTEM_SETTINGS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
'roles' => $this->roles_model->get(),
]);
$this->load->view('pages/ldap_settings');
}
/**
* Save general settings.
*/
public function save(): void
{
try {
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$settings = request('ldap_settings', []);
foreach ($settings as $setting) {
$existing_setting = $this->settings_model
->query()
->where('name', $setting['name'])
->get()
->row_array();
if (!empty($existing_setting)) {
$setting['id'] = $existing_setting['id'];
}
$this->settings_model->save($setting);
}
response();
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Search the LDAP directory.
*
* @return void
*/
public function search(): void
{
try {
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
if (!extension_loaded('ldap')) {
throw new RuntimeException('The LDAP extension is not loaded.');
}
$keyword = request('keyword');
$entries = $this->ldap_client->search($keyword);
json_response($entries);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,102 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Client form controller.
*
* Handles legal contents settings related operations.
*
* @package Controllers
*/
class Legal_settings extends EA_Controller
{
/**
* Legal_contents constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('settings_model');
$this->load->library('accounts');
}
/**
* Render the settings page.
*/
public function index(): void
{
session(['dest_url' => site_url('legal_settings')]);
$user_id = session('user_id');
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'legal_settings' => $this->settings_model->get(),
]);
html_vars([
'page_title' => lang('settings'),
'active_menu' => PRIV_SYSTEM_SETTINGS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
]);
$this->load->view('pages/legal_settings');
}
/**
* Save legal settings.
*/
public function save(): void
{
try {
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$settings = request('legal_settings', []);
foreach ($settings as $setting) {
$existing_setting = $this->settings_model
->query()
->where('name', $setting['name'])
->get()
->row_array();
if (!empty($existing_setting)) {
$setting['id'] = $existing_setting['id'];
}
$this->settings_model->save($setting);
}
response();
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,55 @@
<?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 https://opensource.org/licenses/GPL-3.0 - GPLv3
* @link https://easyappointments.org
* @since v1.4.3
* ---------------------------------------------------------------------------- */
/**
* Localization Controller
*
* Contains all the localization related methods.
*
* @package Controllers
*/
class Localization extends EA_Controller
{
/**
* Change system language for current user.
*
* The language setting is stored in session data and retrieved every time the user visits any of the system pages.
*
* Notice: This method used to be in the Backend_api.php.
*/
public function change_language(): void
{
try {
// Check if language exists in the available languages.
$language = request('language');
if (!in_array($language, config('available_languages'))) {
throw new RuntimeException(
'Translations for the given language does not exist (' . request('language') . ').',
);
}
$language = request('language');
session(['language' => $language]);
config(['language' => $language]);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,98 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Login controller.
*
* Handles the login page functionality.
*
* @package Controllers
*/
class Login extends EA_Controller
{
/**
* Login constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('accounts');
$this->load->library('ldap_client');
$this->load->library('email_messages');
script_vars([
'dest_url' => session('dest_url', site_url('calendar')),
]);
}
/**
* Render the login page.
*/
public function index(): void
{
if (session('user_id')) {
redirect('calendar');
return;
}
html_vars([
'page_title' => lang('login'),
'base_url' => config('base_url'),
'dest_url' => session('dest_url', site_url('calendar')),
'company_name' => setting('company_name'),
]);
$this->load->view('pages/login');
}
/**
* Validate the provided credentials and start a new session if the validation was successful.
*/
public function validate(): void
{
try {
$username = request('username');
if (empty($username)) {
throw new InvalidArgumentException('No username value provided.');
}
$password = request('password');
if (empty($password)) {
throw new InvalidArgumentException('No password value provided.');
}
$user_data = $this->accounts->check_login($username, $password);
if (empty($user_data)) {
$user_data = $this->ldap_client->check_login($username, $password);
}
if (empty($user_data)) {
throw new InvalidArgumentException(lang('invalid_credentials_provided'));
}
$this->session->sess_regenerate();
session($user_data); // Save data in the session.
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,39 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Logout controller.
*
* Handles the logout page functionality.
*
* @package Controllers
*/
class Logout extends EA_Controller
{
/**
* Render the logout page.
*/
public function index(): void
{
$this->session->sess_destroy();
$company_name = setting('company_name');
html_vars([
'page_title' => lang('log_out'),
'company_name' => $company_name,
]);
$this->load->view('pages/logout');
}
}

View File

@ -0,0 +1,102 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Matomo Analytics settings controller.
*
* Handles Matomo Analytics settings related operations.
*
* @package Controllers
*/
class Matomo_analytics_settings extends EA_Controller
{
/**
* Matomo_analytics_settings constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('settings_model');
$this->load->library('accounts');
}
/**
* Render the settings page.
*/
public function index(): void
{
session(['dest_url' => site_url('matomo_analytics_settings')]);
$user_id = session('user_id');
if (cannot('view', PRIV_SYSTEM_SETTINGS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'matomo_analytics_settings' => $this->settings_model->get('name like "matomo_analytics_%"'),
]);
html_vars([
'page_title' => lang('matomo_analytics'),
'active_menu' => PRIV_SYSTEM_SETTINGS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
]);
$this->load->view('pages/matomo_analytics_settings');
}
/**
* Save general settings.
*/
public function save(): void
{
try {
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$settings = request('matomo_analytics_settings', []);
foreach ($settings as $setting) {
$existing_setting = $this->settings_model
->query()
->where('name', $setting['name'])
->get()
->row_array();
if (!empty($existing_setting)) {
$setting['id'] = $existing_setting['id'];
}
$this->settings_model->save($setting);
}
response();
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,70 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Privacy controller.
*
* Handles the privacy related operations.
*
* @package Controllers
*/
class Privacy extends EA_Controller
{
/**
* Privacy constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->driver('cache', ['adapter' => 'file']);
$this->load->model('customers_model');
}
/**
* Remove all customer data (including appointments) from the system.
*/
public function delete_personal_information(): void
{
try {
$display_delete_personal_information = setting('display_delete_personal_information');
if (!$display_delete_personal_information) {
abort(403, 'Forbidden');
}
$customer_token = request('customer_token');
if (empty($customer_token)) {
throw new InvalidArgumentException('Invalid customer token value provided.');
}
$customer_id = $this->cache->get('customer-token-' . $customer_token);
if (empty($customer_id)) {
throw new InvalidArgumentException(
'Customer ID does not exist, please reload the page ' . 'and try again.',
);
}
$this->customers_model->delete($customer_id);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,276 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Providers controller.
*
* Handles the providers related operations.
*
* @package Controllers
*/
class Providers extends EA_Controller
{
public array $allowed_provider_fields = [
'id',
'first_name',
'last_name',
'email',
'alt_number',
'phone_number',
'address',
'city',
'state',
'zip_code',
'notes',
'timezone',
'language',
'is_private',
'ldap_dn',
'id_roles',
'settings',
'services',
];
public array $allowed_provider_setting_fields = [
'username',
'password',
'working_plan',
'working_plan_exceptions',
'notifications',
'calendar_view',
];
public array $allowed_service_fields = ['id', 'name'];
public array $optional_provider_fields = [
'services' => [],
];
public array $optional_provider_setting_fields = [
'working_plan' => null,
'working_plan_exceptions' => '{}',
];
/**
* Providers constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('providers_model');
$this->load->model('services_model');
$this->load->model('roles_model');
$this->load->library('accounts');
$this->load->library('timezones');
$this->load->library('webhooks_client');
$this->optional_provider_setting_fields['working_plan'] = setting('company_working_plan');
}
/**
* Render the backend providers page.
*
* On this page admin users will be able to manage providers, which are eventually selected by customers during the
* booking process.
*/
public function index(): void
{
session(['dest_url' => site_url('providers')]);
$user_id = session('user_id');
if (cannot('view', PRIV_USERS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
$services = $this->services_model->get();
foreach ($services as &$service) {
$this->services_model->only($service, $this->allowed_service_fields);
}
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'company_working_plan' => setting('company_working_plan'),
'date_format' => setting('date_format'),
'time_format' => setting('time_format'),
'first_weekday' => setting('first_weekday'),
'min_password_length' => MIN_PASSWORD_LENGTH,
'timezones' => $this->timezones->to_array(),
'services' => $services,
'default_language' => setting('default_language'),
'default_timezone' => setting('default_timezone'),
]);
html_vars([
'page_title' => lang('providers'),
'active_menu' => PRIV_USERS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
'grouped_timezones' => $this->timezones->to_grouped_array(),
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
'services' => $this->services_model->get(),
]);
$this->load->view('pages/providers');
}
/**
* Filter providers by the provided keyword.
*/
public function search(): void
{
try {
if (cannot('view', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$keyword = request('keyword', '');
$order_by = request('order_by', 'update_datetime DESC');
$limit = request('limit', 1000);
$offset = (int) request('offset', '0');
$providers = $this->providers_model->search($keyword, $limit, $offset, $order_by);
json_response($providers);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new provider.
*/
public function store(): void
{
try {
if (cannot('add', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$provider = request('provider');
$this->providers_model->only($provider, $this->allowed_provider_fields);
$this->providers_model->only($provider['settings'], $this->allowed_provider_setting_fields);
$this->providers_model->optional($provider, $this->optional_provider_fields);
$this->providers_model->optional($provider['settings'], $this->optional_provider_setting_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Find a provider.
*/
public function find(): void
{
try {
if (cannot('view', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$provider_id = request('provider_id');
$provider = $this->providers_model->find($provider_id);
json_response($provider);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a provider.
*/
public function update(): void
{
try {
if (cannot('edit', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$provider = request('provider');
$this->providers_model->only($provider, $this->allowed_provider_fields);
$this->providers_model->only($provider['settings'], $this->allowed_provider_setting_fields);
$this->providers_model->optional($provider, $this->optional_provider_fields);
$this->providers_model->optional($provider['settings'], $this->optional_provider_setting_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Remove a provider.
*/
public function destroy(): void
{
try {
if (cannot('delete', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,87 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Recovery controller.
*
* Handles the recovery page functionality.
*
* @package Controllers
*/
class Recovery extends EA_Controller
{
/**
* User constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('accounts');
$this->load->library('email_messages');
}
/**
* Display the password recovery page.
*/
public function index(): void
{
$company_name = setting('company_name');
html_vars([
'page_title' => lang('forgot_your_password'),
'dest_url' => session('dest_url', site_url('backend')),
'company_name' => $company_name,
]);
$this->load->view('pages/recovery');
}
/**
* Recover the user password and notify the user via email.
*/
public function perform()
{
try {
$username = request('username');
if (empty($username)) {
throw new InvalidArgumentException('No username value provided.');
}
$email = request('email');
if (empty($email)) {
throw new InvalidArgumentException('No email value provided.');
}
$new_password = $this->accounts->regenerate_password($username, $email);
if ($new_password) {
$settings = [
'company_name' => setting('company_name'),
'company_link' => setting('company_link'),
'company_email' => setting('company_email'),
];
$this->email_messages->send_password($new_password, $email, $settings);
}
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,254 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Secretaries controller.
*
* Handles the secretaries related operations.
*
* @package Controllers
*/
class Secretaries extends EA_Controller
{
public array $allowed_provider_fields = ['id', 'first_name', 'last_name'];
public array $allowed_secretary_fields = [
'id',
'first_name',
'last_name',
'email',
'alt_number',
'phone_number',
'address',
'city',
'state',
'zip_code',
'notes',
'timezone',
'language',
'is_private',
'ldap_dn',
'id_roles',
'settings',
'providers',
];
public array $allowed_secretary_setting_fields = ['username', 'password', 'notifications', 'calendar_view'];
public array $optional_secretary_fields = [
'providers' => [],
];
/**
* Secretaries constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('secretaries_model');
$this->load->model('providers_model');
$this->load->model('roles_model');
$this->load->library('accounts');
$this->load->library('timezones');
$this->load->library('webhooks_client');
}
/**
* Render the backend secretaries page.
*
* On this page secretary users will be able to manage secretaries, which are eventually selected by customers during the
* booking process.
*/
public function index(): void
{
session(['dest_url' => site_url('secretaries')]);
$user_id = session('user_id');
if (cannot('view', PRIV_USERS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
$providers = $this->providers_model->get();
foreach ($providers as &$provider) {
$this->providers_model->only($provider, $this->allowed_provider_fields);
}
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'timezones' => $this->timezones->to_array(),
'min_password_length' => MIN_PASSWORD_LENGTH,
'providers' => $providers,
'default_language' => setting('default_language'),
'default_timezone' => setting('default_timezone'),
]);
html_vars([
'page_title' => lang('secretaries'),
'active_menu' => PRIV_USERS,
'user_display_name' => $this->accounts->get_user_display_name($user_id),
'grouped_timezones' => $this->timezones->to_grouped_array(),
'privileges' => $this->roles_model->get_permissions_by_slug($role_slug),
'providers' => $this->providers_model->get(),
]);
$this->load->view('pages/secretaries');
}
/**
* Filter secretaries by the provided keyword.
*/
public function search(): void
{
try {
if (cannot('view', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$keyword = request('keyword', '');
$order_by = request('order_by', 'update_datetime DESC');
$limit = request('limit', 1000);
$offset = (int) request('offset', '0');
$secretaries = $this->secretaries_model->search($keyword, $limit, $offset, $order_by);
json_response($secretaries);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new secretary.
*/
public function store(): void
{
try {
if (cannot('add', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$secretary = request('secretary');
$this->secretaries_model->only($secretary, $this->allowed_secretary_fields);
$this->secretaries_model->only($secretary['settings'], $this->allowed_secretary_setting_fields);
$this->secretaries_model->optional($secretary, $this->optional_secretary_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Find a secretary.
*/
public function find(): void
{
try {
if (cannot('view', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$secretary_id = request('secretary_id');
$secretary = $this->secretaries_model->find($secretary_id);
json_response($secretary);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a secretary.
*/
public function update(): void
{
try {
if (cannot('edit', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$secretary = request('secretary');
$this->secretaries_model->only($secretary, $this->allowed_secretary_fields);
$this->secretaries_model->only($secretary['settings'], $this->allowed_secretary_setting_fields);
$this->secretaries_model->optional($secretary, $this->optional_secretary_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Remove a secretary.
*/
public function destroy(): void
{
try {
if (cannot('delete', PRIV_USERS)) {
abort(403, 'Forbidden');
}
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,209 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Service-categories controller.
*
* Handles the service-categories related operations.
*
* @package Controllers
*/
class Service_categories extends EA_Controller
{
public array $allowed_service_category_fields = ['id', 'name', 'description'];
/**
* Service-categories constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('service_categories_model');
$this->load->model('roles_model');
$this->load->library('accounts');
$this->load->library('timezones');
$this->load->library('webhooks_client');
}
/**
* Render the backend service-categories page.
*
* On this page admin users will be able to manage service-categories, which are eventually selected by customers during the
* booking process.
*/
public function index(): void
{
session(['dest_url' => site_url('service_categories')]);
$user_id = session('user_id');
if (cannot('view', PRIV_SERVICES)) {
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('service_categories'),
'active_menu' => PRIV_SERVICES,
'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),
]);
$this->load->view('pages/service_categories');
}
/**
* Filter service-categories by the provided keyword.
*/
public function search(): void
{
try {
if (cannot('view', PRIV_SERVICES)) {
abort(403, 'Forbidden');
}
$keyword = request('keyword', '');
$order_by = request('order_by', 'update_datetime DESC');
$limit = request('limit', 1000);
$offset = (int) request('offset', '0');
$service_categories = $this->service_categories_model->search($keyword, $limit, $offset, $order_by);
json_response($service_categories);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new service-category.
*/
public function store(): void
{
try {
if (cannot('add', PRIV_SERVICES)) {
abort(403, 'Forbidden');
}
$service_category = request('service_category');
$this->service_categories_model->only($service_category, $this->allowed_service_category_fields);
$service_category_id = $this->service_categories_model->save($service_category);
$service_category = $this->service_categories_model->find($service_category_id);
$this->webhooks_client->trigger(WEBHOOK_SERVICE_CATEGORY_SAVE, $service_category);
json_response([
'success' => true,
'id' => $service_category_id,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Find a service-category.
*/
public function find(): void
{
try {
if (cannot('view', PRIV_SERVICES)) {
abort(403, 'Forbidden');
}
$service_category_id = request('service_category_id');
$service_category = $this->service_categories_model->find($service_category_id);
json_response($service_category);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a service-category.
*/
public function update(): void
{
try {
if (cannot('edit', PRIV_SERVICES)) {
abort(403, 'Forbidden');
}
$service_category = request('service_category');
$this->service_categories_model->only($service_category, $this->allowed_service_category_fields);
$service_category_id = $this->service_categories_model->save($service_category);
$service_category = $this->service_categories_model->find($service_category_id);
$this->webhooks_client->trigger(WEBHOOK_SERVICE_CATEGORY_SAVE, $service_category);
json_response([
'success' => true,
'id' => $service_category_id,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Remove a service-category.
*/
public function destroy(): void
{
try {
if (cannot('delete', PRIV_SERVICES)) {
abort(403, 'Forbidden');
}
$service_category_id = request('service_category_id');
$service_category = $this->service_categories_model->find($service_category_id);
$this->service_categories_model->delete($service_category_id);
$this->webhooks_client->trigger(WEBHOOK_SERVICE_CATEGORY_DELETE, $service_category);
json_response([
'success' => true,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,230 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Services controller.
*
* Handles the services related operations.
*
* @package Controllers
*/
class Services extends EA_Controller
{
public array $allowed_service_fields = [
'id',
'name',
'duration',
'price',
'currency',
'description',
'color',
'location',
'availabilities_type',
'attendants_number',
'is_private',
'id_service_categories',
];
public array $optional_service_fields = [
'id_service_categories' => null,
];
/**
* Services constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('services_model');
$this->load->model('roles_model');
$this->load->library('accounts');
$this->load->library('timezones');
$this->load->library('webhooks_client');
}
/**
* Render the backend services page.
*
* On this page admin users will be able to manage services, which are eventually selected by customers during the
* booking process.
*/
public function index(): void
{
session(['dest_url' => site_url('services')]);
$user_id = session('user_id');
if (cannot('view', PRIV_SERVICES)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$role_slug = session('role_slug');
script_vars([
'user_id' => $user_id,
'role_slug' => $role_slug,
'event_minimum_duration' => EVENT_MINIMUM_DURATION,
]);
html_vars([
'page_title' => lang('services'),
'active_menu' => PRIV_SERVICES,
'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),
]);
$this->load->view('pages/services');
}
/**
* Filter services by the provided keyword.
*/
public function search(): void
{
try {
if (cannot('view', PRIV_SERVICES)) {
abort(403, 'Forbidden');
}
$keyword = request('keyword', '');
$order_by = request('order_by', 'update_datetime DESC');
$limit = request('limit', 1000);
$offset = (int) request('offset', '0');
$services = $this->services_model->search($keyword, $limit, $offset, $order_by);
json_response($services);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new service.
*/
public function store(): void
{
try {
if (cannot('add', PRIV_SERVICES)) {
abort(403, 'Forbidden');
}
$service = request('service');
$this->services_model->only($service, $this->allowed_service_fields);
$this->services_model->optional($service, $this->optional_service_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Find a service.
*/
public function find(): void
{
try {
if (cannot('delete', PRIV_SERVICES)) {
abort(403, 'Forbidden');
}
$service_id = request('service_id');
$service = $this->services_model->find($service_id);
json_response($service);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a service.
*/
public function update(): void
{
try {
if (cannot('edit', PRIV_SERVICES)) {
abort(403, 'Forbidden');
}
$service = request('service');
$this->services_model->only($service, $this->allowed_service_fields);
$this->services_model->optional($service, $this->optional_service_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Remove a service.
*/
public function destroy(): void
{
try {
if (cannot('delete', PRIV_SERVICES)) {
abort(403, 'Forbidden');
}
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,39 @@
<?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
* ---------------------------------------------------------------------------- */
/*
* This file can only be used in a testing environment and only from the termninal.
*/
if (ENVIRONMENT !== 'testing' || !is_cli()) {
show_404();
}
/**
* Test controller.
*
* This controller does not have or need any logic, it is just used so that CI can be loaded properly during the test
* execution.
*/
class Test extends EA_Controller
{
/**
* Placeholder callback.
*
* @return void
*/
public function index(): void
{
//
}
}

View File

@ -0,0 +1,185 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Unavailabilities controller.
*
* Handles the unavailabilities related operations.
*
* @package Controllers
*/
class Unavailabilities extends EA_Controller
{
public array $allowed_unavailability_fields = [
'id',
'start_datetime',
'end_datetime',
'location',
'notes',
'is_unavailability',
'id_users_provider',
];
/**
* Unavailabilities constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('unavailabilities_model');
$this->load->model('roles_model');
$this->load->library('accounts');
$this->load->library('timezones');
$this->load->library('webhooks_client');
}
/**
* Filter unavailabilities by the provided keyword.
*/
public function search(): void
{
try {
if (cannot('view', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$keyword = request('keyword', '');
$order_by = request('order_by', 'update_datetime DESC');
$limit = request('limit', 1000);
$offset = (int) request('offset', '0');
$unavailabilities = $this->unavailabilities_model->search($keyword, $limit, $offset, $order_by);
json_response($unavailabilities);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new unavailability.
*/
public function store(): void
{
try {
if (cannot('add', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$unavailability = request('unavailability');
$this->unavailabilities_model->only($unavailability, $this->allowed_unavailability_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Find an unavailability.
*/
public function find(): void
{
try {
if (cannot('view', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$unavailability_id = request('unavailability_id');
$unavailability = $this->unavailabilities_model->find($unavailability_id);
json_response($unavailability);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a unavailability.
*/
public function update(): void
{
try {
if (cannot('edit', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$unavailability = request('unavailability');
$this->unavailabilities_model->only($unavailability, $this->allowed_unavailability_fields);
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Remove a unavailability.
*/
public function destroy(): void
{
try {
if (cannot('delete', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$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,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,74 @@
<?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.1.0
* ---------------------------------------------------------------------------- */
/**
* Update controller.
*
* Handles the update related operations.
*
* @package Controllers
*/
class Update extends EA_Controller
{
/**
* Update constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('admins_model');
$this->load->model('settings_model');
$this->load->model('services_model');
$this->load->model('providers_model');
$this->load->model('customers_model');
$this->load->library('instance');
}
/**
* This method will update the instance to the latest available version in the server.
*
* IMPORTANT: The code files must exist in the server, this method will not fetch any new files but will update
* the database schema.
*
* This method can be used either by loading the page in the browser or by an ajax request. But it will answer with
* JSON encoded data.
*/
public function index(): void
{
try {
$user_id = session('user_id');
if (cannot('edit', PRIV_SYSTEM_SETTINGS)) {
if ($user_id) {
abort(403, 'Forbidden');
}
redirect('login');
return;
}
$this->instance->migrate();
$view = ['success' => true];
} catch (Throwable $e) {
$view = ['success' => false, 'exception' => $e->getMessage()];
}
html_vars($view);
$this->load->view('pages/update');
}
}

View File

@ -0,0 +1,71 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* User controller.
*
* Handles the user related operations.
*
* @package Controllers
*/
class User extends EA_Controller
{
/**
* User constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('accounts');
$this->load->library('email_messages');
}
/**
* Redirect to the login page.
*/
public function index(): void
{
redirect('login');
}
/**
* Display the login page.
*
* @deprecated Since 1.5 Use the Login controller instead.
*/
public function login()
{
redirect('login');
}
/**
* Display the logout page.
*
* @deprecated Since 1.5 Use the Logout controller instead.
*/
public function logout()
{
redirect('logout');
}
/**
* Display the password recovery page.
*
* @deprecated Since 1.5 Use the Logout controller instead.
*/
public function forgot_password()
{
redirect('recovery');
}
}

View File

@ -0,0 +1,216 @@
<?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
{
public array $allowed_webhook_fields = ['id', 'name', 'url', 'actions', 'secret_token', 'is_ssl_verified', 'notes'];
/**
* 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(): void
{
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_BLOCKED_PERIOD_SAVE,
WEBHOOK_BLOCKED_PERIOD_DELETE,
WEBHOOK_CUSTOMER_SAVE,
WEBHOOK_CUSTOMER_DELETE,
WEBHOOK_SERVICE_SAVE,
WEBHOOK_SERVICE_DELETE,
WEBHOOK_SERVICE_CATEGORY_SAVE,
WEBHOOK_SERVICE_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(): void
{
try {
if (cannot('view', PRIV_WEBHOOKS)) {
abort(403, 'Forbidden');
}
$keyword = request('keyword', '');
$order_by = request('order_by', 'update_datetime DESC');
$limit = request('limit', 1000);
$offset = (int) request('offset', '0');
$webhooks = $this->webhooks_model->search($keyword, $limit, $offset, $order_by);
json_response($webhooks);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new webhook.
*/
public function store(): void
{
try {
if (cannot('add', PRIV_WEBHOOKS)) {
abort(403, 'Forbidden');
}
$webhook = request('webhook');
$this->webhooks_model->only($webhook, $this->allowed_webhook_fields);
$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(): void
{
try {
if (cannot('edit', PRIV_WEBHOOKS)) {
abort(403, 'Forbidden');
}
$webhook = request('webhook');
$this->webhooks_model->only($webhook, $this->allowed_webhook_fields);
$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(): void
{
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(): void
{
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,10 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

View File

@ -0,0 +1,202 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Admins API v1 controller.
*
* @package Controllers
*/
class Admins_api_v1 extends EA_Controller
{
/**
* Admins_api_v1 constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('admins_model');
$this->load->library('api');
$this->api->auth();
$this->api->model('admins_model');
}
/**
* Get an admin collection.
*/
public function index(): void
{
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();
$admins = empty($keyword)
? $this->admins_model->get(null, $limit, $offset, $order_by)
: $this->admins_model->search($keyword, $limit, $offset, $order_by);
foreach ($admins as &$admin) {
$this->admins_model->api_encode($admin);
if (!empty($fields)) {
$this->admins_model->only($admin, $fields);
}
if (!empty($with)) {
$this->admins_model->load($admin, $with);
}
}
json_response($admins);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Get a single admin.
*
* @param int|null $id Admin ID.
*/
public function show(int $id = null): void
{
try {
$occurrences = $this->admins_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$fields = $this->api->request_fields();
$with = $this->api->request_with();
$admin = $this->admins_model->find($id);
$this->admins_model->api_encode($admin);
if (!empty($fields)) {
$this->admins_model->only($admin, $fields);
}
if (!empty($with)) {
$this->admins_model->load($admin, $with);
}
json_response($admin);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new admin.
*/
public function store(): void
{
try {
$admin = request();
$this->admins_model->api_decode($admin);
if (array_key_exists('id', $admin)) {
unset($admin['id']);
}
if (!array_key_exists('settings', $admin)) {
throw new InvalidArgumentException('No settings property provided.');
}
$admin_id = $this->admins_model->save($admin);
$created_admin = $this->admins_model->find($admin_id);
$this->admins_model->api_encode($created_admin);
json_response($created_admin, 201);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update an admin.
*
* @param int $id Admin ID.
*/
public function update(int $id): void
{
try {
$occurrences = $this->admins_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$original_admin = $occurrences[0];
$admin = request();
$this->admins_model->api_decode($admin, $original_admin);
$admin_id = $this->admins_model->save($admin);
$updated_admin = $this->admins_model->find($admin_id);
$this->admins_model->api_encode($updated_admin);
json_response($updated_admin);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Delete an admin.
*
* @param int $id Admin ID.
*/
public function destroy(int $id): void
{
try {
$occurrences = $this->admins_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$this->admins_model->delete($id);
response('', 204);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,380 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Appointments API v1 controller.
*
* @package Controllers
*/
class Appointments_api_v1 extends EA_Controller
{
/**
* Appointments_api_v1 constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->model('appointments_model');
$this->load->model('customers_model');
$this->load->model('providers_model');
$this->load->model('services_model');
$this->load->model('settings_model');
$this->load->library('api');
$this->load->library('synchronization');
$this->load->library('notifications');
$this->api->auth();
$this->api->model('appointments_model');
}
/**
* Get an appointment collection.
*/
public function index(): void
{
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();
$where = null;
// Date query param.
$date = request('date');
if (!empty($date)) {
$where['DATE(start_datetime)'] = (new DateTime($date))->format('Y-m-d');
}
// From query param.
$from = request('from');
if (!empty($from)) {
$where['DATE(start_datetime) >='] = (new DateTime($from))->format('Y-m-d');
}
// Till query param.
$till = request('till');
if (!empty($till)) {
$where['DATE(end_datetime) <='] = (new DateTime($till))->format('Y-m-d');
}
// Service ID query param.
$service_id = request('serviceId');
if (!empty($service_id)) {
$where['id_services'] = $service_id;
}
// Provider ID query param.
$provider_id = request('providerId');
if (!empty($provider_id)) {
$where['id_users_provider'] = $provider_id;
}
// Customer ID query param.
$customer_id = request('customerId');
if (!empty($customer_id)) {
$where['id_users_customer'] = $customer_id;
}
$appointments = empty($keyword)
? $this->appointments_model->get($where, $limit, $offset, $order_by)
: $this->appointments_model->search($keyword, $limit, $offset, $order_by);
foreach ($appointments as &$appointment) {
$this->appointments_model->api_encode($appointment);
$this->aggregates($appointment);
if (!empty($fields)) {
$this->appointments_model->only($appointment, $fields);
}
if (!empty($with)) {
$this->appointments_model->load($appointment, $with);
}
}
json_response($appointments);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Load the relations of the current appointment if the "aggregates" query parameter is present.
*
* This is a compatibility addition to the appointment resource which was the only one to support it.
*
* Use the "attach" query parameter instead as this one will be removed.
*
* @param array $appointment Appointment data.
*
* @deprecated Since 1.5
*/
private function aggregates(array &$appointment): void
{
$aggregates = request('aggregates') !== null;
if ($aggregates) {
$appointment['service'] = $this->services_model->find(
$appointment['id_services'] ?? ($appointment['serviceId'] ?? null),
true,
);
$appointment['provider'] = $this->providers_model->find(
$appointment['id_users_provider'] ?? ($appointment['providerId'] ?? null),
true,
);
$appointment['customer'] = $this->customers_model->find(
$appointment['id_users_customer'] ?? ($appointment['customerId'] ?? null),
true,
);
$this->services_model->api_encode($appointment['service']);
$this->providers_model->api_encode($appointment['provider']);
$this->customers_model->api_encode($appointment['customer']);
}
}
/**
* Get a single appointment.
*
* @param int|null $id Appointment ID.
*/
public function show(int $id = null): void
{
try {
$occurrences = $this->appointments_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$fields = $this->api->request_fields();
$with = $this->api->request_with();
$appointment = $this->appointments_model->find($id);
$this->appointments_model->api_encode($appointment);
if (!empty($fields)) {
$this->appointments_model->only($appointment, $fields);
}
if (!empty($with)) {
$this->appointments_model->load($appointment, $with);
}
json_response($appointment);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new appointment.
*/
public function store(): void
{
try {
$appointment = request();
$this->appointments_model->api_decode($appointment);
if (array_key_exists('id', $appointment)) {
unset($appointment['id']);
}
if (!array_key_exists('end_datetime', $appointment)) {
$appointment['end_datetime'] = $this->calculate_end_datetime($appointment);
}
$appointment_id = $this->appointments_model->save($appointment);
$created_appointment = $this->appointments_model->find($appointment_id);
$this->notify_and_sync_appointment($created_appointment);
$this->appointments_model->api_encode($created_appointment);
json_response($created_appointment, 201);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Calculate the end date time of an appointment based on the selected service.
*
* @param array $appointment Appointment data.
*
* @return string Returns the end date time value.
*
* @throws Exception
*/
private function calculate_end_datetime(array $appointment): string
{
$duration = $this->services_model->value($appointment['id_services'], 'duration');
$end = new DateTime($appointment['start_datetime']);
$end->add(new DateInterval('PT' . $duration . 'M'));
return $end->format('Y-m-d H:i:s');
}
/**
* Send the required notifications and trigger syncing after saving an appointment.
*
* @param array $appointment Appointment data.
* @param string $action Performed action ("store" or "update").
*/
private function notify_and_sync_appointment(array $appointment, string $action = 'store')
{
$manage_mode = $action === 'update';
$service = $this->services_model->find($appointment['id_services'], true);
$provider = $this->providers_model->find($appointment['id_users_provider'], true);
$customer = $this->customers_model->find($appointment['id_users_customer'], true);
$settings = [
'company_name' => setting('company_name'),
'company_email' => setting('company_email'),
'company_link' => setting('company_link'),
'date_format' => setting('date_format'),
'time_format' => setting('time_format'),
];
$this->synchronization->sync_appointment_saved($appointment, $service, $provider, $customer, $settings);
$this->notifications->notify_appointment_saved(
$appointment,
$service,
$provider,
$customer,
$settings,
$manage_mode,
);
}
/**
* Update an appointment.
*
* @param int $id Appointment ID.
*/
public function update(int $id): void
{
try {
$occurrences = $this->appointments_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$original_appointment = $occurrences[0];
$appointment = request();
$this->appointments_model->api_decode($appointment, $original_appointment);
$appointment_id = $this->appointments_model->save($appointment);
$updated_appointment = $this->appointments_model->find($appointment_id);
$this->notify_and_sync_appointment($updated_appointment, 'update');
$this->appointments_model->api_encode($updated_appointment);
json_response($updated_appointment);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Delete an appointment.
*
* @param int $id Appointment ID.
*/
public function destroy(int $id): void
{
try {
$occurrences = $this->appointments_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$deleted_appointment = $occurrences[0];
$service = $this->services_model->find($deleted_appointment['id_services'], true);
$provider = $this->providers_model->find($deleted_appointment['id_users_provider'], true);
$customer = $this->customers_model->find($deleted_appointment['id_users_customer'], true);
$settings = [
'company_name' => setting('company_name'),
'company_email' => setting('company_email'),
'company_link' => setting('company_link'),
'date_format' => setting('date_format'),
'time_format' => setting('time_format'),
];
$this->appointments_model->delete($id);
$this->synchronization->sync_appointment_deleted($deleted_appointment, $provider);
$this->notifications->notify_appointment_deleted(
$deleted_appointment,
$service,
$provider,
$customer,
$settings,
);
response('', 204);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,81 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Availabilities API v1 controller.
*
* @package Controllers
*/
class Availabilities_api_v1 extends EA_Controller
{
/**
* Availabilities_api_v1 constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('api');
$this->api->auth();
$this->load->model('appointments_model');
$this->load->model('providers_model');
$this->load->model('services_model');
$this->load->model('settings_model');
$this->load->library('availability');
}
/**
* Generate the available hours based on the selected date, service and provider.
*
* This resource requires the following query parameters:
*
* - serviceId
* - providerI
* - date
*
* Based on those values it will generate the available hours, just like how the booking page works.
*
* You can then safely create a new appointment starting on one of the selected hours.
*
* Notice: The returned hours are in the provider's timezone.
*
* If no date parameter is provided then the current date will be used.
*/
public function get(): void
{
try {
$provider_id = request('providerId');
$service_id = request('serviceId');
$date = request('date');
if (!$date) {
$date = date('Y-m-d');
}
$provider = $this->providers_model->find($provider_id);
$service = $this->services_model->find($service_id);
$available_hours = $this->availability->get_available_hours($date, $service, $provider);
json_response($available_hours);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,190 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Customers API v1 controller.
*
* @package Controllers
*/
class Customers_api_v1 extends EA_Controller
{
/**
* Customers_api_v1 constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('api');
$this->api->auth();
$this->api->model('customers_model');
}
/**
* Get a customer collection.
*/
public function index(): void
{
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();
$customers = empty($keyword)
? $this->customers_model->get(null, $limit, $offset, $order_by)
: $this->customers_model->search($keyword, $limit, $offset, $order_by);
foreach ($customers as &$customer) {
$this->customers_model->api_encode($customer);
if (!empty($fields)) {
$this->customers_model->only($customer, $fields);
}
if (!empty($with)) {
$this->customers_model->load($customer, $with);
}
}
json_response($customers);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Get a single customer.
*
* @param int|null $id Customer ID.
*/
public function show(int $id = null): void
{
try {
$occurrences = $this->customers_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$fields = $this->api->request_fields();
$customer = $this->customers_model->find($id);
$this->customers_model->api_encode($customer);
if (!empty($fields)) {
$this->customers_model->only($customer, $fields);
}
json_response($customer);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new customer.
*/
public function store(): void
{
try {
$customer = request();
$this->customers_model->api_decode($customer);
if (array_key_exists('id', $customer)) {
unset($customer['id']);
}
$customer_id = $this->customers_model->save($customer);
$created_customer = $this->customers_model->find($customer_id);
$this->customers_model->api_encode($created_customer);
json_response($created_customer, 201);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a customer.
*
* @param int $id Customer ID.
*/
public function update(int $id): void
{
try {
$occurrences = $this->customers_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$original_customer = $occurrences[0];
$customer = request();
$this->customers_model->api_decode($customer, $original_customer);
$customer_id = $this->customers_model->save($customer);
$updated_customer = $this->customers_model->find($customer_id);
$this->customers_model->api_encode($updated_customer);
json_response($updated_customer);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Delete a customer.
*
* @param int $id Customer ID.
*/
public function destroy(int $id): void
{
try {
$occurrences = $this->customers_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$this->customers_model->delete($id);
response('', 204);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,212 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Providers API v1 controller.
*
* @package Controllers
*/
class Providers_api_v1 extends EA_Controller
{
/**
* Providers_api_v1 constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('api');
$this->api->auth();
$this->api->model('providers_model');
}
/**
* Get a provider collection.
*/
public function index(): void
{
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();
$providers = empty($keyword)
? $this->providers_model->get(null, $limit, $offset, $order_by)
: $this->providers_model->search($keyword, $limit, $offset, $order_by);
foreach ($providers as &$provider) {
$this->providers_model->api_encode($provider);
if (!empty($fields)) {
$this->providers_model->only($provider, $fields);
}
if (!empty($with)) {
$this->providers_model->load($provider, $with);
}
}
json_response($providers);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Get a single provider.
*
* @param int|null $id Provider ID.
*/
public function show(int $id = null): void
{
try {
$occurrences = $this->providers_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$fields = $this->api->request_fields();
$with = $this->api->request_with();
$provider = $this->providers_model->find($id);
$this->providers_model->api_encode($provider);
if (!empty($fields)) {
$this->providers_model->only($provider, $fields);
}
if (!empty($with)) {
$this->providers_model->load($provider, $with);
}
json_response($provider);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new provider.
*/
public function store(): void
{
try {
$provider = request();
$this->providers_model->api_decode($provider);
if (array_key_exists('id', $provider)) {
unset($provider['id']);
}
if (!array_key_exists('services', $provider)) {
throw new InvalidArgumentException('No services property provided.');
}
if (!array_key_exists('settings', $provider)) {
throw new InvalidArgumentException('No settings property provided.');
}
if (!array_key_exists('working_plan', $provider['settings'])) {
$provider['settings']['working_plan'] = setting('company_working_plan');
}
if (!array_key_exists('working_plan_exceptions', $provider['settings'])) {
$provider['settings']['working_plan_exceptions'] = '{}';
}
$provider_id = $this->providers_model->save($provider);
$created_provider = $this->providers_model->find($provider_id);
$this->providers_model->api_encode($created_provider);
json_response($created_provider, 201);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a provider.
*
* @param int $id Provider ID.
*/
public function update(int $id): void
{
try {
$occurrences = $this->providers_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$original_provider = $occurrences[0];
$provider = request();
$this->providers_model->api_decode($provider, $original_provider);
$provider_id = $this->providers_model->save($provider);
$updated_provider = $this->providers_model->find($provider_id);
$this->providers_model->api_encode($updated_provider);
json_response($updated_provider);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Delete a provider.
*
* @param int $id Provider ID.
*/
public function destroy(int $id): void
{
try {
$occurrences = $this->providers_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$this->providers_model->delete($id);
response('', 204);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,198 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Secretaries API v1 controller.
*
* @package Controllers
*/
class Secretaries_api_v1 extends EA_Controller
{
/**
* Secretaries_api_v1 constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('api');
$this->api->auth();
$this->api->model('secretaries_model');
}
/**
* Get a secretary collection.
*/
public function index(): void
{
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();
$secretaries = empty($keyword)
? $this->secretaries_model->get(null, $limit, $offset, $order_by)
: $this->secretaries_model->search($keyword, $limit, $offset, $order_by);
foreach ($secretaries as &$secretary) {
$this->secretaries_model->api_encode($secretary);
if (!empty($fields)) {
$this->secretaries_model->only($secretary, $fields);
}
if (!empty($with)) {
$this->secretaries_model->load($secretary, $with);
}
}
json_response($secretaries);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Get a single secretary.
*
* @param int|null $id Secretary ID.
*/
public function show(int $id = null): void
{
try {
$occurrences = $this->secretaries_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$fields = $this->api->request_fields();
$secretary = $this->secretaries_model->find($id);
$this->secretaries_model->api_encode($secretary);
if (!empty($fields)) {
$this->secretaries_model->only($secretary, $fields);
}
json_response($secretary);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new secretary.
*/
public function store(): void
{
try {
$secretary = request();
$this->secretaries_model->api_decode($secretary);
if (array_key_exists('id', $secretary)) {
unset($secretary['id']);
}
if (!array_key_exists('providers', $secretary)) {
throw new InvalidArgumentException('No providers property provided.');
}
if (!array_key_exists('settings', $secretary)) {
throw new InvalidArgumentException('No settings property provided.');
}
$secretary_id = $this->secretaries_model->save($secretary);
$created_secretary = $this->secretaries_model->find($secretary_id);
$this->secretaries_model->api_encode($created_secretary);
json_response($created_secretary, 201);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a secretary.
*
* @param int $id Secretary ID.
*/
public function update(int $id): void
{
try {
$occurrences = $this->secretaries_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$original_secretary = $occurrences[0];
$secretary = request();
$this->secretaries_model->api_decode($secretary, $original_secretary);
$secretary_id = $this->secretaries_model->save($secretary);
$updated_secretary = $this->secretaries_model->find($secretary_id);
$this->secretaries_model->api_encode($updated_secretary);
json_response($updated_secretary);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Delete a secretary.
*
* @param int $id Secretary ID.
*/
public function destroy(int $id): void
{
try {
$occurrences = $this->secretaries_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$this->secretaries_model->delete($id);
response('', 204);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,196 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Service-categories API v1 controller.
*
* @package Controllers
*/
class Service_categories_api_v1 extends EA_Controller
{
/**
* Service_categories_api_v1 constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('api');
$this->api->auth();
$this->api->model('service_categories_model');
}
/**
* Get a service-category collection.
*/
public function index(): void
{
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();
$service_categories = empty($keyword)
? $this->service_categories_model->get(null, $limit, $offset, $order_by)
: $this->service_categories_model->search($keyword, $limit, $offset, $order_by);
foreach ($service_categories as &$service_category) {
$this->service_categories_model->api_encode($service_category);
if (!empty($fields)) {
$this->service_categories_model->only($service_category, $fields);
}
if (!empty($with)) {
$this->service_categories_model->load($service_category, $with);
}
}
json_response($service_categories);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Get a single service-category.
*
* @param int|null $id Service-category ID.
*/
public function show(int $id = null): void
{
try {
$occurrences = $this->service_categories_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$fields = $this->api->request_fields();
$with = $this->api->request_with();
$service_category = $this->service_categories_model->find($id);
$this->service_categories_model->api_encode($service_category);
if (!empty($fields)) {
$this->service_categories_model->only($service_category, $fields);
}
if (!empty($with)) {
$this->service_categories_model->load($service_category, $with);
}
json_response($service_category);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new service-category.
*/
public function store(): void
{
try {
$service_category = request();
$this->service_categories_model->api_decode($service_category);
if (array_key_exists('id', $service_category)) {
unset($service_category['id']);
}
$service_category_id = $this->service_categories_model->save($service_category);
$created_service_category = $this->service_categories_model->find($service_category_id);
$this->service_categories_model->api_encode($created_service_category);
json_response($created_service_category, 201);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update a service-category.
*
* @param int $id Service-category ID.
*/
public function update(int $id): void
{
try {
$occurrences = $this->service_categories_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$original_category = $occurrences[0];
$service_category = request();
$this->service_categories_model->api_decode($service_category, $original_category);
$service_category_id = $this->service_categories_model->save($service_category);
$updated_service_category = $this->service_categories_model->find($service_category_id);
$this->service_categories_model->api_encode($updated_service_category);
json_response($updated_service_category);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Delete a service-category.
*
* @param int $id Service-category ID.
*/
public function destroy(int $id): void
{
try {
$occurrences = $this->service_categories_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$this->service_categories_model->delete($id);
response('', 204);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,196 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Services API v1 controller.
*
* @package Controllers
*/
class Services_api_v1 extends EA_Controller
{
/**
* Services_api_v1 constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('api');
$this->api->auth();
$this->api->model('services_model');
}
/**
* Get an service collection.
*/
public function index(): void
{
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();
$services = empty($keyword)
? $this->services_model->get(null, $limit, $offset, $order_by)
: $this->services_model->search($keyword, $limit, $offset, $order_by);
foreach ($services as &$service) {
$this->services_model->api_encode($service);
if (!empty($fields)) {
$this->services_model->only($service, $fields);
}
if (!empty($with)) {
$this->services_model->load($service, $with);
}
}
json_response($services);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Get a single service.
*
* @param int|null $id Service ID.
*/
public function show(int $id = null): void
{
try {
$occurrences = $this->services_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$fields = $this->api->request_fields();
$with = $this->api->request_with();
$service = $this->services_model->find($id);
$this->services_model->api_encode($service);
if (!empty($fields)) {
$this->services_model->only($service, $fields);
}
if (!empty($with)) {
$this->services_model->load($service, $with);
}
json_response($service);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new service.
*/
public function store(): void
{
try {
$service = request();
$this->services_model->api_decode($service);
if (array_key_exists('id', $service)) {
unset($service['id']);
}
$service_id = $this->services_model->save($service);
$created_service = $this->services_model->find($service_id);
$this->services_model->api_encode($created_service);
json_response($created_service, 201);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update an service.
*
* @param int $id Service ID.
*/
public function update(int $id): void
{
try {
$occurrences = $this->services_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$original_service = $occurrences[0];
$service = request();
$this->services_model->api_decode($service, $original_service);
$service_id = $this->services_model->save($service);
$updated_service = $this->services_model->find($service_id);
$this->services_model->api_encode($updated_service);
json_response($updated_service);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Delete an service.
*
* @param int $id Service ID.
*/
public function destroy(int $id): void
{
try {
$occurrences = $this->services_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$this->services_model->delete($id);
response('', 204);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,108 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Settings API v1 controller.
*
* @package Controllers
*/
class Settings_api_v1 extends EA_Controller
{
/**
* Settings_api_v1 constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('api');
$this->api->auth();
$this->api->model('settings_model');
}
/**
* Get a setting collection.
*/
public function index(): void
{
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();
$settings = empty($keyword)
? $this->settings_model->get(null, $limit, $offset, $order_by)
: $this->settings_model->search($keyword, $limit, $offset, $order_by);
foreach ($settings as &$setting) {
$this->settings_model->api_encode($setting);
if (!empty($fields)) {
$this->settings_model->only($setting, $fields);
}
}
json_response($settings);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Get a setting value by name.
*
* @param string $name Setting name.
*/
public function show(string $name): void
{
try {
$value = setting($name);
json_response([
'name' => $name,
'value' => $value,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Set a setting value by name.
*
* @param string $name Setting name.
*/
public function update(string $name): void
{
try {
$value = request('value');
setting([$name => $value]);
json_response([
'name' => $name,
'value' => $value,
]);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,196 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Unavailabilities API v1 controller.
*
* @package Controllers
*/
class Unavailabilities_api_v1 extends EA_Controller
{
/**
* Unavailabilities_api_v1 constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('api');
$this->api->auth();
$this->api->model('unavailabilities_model');
}
/**
* Get an unavailability collection.
*/
public function index(): void
{
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();
$unavailabilities = empty($keyword)
? $this->unavailabilities_model->get(null, $limit, $offset, $order_by)
: $this->unavailabilities_model->search($keyword, $limit, $offset, $order_by);
foreach ($unavailabilities as &$unavailability) {
$this->unavailabilities_model->api_encode($unavailability);
if (!empty($fields)) {
$this->unavailabilities_model->only($unavailability, $fields);
}
if (!empty($with)) {
$this->unavailabilities_model->load($unavailability, $with);
}
}
json_response($unavailabilities);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Get a single unavailability.
*
* @param int|null $id Unavailability ID.
*/
public function show(int $id = null): void
{
try {
$occurrences = $this->unavailabilities_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$fields = $this->api->request_fields();
$with = $this->api->request_with();
$unavailability = $this->unavailabilities_model->find($id);
$this->unavailabilities_model->api_encode($unavailability);
if (!empty($fields)) {
$this->unavailabilities_model->only($unavailability, $fields);
}
if (!empty($with)) {
$this->unavailabilities_model->load($unavailability, $with);
}
json_response($unavailability);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new unavailability.
*/
public function store(): void
{
try {
$unavailability = request();
$this->unavailabilities_model->api_decode($unavailability);
if (array_key_exists('id', $unavailability)) {
unset($unavailability['id']);
}
$unavailability_id = $this->unavailabilities_model->save($unavailability);
$created_unavailability = $this->unavailabilities_model->find($unavailability_id);
$this->unavailabilities_model->api_encode($created_unavailability);
json_response($created_unavailability, 201);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Update an unavailability.
*
* @param int $id Unavailability ID.
*/
public function update(int $id): void
{
try {
$occurrences = $this->unavailabilities_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$original_unavailability = $occurrences[0];
$unavailability = request();
$this->unavailabilities_model->api_decode($unavailability, $original_unavailability);
$unavailability_id = $this->unavailabilities_model->save($unavailability);
$updated_unavailability = $this->unavailabilities_model->find($unavailability_id);
$this->unavailabilities_model->api_encode($updated_unavailability);
json_response($updated_unavailability);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Delete an unavailability.
*
* @param int $id Unavailability ID.
*/
public function destroy(int $id): void
{
try {
$occurrences = $this->unavailabilities_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$this->unavailabilities_model->delete($id);
response('', 204);
} catch (Throwable $e) {
json_exception($e);
}
}
}

View File

@ -0,0 +1,196 @@
<?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->library('api');
$this->api->auth();
$this->api->model('webhooks_model');
}
/**
* Get a webhook collection.
*/
public function index(): void
{
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): void
{
try {
$occurrences = $this->webhooks_model->get(['id' => $id]);
if (empty($occurrences)) {
response('', 404);
return;
}
$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);
}
json_response($webhook);
} catch (Throwable $e) {
json_exception($e);
}
}
/**
* Store a new webhook.
*/
public function store(): void
{
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): void
{
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): void
{
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

@ -0,0 +1,10 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

View File

@ -0,0 +1,10 @@
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments benchmark.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Benchmark extends CI_Benchmark
{
//
}

45
application/core/EA_Cache.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments cache.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Cache extends CI_Cache
{
//
}

View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments calendar.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Calendar extends CI_Calendar
{
//
}

45
application/core/EA_Config.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments config.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Config extends CI_Config
{
//
}

View File

@ -0,0 +1,157 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments controller.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*
* @property Admins_model $admins_model
* @property Appointments_model $appointments_model
* @property Service_categories_model $service_categories_model
* @property Consents_model $consents_model
* @property Customers_model $customers_model
* @property Providers_model $providers_model
* @property Roles_model $roles_model
* @property Secretaries_model $secretaries_model
* @property Services_model $services_model
* @property Settings_model $settings_model
* @property Unavailabilities_model $unavailabilities_model
* @property Users_model $users_model
* @property Webhooks_model $webhooks_model
* @property Blocked_periods_model $blocked_periods_model
*
* @property Accounts $accounts
* @property Api $api
* @property Availability $availability
* @property Email_messages $email_messages
* @property Captcha_builder $captcha_builder
* @property Google_Sync $google_sync
* @property Caldav_Sync $caldav_sync
* @property Ics_file $ics_file
* @property Instance $instance
* @property Ldap_client $ldap_client
* @property Notifications $notifications
* @property Permissions $permissions
* @property Synchronization $synchronization
* @property Timezones $timezones
* @property Webhooks_client $webhooks_client
*/
class EA_Controller extends CI_Controller
{
/**
* EA_Controller constructor.
*/
public function __construct()
{
parent::__construct();
$this->load->library('accounts');
$this->ensure_user_exists();
$this->configure_language();
$this->load_common_html_vars();
$this->load_common_script_vars();
rate_limit($this->input->ip_address());
}
private function ensure_user_exists()
{
$user_id = session('user_id');
if (!$user_id) {
return;
}
if (!$this->accounts->does_account_exist($user_id)) {
session_destroy();
abort(403, 'Forbidden');
}
}
/**
* Configure the language.
*/
private function configure_language()
{
$session_language = session('language');
if ($session_language) {
$language_codes = config('language_codes');
config([
'language' => $session_language,
'language_code' => array_search($session_language, $language_codes) ?: 'en',
]);
}
$this->lang->load('translations');
}
/**
* Load common script vars for all requests.
*/
private function load_common_html_vars()
{
html_vars([
'base_url' => config('base_url'),
'index_page' => config('index_page'),
'available_languages' => config('available_languages'),
'language' => $this->lang->language,
'csrf_token' => $this->security->get_csrf_hash(),
]);
}
/**
* Load common script vars for all requests.
*/
private function load_common_script_vars()
{
script_vars([
'base_url' => config('base_url'),
'index_page' => config('index_page'),
'available_languages' => config('available_languages'),
'csrf_token' => $this->security->get_csrf_hash(),
'language' => config('language'),
'language_code' => config('language_code'),
]);
}
}

View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments DB forge.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_DB_forge extends CI_DB_forge
{
//
}

View File

@ -0,0 +1,47 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments DB query builder.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*
* @method int insert_id()
*/
class EA_DB_query_builder extends CI_DB_query_builder
{
//
}

View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments DB utility.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_DB_utility extends CI_DB_utility
{
//
}

45
application/core/EA_Email.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments email.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Email extends CI_Email
{
//
}

45
application/core/EA_Encrypt.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments encrypt.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Encrypt extends CI_Encrypt
{
//
}

View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments encryption.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Encryption extends CI_Encryption
{
//
}

View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments exceptions.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Exceptions extends CI_Exceptions
{
//
}

45
application/core/EA_Hooks.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments hooks.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Hooks extends CI_Hooks
{
//
}

83
application/core/EA_Input.php Executable file
View File

@ -0,0 +1,83 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments input.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*
* @property string $raw_input_stream
*/
class EA_Input extends CI_Input
{
/**
* Fetch an item from JSON data.
*
* @param string|null $index Index for item to be fetched from the JSON payload.
* @param bool|false $xss_clean Whether to apply XSS filtering
*
* @return mixed
*/
public function json(string $index = null, bool $xss_clean = false)
{
/** @var EA_Controller $CI */
$CI = &get_instance();
if (strpos((string) $CI->input->get_request_header('Content-Type'), 'application/json') === false) {
return null;
}
$input_stream = $CI->input->raw_input_stream;
if (empty($input_stream)) {
throw new RuntimeException('Cannot get JSON attribute from an empty input stream.');
}
$payload = json_decode($input_stream, true);
if ($xss_clean) {
foreach ($payload as $name => $value) {
$payload[$name] = $CI->security->xss_clean($value);
}
}
if (empty($index)) {
return $payload;
}
return $payload[$index] ?? null;
}
}

45
application/core/EA_Lang.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments lang.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Lang extends CI_Lang
{
//
}

77
application/core/EA_Loader.php Executable file
View File

@ -0,0 +1,77 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments loader.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Loader extends CI_Loader
{
/**
* Override the original view loader method so that layouts are also supported.
*
* @param string $view View filename.
* @param array $vars An associative array of data to be extracted for use in the view.
* @param bool $return Whether to return the view output or leave it to the Output class.
*
* @return object|string
*/
public function view($view, $vars = [], $return = false)
{
$layout = config('layout');
$is_layout_page = empty($layout); // This is a layout page if "layout" was undefined before the page got rendered.
$result = $this->_ci_load([
'_ci_view' => $view,
'_ci_vars' => $this->_ci_prepare_view_vars($vars),
'_ci_return' => $return,
]);
$layout = config('layout');
if ($layout && $is_layout_page) {
$result = $this->_ci_load([
'_ci_view' => $layout['filename'],
'_ci_vars' => $this->_ci_prepare_view_vars($vars),
'_ci_return' => $return,
]);
}
return $result;
}
}

45
application/core/EA_Log.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments log.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Log extends CI_Log
{
//
}

View File

@ -0,0 +1,55 @@
<?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
* ---------------------------------------------------------------------------- */
require_once __DIR__ . '/../../system/libraries/Migration.php';
/**
* Easy!Appointments migration.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Migration extends CI_Migration
{
/**
* Get the current migration version.
*
* @return int
*/
public function current_version(): int
{
return $this->_get_version();
}
}

214
application/core/EA_Model.php Executable file
View File

@ -0,0 +1,214 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments model.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Model extends CI_Model
{
/**
* @var array
*/
protected array $casts = [];
/**
* EA_Model constructor.
*/
public function __construct()
{
//
}
/**
* Get a specific field value from the database.
*
* @param string $field Name of the value to be returned.
* @param int $record_id Record ID.
*
* @return string Returns the selected record value from the database.
*
* @throws InvalidArgumentException
*
* @deprecated Since 1.5
*/
public function get_value(string $field, int $record_id): string
{
if (method_exists($this, 'value')) {
return $this->value($field, $record_id);
}
throw new RuntimeException('The "get_value" is not defined in model: ', __CLASS__);
}
/**
* Get a specific record from the database.
*
* @param int $record_id The ID of the record to be returned.
*
* @return array Returns an array with the record data.
*
* @throws InvalidArgumentException
*
* @deprecated Since 1.5
*/
public function get_row(int $record_id): array
{
if (method_exists($this, 'find')) {
return $this->find($record_id);
}
throw new RuntimeException('The "get_row" is not defined in model: ', __CLASS__);
}
/**
* Get all records 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.
*
* @return array Returns an array of records.
*/
public function get_batch($where = null, int $limit = null, int $offset = null, string $order_by = null): array
{
return $this->get($where, $limit, $offset, $order_by);
}
/**
* Save (insert or update) a record.
*
* @param array $record Associative array with the record data.
*
* @return int Returns the record ID.
*
* @throws InvalidArgumentException
*/
public function add(array $record): int
{
return $this->save($record);
}
/**
* Easily cast fields to the correct data type.
*
* The integrated MySQL library will return all values as strings, something that can easily becoming problematic,
* especially when comparing database values.
*
* @param array $record Record data.
*/
public function cast(array &$record)
{
foreach ($this->casts as $attribute => $cast) {
if (!isset($record[$attribute])) {
continue;
}
switch ($cast) {
case 'integer':
$record[$attribute] = (int) $record[$attribute];
break;
case 'float':
$record[$attribute] = (float) $record[$attribute];
break;
case 'boolean':
$record[$attribute] = (bool) $record[$attribute];
break;
case 'string':
$record[$attribute] = (string) $record[$attribute];
break;
default:
throw new RuntimeException('Unsupported cast type provided: ' . $cast);
}
}
}
/**
* Only keep the requested fields of the provided record.
*
* @param array $record Record data (single or multiple records).
* @param array $fields Requested field names.
*/
public function only(array &$record, array $fields)
{
if (is_assoc($record)) {
$record = array_fields($record, $fields);
} else {
foreach ($record as &$record_item) {
$record_item = array_fields($record_item, $fields);
}
}
}
/**
* Ensure a field exists in an array by using its value or NULL.
*
* @param array $record Record data (single or multiple records).
* @param array $fields Requested field names.
*/
public function optional(array &$record, array $fields)
{
if (is_assoc($record)) {
foreach ($fields as $field => $default) {
$record[$field] = $record[$field] ?? $default;
}
} else {
foreach ($record as &$record_item) {
foreach ($fields as $field => $default) {
$record_item[$field] = $record_item[$field] ?? $default;
}
}
}
}
/**
* Get the DB field name based on an API field name.
*
* @param string $api_field API resource key.
*
* @return string|null Returns the column field or null if non found.
*/
public function db_field(string $api_field): ?string
{
return $this->api_resource[$api_field] ?? null;
}
}

45
application/core/EA_Output.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments output.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Output extends CI_Output
{
//
}

View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments profiler.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Profiler extends CI_Profiler
{
//
}

45
application/core/EA_Router.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments router.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Router extends CI_Router
{
//
}

View File

@ -0,0 +1,95 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments security.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Security extends CI_Security
{
/**
* CSRF Verify
*
* @return CI_Security
*/
public function csrf_verify()
{
// If it's not a POST request we will set the CSRF cookie
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') {
return $this->csrf_set_cookie();
}
// Check if URI has been whitelisted from CSRF checks
if ($exclude_uris = config_item('csrf_exclude_uris')) {
$uri = load_class('URI', 'core');
foreach ($exclude_uris as $excluded) {
if (preg_match('#^' . $excluded . '$#i' . (UTF8_ENABLED ? 'u' : ''), $uri->uri_string())) {
return $this;
}
}
}
// Check CSRF token validity, but don't error on mismatch just yet - we'll want to regenerate
$csrf_token = $_POST[$this->_csrf_token_name] ?? ($_SERVER['HTTP_X_CSRF'] ?? null);
$valid =
isset($csrf_token, $_COOKIE[$this->_csrf_cookie_name]) &&
is_string($csrf_token) &&
is_string($_COOKIE[$this->_csrf_cookie_name]) &&
hash_equals($csrf_token, $_COOKIE[$this->_csrf_cookie_name]);
// We kill this since we're done and we don't want to pollute the _POST array
unset($_POST[$this->_csrf_token_name]);
// Regenerate on every submission?
if (config_item('csrf_regenerate')) {
// Nothing should last forever
unset($_COOKIE[$this->_csrf_cookie_name]);
$this->_csrf_hash = null;
}
$this->_csrf_set_hash();
$this->csrf_set_cookie();
if ($valid !== true) {
$this->csrf_show_error();
}
log_message('info', 'CSRF token verified');
return $this;
}
}

45
application/core/EA_Session.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments session.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Session extends CI_Session
{
//
}

45
application/core/EA_URI.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments URI.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_URI extends CI_URI
{
//
}

45
application/core/EA_Upload.php Executable file
View File

@ -0,0 +1,45 @@
<?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
* ---------------------------------------------------------------------------- */
/**
* Easy!Appointments upload.
*
* @property EA_Benchmark $benchmark
* @property EA_Cache $cache
* @property EA_Calendar $calendar
* @property EA_Config $config
* @property EA_DB_forge $dbforge
* @property EA_DB_query_builder $db
* @property EA_DB_utility $dbutil
* @property EA_Email $email
* @property EA_Encrypt $encrypt
* @property EA_Encryption $encryption
* @property EA_Exceptions $exceptions
* @property EA_Hooks $hooks
* @property EA_Input $input
* @property EA_Lang $lang
* @property EA_Loader $load
* @property EA_Log $log
* @property EA_Migration $migration
* @property EA_Output $output
* @property EA_Profiler $profiler
* @property EA_Router $router
* @property EA_Security $security
* @property EA_Session $session
* @property EA_Upload $upload
* @property EA_URI $uri
*/
class EA_Upload extends CI_Upload
{
//
}

View File

@ -0,0 +1,74 @@
<?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
* ---------------------------------------------------------------------------- */
if (!function_exists('is_assoc')) {
/**
* Check if an array is an associative array.
*
* @param array $array
*
* @return bool
*/
function is_assoc(array $array): bool
{
if (empty($array)) {
return false;
}
return array_keys($array) !== range(0, count($array) - 1);
}
}
if (!function_exists('array_find')) {
/**
* Find the first array element based on the provided function.
*
* @param array $array
* @param callable $callback
*
* @return mixed
*/
function array_find(array $array, callable $callback): mixed
{
if (empty($array)) {
return null;
}
if (!is_callable($callback)) {
throw new InvalidArgumentException('No filter function provided.');
}
return array_values(array_filter($array, $callback))[0] ?? null;
}
}
if (!function_exists('array_fields')) {
/**
* Keep only the provided fields of an array.
*
* @param array $array
* @param array $fields
*
* @return array
*/
function array_fields(array $array, array $fields): array
{
return array_filter(
$array,
function ($field) use ($fields) {
return in_array($field, $fields);
},
ARRAY_FILTER_USE_KEY,
);
}
}

View File

@ -0,0 +1,40 @@
<?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.0
* ---------------------------------------------------------------------------- */
/**
* Assets URL helper function.
*
* This function will create an asset file URL that includes a cache busting parameter in order
* to invalidate the browser cache in case of an update.
*
* @param string $uri Relative URI (just like the one used in the base_url helper).
* @param string|null $protocol Valid URI protocol.
*
* @return string Returns the final asset URL.
*/
function asset_url(string $uri = '', string $protocol = null): string
{
$debug = config('debug');
$cache_busting_token = !$debug ? '?' . config('cache_busting_token') : '';
if (str_contains(basename($uri), '.js') && !str_contains(basename($uri), '.min.js') && !$debug) {
$uri = str_replace('.js', '.min.js', $uri);
}
if (str_contains(basename($uri), '.css') && !str_contains(basename($uri), '.min.css') && !$debug) {
$uri = str_replace('.css', '.min.css', $uri);
}
return base_url($uri . $cache_busting_token, $protocol);
}

View File

@ -0,0 +1,171 @@
<?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.0
* ---------------------------------------------------------------------------- */
/**
* Get / set the specified config value.
*
* If an array is passed as the key, we will assume you want to set an array of values.
*
* Example "Get":
*
* $version = config('version', '1.0.0');
*
* Example "Set":
*
* config(['version' => '1.0.0']);
*
* @param array|string $key Configuration key.
* @param mixed|null $default Default value in case the requested config has no value.
*
* @return mixed|NULL Returns the requested value or NULL if you assign a new configuration value.
*
* @throws InvalidArgumentException
*/
function config(array|string $key, mixed $default = null): mixed
{
/** @var EA_Controller $CI */
$CI = &get_instance();
if (empty($key)) {
throw new InvalidArgumentException('The $key argument cannot be empty.');
}
if (is_array($key)) {
foreach ($key as $item => $value) {
$CI->config->set_item($item, $value);
}
return null;
}
$value = $CI->config->item($key);
return $value ?? $default;
}
if (!function_exists('script_vars')) {
/**
* Get / set the specified JS config value.
*
* If an array is passed as the key, we will assume you want to set an array of values.
*
* Example "Get":
*
* $version = script_vars('version', '1.0.0');
*
* Example "Set":
*
* script_vars(['version' => '1.0.0']);
*
* @param array|string|null $key Configuration key.
* @param mixed|null $default Default value in case the requested config has no value.
*
* @return mixed|NULL Returns the requested value or NULL if you assign a new configuration value.
*
* @throws InvalidArgumentException
*/
function script_vars(array|string $key = null, mixed $default = null): mixed
{
$script_vars = config('script_vars', []);
if (empty($key)) {
return $script_vars;
}
if (is_array($key)) {
foreach ($key as $item => $value) {
$script_vars[$item] = $value;
}
config(['script_vars' => $script_vars]);
return null;
}
$value = $script_vars[$key] ?? null;
return $value ?? $default;
}
}
if (!function_exists('html_vars')) {
/**
* Get / set the specified HTML variable.
*
* If an array is passed as the key, we will assume you want to set an array of values.
*
* Example "Get":
*
* $version = html_vars('title', 'Default Title');
*
* Example "Set":
*
* html_vars(['title' => 'Test Title']);
*
* @param array|string|null $key Variable key.
* @param mixed|null $default Default value in case the requested variable has no value.
*
* @return mixed|NULL Returns the requested value or NULL if you assign a new configuration value.
*
* @throws InvalidArgumentException
*/
function html_vars(array|string $key = null, mixed $default = null): mixed
{
$html_vars = config('html_vars', []);
if (empty($key)) {
return $html_vars;
}
if (is_array($key)) {
foreach ($key as $item => $value) {
$html_vars[$item] = $value;
}
config(['html_vars' => $html_vars]);
return null;
}
$value = $html_vars[$key] ?? null;
return $value ?? $default;
}
}
if (!function_exists('vars')) {
/**
* Get / set the specified HTML & JS config value.
*
* If an array is passed as the key, we will assume you want to set an array of values.
*
* Example "Get":
*
* $version = vars('version', '1.0.0');
*
* Example "Set":
*
* vars(['version' => '1.0.0']);
*
* @param array|string|null $key Configuration key.
* @param mixed|null $default Default value in case the requested config has no value.
*
* @return mixed|NULL Returns the requested value or NULL if you assign a new configuration value.
*
* @throws InvalidArgumentException
*/
function vars(array|string $key = null, mixed $default = null): mixed
{
return html_vars($key) ?? (script_vars($key) ?? $default);
}
}

Some files were not shown because too many files have changed in this diff Show More