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']) {
>
-
+
+
+
+
+ >
+
+ >
+
+
+
+
+
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()
-
+
+
+
' + 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 #