diff --git a/classes/MaintenanceAccess.php b/classes/MaintenanceAccess.php new file mode 100644 index 000000000..11e0f758d --- /dev/null +++ b/classes/MaintenanceAccess.php @@ -0,0 +1,96 @@ + +* @copyright 2010-2022 Webkul IN +* @license https://store.webkul.com/license.html +*/ + +class MaintenanceAccessCore extends ObjectModel +{ + + public $id_maintenance_access; + public $ip_address; + public $email; + public $date_add; + const USERNAME_ATTEMPTS_PER_QUARTER_HOUR = 3; + const IP_ATTEMPTS_QUARTER_HOUR = 12; + const USERNAME_ATTEMPTS_PER_HOUR = 6; + const IP_ATTEMPTS_PER_HOUR = 24; + const QUARTER_HOUR = 60*15; + const HOUR = 60*60; + + /** + * @see ObjectModel::$definition + */ + public static $definition = array( + 'table' => 'maintenance_access', + 'primary' => 'id_maintenance_access', + 'fields' => array( + 'ip_address' => array('type' => self::TYPE_STRING, 'size' => 50), + 'email' => array('type' => self::TYPE_STRING, 'validate' => 'isEmail', 'required' => true, 'size' => 128), + 'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'), + ), + ); + + public function cleanData() + { + $time = (time() - (self::HOUR*2)); + Db::getInstance()->execute( + 'DELETE FROM `' . _DB_PREFIX_ .'maintenance_access` + WHERE `date_add` < "' . pSQL(date("Y-m-d H:i:s", $time)) . '"' + ); + } + + public function getUserFailedCount($time, $email=false, $ip_address=false, $attempts) + { + $sql = "SELECT COUNT(`id_maintenance_access`) FROM `". _DB_PREFIX_ ."maintenance_access` WHERE `date_add` > '" + . pSQL(date('Y-m-d H:i:s', $time)) ."'"; + + if ($email) { + $sql .= " AND `email` = '".pSQL($email)."'"; + } + + if ($ip_address) { + $sql .= " AND `ip_address` = '".pSQL($ip_address)."'"; + } + + return Db::getInstance()->getValue($sql) >= $attempts; + } + + public function checkLimit($email) + { + $has_error = false; + $minutes_ago = time() - self::QUARTER_HOUR; + $hours_ago = time() - self::HOUR; + $ip_address = Tools::getRemoteAddr(); + + $has_error |= $this->getUserFailedCount( + $minutes_ago, $email, false, self::USERNAME_ATTEMPTS_PER_QUARTER_HOUR + )? true : $has_error; + $has_error |= $this->getUserFailedCount( + $minutes_ago, false, $ip_address, self::IP_ATTEMPTS_QUARTER_HOUR + )? true : $has_error; + $has_error |= $this->getUserFailedCount( + $hours_ago, $email, false, self::USERNAME_ATTEMPTS_PER_HOUR + )? true : $has_error; + $has_error |= $this->getUserFailedCount( + $hours_ago, false, $ip_address, self::IP_ATTEMPTS_PER_HOUR + )? true : $has_error; + + return $has_error; + } +} \ No newline at end of file diff --git a/classes/controller/FrontController.php b/classes/controller/FrontController.php index 11dba43e9..9663e7e4d 100644 --- a/classes/controller/FrontController.php +++ b/classes/controller/FrontController.php @@ -214,7 +214,43 @@ class FrontControllerCore extends Controller self::$initialized = true; parent::init(); + $this->context->smarty->assign([ + 'css_dir' => _THEME_CSS_DIR_, + 'img_ps_dir' => _PS_IMG_, + 'tpl_dir' => _PS_THEME_DIR_, + 'js_dir' => _THEME_JS_DIR_, + 'base_dir' => __PS_BASE_URI__, + 'language_code' => $this->context->language->language_code ? $this->context->language->language_code : $this->context->language->iso_code, + ]); + /* get page name to display it in body id */ + + // Are we in a payment module + $module_name = ''; + if (Validate::isModuleName(Tools::getValue('module'))) { + $module_name = Tools::getValue('module'); + } + + if (!empty($this->page_name)) { + $page_name = $this->page_name; + } elseif (!empty($this->php_self)) { + $page_name = $this->php_self; + } elseif (Tools::getValue('fc') == 'module' && $module_name != '' && (Module::getInstanceByName($module_name) instanceof PaymentModule)) { + $page_name = 'module-payment-submit'; + } + // @retrocompatibility Are we in a module ? + elseif (preg_match('#^'.preg_quote($this->context->shop->physical_uri, '#').'modules/([a-zA-Z0-9_-]+?)/(.*)$#', $_SERVER['REQUEST_URI'], $m)) { + $page_name = 'module-'.$m[1].'-'.str_replace(array('.php', '/'), array('', '-'), $m[2]); + } else { + $page_name = Dispatcher::getInstance()->getController(); + $page_name = (preg_match('/^[0-9]/', $page_name) ? 'page_'.$page_name : $page_name); + } + + $this->context->smarty->assign(Meta::getMetaTags($this->context->language->id, $page_name)); + $this->context->smarty->assign('request_uri', Tools::safeOutput(urldecode($_SERVER['REQUEST_URI']))); + + Tools::setCookieLanguage($this->context->cookie); + $this->displayMaintenancePage(); // If current URL use SSL, set it true (used a lot for module redirect) if (Tools::usingSecureMode()) { $useSSL = true; @@ -236,12 +272,10 @@ class FrontControllerCore extends Controller $this->context->smarty->assign('account_created', 1); unset($this->context->cookie->account_created); } - ob_start(); // Init cookie language // @TODO This method must be moved into switchLanguage - Tools::setCookieLanguage($this->context->cookie); $protocol_link = (Configuration::get('PS_SSL_ENABLED') || Tools::usingSecureMode()) ? 'https://' : 'http://'; $useSSL = ((isset($this->ssl) && $this->ssl && Configuration::get('PS_SSL_ENABLED')) || Tools::usingSecureMode()) ? true : false; @@ -370,32 +404,6 @@ class FrontControllerCore extends Controller $this->context->cart = $cart; } - /* get page name to display it in body id */ - - // Are we in a payment module - $module_name = ''; - if (Validate::isModuleName(Tools::getValue('module'))) { - $module_name = Tools::getValue('module'); - } - - if (!empty($this->page_name)) { - $page_name = $this->page_name; - } elseif (!empty($this->php_self)) { - $page_name = $this->php_self; - } elseif (Tools::getValue('fc') == 'module' && $module_name != '' && (Module::getInstanceByName($module_name) instanceof PaymentModule)) { - $page_name = 'module-payment-submit'; - } - // @retrocompatibility Are we in a module ? - elseif (preg_match('#^'.preg_quote($this->context->shop->physical_uri, '#').'modules/([a-zA-Z0-9_-]+?)/(.*)$#', $_SERVER['REQUEST_URI'], $m)) { - $page_name = 'module-'.$m[1].'-'.str_replace(array('.php', '/'), array('', '-'), $m[2]); - } else { - $page_name = Dispatcher::getInstance()->getController(); - $page_name = (preg_match('/^[0-9]/', $page_name) ? 'page_'.$page_name : $page_name); - } - - $this->context->smarty->assign(Meta::getMetaTags($this->context->language->id, $page_name)); - $this->context->smarty->assign('request_uri', Tools::safeOutput(urldecode($_SERVER['REQUEST_URI']))); - /* Breadcrumb */ $navigation_pipe = (Configuration::get('PS_NAVIGATION_PIPE') ? Configuration::get('PS_NAVIGATION_PIPE') : '>'); $this->context->smarty->assign('navigationPipe', $navigation_pipe); @@ -404,7 +412,6 @@ class FrontControllerCore extends Controller if (!empty($this->php_self) && !Tools::getValue('ajax')) { $this->canonicalRedirection($this->context->link->getPageLink($this->php_self, $this->ssl, $this->context->language->id)); } - Product::initPricesComputation(); $display_tax_label = $this->context->country->display_tax_label; @@ -444,13 +451,11 @@ class FrontControllerCore extends Controller 'force_ssl' => Configuration::get('PS_SSL_ENABLED') && Configuration::get('PS_SSL_ENABLED_EVERYWHERE'), 'content_dir' => $protocol_content.Tools::getHttpHost().__PS_BASE_URI__, 'base_uri' => $protocol_content.Tools::getHttpHost().__PS_BASE_URI__.(!Configuration::get('PS_REWRITING_SETTINGS') ? 'index.php' : ''), - 'tpl_dir' => _PS_THEME_DIR_, 'tpl_uri' => _THEME_DIR_, 'modules_dir' => _MODULE_DIR_, 'mail_dir' => _MAIL_DIR_, 'lang_iso' => $this->context->language->iso_code, 'lang_id' => (int)$this->context->language->id, - 'language_code' => $this->context->language->language_code ? $this->context->language->language_code : $this->context->language->iso_code, 'come_from' => Tools::getHttpHost(true, true).Tools::htmlentitiesUTF8(str_replace(array('\'', '\\'), '', urldecode($_SERVER['REQUEST_URI']))), 'cart_qties' => (int)$cart->nbProducts(), 'currencies' => Currency::getCurrencies(), @@ -494,7 +499,6 @@ class FrontControllerCore extends Controller )); $assign_array = array( - 'img_ps_dir' => _PS_IMG_, 'img_cat_dir' => _THEME_CAT_DIR_, 'img_lang_dir' => _THEME_LANG_DIR_, 'img_prod_dir' => _THEME_PROD_DIR_, @@ -504,8 +508,6 @@ class FrontControllerCore extends Controller 'img_store_dir' => _THEME_STORE_DIR_, 'img_col_dir' => _THEME_COL_DIR_, 'img_dir' => _THEME_IMG_DIR_, - 'css_dir' => _THEME_CSS_DIR_, - 'js_dir' => _THEME_JS_DIR_, 'pic_dir' => _THEME_PROD_PIC_DIR_ ); @@ -538,14 +540,12 @@ class FrontControllerCore extends Controller self::$link = $link; $defaultCountry = $this->context->country; - $this->displayMaintenancePage(); - if ($this->restrictedCountry) { $this->displayRestrictedCountryPage(); } if (Tools::isSubmit('live_edit') && !$this->checkLiveEditAccess()) { - Tools::redirect('index.php?controller=404'); + Tools::redirect('index.php'); } $this->iso = $iso; @@ -749,22 +749,101 @@ class FrontControllerCore extends Controller */ protected function displayMaintenancePage() { - if ($this->maintenance == true || !(int)Configuration::get('PS_SHOP_ENABLE')) { - $this->maintenance = true; - if (!in_array(Tools::getRemoteAddr(), explode(',', Configuration::get('PS_MAINTENANCE_IP')))) { - header('HTTP/1.1 503 temporarily overloaded'); - - $this->context->smarty->assign($this->initLogoAndFavicon()); - $this->context->smarty->assign(array( - 'HOOK_MAINTENANCE' => Hook::exec('displayMaintenance', array()), - )); - - // If the controller is a module, then getTemplatePath will try to find the template in the modules, so we need to instanciate a real frontcontroller - $front_controller = preg_match('/ModuleFrontController$/', get_class($this)) ? new FrontController() : $this; - $this->smartyOutputContent($front_controller->getTemplatePath($this->getThemeDir().'maintenance.tpl')); - exit; + if (isset(Context::getContext()->cookie->remote_addr) + && Context::getContext()->cookie->remote_addr != ip2long(Tools::getRemoteAddr()) + ) { + $cookie = Context::getContext()->cookie; + if (isset($cookie->remote_addr)) { + $cookie->enable_maintenance_view = false; + $cookie->remote_addr = false; + $cookie->write(); } } + + if (Tools::isSubmit('SubmitLogin') + && Configuration::get('PS_ALLOW_EMP') + && !Context::getContext()->cookie->enable_maintenance_view + ) { + $email = trim(Tools::getValue('email')); + $passwd = trim(Tools::getValue('passwd')); + $this->processLogin($email, $passwd); + } + + if (!Context::getContext()->cookie->enable_maintenance_view || !Configuration::get('PS_ALLOW_EMP')) { + Context::getContext()->cookie->__unset('enable_maintenance_view'); + Context::getContext()->cookie->write(); + if ($this->maintenance == true || !(int)Configuration::get('PS_SHOP_ENABLE')) { + $this->maintenance = true; + if (!in_array(Tools::getRemoteAddr(), explode(',', Configuration::get('PS_MAINTENANCE_IP')))) { + header('HTTP/1.1 503 temporarily overloaded'); + + $this->context->smarty->assign($this->initLogoAndFavicon()); + $this->context->smarty->assign(array( + 'HOOK_MAINTENANCE' => Hook::exec('displayMaintenance', array()), + 'allowEmployee' => Configuration::get('PS_ALLOW_EMP'), + 'maintenance' => $this->context->link->getAdminLink('AdminMaintenance', false), + )); + // If the controller is a module, then getTemplatePath will try to find the template in the modules, so we need to instanciate a real frontcontroller + $front_controller = preg_match('/ModuleFrontController$/', get_class($this)) ? new FrontController() : $this; + $this->smartyOutputContent($front_controller->getTemplatePath($this->getThemeDir().'maintenance.tpl')); + exit; + } + } + } + } + + protected function processLogin($email, $passwd) + { + $result = false; + $objMaintenanceAccess = new MaintenanceAccess(); + + /* Check fields validity */ + if (empty($email)) { + $this->errors[] = Tools::displayError('Email is empty.'); + } elseif (!Validate::isEmail($email)) { + $this->errors[] = Tools::displayError('Invalid email address.'); + } + + if (empty($passwd)) { + $this->errors[] = Tools::displayError('The password field is blank.'); + } elseif (!Validate::isPasswd($passwd)) { + $this->errors[] = Tools::displayError('Invalid password.'); + } + + if ($objMaintenanceAccess->checkLimit($email)) { + $this->errors[] =Tools::displayError('You have exceeded the limit of login attempts, please try after some time'); + } + + if (!count($this->errors)) { + // Find employee + $this->context->employee = new Employee(); + $is_employee_loaded = $this->context->employee->getByEmail($email, $passwd); + $employee_associated_shop = $this->context->employee->getAssociatedShops(); + if (!$is_employee_loaded) { + $this->errors[] = Tools::displayError('The Employee does not exist, or the password provided is incorrect.'); + $this->context->employee->logout(); + } elseif (empty($employee_associated_shop) && !$this->context->employee->isSuperAdmin()) { + $this->errors[] = Tools::displayError('This employee does not manage the shop anymore (Either the shop has been deleted or permissions have been revoked).'); + $this->context->employee->logout(); + } else { + // Update cookie + $cookie = Context::getContext()->cookie; + $cookie->enable_maintenance_view = $this->context->employee->id; + $cookie->remote_addr = ip2long(Tools::getRemoteAddr()); + $cookie->write(); + } + if (count($this->errors)) { + $this->context->smarty->assign('errors', $this->errors); + $objMaintenanceAccess->email = $email; + $objMaintenanceAccess->ip_address = Tools::getRemoteAddr(); + $objMaintenanceAccess->save(); + } else { + Tools::redirect('index.php'); + } + } else { + $this->context->smarty->assign('errors', $this->errors); + } + return $result; } /** diff --git a/controllers/admin/AdminMaintenanceController.php b/controllers/admin/AdminMaintenanceController.php index b289891b4..d6911d4da 100644 --- a/controllers/admin/AdminMaintenanceController.php +++ b/controllers/admin/AdminMaintenanceController.php @@ -48,6 +48,14 @@ class AdminMaintenanceControllerCore extends AdminController 'cast' => 'intval', 'type' => 'bool' ), + 'PS_ALLOW_EMP' => array( + 'title' => $this->l('Allow Employees'), + 'desc' => $this->l('Allow the employees to access the site using email and password.'), + 'validation' => 'isBool', + 'cast' => 'intval', + 'type' => 'bool', + 'class' => "hello" + ), 'PS_MAINTENANCE_IP' => array( 'title' => $this->l('Maintenance IP'), 'hint' => $this->l('IP addresses allowed to access the front office even if the site is disabled. Please use a comma to separate them (e.g. 42.24.4.2,127.0.0.1,99.98.97.96)'), @@ -60,4 +68,10 @@ class AdminMaintenanceControllerCore extends AdminController ), ); } + + public function setMedia() + { + parent::setMedia(); + $this->addJS(_PS_JS_DIR_.'maintenance.js'); + } } diff --git a/img/maintenance_banner.png b/img/maintenance_banner.png new file mode 100644 index 000000000..7bb75db80 Binary files /dev/null and b/img/maintenance_banner.png differ diff --git a/install/data/db_structure.sql b/install/data/db_structure.sql index a3aa8160d..75266469e 100644 --- a/install/data/db_structure.sql +++ b/install/data/db_structure.sql @@ -2689,3 +2689,11 @@ CREATE TABLE IF NOT EXISTS `PREFIX_cms_role_lang` ( PRIMARY KEY (`id_cms_role`,`id_lang`, id_shop) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `PREFIX_maintenance_access` ( + `id_maintenance_access` int(11) unsigned NOT NULL AUTO_INCREMENT, + `ip_address` varchar(50) NOT NULL, + `email` varchar(128) NOT NULL, + `date_add` datetime NOT NULL, + PRIMARY KEY (`id_maintenance_access`) +) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; diff --git a/js/maintenance.js b/js/maintenance.js new file mode 100644 index 000000000..26015d1a9 --- /dev/null +++ b/js/maintenance.js @@ -0,0 +1,21 @@ +$(document).ready(function(){ + + if ($("#PS_SHOP_ENABLE_on").is(':checked')) { + $('#conf_id_PS_ALLOW_EMP').hide(); + } + if ($("#PS_SHOP_ENABLE_off").is(':checked')) { + $("#conf_id_PS_ALLOW_EMP").show(); + } + + $(".clicker.blue").on('click', function(){ + $('.hiddendiv').show('slow'); + }); + + $("#cancelLogin").on('click', function(){ + $('.hiddendiv').hide('slow'); + }); + + $("input[type='radio'][name='PS_SHOP_ENABLE']").change(function() { + $("#conf_id_PS_ALLOW_EMP").toggle('slow'); + }); +}); \ No newline at end of file diff --git a/themes/hotel-reservation-theme/css/maintenance.css b/themes/hotel-reservation-theme/css/maintenance.css index 776764667..9ff35daca 100644 --- a/themes/hotel-reservation-theme/css/maintenance.css +++ b/themes/hotel-reservation-theme/css/maintenance.css @@ -1,32 +1,146 @@ /********************************************************************* Maintenance Page Styles **********************************************************************/ +body { + font: 14px; + font-family: "Open Sans"; +} #body { background-color: #F7F7F7; } -#maintenance { - margin: 50px 0 0 0; - background-color: #fff; - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); } - @media (min-width: 1200px) { - #maintenance { - margin: 126px 0 0 0; - padding: 50px; } } - #maintenance .logo { - margin: 0 0 31px 0; - text-align: center; } - #maintenance h1 { - font: 24px "Open Sans", sans-serif; +h1 { + font-size: 50px; +} + +p { + font-size: 16px; + color: #666; +} + +.blue { + color: #1A7EFF; + text-decoration: none; +} + +.containter { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + column-gap: 60px; +} + +.left { + text-align: right; +} + +.clicker { + outline: none; + cursor: pointer; +} + +.hiddendiv { + display: none; + width: 300px; +} + +.text-center { + text-align: center; +} + +input[type='text'], +input[type='email'], +input[type='password'] { + border: 1px solid #c9c9c9; + width: 90%; + padding: 10px 14px; + font-size: 14px; + margin-bottom: 14px; + border-radius: 4px; +} + +button[type='submit'] { + border: 1px solid #1A7EFF; + background-color: #1A7EFF; + padding: 10px 20px; + font-size: 14px; + margin-bottom: 14px; + color: white; + border-radius: 4px; + cursor: pointer; +} + +button[type='button'].cancel-login { + border: 1px solid #c5c5c5; + background-color: #fff; + padding: 10px 20px; + font-size: 14px; + margin-bottom: 14px; color: #333; - padding: 0 0 14px 0; - margin: 0 0 19px 0; } - #maintenance #message { - font: 14px "Open Sans", sans-serif; - padding: 0 0 0 18px; - color: #555454; - text-align: center; } + border-radius: 4px; + cursor: pointer; +} + +@media screen and (max-width: 780px) { + .containter { + flex-direction:column; + } +} + +.alert { + position: relative; + padding: 0.8rem 1.5rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.alert ol { + margin: 5px; +} + +.alert li { + font-size: 1rem; + font-weight: 400; + line-height: 1.5; +} + +.alert.alert-danger p { + color: #FFF; +} + +.alert.alert-danger li { + font-size: 14px; +} + +.alert-danger { + color: #842029; + background-color: #f8d7da; + border-color: #f5c2c7; +} + +.alert.alert-danger { + text-shadow: 1px 1px rgb(0 0 0 / 10%); +} + +.alert-danger .alert-link { + font-weight: bold; + font-size: 16px; + color: #FFF; +} + +#maintenance .logo { + padding: 12px; + text-align: center; } +#maintenance h2 { + font: 24px; + color: #333; + padding: 0 0 14px 0; + margin: 0 0 19px 0; } +#maintenance #message { + padding: 0 0 0 18px; + color: #555454; + text-align: center; } .container { padding-right: 15px; @@ -48,4 +162,4 @@ padding-left: 0; padding-right: 0; } } -/*# sourceMappingURL=maintenance.css.map */ +/*# sourceMappingURL=maintenance.css.map */ \ No newline at end of file diff --git a/themes/hotel-reservation-theme/maintenance.tpl b/themes/hotel-reservation-theme/maintenance.tpl index a1a3a02e7..d07051087 100644 --- a/themes/hotel-reservation-theme/maintenance.tpl +++ b/themes/hotel-reservation-theme/maintenance.tpl @@ -25,33 +25,91 @@ + {$meta_title|escape:'html':'UTF-8'} -{if isset($meta_description)} - -{/if} -{if isset($meta_keywords)} - -{/if} + {if isset($meta_description)} + + {/if} + {if isset($meta_keywords)} + + {/if} - - + + + + + -
-
- - {$HOOK_MAINTENANCE} -
-

{l s='We\'ll be back soon.'}

- {l s='We are currently updating our site and will be back really soon.'} -
- {l s='Thanks for your patience.'} -
-
-
+
+ +
+ {if isset($errors) && $errors} +
+ {l s='Error!'} +
    + {foreach from=$errors key=k item=error} +
  1. {$error}
  2. + {/foreach} +
+
+ {/if} +
+
+
+
+ +
+
+
+

{l s='We\'ll be back soon.'}

+

{l s='We are currently updating our site and will be back really soon.'}

+

{l s='Thanks for your patience!'}

+ {if isset($allowEmployee) && $allowEmployee} +
+

{l s='Are you member?'}

+
+
+
+
+
+ +
+ +
+
+ +
+ +
+ + +
+
+
+
+
+ {/if} +
+
+
- + + \ No newline at end of file