diff --git a/admin/manage_custom_nav_elements.php b/admin/manage_custom_nav_elements.php new file mode 100644 index 00000000..30fca626 --- /dev/null +++ b/admin/manage_custom_nav_elements.php @@ -0,0 +1,275 @@ +'); + +/* Get all the required files and functions */ +require(HESK_PATH . 'hesk_settings.inc.php'); +require(HESK_PATH . 'inc/common.inc.php'); +require(HESK_PATH . 'inc/admin_functions.inc.php'); +require(HESK_PATH . 'inc/mail_functions.inc.php'); +hesk_load_database_functions(); + +hesk_session_start(); +hesk_dbConnect(); +hesk_isLoggedIn(); + +//hesk_checkPermission('can_man_custom_nav'); + +/* Print header */ +require_once(HESK_PATH . 'inc/headerAdmin.inc.php'); + +/* Print main manage users page */ +require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); +?> +
+
+
+
+

+ +

+
+ +
+
+
+
+
+ +
+
+ + + + + + + + + + + + + +
+
+
+
+
+ +
+
+
+
+ + + +get = array(); @@ -50,6 +55,10 @@ class ApplicationContext { // API Checker $this->get[ApiChecker::class] = new ApiChecker($this->get[ModsForHeskSettingsGateway::class]); + // Custom Navigation + $this->get[CustomNavElementGateway::class] = new CustomNavElementGateway(); + $this->get[CustomNavElementHandler::class] = new CustomNavElementHandler($this->get[CustomNavElementGateway::class]); + // Logging $this->get[LoggingGateway::class] = new LoggingGateway(); diff --git a/api/BusinessLogic/Navigation/CustomNavElement.php b/api/BusinessLogic/Navigation/CustomNavElement.php new file mode 100644 index 00000000..74b4ca1a --- /dev/null +++ b/api/BusinessLogic/Navigation/CustomNavElement.php @@ -0,0 +1,30 @@ +customNavElementGateway = $customNavElementGateway; + } + + + function getAllCustomNavElements($heskSettings) { + return $this->customNavElementGateway->getAllCustomNavElements($heskSettings); + } + + function getCustomNavElement($id, $heskSettings) { + $elements = $this->getAllCustomNavElements($heskSettings); + + foreach ($elements as $element) { + if ($element->id === intval($id)) { + return output($element); + } + } + + throw new ApiFriendlyException("Custom nav element {$id} not found!", "Element Not Found", 404); + } + + function deleteCustomNavElement($id, $heskSettings) { + $this->customNavElementGateway->deleteCustomNavElement($id, $heskSettings); + $this->customNavElementGateway->resortAllElements($heskSettings); + } + + function saveCustomNavElement($element, $heskSettings) { + $this->customNavElementGateway->saveCustomNavElement($element, $heskSettings); + } + + function createCustomNavElement($element, $heskSettings) { + $element = $this->customNavElementGateway->createCustomNavElement($element, $heskSettings); + $this->customNavElementGateway->resortAllElements($heskSettings); + + return $element; + } + + function sortCustomNavElement($elementId, $direction, $heskSettings) { + /* @var $element CustomNavElement */ + $elements = $this->customNavElementGateway->getAllCustomNavElements($heskSettings); + $elementToChange = null; + foreach ($elements as $element) { + if ($element->id === intval($elementId)) { + $elementToChange = $element; + } + } + + + if ($direction === Direction::UP) { + $elementToChange->sort -= 15; + } else { + $elementToChange->sort += 15; + } + + $this->customNavElementGateway->saveCustomNavElement($elementToChange, $heskSettings); + $this->customNavElementGateway->resortAllElements($heskSettings); + } +} \ No newline at end of file diff --git a/api/BusinessLogic/Navigation/CustomNavElementPlace.php b/api/BusinessLogic/Navigation/CustomNavElementPlace.php new file mode 100644 index 00000000..3114fdc7 --- /dev/null +++ b/api/BusinessLogic/Navigation/CustomNavElementPlace.php @@ -0,0 +1,10 @@ +id = intval($dataRow['id']); $userContext->username = $dataRow['user']; diff --git a/api/Controllers/InternalApiController.php b/api/Controllers/InternalApiController.php index 29d82642..c629e136 100644 --- a/api/Controllers/InternalApiController.php +++ b/api/Controllers/InternalApiController.php @@ -7,6 +7,13 @@ use BusinessLogic\Exceptions\InternalUseOnlyException; use BusinessLogic\Helpers; abstract class InternalApiController { + static function staticCheckForInternalUseOnly() { + $tokenHeader = Helpers::getHeader('X-AUTH-TOKEN'); + if ($tokenHeader !== null && trim($tokenHeader) !== '') { + throw new InternalUseOnlyException(); + } + } + function checkForInternalUseOnly() { $tokenHeader = Helpers::getHeader('X-AUTH-TOKEN'); if ($tokenHeader !== null && trim($tokenHeader) !== '') { diff --git a/api/Controllers/Navigation/CustomNavElementController.php b/api/Controllers/Navigation/CustomNavElementController.php new file mode 100644 index 00000000..d4a514e7 --- /dev/null +++ b/api/Controllers/Navigation/CustomNavElementController.php @@ -0,0 +1,101 @@ +get[CustomNavElementHandler::class]; + + output($handler->getAllCustomNavElements($hesk_settings)); + } + + static function sort($id, $direction) { + global $applicationContext, $hesk_settings; + + self::staticCheckForInternalUseOnly(); + + /* @var $handler CustomNavElementHandler */ + $handler = $applicationContext->get[CustomNavElementHandler::class]; + + $handler->sortCustomNavElement(intval($id), $direction, $hesk_settings); + } + + function get($id) { + global $applicationContext, $hesk_settings; + + $this->checkForInternalUseOnly(); + + /* @var $handler CustomNavElementHandler */ + $handler = $applicationContext->get[CustomNavElementHandler::class]; + + output($handler->getCustomNavElement($id, $hesk_settings)); + } + + function post() { + global $applicationContext, $hesk_settings; + + $this->checkForInternalUseOnly(); + + /* @var $handler CustomNavElementHandler */ + $handler = $applicationContext->get[CustomNavElementHandler::class]; + + $data = JsonRetriever::getJsonData(); + $element = $handler->createCustomNavElement($this->buildElementModel($data), $hesk_settings); + + return output($element, 201); + } + + function put($id) { + global $applicationContext, $hesk_settings; + + $this->checkForInternalUseOnly(); + + /* @var $handler CustomNavElementHandler */ + $handler = $applicationContext->get[CustomNavElementHandler::class]; + + $data = JsonRetriever::getJsonData(); + $handler->saveCustomNavElement($this->buildElementModel($data, $id), $hesk_settings); + + return http_response_code(204); + } + + function delete($id) { + global $applicationContext, $hesk_settings; + + $this->checkForInternalUseOnly(); + + /* @var $handler CustomNavElementHandler */ + $handler = $applicationContext->get[CustomNavElementHandler::class]; + + $handler->deleteCustomNavElement($id, $hesk_settings); + + return http_response_code(204); + } + + private function buildElementModel($data, $id = null) { + $element = new CustomNavElement(); + $element->id = $id; + $element->place = intval(Helpers::safeArrayGet($data, 'place')); + $element->fontIcon = Helpers::safeArrayGet($data, 'fontIcon'); + $element->imageUrl = Helpers::safeArrayGet($data, 'imageUrl'); + $element->text = Helpers::safeArrayGet($data, 'text'); + $element->subtext = Helpers::safeArrayGet($data, 'subtext'); + $element->url = Helpers::safeArrayGet($data, 'url'); + $element->sort = intval(Helpers::safeArrayGet($data, 'sort')); + + return $element; + } +} \ No newline at end of file diff --git a/api/Core/json_error.php b/api/Core/json_error.php index 24154ae5..1ff438c0 100644 --- a/api/Core/json_error.php +++ b/api/Core/json_error.php @@ -1,12 +1,13 @@ init(); + + $columns = '`t1`.`id`, `t1`.`image_url`, `t1`.`font_icon`, `t1`.`place`, `t1`.`url`, `t1`.`sort`, + `t2`.`language`, `t2`.`text`, `t2`.`subtext`'; + + $rs = hesk_dbQuery("SELECT {$columns} FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element` AS `t1` + INNER JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element_to_text` AS `t2` + ON `t1`.`id` = `t2`.`nav_element_id` + ORDER BY `t1`.`place` ASC, `t1`.`sort` ASC"); + + $elements = array(); + + /* @var $element CustomNavElement */ + $element = null; + $previousId = -1; + while ($row = hesk_dbFetchAssoc($rs)) { + $id = intval($row['id']); + if ($previousId !== $id) { + if ($element !== null) { + $elements[] = $element; + } + $element = new CustomNavElement(); + $element->id = $id; + $element->place = intval($row['place']); + $element->imageUrl = $row['image_url']; + $element->fontIcon = $row['font_icon']; + $element->url = $row['url']; + $element->sort = $row['sort']; + $element->text = array(); + $element->subtext = array(); + } + + $element->text[$row['language']] = $row['text']; + $element->subtext[$row['language']] = $row['subtext']; + + $previousId = $id; + } + + if ($element !== null) { + $elements[] = $element; + } + + $this->close(); + + return $elements; + } + + /** + * @param $id int + * @param $heskSettings array + */ + function deleteCustomNavElement($id, $heskSettings) { + $this->init(); + + hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element_to_text` + WHERE `nav_element_id` = " . intval($id)); + hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element` + WHERE `id` = " . intval($id)); + + $this->close(); + } + + /** + * @param $element CustomNavElement + * @param $heskSettings array + */ + function saveCustomNavElement($element, $heskSettings) { + $this->init(); + + //-- Delete previous records - easier than inserting/updating + hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element_to_text` + WHERE `nav_element_id` = " . intval($element->id)); + + $languageTextAndSubtext = array(); + foreach ($element->text as $key => $text) { + $languageTextAndSubtext[$key]['text'] = $text; + } + foreach ($element->subtext as $key => $subtext) { + $languageTextAndSubtext[$key]['subtext'] = $subtext; + } + + foreach ($languageTextAndSubtext as $key => $values) { + $subtext = 'NULL'; + if (isset($values['subtext'])) { + $subtext = "'" . hesk_dbEscape($values['subtext']) . "'"; + } + hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element_to_text` + (`nav_element_id`, `language`, `text`, `subtext`) VALUES (" . intval($element->id) . ", + '" . hesk_dbEscape($key) . "', + '" . hesk_dbEscape($values['text']) . "', + " . $subtext . ")"); + } + + $imageUrl = $element->imageUrl == null ? 'NULL' : "'" . hesk_dbEscape($element->imageUrl) . "'"; + $fontIcon = $element->fontIcon == null ? 'NULL' : "'" . hesk_dbEscape($element->fontIcon) . "'"; + hesk_dbQuery("UPDATE `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element` + SET `image_url` = {$imageUrl}, + `font_icon` = {$fontIcon}, + `url` = '" . hesk_dbEscape($element->url) . "', + `sort` = " . intval($element->sort) . ", + `place` = " . intval($element->place) . + " WHERE `id` = " . intval($element->id)); + + $this->close(); + } + + /** + * @param $element CustomNavElement + * @param $heskSettings array + * @return CustomNavElement + */ + function createCustomNavElement($element, $heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT MAX(`sort`) AS `sort` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element` + WHERE `place` = " . intval($element->place)); + $maxSort = hesk_dbFetchAssoc($rs); + $sortValue = intval($maxSort['sort']) + 1; + + $imageUrl = $element->imageUrl == null ? 'NULL' : "'" . hesk_dbEscape($element->imageUrl) . "'"; + $fontIcon = $element->fontIcon == null ? 'NULL' : "'" . hesk_dbEscape($element->fontIcon) . "'"; + hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element` + (`image_url`, `font_icon`, `place`, `sort`, `url`) + VALUES ({$imageUrl}, {$fontIcon}, " . intval($element->place) . ", " . $sortValue . ", '" . hesk_dbEscape($element->url) . "')"); + + $element->id = hesk_dbInsertID(); + + + $languageTextAndSubtext = array(); + foreach ($element->text as $key => $text) { + $languageTextAndSubtext[$key]['text'] = $text; + } + foreach ($element->subtext as $key => $subtext) { + $languageTextAndSubtext[$key]['subtext'] = $subtext; + } + + foreach ($languageTextAndSubtext as $key => $values) { + $subtext = 'NULL'; + if (isset($values['subtext'])) { + $subtext = "'" . hesk_dbEscape($values['subtext']) . "'"; + } + hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element_to_text` + (`nav_element_id`, `language`, `text`, `subtext`) VALUES (" . intval($element->id) . ", + '" . hesk_dbEscape($key) . "', + '" . hesk_dbEscape($values['text']) . "', + " . $subtext . ")"); + } + + $this->close(); + + return $element; + } + + + function resortAllElements($heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT `id` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element` + ORDER BY `place` ASC, `sort` ASC"); + + $sortValue = 10; + while ($row = hesk_dbFetchAssoc($rs)) { + hesk_dbQuery("UPDATE `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_nav_element` + SET `sort` = " . intval($sortValue) . " + WHERE `id` = " . intval($row['id'])); + + $sortValue += 10; + } + + $this->close(); + } +} \ No newline at end of file diff --git a/api/index.php b/api/index.php index 92e87d99..6ec11b83 100644 --- a/api/index.php +++ b/api/index.php @@ -98,14 +98,14 @@ function exceptionHandler($exception) { $userContext, $hesk_settings); $logIdText = $logId === null ? "Additionally, the error could not be logged! :'(" : "Log ID: {$logId}"; - print_error("SQL Exception", "Fought an uncaught SQL exception. Check the logs for more information. {$logIdText}"); + print_error("SQL Exception", "Fought an uncaught SQL exception. Check the logs for more information. {$logIdText}", $logId); } else { $logId = tryToLog(getLoggingLocation($exception), $exception->getMessage(), $exception->getTraceAsString(), $userContext, $hesk_settings); $logIdText = $logId === null ? "Additionally, the error could not be logged! :'(" : "Log ID: {$logId}"; - print_error("Exception Occurred", "Fought an uncaught exception. Check the logs for more information. {$logIdText}"); + print_error("Exception Occurred", "Fought an uncaught exception. Check the logs for more information. {$logIdText}", $logId); } die(); @@ -186,6 +186,11 @@ Link::all(array( /* Internal use only routes */ // Resend email response '/v1-internal/staff/tickets/{i}/resend-email' => \Controllers\Tickets\ResendTicketEmailToCustomerController::class, + // Custom Navigation + '/v1-internal/custom-navigation/all' => \Controllers\Navigation\CustomNavElementController::class . '::getAll', + '/v1-internal/custom-navigation' => \Controllers\Navigation\CustomNavElementController::class, + '/v1-internal/custom-navigation/{i}' => \Controllers\Navigation\CustomNavElementController::class, + '/v1-internal/custom-navigation/{i}/sort/{s}' => \Controllers\Navigation\CustomNavElementController::class . '::sort', // Any URL that doesn't match goes to the 404 handler '404' => 'handle404' diff --git a/css/colors.css b/css/colors.css index 1062549e..6dfae0a4 100644 --- a/css/colors.css +++ b/css/colors.css @@ -2,6 +2,10 @@ color: #fff; } +.black { + color: #000; +} + .red, .important, .critical-priority { diff --git a/css/mods-for-hesk-new.css b/css/mods-for-hesk-new.css index 1722347f..d5a386e5 100644 --- a/css/mods-for-hesk-new.css +++ b/css/mods-for-hesk-new.css @@ -290,4 +290,8 @@ div.ticket-info { .black { color: black; +} + +#toast-container > div { + opacity: 1; } \ No newline at end of file diff --git a/css/toastr.min.css b/css/toastr.min.css new file mode 100644 index 00000000..064afd07 --- /dev/null +++ b/css/toastr.min.css @@ -0,0 +1 @@ +.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#FFF}.toast-message a:hover{color:#CCC;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#FFF;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);line-height:1}.toast-close-button:focus,.toast-close-button:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}.rtl .toast-close-button{left:-.3em;float:left;right:.3em}button.toast-close-button{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999;pointer-events:none}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#FFF;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}#toast-container>div.rtl{direction:rtl;padding:15px 50px 15px 15px;background-position:right 15px center}#toast-container>div:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51A351}.toast-error{background-color:#BD362F}.toast-info{background-color:#2F96B4}.toast-warning{background-color:#F89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}} \ No newline at end of file diff --git a/inc/common.inc.php b/inc/common.inc.php index 920e0787..c4a7d237 100644 --- a/inc/common.inc.php +++ b/inc/common.inc.php @@ -2142,4 +2142,16 @@ function mfh_get_stars($rating) { } return $markup; +} + +function mfh_get_hidden_fields_for_language($keys) { + global $hesklang; + + $output = '
'; + foreach ($keys as $key) { + $output .= sprintf('

%s

', $key, $hesklang[$key]); + } + $output .= '
'; + + return $output; } \ No newline at end of file diff --git a/inc/header.inc.php b/inc/header.inc.php index ef257049..ebc825e4 100644 --- a/inc/header.inc.php +++ b/inc/header.inc.php @@ -325,7 +325,26 @@ if ($modsForHesk_settings['show_icons']) { >  - + +
  • + + + <?php echo $row['text']; ?>> + + > + + + +
  • + diff --git a/index.php b/index.php index 41fddc81..12165ed7 100644 --- a/index.php +++ b/index.php @@ -1391,7 +1391,38 @@ function print_start() - + +
    + +
    +
    +
    +
    + + <?php echo $row['text']; ?> + + + +
    +
    +
    + +
    +
    +
    +
    +
    +
    + ' + notFoundText + ''); + return; + } + + var currentPlace = 0; + var addedElementToPlace = false; + var first = true; + var lastElement = null; + $.each(data, function() { + if (this.place !== currentPlace) { + if (lastElement !== null) { + //-- Hide the down arrow on the last element + $('[data-value="' + lastElement.id + '"]').parent().parent() + .find('[data-direction="down"]').css('visibility', 'hidden'); + lastElement = null; + } + + $('#table-body').append('' + places[this.place] + ''); + currentPlace = this.place; + addedElementToPlace = false; + first = true; + } + + var $template = $($('#nav-element-template').html()); + + $template.find('span[data-property="id"]').text(this.id).attr('data-value', this.id); + if (this.imageUrl === null) { + $template.find('span[data-property="image-or-font"]').html(''); + } else { + $template.find('span[data-property="image-or-font"]').text(this.imageUrl); + } + + $template.find('span[data-property="url"]').text(this.url); + + var text = ''; + $.each(this.text, function(key, value) { + text += '
  • ' + escape(key) + ': ' + escape(value) + '
  • '; + }); + $template.find('ul[data-property="text"]').html(text); + + var subtext = '-'; + if (this.place === 1) { + subtext = ''; + $.each(this.subtext, function(key, value) { + subtext += '
  • ' + escape(key) + ': ' + escape(value) + '
  • '; + }); + } + $template.find('ul[data-property="subtext"]').html(subtext); + + if (first) { + $template.find('[data-direction="up"]').css('visibility', 'hidden'); + first = false; + } + + $tableBody.append($template); + + elements[this.id] = this; + + addedElementToPlace = true; + lastElement = this; + }); + + if (lastElement) { + //-- Hide the down arrow on the last element + $('[data-value="' + lastElement.id + '"]').parent().parent() + .find('[data-direction="down"]').css('visibility', 'hidden'); + } + }, + error: function(data) { + mfhAlert.errorWithLog(mfhLang.text('failed_to_load_custom_nav_elements'), data.responseJSON); + console.error(data); + }, + complete: function() { + $('#overlay').hide(); + } + }); +} + +function escape(str) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; +} + +function bindEditModal() { + $(document).on('click', '[data-action="edit"]', function() { + var element = elements[$(this).parent().parent().find('[data-property="id"]').text()]; + var $modal = $('#nav-element-modal'); + + $modal.find('#edit-label').show(); + $modal.find('#create-label').hide(); + $modal.find('select[name="place"]').val(element.place); + $modal.find('input[name="id"]').val(element.id); + $modal.find('input[name="url"]').val(element.url); + var $textLanguages = $modal.find('[data-text-language]'); + $.each($textLanguages, function() { + var language = $(this).data('text-language'); + + $(this).val(element.text[language]); + }); + + var $subtextLanguages = $modal.find('[data-subtext-language]'); + $.each($subtextLanguages, function() { + var language = $(this).data('subtext-language'); + + $(this).val(element.subtext[language]); + }); + + if (element.place === 1) { + $('#subtext').show(); + } else { + $('#subtext').hide(); + } + + if (element.imageUrl !== null) { + $modal.find('select[name="image-type"]').val('image-url'); + $modal.find('input[name="image-url"]').val(element.imageUrl); + $modal.find('#font-icon-group').hide(); + $modal.find('#image-url-group').show(); + } else { + $modal.find('select[name="image-type"]').val('font-icon'); + $('[data-toggle="nav-iconpicker"]').iconpicker('setIcon', element.fontIcon); + $modal.find('#font-icon-group').show(); + $modal.find('#image-url-group').hide(); + } + + + $modal.modal('show'); + }); +} + +function bindCreateModal() { + $('#create-button').click(function() { + var $modal = $('#nav-element-modal'); + $modal.find('#edit-label').hide(); + $modal.find('#create-label').show(); + $modal.find('select[name="place"]').val(1); + $modal.find('input[name="id"]').val(-1); + var $textLanguages = $modal.find('[data-text-language]'); + $.each($textLanguages, function() { + var language = $(this).data('text-language'); + + $(this).val(''); + }); + + var $subtextLanguages = $modal.find('[data-subtext-language]'); + $.each($subtextLanguages, function() { + var language = $(this).data('subtext-language'); + + $(this).val(''); + }); + + $('#subtext').show(); + + $modal.find('select[name="image-type"]').val('image-url'); + $modal.find('input[name="image-url"]').val(''); + $modal.find('#font-icon-group').hide(); + $modal.find('#image-url-group').show(); + $modal.find('input[name="url"]').val(''); + + $modal.modal('show'); + }); +} + +function bindDeleteButton() { + $(document).on('click', '[data-action="delete"]', function() { + $('#overlay').show(); + + var heskUrl = $('#heskUrl').text(); + var element = elements[$(this).parent().parent().find('[data-property="id"]').text()]; + + $.ajax({ + method: 'DELETE', + url: heskUrl + '/api/v1-internal/custom-navigation/' + element.id, + headers: { 'X-Internal-Call': true }, + success: function() { + mfhAlert.success(mfhLang.text('custom_nav_element_deleted')); + loadTable(); + }, + error: function(data) { + $('#overlay').hide(); + mfhAlert.errorWithLog(mfhLang.text('error_deleting_custom_nav_element'), data.responseJSON); + console.error(data); + } + }); + }); +} + +function bindSortButtons() { + $(document).on('click', '[data-action="sort"]', function() { + $('#overlay').show(); + var heskUrl = $('#heskUrl').text(); + var direction = $(this).data('direction'); + var element = elements[$(this).parent().parent().find('[data-property="id"]').text()]; + + $.ajax({ + method: 'POST', + url: heskUrl + '/api/v1-internal/custom-navigation/' + element.id + '/sort/' + direction, + headers: { 'X-Internal-Call': true }, + success: function() { + loadTable(); + }, + error: function(data) { + mfhAlert.errorWithLog(mfhLang.text('error_sorting_custom_nav_elements'), data.responseJSON); + console.error(data); + $('#overlay').hide(); + } + }) + }); +} \ No newline at end of file diff --git a/js/modsForHesk-javascript.js b/js/modsForHesk-javascript.js index 9e9e3e26..535ec778 100644 --- a/js/modsForHesk-javascript.js +++ b/js/modsForHesk-javascript.js @@ -84,7 +84,11 @@ var loadJquery = function() $('[data-hide]').click(function() { var hide = $(this).attr('data-hide'); $('#' + hide).hide(); - }) + }); + + //-- Initialize toastr properties + toastr.options.progressBar = true; + toastr.options.closeButton = true; }; function refreshBackgroundVolatileItems() { diff --git a/js/toastr.min.js b/js/toastr.min.js new file mode 100644 index 00000000..7c0c07c2 --- /dev/null +++ b/js/toastr.min.js @@ -0,0 +1,2 @@ +!function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return g({type:O.error,iconClass:m().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=m()),v=e("#"+t.containerId),v.length?v:(n&&(v=d(t)),v)}function o(e,t,n){return g({type:O.info,iconClass:m().iconClasses.info,message:e,optionsOverride:n,title:t})}function s(e){C=e}function i(e,t,n){return g({type:O.success,iconClass:m().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return g({type:O.warning,iconClass:m().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e,t){var o=m();v||n(o),u(e,o,t)||l(o)}function c(t){var o=m();return v||n(o),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function l(t){for(var n=v.children(),o=n.length-1;o>=0;o--)u(e(n[o]),t)}function u(t,n,o){var s=!(!o||!o.force)&&o.force;return!(!t||!s&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0)}function d(t){return v=e("
    ").attr("id",t.containerId).addClass(t.positionClass),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function f(e){C&&C(e)}function g(t){function o(e){return null==e&&(e=""),e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function s(){c(),u(),d(),p(),g(),C(),l(),i()}function i(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}I.attr("aria-live",e)}function a(){E.closeOnHover&&I.hover(H,D),!E.onclick&&E.tapToDismiss&&I.click(b),E.closeButton&&j&&j.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),E.onCloseClick&&E.onCloseClick(e),b(!0)}),E.onclick&&I.click(function(e){E.onclick(e),b()})}function r(){I.hide(),I[E.showMethod]({duration:E.showDuration,easing:E.showEasing,complete:E.onShown}),E.timeOut>0&&(k=setTimeout(b,E.timeOut),F.maxHideTime=parseFloat(E.timeOut),F.hideEta=(new Date).getTime()+F.maxHideTime,E.progressBar&&(F.intervalId=setInterval(x,10)))}function c(){t.iconClass&&I.addClass(E.toastClass).addClass(y)}function l(){E.newestOnTop?v.prepend(I):v.append(I)}function u(){if(t.title){var e=t.title;E.escapeHtml&&(e=o(t.title)),M.append(e).addClass(E.titleClass),I.append(M)}}function d(){if(t.message){var e=t.message;E.escapeHtml&&(e=o(t.message)),B.append(e).addClass(E.messageClass),I.append(B)}}function p(){E.closeButton&&(j.addClass(E.closeClass).attr("role","button"),I.prepend(j))}function g(){E.progressBar&&(q.addClass(E.progressClass),I.prepend(q))}function C(){E.rtl&&I.addClass("rtl")}function O(e,t){if(e.preventDuplicates){if(t.message===w)return!0;w=t.message}return!1}function b(t){var n=t&&E.closeMethod!==!1?E.closeMethod:E.hideMethod,o=t&&E.closeDuration!==!1?E.closeDuration:E.hideDuration,s=t&&E.closeEasing!==!1?E.closeEasing:E.hideEasing;if(!e(":focus",I).length||t)return clearTimeout(F.intervalId),I[n]({duration:o,easing:s,complete:function(){h(I),clearTimeout(k),E.onHidden&&"hidden"!==P.state&&E.onHidden(),P.state="hidden",P.endTime=new Date,f(P)}})}function D(){(E.timeOut>0||E.extendedTimeOut>0)&&(k=setTimeout(b,E.extendedTimeOut),F.maxHideTime=parseFloat(E.extendedTimeOut),F.hideEta=(new Date).getTime()+F.maxHideTime)}function H(){clearTimeout(k),F.hideEta=0,I.stop(!0,!0)[E.showMethod]({duration:E.showDuration,easing:E.showEasing})}function x(){var e=(F.hideEta-(new Date).getTime())/F.maxHideTime*100;q.width(e+"%")}var E=m(),y=t.iconClass||E.iconClass;if("undefined"!=typeof t.optionsOverride&&(E=e.extend(E,t.optionsOverride),y=t.optionsOverride.iconClass||y),!O(E,t)){T++,v=n(E,!0);var k=null,I=e("
    "),M=e("
    "),B=e("
    "),q=e("
    "),j=e(E.closeHtml),F={intervalId:null,hideEta:null,maxHideTime:null},P={toastId:T,state:"visible",startTime:new Date,options:E,map:t};return s(),r(),a(),f(P),E.debug&&console&&console.log(P),I}}function m(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),w=void 0))}var v,C,w,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:c,error:t,getContainer:n,info:o,options:{},subscribe:s,success:i,version:"2.1.3",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)}); +//# sourceMappingURL=toastr.js.map diff --git a/language/en/text.php b/language/en/text.php index 18aca4be..16fa082b 100644 --- a/language/en/text.php +++ b/language/en/text.php @@ -52,6 +52,34 @@ $hesklang['resend_email_notification'] = 'Re-send Email Notification'; $hesklang['email_notification_sent'] = 'Email notification sent!'; $hesklang['email_notification_resend_failed'] = 'Error occurred when trying to send notification email.'; $hesklang['edit_category'] = 'Edit Category'; +$hesklang['custom_nav_menu_elements'] = 'Custom Nav Menu Elements'; +$hesklang['create_new'] = 'Create New'; +$hesklang['custom_nav_element_deleted'] = 'Custom Navigation Element Deleted!'; +$hesklang['no_custom_nav_elements_found'] = 'No custom nav menu elements found'; +$hesklang['alert_success'] = 'Success'; // Used for alert messages +$hesklang['alert_error'] = 'Error'; // Used for alert messages +$hesklang['failed_to_load_custom_nav_elements'] = 'Failed to load custom nav elements!'; +$hesklang['custom_nav_element_deleted'] = 'Custom nav element deleted!'; +$hesklang['error_deleting_custom_nav_element'] = 'Error deleting custom nav element!'; +$hesklang['error_sorting_custom_nav_elements'] = 'Error sorting custom nav elements!'; +$hesklang['custom_nav_element_created'] = 'Custom nav element created!'; +$hesklang['custom_nav_element_saved'] = 'Custom nav element saved!'; +$hesklang['homepage_block'] = 'Homepage - Block'; +$hesklang['customer_navigation'] = 'Customer Navigation'; +$hesklang['staff_navigation'] = 'Staff Navigation'; +$hesklang['custom_nav_text'] = 'Text'; +$hesklang['custom_nav_subtext'] = 'Subtext'; +$hesklang['image_url_slash_font_icon'] = 'Image URL / Font Icon'; +$hesklang['edit_custom_nav_element_title_case'] = 'Edit Custom Nav Element'; +$hesklang['create_custom_nav_element_title_case'] = 'Create Custom Nav Element'; +$hesklang['place'] = 'Place'; +$hesklang['image_type'] = 'Image Type'; +$hesklang['image_url'] = 'Image URL'; +$hesklang['image_url_help'] = 'The URL of the image you wish to use. For customer/staff navigation, recommended size is 16x16px. For the homepage blocks, recommended size is 32x32px.'; +$hesklang['font_icon'] = 'Font Icon'; +$hesklang['error_saving_custom_nav_element'] = 'Error saving custom nav element!'; +$hesklang['place_help'] = 'The location of the custom navigation element.'; +$hesklang['url_help'] = 'The URL where the user should be taken to. Both relative and absolute URLs are supported.'; // ADDED OR MODIFIED IN Mods for HESK 3.0.0 $hesklang['you_have_x_messages'] = 'You have %s new %s'; // %s: Number of new messages, "message" or "messages", depending on #