diff --git a/application/controllers/settings/Client_form.php b/application/controllers/settings/Client_form.php new file mode 100644 index 00000000..d431783e --- /dev/null +++ b/application/controllers/settings/Client_form.php @@ -0,0 +1,133 @@ +<?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.5.0 + * ---------------------------------------------------------------------------- */ + +/** + * Client form controller. + * + * Handles client form settings related operations. + * + * @package Controllers + */ +class Client_form extends EA_Controller { + /** + * @var array + */ + protected array $permissions; + + /** + * Calendar 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'); + + $role_slug = session('role_slug'); + + if ($role_slug) + { + $this->permissions = $this->roles_model->get_permissions_by_slug($role_slug); + } + } + + /** + * Render the settings page. + */ + public function index() + { + session(['dest_url' => site_url('services')]); + + if (cannot('view', 'services')) + { + show_error('Forbidden', 403); + } + + $user_id = session('user_id'); + + $role_slug = session('role_slug'); + + $this->load->view('pages/settings/client_form/client_form_page', [ + 'page_title' => lang('settings'), + '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), + 'system_settings' => $this->settings_model->get(), + ]); + } + + /** + * Save general settings. + */ + public function save() + { + try + { + if ($this->permissions[PRIV_SYSTEM_SETTINGS]['edit'] == FALSE) + { + throw new Exception('You do not have the required permissions for this task.'); + } + + $settings = json_decode(request('settings', FALSE), TRUE); + + // Check if phone number settings are valid. + + $phone_number_required = setting('phone_number_required'); + + $phone_number_shown = FALSE; + + foreach ($settings as $setting) + { + if ($setting['name'] === 'show_phone_number') + { + $phone_number_shown = $setting['value']; + } + } + + if ($phone_number_required && ! $phone_number_shown) + { + throw new RuntimeException('You cannot hide the phone number in the booking form while it\'s also required!'); + } + + 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); + } + } +} diff --git a/application/views/pages/settings/client_form/client_form_page.php b/application/views/pages/settings/client_form/client_form_page.php new file mode 100755 index 00000000..142178ab --- /dev/null +++ b/application/views/pages/settings/client_form/client_form_page.php @@ -0,0 +1,193 @@ +<?php +/** + * @var array $system_settings + * @var array $user_settings + * @var string $timezones + * @var array $privileges + */ +?> + +<?php extend('layouts/backend/backend_layout') ?> + +<?php section('content') ?> + +<script src="<?= asset_url('assets/js/backend_settings_client_form_helper.js') ?>"></script> +<script src="<?= asset_url('assets/js/backend_settings_client_form.js') ?>"></script> +<script> + var GlobalVariables = { + csrfToken: <?= json_encode($this->security->get_csrf_hash()) ?>, + baseUrl: <?= json_encode(config('base_url')) ?>, + dateFormat: <?= json_encode(setting('date_format')) ?>, + timeFormat: <?= json_encode(setting('time_format')) ?>, + firstWeekday: <?= json_encode(setting('first_weekday')) ?>, + timezones: <?= json_encode($timezones) ?>, + settings: { + system: <?= json_encode($system_settings) ?>, + }, + user: { + id: <?= session('user_id') ?>, + email: <?= json_encode(session('user_email')) ?>, + timezone: <?= json_encode(session('timezone')) ?>, + role_slug: <?= json_encode(session('role_slug')) ?>, + privileges: <?= json_encode($privileges) ?> + } + }; + + $(function () { + BackendSettingsClientForm.initialize(true); + }); +</script> + +<div id="client-form-page" class="container-fluid backend-page"> + <div id="client-form"> + <form> + <fieldset> + <legend class="border-bottom mb-4"> + <?= lang('client_form') ?> + + <?php if ($privileges[PRIV_SYSTEM_SETTINGS]['edit'] == TRUE): ?> + <button type="button" class="save-settings btn btn-primary btn-sm mb-2" + data-tippy-content="<?= lang('save') ?>"> + <i class="fas fa-check-square mr-2"></i> + <?= lang('save') ?> + </button> + <?php endif ?> + </legend> + + <div class="wrapper row"> + <div class="col-12 col-sm-3"> + <div class="form-group"> + <label for="show-phone-number"> + <?= lang('phone_number') ?> + </label> + <button id="show-phone-number" data-field="show_phone_number" type="button" + class="hide-toggle form-control form-sub-button"> + <span class="hide-toggle-visible hidden"> + <img src="<?= base_url('assets/img/eye.svg') ?>" alt="eye"/> + <?= lang('visible') ?> + </span> + <span class="hide-toggle-hidden"> + <img src="<?= base_url('assets/img/eye-hidden.svg') ?>" alt="eye-hidden"/> + <?= lang('hidden') ?> + </span> + </button> + </div> + <div class="form-group"> + <label for="show-address"> + <?= lang('address') ?> + </label> + <button id="show-address" data-field="show_address" type="button" + class="hide-toggle form-control form-sub-button"> + <span class="hide-toggle-visible hidden"> + <img src="<?= base_url('assets/img/eye.svg') ?>" alt="eye"/> + <?= lang('visible') ?> + </span> + <span class="hide-toggle-hidden"> + <img src="<?= base_url('assets/img/eye-hidden.svg') ?>" alt="eye-hidden"/> + <?= lang('hidden') ?> + </span> + </button> + </div> + <div class="form-group"> + <label for="show-city"> + <?= lang('city') ?> + </label> + <button id="show-city" data-field="show_city" type="button" + class="hide-toggle form-control form-sub-button"> + <span class="hide-toggle-visible hidden"> + <img src="<?= base_url('assets/img/eye.svg') ?>" alt="eye"/> + <?= lang('visible') ?> + </span> + <span class="hide-toggle-hidden"> + <img src="<?= base_url('assets/img/eye-hidden.svg') ?>" alt="eye-hidden"/> + <?= lang('hidden') ?> + </span> + </button> + </div> + <div class="form-group"> + <label for="show-zip-code"> + <?= lang('zip_code') ?> + </label> + <button id="show-zip-code" data-field="show_zip_code" type="button" + class="hide-toggle form-control form-sub-button"> + <span class="hide-toggle-visible hidden"> + <img src="<?= base_url('assets/img/eye.svg') ?>" alt="eye"/> + <?= lang('visible') ?> + </span> + <span class="hide-toggle-hidden"> + <img src="<?= base_url('assets/img/eye-hidden.svg') ?>" alt="eye-hidden"/> + <?= lang('hidden') ?> + </span> + </button> + </div> + <div class="form-group"> + <label for="show-notes"> + <?= lang('notes') ?> + </label> + <button id="show-notes" data-field="show_notes" type="button" + class="hide-toggle form-control form-sub-button"> + <span class="hide-toggle-visible hidden"> + <img src="<?= base_url('assets/img/eye.svg') ?>" alt="eye"/> + <?= lang('visible') ?> + </span> + <span class="hide-toggle-hidden"> + <img src="<?= base_url('assets/img/eye-hidden.svg') ?>" alt="eye-hidden"/> + <?= lang('hidden') ?> + </span> + </button> + </div> + </div> + + <div class="col-12 col-sm-9"> + <div class="form-group"> + <div class="custom-control custom-switch"> + <input type="checkbox" class="custom-control-input" id="customer-notifications"> + <label class="custom-control-label" for="customer-notifications"> + <?= lang('customer_notifications') ?> + </label> + </div> + <span class="form-text text-muted"> + <?= lang('customer_notifications_hint') ?> + </span> + </div> + <div class="form-group"> + <div class="custom-control custom-switch"> + <input type="checkbox" class="custom-control-input" id="require-captcha"> + <label class="custom-control-label" for="require-captcha"> + CAPTCHA + </label> + </div> + <span class="form-text text-muted"> + <?= lang('require_captcha_hint') ?> + </span> + </div> + <div class="form-group"> + <div class="custom-control custom-switch"> + <input type="checkbox" class="custom-control-input" id="require-phone-number"> + <label class="custom-control-label" for="require-phone-number"> + <?= lang('phone_number') ?> + </label> + </div> + <span class="form-text text-muted"> + <?= lang('require_phone_number_hint') ?> + </span> + </div> + <div class="form-group"> + <div class="custom-control custom-switch"> + <input type="checkbox" class="custom-control-input" id="display-any-provider"> + <label class="custom-control-label" for="display-any-provider"> + <?= lang('any_provider') ?> + </label> + </div> + <span class="form-text text-muted"> + <?= lang('display_any_provider_hint') ?> + </span> + </div> + </div> + </div> + </fieldset> + </form> + </div> +</div> + +<?php section('content') ?> diff --git a/assets/js/backend_settings_client_form.js b/assets/js/backend_settings_client_form.js new file mode 100644 index 00000000..a519fe94 --- /dev/null +++ b/assets/js/backend_settings_client_form.js @@ -0,0 +1,154 @@ +/* ---------------------------------------------------------------------------- + * Easy!Appointments - Open Source Web Scheduler + * + * @package EasyAppointments + * @author A.Tselegidis <alextselegidis@gmail.com> + * @copyright Copyright (c) 2013 - 2020, Alex Tselegidis + * @license http://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link http://easyappointments.org + * @since v1.0.0 + * ---------------------------------------------------------------------------- */ + +window.BackendSettingsClientForm = window.BackendSettingsClientForm || {}; + +/** + * Backend Settings + * + * Contains the functionality of the backend settings page. Can either work for system or user settings, + * but the actions allowed to the user are restricted to his role (only admin has full privileges). + * + * @module BackendSettingsClientForm + */ +(function (exports) { + 'use strict'; + + // Constants + exports.SETTINGS_SYSTEM = 'SETTINGS_SYSTEM'; + + /** + * Tab settings object. + * + * @type {Object} + */ + var settings = {}; + + /** + * Initialize Page + * + * @param {bool} defaultEventHandlers Optional (true), determines whether to bind the default event handlers. + */ + exports.initialize = function (defaultEventHandlers) { + defaultEventHandlers = defaultEventHandlers || true; + + // Apply setting values from database. + GlobalVariables.settings.system.forEach(function (setting) { + $('input[data-field="' + setting.name + '"]').val(setting.value); + $('select[data-field="' + setting.name + '"]').val(setting.value); + + if (setting.name === 'customer_notifications') { + $('#customer-notifications').prop('checked', Boolean(Number(setting.value))); + } + + if (setting.name === 'require_captcha') { + $('#require-captcha').prop('checked', Boolean(Number(setting.value))); + } + + if (setting.name === 'require_phone_number') { + $('#require-phone-number').prop('checked', Boolean(Number(setting.value))); + } + + if (setting.name === 'display_any_provider') { + $('#display-any-provider').prop('checked', Boolean(Number(setting.value))); + } + + if (setting.name === 'display_cookie_notice') { + $('#display-cookie-notice').prop('checked', Boolean(Number(setting.value))); + } + + }); + + // Set default settings helper. + settings = new SystemSettingsClientFormHelper(); + + if (defaultEventHandlers) { + bindEventHandlers(); + } + + Backend.placeFooterToBottom(); + }; + + /** + * Bind the backend/settings default event handlers. + * + * This method depends on the backend/settings html, so do not use this method on a different page. + */ + function bindEventHandlers() { + /** + * Event: Save Settings Button "Click" + * + * Store the setting changes into the database. + */ + $('.save-settings').on('click', function () { + var data = settings.get(); + settings.save(data); + }); + + /** + * Event: Visible/Hidden button "Click" + * + * Change the state of the Visible/Hidden button + */ + $('.hide-toggle').on('click', function () { + var $input = $(this); + $input.find('span').toggleClass('hidden'); + }); + + /** + * set a Visible/Hidden toggle button to a certain state + * + * @argument $element for which jquery element to set the state for + * @argument isVisible a boolean which is true if the button should display 'visible' and false when the button should display 'hidden' + * + */ + function setShowToggleValue($element, isVisible) { + if (getShowToggleValue($element) !== isVisible) { + $element.find('span').toggleClass('hidden'); + } + } + + /** + * get the Visible/Hidden toggle button + * + * @argument $element for which jquery element to set the state for + * + * @return the state of the button. True for visible, false for hidden. + */ + function getShowToggleValue($element) { + var visiblePartArray = $element.find('.hide-toggle-visible'); + return !visiblePartArray.hasClass('hidden'); + } + + /** + * Event: require phone number switch "Click" + * + * make sure that our phone number is visible when it is required. + */ + $('#show-phone-number').on('click', function () { + if (!getShowToggleValue($(this))) { + //if button is set to hidden + $('#require-phone-number').prop('checked', false); + } + }); + + /** + * Event: require phone number switch "Click" + * + * make sure that our phone number is visible when it is required. + */ + $('#require-phone-number').on('click', function () { + if ($(this).prop('checked')) { + setShowToggleValue($('#show-phone-number'), true); + } + }); + } +})(window.BackendSettingsClientForm); diff --git a/assets/js/backend_settings_client_form_helper.js b/assets/js/backend_settings_client_form_helper.js new file mode 100644 index 00000000..bc59f3de --- /dev/null +++ b/assets/js/backend_settings_client_form_helper.js @@ -0,0 +1,162 @@ +/* ---------------------------------------------------------------------------- + * Easy!Appointments - Open Source Web Scheduler + * + * @package EasyAppointments + * @author A.Tselegidis <alextselegidis@gmail.com> + * @copyright Copyright (c) 2013 - 2020, Alex Tselegidis + * @license http://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link http://easyappointments.org + * @since v1.0.0 + * ---------------------------------------------------------------------------- */ + +(function () { + 'use strict'; + + /** + * "System Settings" Tab Helper Class + * + * @class SystemSettingsClientFormHelper + */ + var SystemSettingsClientFormHelper = function () {}; + + /** + * Save the system settings. + * + * This method is run after changes are detected on the tab input fields. + * + * @param {Array} settings Contains the system settings data. + */ + SystemSettingsClientFormHelper.prototype.save = function (settings) { + if (!this.validate()) { + return; // Validation failed, do not proceed. + } + + var url = GlobalVariables.baseUrl + '/index.php/settings/client_form/save'; + + var data = { + csrfToken: GlobalVariables.csrfToken, + settings: JSON.stringify(settings), + type: BackendSettingsClientForm.SETTINGS_SYSTEM + }; + + $.post(url, data).done(function () { + Backend.displayNotification(EALang.settings_saved); + }); + }; + + /** + * Get the state of a visible/hidden toggle button + * + * This method uses the DOM elements of the backend/settings page, so it can't be used in another page. + * + * @argument the element jquery of a button object that is a visible/hidden toggle. + * + * @return '0' when the button shows 'invisible' and '1' when the button shows 'visible'. Will always return '0' on an error. + */ + function getToggleButtonState($element) { + var visiblePartArray = $element.find('.hide-toggle-visible'); + var invisiblePartArray = $element.find('.hide-toggle-hidden'); + if (!(visiblePartArray.length === 0 || invisiblePartArray.length === 0)) { + if (visiblePartArray.hasClass('hidden')) { + //our button is currently invisible + return '0'; //invisible + } else { + //our button is currently visible + return '1'; //visible + } + } else { + return '0'; //invisible + } + } + + /** + * Prepare the system settings array. + * + * This method uses the DOM elements of the backend/settings page, so it can't be used in another page. + * + * @return {Array} Returns the system settings array. + */ + SystemSettingsClientFormHelper.prototype.get = function () { + var settings = []; + + settings.push({ + name: 'customer_notifications', + value: $('#customer-notifications').prop('checked') ? '1' : '0' + }); + + settings.push({ + name: 'require_captcha', + value: $('#require-captcha').prop('checked') ? '1' : '0' + }); + + settings.push({ + name: 'require_phone_number', + value: $('#require-phone-number').prop('checked') ? '1' : '0' + }); + + settings.push({ + name: 'display_any_provider', + value: $('#display-any-provider').prop('checked') ? '1' : '0' + }); + + settings.push({ + name: 'show_phone_number', + value: getToggleButtonState($('#show-phone-number')) + }); + + settings.push({ + name: 'show_address', + value: getToggleButtonState($('#show-address')) + }); + + settings.push({ + name: 'show_city', + value: getToggleButtonState($('#show-city')) + }); + + settings.push({ + name: 'show_zip_code', + value: getToggleButtonState($('#show-zip-code')) + }); + + settings.push({ + name: 'show_notes', + value: getToggleButtonState($('#show-notes')) + }); + + return settings; + }; + + /** + * Validate the settings data. + * + * If the validation fails then display a message to the user. + * + * @return {Boolean} Returns the validation result. + */ + SystemSettingsClientFormHelper.prototype.validate = function () { + $('#client-form .has-error').removeClass('has-error'); + + try { + // Validate required fields. + var missingRequired = false; + $('#client-form .required').each(function (index, requiredField) { + if (!$(requiredField).val()) { + $(requiredField).closest('.form-group').addClass('has-error'); + missingRequired = true; + } + }); + + if (missingRequired) { + throw new Error(EALang.fields_are_required); + } + + return true; + } catch (error) { + Backend.displayNotification(error.message); + return false; + } + }; + + window.SystemSettingsClientFormHelper = SystemSettingsClientFormHelper; +})();