Merge branch '395-custom-blocks-to-database' into '3-1-0'

Move Custom Blocks/Nav Elements to Database

See merge request !60
inline-attachment-viewing
Mike Koch 7 years ago
commit 09c650d487

@ -0,0 +1,275 @@
<?php
define('IN_SCRIPT', 1);
define('HESK_PATH', '../');
define('PAGE_TITLE', 'ADMIN_CUSTOM_NAV_ELEMENTS');
define('MFH_PAGE_LAYOUT', 'TOP_ONLY');
define('EXTRA_JS', '<script src="'.HESK_PATH.'internal-api/js/manage-custom-nav-elements.js"></script>');
/* 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');
?>
<div class="content-wrapper">
<section class="content">
<div class="box">
<div class="box-header with-border">
<h1 class="box-title">
<?php echo $hesklang['custom_nav_menu_elements']; ?>
</h1>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse">
<i class="fa fa-minus"></i>
</button>
</div>
</div>
<div class="box-body">
<div class="row">
<div class="col-md-12 text-right">
<button id="create-button" class="btn btn-success">
<i class="fa fa-plus-circle"></i>&nbsp;
<?php echo $hesklang['create_new']; ?>
</button>
</div>
<div class="col-md-12">
<table class="table table-striped">
<thead>
<tr>
<th><?php echo $hesklang['id']; ?></th>
<th><?php echo $hesklang['custom_nav_text']; ?></th>
<th><?php echo $hesklang['custom_nav_subtext']; ?></th>
<th><?php echo $hesklang['image_url_slash_font_icon']; ?></th>
<th><?php echo $hesklang['url']; ?></th>
<th><?php echo $hesklang['actions']; ?></th>
</tr>
</thead>
<tbody id="table-body">
</tbody>
</table>
</div>
</div>
</div>
<div class="overlay" id="overlay">
<i class="fa fa-spinner fa-spin"></i>
</div>
</div>
</section>
</div>
<div class="modal fade" id="nav-element-modal" tabindex="-1" role="dialog" style="overflow: hidden">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header" style="cursor: move">
<button type="button" class="close cancel-callback" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="edit-label">
<?php echo $hesklang['edit_custom_nav_element_title_case']; ?>
</h4>
<h4 class="modal-title" id="create-label">
<?php echo $hesklang['create_custom_nav_element_title_case']; ?>
</h4>
</div>
<form id="manage-nav-element" class="form-horizontal" data-toggle="validator">
<input type="hidden" name="id">
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label for="place" class="col-md-4 col-sm-12 control-label">
<?php echo $hesklang['place']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="htmlpopover"
title="<?php echo $hesklang['place']; ?>"
data-content="<?php echo $hesklang['place_help']; ?>"></i>
</label>
<div class="col-md-8 col-sm-12">
<select name="place" id="place" class="form-control"
data-error="<?php echo htmlspecialchars($hesklang['this_field_is_required']); ?>"
required>
<option value="1"><?php echo $hesklang['homepage_block']; ?></option>
<option value="2"><?php echo $hesklang['customer_navigation']; ?></option>
<option value="3"><?php echo $hesklang['staff_navigation']; ?></option>
</select>
<div class="help-block with-errors"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 col-sm-12">
<h4><?php echo $hesklang['custom_nav_text']; ?></h4>
<?php foreach ($hesk_settings['languages'] as $language => $value): ?>
<div class="form-group">
<label for="text[<?php echo $language; ?>]" class="col-md-4 col-sm-12 control-label">
<?php echo $language; ?>
</label>
<div class="col-md-8 col-sm-12">
<input type="text" name="text" class="form-control"
data-text-language="<?php echo $language; ?>"
id="text[<?php echo $language; ?>" placeholder="<?php echo $hesklang['custom_nav_text']; ?>"
data-error="<?php echo htmlspecialchars($hesklang['this_field_is_required']); ?>"
required>
<div class="help-block with-errors"></div>
</div>
</div>
<?php endforeach; ?>
<div id="subtext">
<h4><?php echo $hesklang['custom_nav_subtext']; ?></h4>
<?php foreach ($hesk_settings['languages'] as $language => $value): ?>
<div class="form-group">
<label for="subtext[<?php echo $language; ?>]" class="col-md-4 col-sm-12 control-label">
<?php echo $language; ?>
</label>
<div class="col-md-8 col-sm-12">
<input type="text" name="subtext" class="form-control"
data-subtext-language="<?php echo $language; ?>"
id="subtext[<?php echo $language; ?>" placeholder="<?php echo $hesklang['custom_nav_subtext']; ?>"
data-error="<?php echo htmlspecialchars($hesklang['this_field_is_required']); ?>"
required>
<div class="help-block with-errors"></div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="col-md-6 col-sm-12">
<h4><?php echo $hesklang['url']; ?></h4>
<div class="form-group">
<label for="image-type" class="col-md-4 col-sm-12 control-label">
<?php echo $hesklang['url']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="htmlpopover"
title="<?php echo $hesklang['url']; ?>"
data-content="<?php echo $hesklang['url_help']; ?>"></i>
</label>
<div class="col-md-8 col-sm-12">
<input type="text" name="url" class="form-control"
data-error="<?php echo htmlspecialchars($hesklang['this_field_is_required']); ?>"
placeholder="<?php echo $hesklang['url']; ?>" required>
<div class="help-block with-errors"></div>
</div>
</div>
<h4><?php echo $hesklang['image']; ?></h4>
<div class="form-group">
<label for="image-type" class="col-md-4 col-sm-12 control-label"><?php echo $hesklang['image_type']; ?></label>
<div class="col-md-8 col-sm-12">
<select name="image-type" id="image-type" class="form-control"
data-error="<?php echo htmlspecialchars($hesklang['this_field_is_required']); ?>"
required>
<option value="image-url"><?php echo $hesklang['image_url']; ?></option>
<option value="font-icon"><?php echo $hesklang['font_icon']; ?></option>
</select>
<div class="help-block with-errors"></div>
</div>
</div>
<div class="form-group" id="image-url-group">
<label for="image-url" class="col-md-4 col-sm-12 control-label">
<?php echo $hesklang['image_url']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="htmlpopover"
title="<?php echo $hesklang['image_url']; ?>"
data-content="<?php echo $hesklang['image_url_help']; ?>"></i>
</label>
<div class="col-md-8 col-sm-12">
<input type="text" name="image-url" class="form-control"
data-error="<?php echo htmlspecialchars($hesklang['this_field_is_required']); ?>"
placeholder="<?php echo $hesklang['image_url']; ?>" required>
<div class="help-block with-errors"></div>
</div>
</div>
<div class="form-group" id="font-icon-group">
<p style="display:none" id="no-icon"><?php echo $hesklang['sm_no_icon']; ?></p>
<p style="display:none" id="search-icon"><?php echo $hesklang['sm_search_icon']; ?></p>
<p style="display:none"
id="footer-icon"><?php echo $hesklang['sm_iconpicker_footer_label']; ?></p>
<label for="font-icon" class="col-md-4 col-sm-12 control-label"><?php echo $hesklang['font_icon']; ?></label>
<div class="col-md-8 col-sm-12">
<div class="btn btn-default iconpicker-container" data-toggle="nav-iconpicker">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<div class="btn-group" id="action-buttons">
<button type="button" class="btn btn-default cancel-button" data-dismiss="modal">
<i class="fa fa-times-circle"></i>
<span><?php echo $hesklang['cancel']; ?></span>
</button>
<button type="submit" class="btn btn-success save-button">
<i class="fa fa-check-circle"></i>
<span><?php echo $hesklang['save']; ?></span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<?php
echo mfh_get_hidden_fields_for_language(
array(
'edit',
'delete',
'no_custom_nav_elements_found',
'failed_to_load_custom_nav_elements',
'custom_nav_element_deleted',
'error_deleting_custom_nav_element',
'error_sorting_custom_nav_elements',
'custom_nav_element_created',
'custom_nav_element_saved',
'homepage_block',
'customer_navigation',
'staff_navigation',
'error_saving_custom_nav_element',
)
);
?>
<script type="text/html" id="nav-element-template">
<tr>
<td><span data-property="id" data-value="x"></span></td>
<td><span>
<ul data-property="text" class="list-unstyled"></ul>
</span></td>
<td><span>
<ul data-property="subtext" class="list-unstyled"></ul>
</span></td>
<td><span data-property="image-or-font"></span></td>
<td><span data-property="url"></span></td>
<td>
<a href="#" data-action="sort"
data-direction="up">
<i class="fa fa-fw fa-arrow-up icon-link green"
data-toggle="tooltip" title="<?php echo $hesklang['move_up']; ?>"></i>
</a>
<a href="#" data-action="sort"
data-direction="down">
<i class="fa fa-fw fa-arrow-down icon-link green"
data-toggle="tooltip" title="<?php echo $hesklang['move_dn'] ?>"></i>
</a>
<a href="#" data-action="edit">
<i class="fa fa-fw fa-pencil icon-link orange"
data-toggle="tooltip" title="<?php echo $hesklang['edit']; ?>"></i>
</a>
<a href="#" data-action="delete">
<i class="fa fa-fw fa-times icon-link red"
data-toggle="tooltip" title="<?php echo $hesklang['delete']; ?>"></i>
</a>
</td>
</tr>
</script>
<?php
require_once(HESK_PATH . 'inc/footer.inc.php');

@ -9,6 +9,7 @@ use BusinessLogic\Emails\EmailSenderHelper;
use BusinessLogic\Emails\EmailTemplateParser;
use BusinessLogic\Emails\EmailTemplateRetriever;
use BusinessLogic\Emails\MailgunEmailSender;
use BusinessLogic\Navigation\CustomNavElementHandler;
use BusinessLogic\Security\BanRetriever;
use BusinessLogic\Security\UserContextBuilder;
use BusinessLogic\Security\UserToTicketChecker;
@ -30,6 +31,7 @@ use DataAccess\Files\FileDeleter;
use DataAccess\Files\FileReader;
use DataAccess\Files\FileWriter;
use DataAccess\Logging\LoggingGateway;
use DataAccess\Navigation\CustomNavElementGateway;
use DataAccess\Security\BanGateway;
use DataAccess\Security\UserGateway;
use DataAccess\Settings\ModsForHeskSettingsGateway;
@ -41,6 +43,9 @@ use DataAccess\Tickets\VerifiedEmailGateway;
class ApplicationContext {
public $get;
/**
* ApplicationContext constructor.
*/
function __construct() {
$this->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();

@ -0,0 +1,30 @@
<?php
namespace BusinessLogic\Navigation;
class CustomNavElement {
/* @var $id int*/
public $id;
/* @var $text string[] */
public $text;
/* @var $subtext string[]|null */
public $subtext;
/* @var $imageUrl string|null */
public $imageUrl;
/* @var $fontIcon string|null */
public $fontIcon;
/* @var $place int */
public $place;
/* @var $url string */
public $url;
/* @var $sort int */
public $sort;
}

@ -0,0 +1,70 @@
<?php
namespace BusinessLogic\Navigation;
// TODO Test!
use BusinessLogic\Exceptions\ApiFriendlyException;
use DataAccess\Navigation\CustomNavElementGateway;
class CustomNavElementHandler {
/* @var $customNavElementGateway CustomNavElementGateway */
private $customNavElementGateway;
function __construct($customNavElementGateway) {
$this->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);
}
}

@ -0,0 +1,10 @@
<?php
namespace BusinessLogic\Navigation;
class CustomNavElementPlace {
const HOMEPAGE_BLOCK = 1;
const CUSTOMER_NAVIGATION = 2;
const ADMIN_NAVIGATION = 3;
}

@ -0,0 +1,9 @@
<?php
namespace BusinessLogic\Navigation;
class Direction {
const UP = 'up';
const DOWN = 'down';
}

@ -61,7 +61,6 @@ class UserContext {
* @return UserContext the built user context
*/
static function fromDataRow($dataRow) {
var_dump($dataRow);
$userContext = new UserContext();
$userContext->id = intval($dataRow['id']);
$userContext->username = $dataRow['user'];

@ -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) !== '') {

@ -0,0 +1,101 @@
<?php
namespace Controllers\Navigation;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Helpers;
use BusinessLogic\Navigation\CustomNavElement;
use BusinessLogic\Navigation\CustomNavElementHandler;
use Controllers\InternalApiController;
use Controllers\JsonRetriever;
class CustomNavElementController extends InternalApiController {
static function getAll() {
global $applicationContext, $hesk_settings;
self::staticCheckForInternalUseOnly();
/* @var $handler CustomNavElementHandler */
$handler = $applicationContext->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;
}
}

@ -1,12 +1,13 @@
<?php
function print_error($title, $message, $response_code = 500) {
function print_error($title, $message, $logId = null, $response_code = 500) {
require_once(__DIR__ . '/output.php');
$error = array();
$error['type'] = 'ERROR';
$error['title'] = $title;
$error['message'] = $message;
$error['logId'] = $logId;
print output($error, $response_code);
return;

@ -0,0 +1,182 @@
<?php
namespace DataAccess\Navigation;
use BusinessLogic\Navigation\CustomNavElement;
use DataAccess\CommonDao;
class CustomNavElementGateway extends CommonDao {
function getAllCustomNavElements($heskSettings) {
$this->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();
}
}

@ -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'

@ -2,6 +2,10 @@
color: #fff;
}
.black {
color: #000;
}
.red,
.important,
.critical-priority {

@ -290,4 +290,8 @@ div.ticket-info {
.black {
color: black;
}
#toast-container > div {
opacity: 1;
}

File diff suppressed because one or more lines are too long

@ -2142,4 +2142,16 @@ function mfh_get_stars($rating) {
}
return $markup;
}
function mfh_get_hidden_fields_for_language($keys) {
global $hesklang;
$output = '<div class="hide">';
foreach ($keys as $key) {
$output .= sprintf('<p id="lang_%s">%s</p>', $key, $hesklang[$key]);
}
$output .= '</div>';
return $output;
}

@ -325,7 +325,26 @@ if ($modsForHesk_settings['show_icons']) {
<a href="<?php echo HESK_PATH; ?>calendar.php"><i class="fa fa-calendar" <?php echo $iconDisplay; ?>></i>&nbsp;<?php echo $hesklang['calendar_title_case']; ?></a>
</li>
<?php endif; ?>
<?php include('custom/header-custom.inc.php'); ?>
<?php
$customNavRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "custom_nav_element` AS `t1`
INNER JOIN `" . hesk_dbEscape($hesk_settings['db_pfix']) . "custom_nav_element_to_text` AS `t2`
ON `t1`.`id` = `t2`.`nav_element_id`
AND `t2`.`language` = '" . hesk_dbEscape($hesk_settings['language']) . "'
WHERE `t1`.`place` = 2");
while ($row = hesk_dbFetchAssoc($customNavRs)):
?>
<li>
<a href="<?php echo $row['url']; ?>">
<?php if ($row['image_url'] !== null): ?>
<img src="<?php echo $row['image_url']; ?>" alt="<?php echo $row['text']; ?>" <?php echo $iconDisplay; ?>>
<?php else: ?>
<i class="<?php echo $row['font_icon']; ?>" <?php echo $iconDisplay; ?>></i>
<?php endif; ?>
<?php echo $row['text']; ?>
</a>
</li>
<?php endwhile; ?>
</ul>
<?php if ($hesk_settings['can_sel_lang']) { ?>
<div class="navbar-form navbar-right" role="search" style="margin-right: 20px; min-width: 80px;">

@ -60,11 +60,12 @@ $modsForHesk_settings = mfh_getSettings();
<link rel="stylesheet" href="<?php echo HESK_PATH; ?>css/bootstrap-colorpicker.min.css?v=<?php echo MODS_FOR_HESK_BUILD; ?>">
<link rel="stylesheet" href="<?php echo HESK_PATH; ?>css/AdminLTE.min.css?v=<?php echo MODS_FOR_HESK_BUILD; ?>">
<link rel="stylesheet" href="<?php echo HESK_PATH; ?>css/skins/_all-skins.min.css?v=<?php echo MODS_FOR_HESK_BUILD; ?>">
<link rel="stylesheet" href="<?php echo HESK_PATH; ?>css/jquery.fancybox.min.css?v=<?php echo MODS_FOR_HESK_BUILD ?>">
<link rel="stylesheet" href="<?php echo HESK_PATH; ?>css/toastr.min.css?v=<?php echo MODS_FOR_HESK_BUILD ?>">
<link rel="stylesheet" href="<?php echo HESK_PATH; ?>css/mods-for-hesk-new.css?v=<?php echo MODS_FOR_HESK_BUILD; ?>">
<link rel="stylesheet" href="<?php echo HESK_PATH; ?>css/colors.css?v=<?php echo MODS_FOR_HESK_BUILD; ?>">
<link rel="stylesheet" href="<?php echo HESK_PATH; ?>css/positions.css?v=<?php echo MODS_FOR_HESK_BUILD; ?>">
<link rel="stylesheet" href="<?php echo HESK_PATH; ?>css/displays.css?v=<?php echo MODS_FOR_HESK_BUILD; ?>">
<link rel="stylesheet" href="<?php echo HESK_PATH; ?>css/jquery.fancybox.min.css?v=<? echo MODS_FOR_HESK_BUILD ?>">
<?php if (defined('USE_JQUERY_2')): ?>
<script src="<?php echo HESK_PATH; ?>js/jquery-2.2.4.min.js?v=<?php echo MODS_FOR_HESK_BUILD; ?>"></script>
<?php else: ?>
@ -89,6 +90,9 @@ $modsForHesk_settings = mfh_getSettings();
<script type="text/javascript" src="<?php echo HESK_PATH; ?>js/jquery.jgrowl.min.js?v=<?php echo MODS_FOR_HESK_BUILD; ?>"></script>
<script type="text/javascript" src="<?php echo HESK_PATH; ?>js/bootstrap-colorpicker.min.js?v=<?php echo MODS_FOR_HESK_BUILD; ?>"></script>
<script type="text/javascript" src="<?php echo HESK_PATH; ?>js/jquery.slimscroll.min.js?v=<?php echo MODS_FOR_HESK_BUILD; ?>"></script>
<script type="text/javascript" src="<?php echo HESK_PATH; ?>js/toastr.min.js?v=<?php echo MODS_FOR_HESK_BUILD ?>"></script>
<script type="text/javascript" src="<?php echo HESK_PATH; ?>internal-api/js/alerts.js?v=<?php echo MODS_FOR_HESK_BUILD ?>"></script>
<script type="text/javascript" src="<?php echo HESK_PATH; ?>internal-api/js/lang.js?v=<?php echo MODS_FOR_HESK_BUILD ?>"></script>
<script type="text/javascript" src="<?php echo HESK_PATH; ?>js/jquery.fancybox.min.js?v=<?php echo MODS_FOR_HESK_BUILD; ?>"></script>
<?php
if (defined('EXTRA_JS')) {
@ -249,7 +253,11 @@ if (defined('MFH_PAGE_LAYOUT') && MFH_PAGE_LAYOUT == 'TOP_ONLY') {
?>
<body onload="<?php echo $onload;
unset($onload); ?>" class="<?php echo $layout_tag ?> fixed js <?php echo $modsForHesk_settings['admin_color_scheme']; ?>">
<span style="display: none" id="heskUrl"><?php echo $hesk_settings['hesk_url']; ?></span>
<?php // GLOBAL JAVASCRIPT IDs ?>
<p style="display: none" id="heskUrl"><?php echo $hesk_settings['hesk_url']; ?></p>
<p style="display: none" id="lang_alert_success"><?php echo $hesklang['alert_success']; ?></p>
<p style="display: none" id="lang_alert_error"><?php echo $hesklang['alert_error']; ?></p>
<?php
include(HESK_PATH . 'header.txt');

@ -123,7 +123,26 @@ $mails = mfh_get_mail_headers_for_dropdown($_SESSION['id'], $hesk_settings, $hes
</li>
<?php
endif;
?>
$customNavRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "custom_nav_element` AS `t1`
INNER JOIN `" . hesk_dbEscape($hesk_settings['db_pfix']) . "custom_nav_element_to_text` AS `t2`
ON `t1`.`id` = `t2`.`nav_element_id`
AND `t2`.`language` = '" . hesk_dbEscape($hesk_settings['language']) . "'
WHERE `t1`.`place` = 3");
while ($row = hesk_dbFetchAssoc($customNavRs)):
?>
<li>
<a href="<?php echo $row['url']; ?>">
<?php if ($row['image_url'] !== null): ?>
<img src="<?php echo $row['image_url']; ?>" alt="<?php echo $row['text']; ?>" <?php echo $iconDisplay; ?>>
<?php else: ?>
<i class="<?php echo $row['font_icon']; ?>" <?php echo $iconDisplay; ?>></i>
<?php endif; ?>
<?php echo $row['text']; ?>
</a>
</li>
<?php endwhile; ?>
</ul>
</div>
<!-- Navbar Right Menu -->

@ -1391,7 +1391,38 @@ function print_start()
</div>
</a>
</div>
<?php endif; ?>
<?php endif;
$customNavRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "custom_nav_element` AS `t1`
INNER JOIN `" . hesk_dbEscape($hesk_settings['db_pfix']) . "custom_nav_element_to_text` AS `t2`
ON `t1`.`id` = `t2`.`nav_element_id`
AND `t2`.`language` = '" . hesk_dbEscape($hesk_settings['language']) . "'
WHERE `t1`.`place` = 1");
while ($row = hesk_dbFetchAssoc($customNavRs)):
?>
<div class="col-sm-6 col-xs-12">
<a href="<?php echo $row['url']; ?>" class="button-link">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-xs-1">
<?php if ($row['image_url'] !== null): ?>
<img src="<?php echo $row['image_url']; ?>" alt="<?php echo $row['text']; ?>">
<?php else: ?>
<i class="<?php echo $row['font_icon']; ?> black" style="font-size: 32px"></i>
<?php endif; ?>
</div>
<div class="col-xs-11">
<b><?php echo $row['text']; ?></b><br>
<?php echo $row['subtext']; ?>
</div>
</div>
</div>
</div>
</a>
</div>
<?php endwhile; ?>
</div>
<?php
if ($hesk_settings['kb_enable'])

@ -993,4 +993,19 @@ function execute310Scripts() {
hesk_dbConnect();
executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "logging` ADD COLUMN `stack_trace` TEXT");
executeQuery("CREATE TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "custom_nav_element`
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
image_url TEXT,
font_icon VARCHAR(200),
place INT NOT NULL,
url VARCHAR(500) NOT NULL,
sort INT NOT NULL)");
executeQuery("CREATE TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "custom_nav_element_to_text`
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
nav_element_id INT NOT NULL,
language VARCHAR(200) NOT NULL,
text VARCHAR(200) NOT NULL,
subtext VARCHAR(200))");
updateVersion('3.1.0');
}

@ -0,0 +1,31 @@
var mfhAlert = {
success: success,
error: error,
errorWithLog: errorWithLog
};
function success(message, title) {
if (title === undefined) {
title = $('#lang_alert_success').text();
}
toastr.success(message, title);
}
function error(message, title) {
if (title === undefined) {
title = $('#lang_alert_error').text();
}
toastr.error(message, title);
}
function errorWithLog(message, responseJSON, title) {
var displayMessage = message;
if (responseJSON !== undefined &&
responseJSON.logId !== undefined) {
displayMessage += ' (' + responseJSON.logId + ')';
}
mfhAlert.error(displayMessage, title);
}

@ -0,0 +1,5 @@
var mfhLang = {
text: function(key) {
return $('#lang_' + key).text();
}
};

@ -0,0 +1,350 @@
var elements = [];
$(document).ready(function() {
loadTable();
bindEditModal();
bindCreateModal();
bindDeleteButton();
bindSortButtons();
$('[data-toggle="nav-iconpicker"]').iconpicker({
iconset: ['fontawesome', 'octicon'],
selectedClass: "btn-warning",
labelNoIcon: $('#no-icon').text(),
searchText: $('#search-icon').text(),
labelFooter: $('#footer-icon').text(),
resetButton: false,
search: false,
icon: 'fa fa-adn'
});
$('select[name="place"]').change(function() {
var $subtextField = $('#subtext');
if (parseInt($(this).val()) === 1) {
$subtextField.show();
} else {
$subtextField.hide();
}
});
$('select[name="image-type"]').change(function() {
var $imageUrl = $('#image-url-group');
var $fontIcon = $('#font-icon-group');
if ($(this).val() === 'image-url') {
$imageUrl.show();
$fontIcon.hide();
} else {
$imageUrl.hide();
$fontIcon.show();
}
});
$('form#manage-nav-element').submit(function(e) {
e.preventDefault();
var heskUrl = $('#heskUrl').text();
var $modal = $('#nav-element-modal');
var place = parseInt($modal.find('select[name="place"]').val());
var $textLanguages = $modal.find('[data-text-language]');
var text = {};
$.each($textLanguages, function() {
text[$(this).data('text-language')] = $(this).val();
});
var subtext = {};
if (place === 1) {
var $subtextLanguages = $modal.find('[data-subtext-language]');
$.each($subtextLanguages, function() {
subtext[$(this).data('subtext-language')] = $(this).val();
});
}
var imageUrl = null;
var fontIcon = null;
if ($modal.find('select[name="image-type"]').val() === 'image-url') {
imageUrl = $modal.find('input[name="image-url"]').val();
} else {
fontIcon = $modal.find('.iconpicker').find('input[type="hidden"]').val();
}
var id = parseInt($modal.find('input[name="id"]').val());
var navUrl = $modal.find('input[name="url"]').val();
var data = {
place: place,
text: text,
subtext: subtext,
imageUrl: imageUrl,
fontIcon: fontIcon,
url: navUrl
};
var url = heskUrl + '/api/v1-internal/custom-navigation/';
var method = 'POST';
if (id !== -1) {
url += id;
method = 'PUT';
}
$modal.find('#action-buttons').find('.cancel-button').attr('disabled', 'disabled');
$modal.find('#action-buttons').find('.save-button').attr('disabled', 'disabled');
$.ajax({
method: method,
url: url,
headers: { 'X-Internal-Call': true },
data: JSON.stringify(data),
success: function(data) {
if (id === -1) {
mfhAlert.success(mfhLang.text('custom_nav_element_created'));
} else {
mfhAlert.success(mfhLang.text('custom_nav_element_saved'));
}
$modal.modal('hide');
loadTable();
},
error: function(data) {
mfhAlert.errorWithLog(mfhLang.text('error_saving_custom_nav_element'), data.responseJSON);
console.error(data);
},
complete: function() {
$modal.find('#action-buttons').find('.cancel-button').removeAttr('disabled');
$modal.find('#action-buttons').find('.save-button').removeAttr('disabled');
}
});
});
});
function loadTable() {
$('#overlay').show();
var heskUrl = $('#heskUrl').text();
var notFoundText = mfhLang.text('no_custom_nav_elements_found');
var places = [];
var $tableBody = $('#table-body');
places[1] = mfhLang.text('homepage_block');
places[2] = mfhLang.text('customer_navigation');
places[3] = mfhLang.text('staff_navigation');
$.ajax({
method: 'GET',
url: heskUrl + '/api/v1-internal/custom-navigation/all',
headers: { 'X-Internal-Call': true },
success: function(data) {
$tableBody.html('');
elements = [];
if (data.length === 0) {
$('#table-body').append('<tr><td colspan="6">' + notFoundText + '</td></tr>');
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('<tr><td colspan="6" class="bg-gray"><i><b>' + places[this.place] + '</b></i></td></tr>');
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('<i class="' + escape(this.fontIcon) + '"></i>');
} 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 += '<li><b>' + escape(key) + ':</b> ' + escape(value) + '</li>';
});
$template.find('ul[data-property="text"]').html(text);
var subtext = '-';
if (this.place === 1) {
subtext = '';
$.each(this.subtext, function(key, value) {
subtext += '<li><b>' + escape(key) + ':</b> ' + escape(value) + '</li>';
});
}
$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();
}
})
});
}

@ -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() {

2
js/toastr.min.js vendored

File diff suppressed because one or more lines are too long

@ -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 <b>16x16px</b>. For the homepage blocks, recommended size is <b>32x32px</b>.';
$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 #

Loading…
Cancel
Save