Merge branch '3-3-0' into '357-calendar-planning-phase-two'

# Conflicts:
#   api/BusinessLogic/Security/UserContext.php
#   api/BusinessLogic/Security/UserPrivilege.php
#   api/index.php
#   language/en/text.php
master
Mike Koch 6 years ago
commit f53065f7f6

@ -54,7 +54,15 @@ else {
?>
<div class="content-wrapper">
<section class="content">
<?php hesk_handle_messages(); ?>
<?php
// Service messages
$service_messages = mfh_get_service_messages('STAFF_HOME');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
hesk_handle_messages();
?>
<div class="box">
<div class="box-header with-border">
<h1 class="box-title">

@ -939,6 +939,11 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
/* This will handle error, success and notice messages */
hesk_handle_messages();
$service_messages = mfh_get_service_messages('STAFF_VIEW_TICKET');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
// Prepare special custom fields
foreach ($hesk_settings['custom_fields'] as $k=>$v) {
if ($v['use'] && hesk_is_custom_field_in_category($k, $ticket['category']) ) {

@ -278,6 +278,12 @@ function print_login()
<?php
/* This will handle error, success and notice messages */
hesk_handle_messages();
// Service messages
$service_messages = mfh_get_service_messages('STAFF_LOGIN');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
?>
</div>
<div class="login-logo">

@ -121,7 +121,9 @@ function hesk_kb_header($kb_link, $catid=1)
</ol>
<?php
show_subnav('view', $catid);
echo '<div style="margin-left:40px;margin-right:40px">';
hesk_kbSearchLarge(1);
echo '</div>';
} // END hesk_kb_header()
@ -214,7 +216,15 @@ function hesk_show_kb_article($artid)
?>
<div class="content-wrapper">
<?php hesk_kb_header($hesk_settings['kb_link'], $article['catid']); ?>
<?php
hesk_kb_header($hesk_settings['kb_link'], $article['catid']);
echo '<div style="margin-left:40px;margin-right:40px">';
$service_messages = mfh_get_service_messages('STAFF_VIEW_KB_ARTICLE');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
echo '</div>';
?>
<section class="content">
<div class="box">
<div class="box-header with-border">
@ -397,6 +407,13 @@ function hesk_show_kb_category($catid, $is_search = 0) {
{
/* Print header */
hesk_kb_header($hesk_settings['kb_link'], $catid);
echo '<div style="margin-left:40px;margin-right:40px">';
$service_messages = mfh_get_service_messages('STAFF_KB_HOME');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
echo '</div>';
} ?>
<section class="content">
<?php if ($thiscat['parent']): ?>

@ -270,6 +270,12 @@ if (!isset($_SESSION['hide']['treemenu']))
<?php
show_subnav();
// Service messages
$service_messages = mfh_get_service_messages('STAFF_KB_HOME');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
// Show a notice if total public articles is less than 5
if ($total_articles < 5)
{

@ -157,6 +157,11 @@ $show_quick_help = $show['show'];
/* This will handle error, success and notice messages */
hesk_handle_messages();
$service_messages = mfh_get_service_messages('STAFF_SUBMIT_TICKET');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
if ($show_quick_help): ?>
<div class="box">
<div class="box-header with-border">

File diff suppressed because it is too large Load Diff

@ -4,8 +4,8 @@ namespace BusinessLogic\Exceptions;
class MissingAuthenticationTokenException extends ApiFriendlyException {
function __construct() {
parent::__construct("An 'X-Auth-Token' is required for all requests",
parent::__construct("An 'X-Auth-Token' is required for this request",
'Security Exception',
400);
401);
}
}

@ -58,7 +58,7 @@ class UserContext extends \BaseClass {
public $active;
function isAnonymousUser() {
return $this->username === "API - ANONYMOUS USER";
return $this->id === -1;
}
static function buildAnonymousUser() {

@ -17,4 +17,5 @@ class UserPrivilege extends \BaseClass {
const CAN_MANAGE_CATEGORIES = 'can_man_cat';
const CAN_VIEW_ASSIGNED_TO_OTHER = 'can_view_ass_others';
const CAN_VIEW_UNASSIGNED = 'can_view_unassigned';
const CAN_MANAGE_SERVICE_MESSAGES = 'can_service_msg';
}

@ -0,0 +1,12 @@
<?php
namespace BusinessLogic\ServiceMessages;
class GetServiceMessagesFilter {
/* @var $includeStaffServiceMessages bool */
public $includeStaffServiceMessages = true;
/* @var $includeDrafts bool */
public $includeDrafts = true;
}

@ -0,0 +1,39 @@
<?php
namespace BusinessLogic\ServiceMessages;
class ServiceMessage extends \BaseClass {
/* @var $id int */
public $id;
/* @var $dateCreated string */
public $dateCreated;
/* @var $createdBy int */
public $createdBy;
/* @var $title string */
public $title;
/* @var $message string */
public $message;
/* @var $style string */
public $style;
/* @var $published bool */
public $published;
/* @var $order int */
public $order;
/* @var $icon string */
public $icon;
/* @var $locations string[] */
public $locations;
/* @var $language string */
public $language;
}

@ -0,0 +1,164 @@
<?php
namespace BusinessLogic\ServiceMessages;
// TODO Test
use BusinessLogic\Exceptions\ValidationException;
use BusinessLogic\Navigation\Direction;
use BusinessLogic\ValidationModel;
use DataAccess\ServiceMessages\ServiceMessagesGateway;
class ServiceMessageHandler extends \BaseClass {
/* @var $serviceMessageGateway ServiceMessagesGateway */
private $serviceMessageGateway;
function __construct(ServiceMessagesGateway $serviceMessagesGateway) {
$this->serviceMessageGateway = $serviceMessagesGateway;
}
function createServiceMessage($serviceMessage, $heskSettings) {
$this->validate($serviceMessage, $heskSettings);
if ($serviceMessage->icon === null) {
switch ($serviceMessage->style) {
case ServiceMessageStyle::NONE:
$serviceMessage->icon = '';
break;
case ServiceMessageStyle::INFO:
$serviceMessage->icon = 'fa fa-comment';
break;
case ServiceMessageStyle::NOTICE:
$serviceMessage->icon = 'fa fa-exclamation-triangle';
break;
case ServiceMessageStyle::ERROR:
$serviceMessage->icon = 'fa fa-times-circle';
break;
case ServiceMessageStyle::SUCCESS:
$serviceMessage->icon = 'fa fa-check-circle';
break;
}
}
return $this->serviceMessageGateway->createServiceMessage($serviceMessage, $heskSettings);
}
function getServiceMessages($heskSettings, $searchFilter) {
return $this->serviceMessageGateway->getServiceMessages($heskSettings, $searchFilter);
}
function editServiceMessage($serviceMessage, $heskSettings) {
$this->validate($serviceMessage, $heskSettings, false);
if ($serviceMessage->icon === null) {
switch ($serviceMessage->style) {
case ServiceMessageStyle::NONE:
$serviceMessage->icon = '';
break;
case ServiceMessageStyle::INFO:
$serviceMessage->icon = 'fa fa-comment';
break;
case ServiceMessageStyle::NOTICE:
$serviceMessage->icon = 'fa fa-exclamation-triangle';
break;
case ServiceMessageStyle::ERROR:
$serviceMessage->icon = 'fa fa-times-circle';
break;
case ServiceMessageStyle::SUCCESS:
$serviceMessage->icon = 'fa fa-check-circle';
break;
}
}
return $this->serviceMessageGateway->updateServiceMessage($serviceMessage, $heskSettings);
}
function deleteServiceMessage($id, $heskSettings) {
$this->serviceMessageGateway->deleteServiceMessage($id, $heskSettings);
}
function sortServiceMessage($id, $direction, $heskSettings) {
$serviceMessages = $this->serviceMessageGateway->getServiceMessages($heskSettings, new GetServiceMessagesFilter());
$serviceMessage = null;
foreach ($serviceMessages as $innerServiceMessage) {
if (intval($innerServiceMessage->id) === intval($id)) {
$serviceMessage = $innerServiceMessage;
break;
}
}
if ($serviceMessage === null) {
throw new \BaseException("Could not find service message with ID {$id}!");
}
if ($direction === Direction::UP) {
$serviceMessage->order -= 15;
} else {
$serviceMessage->order += 15;
}
$this->serviceMessageGateway->updateServiceMessage($serviceMessage, $heskSettings);
$this->serviceMessageGateway->resortAllServiceMessages($heskSettings);
}
/**
* @param $serviceMessage ServiceMessage
* @param bool $isNew
* @throws ValidationException
*/
private function validate($serviceMessage, $heskSettings, $isNew = true) {
$validationModel = new ValidationModel();
if ($isNew && $serviceMessage->createdBy < 1) {
$validationModel->errorKeys[] = 'MISSING_CREATOR';
}
if ($serviceMessage->message === null || trim($serviceMessage->message) === '') {
$validationModel->errorKeys[] = 'MISSING_MESSAGE';
} else {
$htmlPurifier = new \HeskHTMLPurifier($heskSettings['cache_dir']);
$serviceMessage->message = $htmlPurifier->heskPurify($serviceMessage->message);
}
if ($serviceMessage->language === null || trim($serviceMessage->language) === '') {
$validationModel->errorKeys[] = 'MISSING_LANGUAGE';
}
$languageFound = false;
foreach ($heskSettings['languages'] as $key => $value) {
if ($value['folder'] === $serviceMessage->language || $serviceMessage->language === 'ALL') {
$languageFound = true;
break;
}
}
if (!$languageFound && !in_array('MISSING_LANGUAGE', $validationModel->errorKeys)) {
$validationModel->errorKeys[] = 'LANGUAGE_NOT_INSTALLED';
}
if ($serviceMessage->title === null || trim($serviceMessage->title) === '') {
$validationModel->errorKeys[] = 'MISSING_TITLE';
}
if ($serviceMessage->style === null || trim($serviceMessage->style) === '') {
$validationModel->errorKeys[] = 'MISSING_STYLE';
}
try {
ServiceMessageStyle::getIdForStyle($serviceMessage->style);
} catch (\Exception $e) {
$validationModel->errorKeys[] = 'INVALID_STYLE';
}
if ($serviceMessage->locations === null || count($serviceMessage->locations) === 0) {
$validationModel->errorKeys[] = 'MISSING_LOCATIONS';
} else {
$locations = ServiceMessageLocation::getAll();
foreach ($serviceMessage->locations as $location) {
if (!in_array($location, $locations)) {
$validationModel->errorKeys[] = 'INVALID_LOCATION';
break;
}
}
}
if (count($validationModel->errorKeys) > 0) {
// Validation failed
throw new ValidationException($validationModel);
}
}
}

@ -0,0 +1,34 @@
<?php
namespace BusinessLogic\ServiceMessages;
class ServiceMessageLocation {
const CUSTOMER_HOME = 'CUSTOMER_HOME';
const CUSTOMER_KB_HOME = 'CUSTOMER_KB_HOME';
const CUSTOMER_VIEW_KB_ARTICLE = 'CUSTOMER_VIEW_KB_ARTICLE';
const CUSTOMER_SUBMIT_TICKET = 'CUSTOMER_SUBMIT_TICKET';
const CUSTOMER_VIEW_TICKET = 'CUSTOMER_VIEW_TICKET';
const STAFF_LOGIN = 'STAFF_LOGIN';
const STAFF_HOME = 'STAFF_HOME';
const STAFF_KB_HOME = 'STAFF_KB_HOME';
const STAFF_VIEW_KB_ARTICLE = 'STAFF_VIEW_KB_ARTICLE';
const STAFF_SUBMIT_TICKET = 'STAFF_SUBMIT_TICKET';
const STAFF_VIEW_TICKET = 'STAFF_VIEW_TICKET';
static function getAll() {
return array(
self::CUSTOMER_HOME,
self::CUSTOMER_KB_HOME,
self::CUSTOMER_VIEW_KB_ARTICLE,
self::CUSTOMER_SUBMIT_TICKET,
self::CUSTOMER_VIEW_TICKET,
self::STAFF_LOGIN,
self::STAFF_HOME,
self::STAFF_KB_HOME,
self::STAFF_VIEW_KB_ARTICLE,
self::STAFF_SUBMIT_TICKET,
self::STAFF_VIEW_TICKET,
);
}
}

@ -0,0 +1,44 @@
<?php
namespace BusinessLogic\ServiceMessages;
class ServiceMessageStyle {
const NONE = 'NONE'; // 0
const SUCCESS = 'SUCCESS'; // 1
const INFO = 'INFO'; // 2
const NOTICE = 'NOTICE'; // 3
const ERROR = 'ERROR'; // 4
static function getStyleById($id) {
$styles = array(
0 => self::NONE,
1 => self::SUCCESS,
2 => self::INFO,
3 => self::NOTICE,
4 => self::ERROR
);
if (!isset($styles[$id])) {
throw new \Exception("Style {$id} is not a valid service message style.");
}
return $styles[$id];
}
static function getIdForStyle($style) {
$styles = array(
self::NONE => 0,
self::SUCCESS => 1,
self::INFO => 2,
self::NOTICE => 3,
self::ERROR => 4
);
if (!isset($styles[$style])) {
throw new \Exception("Style {$style} is not a valid service message style.");
}
return $styles[$style];
}
}

@ -0,0 +1,139 @@
<?php
namespace Controllers\ServiceMessages;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Helpers;
use BusinessLogic\Security\UserContext;
use BusinessLogic\Security\UserPrivilege;
use BusinessLogic\ServiceMessages\GetServiceMessagesFilter;
use BusinessLogic\ServiceMessages\ServiceMessage;
use BusinessLogic\ServiceMessages\ServiceMessageHandler;
use Controllers\ControllerWithSecurity;
use Controllers\JsonRetriever;
class ServiceMessagesController extends \BaseClass {
/**
* @param $userContext UserContext
* @throws ApiFriendlyException
*/
function checkSecurity($userContext) {
if (!$userContext->admin && !in_array(UserPrivilege::CAN_MANAGE_SERVICE_MESSAGES, $userContext->permissions)) {
throw new ApiFriendlyException("User does not have permission to access the following URI: " . $_SERVER['REQUEST_URI'], "Access Forbidden", 403);
}
}
static function staticCheckSecurity($userContext) {
if (!$userContext->admin && !in_array(UserPrivilege::CAN_MANAGE_SERVICE_MESSAGES, $userContext->permissions)) {
throw new ApiFriendlyException("User does not have permission to access the following URI: " . $_SERVER['REQUEST_URI'], "Access Forbidden", 403);
}
}
function get() {
/* @var $userContext UserContext */
/* @var $hesk_settings array */
global $applicationContext, $hesk_settings, $userContext;
$searchFilter = new GetServiceMessagesFilter();
if ($userContext->isAnonymousUser()) {
$searchFilter->includeDrafts = false;
$searchFilter->includeStaffServiceMessages = false;
} elseif (!$userContext->admin && !in_array(UserPrivilege::CAN_MANAGE_SERVICE_MESSAGES, $userContext->permissions)) {
$searchFilter->includeDrafts = false;
}
/* @var $handler ServiceMessageHandler */
$handler = $applicationContext->get(ServiceMessageHandler::clazz());
return output($handler->getServiceMessages($hesk_settings, $searchFilter));
}
function post() {
global $applicationContext, $userContext, $hesk_settings;
$this->checkSecurity($userContext);
/* @var $handler ServiceMessageHandler */
$handler = $applicationContext->get(ServiceMessageHandler::clazz());
$data = JsonRetriever::getJsonData();
$element = $handler->createServiceMessage($this->buildElementModel($data, $userContext), $hesk_settings);
return output($element, 201);
}
function put($id) {
global $applicationContext, $hesk_settings, $userContext;
$this->checkSecurity($userContext);
/* @var $handler ServiceMessageHandler */
$handler = $applicationContext->get(ServiceMessageHandler::clazz());
$data = JsonRetriever::getJsonData();
$serviceMessage = $this->buildElementModel($data, null, false);
$serviceMessage->id = $id;
$element = $handler->editServiceMessage($serviceMessage, $hesk_settings);
return output($element);
}
function delete($id) {
global $applicationContext, $hesk_settings, $userContext;
$this->checkSecurity($userContext);
/* @var $handler ServiceMessageHandler */
$handler = $applicationContext->get(ServiceMessageHandler::clazz());
$handler->deleteServiceMessage($id, $hesk_settings);
return http_response_code(204);
}
/**
* @param $data array
* @param $userContext UserContext
* @return ServiceMessage
*/
private function buildElementModel($data, $userContext, $creating = true) {
$serviceMessage = new ServiceMessage();
if (!$creating) {
$serviceMessage->order = Helpers::safeArrayGet($data, 'order');
}
if ($creating) {
$serviceMessage->createdBy = $userContext->id;
}
$serviceMessage->title = Helpers::safeArrayGet($data, 'title');
$serviceMessage->icon = Helpers::safeArrayGet($data, 'icon');
$serviceMessage->message = Helpers::safeArrayGet($data, 'message');
$serviceMessage->published = Helpers::safeArrayGet($data, 'published');
$serviceMessage->style = Helpers::safeArrayGet($data, 'style');
$serviceMessage->language = Helpers::safeArrayGet($data, 'language');
$jsonLocations = Helpers::safeArrayGet($data, 'locations');
if ($jsonLocations !== null && !empty($jsonLocations)) {
foreach ($jsonLocations as $key => $value) {
$serviceMessage->locations[] = $value;
}
}
return $serviceMessage;
}
static function sort($id, $direction) {
/* @var $userContext UserContext */
global $applicationContext, $hesk_settings, $userContext;
self::staticCheckSecurity($userContext);
/* @var $handler ServiceMessageHandler */
$handler = $applicationContext->get(ServiceMessageHandler::clazz());
$handler->sortServiceMessage(intval($id), $direction, $hesk_settings);
}
}

@ -7,7 +7,11 @@ function print_error($title, $message, $logId = null, $response_code = 500) {
$error['type'] = 'ERROR';
$error['title'] = $title;
$error['message'] = $message;
$error['logId'] = $logId;
if ($logId !== null) {
$error['logId'] = $logId;
}
print output($error, $response_code);
return;

@ -0,0 +1,182 @@
<?php
namespace DataAccess\ServiceMessages;
use BusinessLogic\ServiceMessages\GetServiceMessagesFilter;
use BusinessLogic\ServiceMessages\ServiceMessage;
use BusinessLogic\ServiceMessages\ServiceMessageLocation;
use BusinessLogic\ServiceMessages\ServiceMessageStyle;
use DataAccess\CommonDao;
class ServiceMessagesGateway extends CommonDao {
/**
* @param $serviceMessage ServiceMessage
* @return ServiceMessage
*/
function createServiceMessage($serviceMessage, $heskSettings) {
$this->init();
// Get the latest service message order
$res = hesk_dbQuery("SELECT `order` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "service_messages` ORDER BY `order` DESC LIMIT 1");
$row = hesk_dbFetchRow($res);
$myOrder = intval($row[0]) + 10;
$style = ServiceMessageStyle::getIdForStyle($serviceMessage->style);
$type = $serviceMessage->published ? 0 : 1;
// Insert service message into database
hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "service_messages` (`author`,`title`,`message`,`style`,`type`,`order`, `icon`, `mfh_language`) VALUES (
'" . intval($serviceMessage->createdBy) . "',
'" . hesk_dbEscape($serviceMessage->title) . "',
'" . hesk_dbEscape($serviceMessage->message) . "',
'" . hesk_dbEscape($style) . "',
'{$type}',
'{$myOrder}',
'" . hesk_dbEscape($serviceMessage->icon) . "',
'" . hesk_dbEscape($serviceMessage->language) . "'
)");
$serviceMessage->id = hesk_dbInsertID();
foreach ($serviceMessage->locations as $location) {
hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "mfh_service_message_to_location`
(`service_message_id`, `location`) VALUES (" . intval($serviceMessage->id) . ", '" . hesk_dbEscape($location) . "')");
}
// Get the autogenerated fields
$rs = hesk_dbQuery("SELECT `dt`, `order` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "service_messages`
WHERE `id` = " . intval($serviceMessage->id));
$row = hesk_dbFetchAssoc($rs);
$serviceMessage->dateCreated = $row['dt'];
$serviceMessage->order = intval($row['order']);
$this->close();
return $serviceMessage;
}
/**
* @param $heskSettings
* @param $searchFilter GetServiceMessagesFilter
* @return ServiceMessage[]
*/
function getServiceMessages($heskSettings, $searchFilter) {
$this->init();
$serviceMessages = array();
$sql = "SELECT DISTINCT `service_messages`.* FROM `". hesk_dbEscape($heskSettings['db_pfix']) . "service_messages` AS `service_messages` ";
if (!$searchFilter->includeStaffServiceMessages) {
$sql .= "INNER JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "mfh_service_message_to_location` AS `location`
ON `location`.`service_message_id` = `service_messages`.`id` AND `location`.`location` LIKE 'CUSTOMER%' ";
}
if (!$searchFilter->includeDrafts) {
$sql .= "WHERE `type` = '0' ";
}
$sql .= "ORDER BY `order`";
$rs = hesk_dbQuery($sql);
while ($row = hesk_dbFetchAssoc($rs)) {
$serviceMessage = new ServiceMessage();
$serviceMessage->id = $row['id'];
$serviceMessage->published = intval($row['type']) !== 1;
$serviceMessage->createdBy = intval($row['author']);
$serviceMessage->order = intval($row['order']);
$serviceMessage->dateCreated = $row['dt'];
$serviceMessage->title = $row['title'];
$serviceMessage->message = $row['message'];
$serviceMessage->style = ServiceMessageStyle::getStyleById($row['style']);
$serviceMessage->icon = $row['icon'];
$serviceMessage->language = $row['mfh_language'];
$serviceMessage->locations = array();
$locationSql = "SELECT `location` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "mfh_service_message_to_location`
WHERE `service_message_id` = " . intval($serviceMessage->id);
if (!$searchFilter->includeStaffServiceMessages) {
$locationSql .= " AND `location` LIKE 'CUSTOMER%'";
}
$locationsRs = hesk_dbQuery($locationSql);
while ($innerRow = hesk_dbFetchAssoc($locationsRs)) {
$serviceMessage->locations[] = $innerRow['location'];
}
$serviceMessages[] = $serviceMessage;
}
$this->close();
return $serviceMessages;
}
function updateServiceMessage($serviceMessage, $heskSettings) {
$this->init();
$style = ServiceMessageStyle::getIdForStyle($serviceMessage->style);
$type = $serviceMessage->published ? 0 : 1;
hesk_dbQuery("UPDATE `" . hesk_dbEscape($heskSettings['db_pfix']) . "service_messages`
SET `title` = '" . hesk_dbEscape($serviceMessage->title) . "',
`message` = '" . hesk_dbEscape($serviceMessage->message) . "',
`style` = '" . intval($style) . "',
`type` = '{$type}',
`icon` = '" . hesk_dbEscape($serviceMessage->icon) . "',
`order` = " . intval($serviceMessage->order) . ",
`mfh_language` = '" . hesk_dbEscape($serviceMessage->language) . "'
WHERE `id` = " . intval($serviceMessage->id));
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "mfh_service_message_to_location`
WHERE `service_message_id` = " . intval($serviceMessage->id));
foreach ($serviceMessage->locations as $location) {
hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "mfh_service_message_to_location`
(`service_message_id`, `location`) VALUES (" . intval($serviceMessage->id) . ", '" . hesk_dbEscape($location) . "')");
}
$otherFieldsRs = hesk_dbQuery("SELECT `dt`, `author`, `order` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "service_messages`
WHERE `id` = " . intval($serviceMessage->id));
$otherFields = hesk_dbFetchAssoc($otherFieldsRs);
$serviceMessage->createdBy = intval($otherFields['author']);
$serviceMessage->dateCreated = $otherFields['dt'];
$this->close();
return $serviceMessage;
}
function deleteServiceMessage($id, $heskSettings) {
$this->init();
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "mfh_service_message_to_location`
WHERE `service_message_id` = " . intval($id));
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "service_messages`
WHERE `id` = " . intval($id));
$this->close();
}
function resortAllServiceMessages($heskSettings) {
$this->init();
$rs = hesk_dbQuery("SELECT `id` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "service_messages`
ORDER BY `order` ASC");
$sortValue = 10;
while ($row = hesk_dbFetchAssoc($rs)) {
hesk_dbQuery("UPDATE `" . hesk_dbEscape($heskSettings['db_pfix']) . "service_messages`
SET `order` = " . intval($sortValue) . "
WHERE `id` = " . intval($row['id']));
$sortValue += 10;
}
$this->close();
}
}

@ -12,6 +12,7 @@ require_once(__DIR__ . '/Core/output.php');
require_once(__DIR__ . '/../hesk_settings.inc.php');
require_once(__DIR__ . '/http_response_code.php');
require_once(__DIR__ . '/../inc/admin_functions.inc.php');
require_once(__DIR__ . '/../inc/htmlpurifier/HeskHTMLPurifier.php');
hesk_load_api_database_functions();

@ -45,8 +45,15 @@ function internalOrAuthHandler() {
function publicHandler() {
global $userContext;
//-- Create an "anonymous" UserContext
$userContext = \BusinessLogic\Security\UserContext::buildAnonymousUser();
// Check if we passed in a X-Auth-Token or X-Internal-Call header. Those take priority
if (\BusinessLogic\Helpers::getHeader('X-INTERNAL-CALL') === 'true') {
internalHandler();
} elseif (\BusinessLogic\Helpers::getHeader('X-AUTH-TOKEN') !== null) {
authTokenHandler();
} else {
//-- Create an "anonymous" UserContext
$userContext = \BusinessLogic\Security\UserContext::buildAnonymousUser();
}
}
function assertApiIsEnabled() {
@ -105,7 +112,7 @@ function exceptionHandler($exception) {
/* @var $castedException \BusinessLogic\Exceptions\ApiFriendlyException */
$castedException = $exception;
print_error($castedException->title, $castedException->getMessage(), $castedException->httpResponseCode);
print_error($castedException->title, $castedException->getMessage(), null, $castedException->httpResponseCode);
} elseif (exceptionIsOfType($exception, \Core\Exceptions\SQLException::clazz())) {
/* @var $castedException \Core\Exceptions\SQLException */
$castedException = $exception;
@ -207,6 +214,16 @@ Link::all(array(
'/v1/calendar/events' => action(\Controllers\Calendar\CalendarController::clazz(), array(RequestMethod::GET), SecurityHandler::OPEN),
'/v1/calendar/events/staff' => action(\Controllers\Calendar\CalendarController::clazz(), array(RequestMethod::GET, RequestMethod::POST), SecurityHandler::INTERNAL_OR_AUTH_TOKEN),
'/v1/calendar/events/staff/{i}' => action(\Controllers\Calendar\CalendarController::clazz(), array(RequestMethod::PUT, RequestMethod::DELETE), SecurityHandler::INTERNAL_OR_AUTH_TOKEN),
// Service Messages
'/v1/service-messages' => action(\Controllers\ServiceMessages\ServiceMessagesController::clazz(),
array(RequestMethod::GET, RequestMethod::POST),
SecurityHandler::OPEN),
'/v1/service-messages/{i}' => action(\Controllers\ServiceMessages\ServiceMessagesController::clazz(),
array(RequestMethod::PUT, RequestMethod::DELETE),
SecurityHandler::INTERNAL_OR_AUTH_TOKEN),
'/v1-internal/service-messages/{i}/sort/{s}' => action(\Controllers\ServiceMessages\ServiceMessagesController::clazz() . '::sort',
array(RequestMethod::POST),
SecurityHandler::INTERNAL),
/* Internal use only routes */
// Resend email response

@ -185,12 +185,34 @@ function hesk_service_message($sm)
?>
<div class="<?php echo $style; ?>">
<?php echo $faIcon == '' ? '' : '<i class="' . $faIcon . '"></i> '; ?>
<b><?php echo $sm['title']; ?></b><?php echo $sm['message']; ?>
<b><?php echo $sm['title']; ?></b><br>
<?php echo $sm['message']; ?>
</div>
<br/>
<?php
} // END hesk_service_message()
function mfh_get_service_messages($location) {
global $hesk_settings;
$language = $hesk_settings['languages'][$hesk_settings['language']]['folder'];
$res = hesk_dbQuery('SELECT `title`, `message`, `style`, `icon` FROM `'.hesk_dbEscape($hesk_settings['db_pfix'])."service_messages` AS `sm`
INNER JOIN `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_service_message_to_location` AS `location`
ON `sm`.`id` = `location`.`service_message_id`
AND `location`.`location` = '" . hesk_dbEscape($location) . "'
AND `sm`.`mfh_language` IN ('ALL', '" . hesk_dbEscape($language) . "')
WHERE `type`='0'
ORDER BY `order` ASC");
$sm = array();
while ($row = hesk_dbFetchAssoc($res)) {
$sm[] = $row;
}
return $sm;
}
function hesk_isBannedIP($ip)
{

@ -79,7 +79,6 @@ function print_select_category($number_of_categories)
?>
<div style="text-align: center">
<h3><?php echo $hesklang['select_category_text']; ?></h3>
<div class="select_category">
@ -322,6 +321,13 @@ function print_add_ticket()
<h2><?php hesk_showTopBar($hesklang['submit_ticket']); ?></h2>
<small><?php echo $hesklang['use_form_below']; ?></small>
<div class="blankSpace"></div>
<?php
// Service messages
$service_messages = mfh_get_service_messages('CUSTOMER_SUBMIT_TICKET');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
?>
<div align="left" class="h3"><?php echo $hesklang['add_ticket_general_information']; ?></div>
<div class="footerWithBorder"></div>
@ -1315,15 +1321,13 @@ function print_start()
</ol>
<?php
// Service messages
$res = hesk_dbQuery('SELECT `title`, `message`, `style`, `icon` FROM `'.hesk_dbEscape($hesk_settings['db_pfix'])."service_messages` WHERE `type`='0' ORDER BY `order` ASC");
if (hesk_dbNumRows($res) > 0)
{
$service_messages = mfh_get_service_messages('CUSTOMER_HOME');
if (count($service_messages) > 0) {
?>
<div class="row">
<div class="col-md-12">
<?php
while ($sm=hesk_dbFetchAssoc($res))
{
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
?>

@ -218,5 +218,9 @@ function getAllMigrations() {
161 => new UpdateMigration('3.2.2', '3.2.1', 161),
162 => new UpdateMigration('3.2.3', '3.2.2', 162),
163 => new UpdateMigration('3.2.4', '3.2.3', 163),
// 3.3.0
164 => new \v330\ServiceMessagesImprovements\CreateServiceMessageToLocationTable(164),
165 => new \v330\ServiceMessagesImprovements\UpdateExistingServiceMessagesLocations(165),
166 => new \v330\ServiceMessagesImprovements\AddLanguageColumnToServiceMessages(166),
);
}

@ -0,0 +1,17 @@
<?php
namespace v330\ServiceMessagesImprovements;
class AddLanguageColumnToServiceMessages extends \AbstractUpdatableMigration {
function innerUp($hesk_settings) {
$this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages`
ADD COLUMN `mfh_language` VARCHAR(255) NOT NULL DEFAULT 'ALL'");
}
function innerDown($hesk_settings) {
$this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages`
DROP COLUMN `mfh_language`");
}
}

@ -0,0 +1,16 @@
<?php
namespace v330\ServiceMessagesImprovements;
class CreateServiceMessageToLocationTable extends \AbstractUpdatableMigration {
function innerUp($hesk_settings) {
$this->executeQuery("CREATE TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_service_message_to_location`
(`service_message_id` INT NOT NULL, `location` VARCHAR(100) NOT NULL)");
}
function innerDown($hesk_settings) {
$this->executeQuery("DROP TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_service_message_to_location`");
}
}

@ -0,0 +1,19 @@
<?php
namespace v330\ServiceMessagesImprovements;
use BusinessLogic\ServiceMessages\ServiceMessageLocation;
class UpdateExistingServiceMessagesLocations extends \AbstractUpdatableMigration {
function innerUp($hesk_settings) {
$this->executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_service_message_to_location` (`service_message_id`, `location`)
SELECT `id`, '" . hesk_dbEscape(ServiceMessageLocation::CUSTOMER_HOME) . "' FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages`");
}
function innerDown($hesk_settings) {
$this->executeQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_service_message_to_location`
WHERE `service_message_id` IN (SELECT `id` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages`)");
}
}

@ -0,0 +1,361 @@
var serviceMessages = [];
var g_styles = [];
g_styles["ERROR"] = 4;
g_styles["NOTICE"] = 3;
g_styles["INFO"] = 2;
g_styles["SUCCESS"] = 1;
g_styles["NONE"] = 0;
$(document).ready(function() {
loadTable();
bindEditModal();
bindFormSubmit();
bindDeleteButton();
bindCreateModal();
bindSortButtons();
bindPreview();
});
function loadTable() {
$('#overlay').show();
var heskUrl = $('p#hesk-path').text();
var $tableBody = $('#table-body');
$.ajax({
method: 'GET',
url: heskUrl + 'api/index.php/v1/service-messages',
headers: { 'X-Internal-Call': true },
success: function(data) {
$tableBody.html('');
if (data.length === 0) {
$tableBody.append('<tr><td colspan="4">' + mfhLang.text('no_sm') + '</td></tr>');
$('#overlay').hide();
return;
}
var first = true;
var lastElement = null;
$.each(data, function() {
var $template = $($('#service-message-template').html());
$template.find('[data-property="id"]').attr('data-value', this.id);
$template.find('span[data-property="title"]').html(
getFormattedTitle(this.icon, this.title, this.style));
$template.find('span[data-property="author"]').text(users[this.createdBy].name);
if (this.published) {
$template.find('span[data-property="type"]').text(mfhLang.text('sm_published'));
} else {
$template.find('span[data-property="type"]').text(mfhLang.text('sm_draft'));
}
$template.find('[data-property="language"]').text(this.language === 'ALL' ?
mfhLang.text('all') :
languages[this.language]);
$tableBody.append($template);
serviceMessages[this.id] = this;
lastElement = this;
if (first) {
$template.find('[data-direction="up"]').css('visibility', 'hidden');
first = false;
}
});
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('error_retrieving_sm'), data.responseJSON);
console.error(data);
},
complete: function() {
$('#overlay').hide();
}
});
}
function getFormattedTitle(icon, title, style) {
var $template = $($('#service-message-title-template').html());
var alertClass = 'none';
switch (style) {
case 'ERROR':
alertClass = 'alert alert-danger';
break;
case 'NOTICE':
alertClass = 'alert alert-warning';
break;
case 'INFO':
alertClass = 'alert alert-info';
break;
case 'SUCCESS':
alertClass = 'alert alert-success';
break;
}
$template.addClass(alertClass)
.find('[data-property="icon"]').addClass(icon).end()
.find('[data-property="title"]').text(title);
return $template;
}
function getServiceMessagePreview(icon, title, message, style) {
var $template = $('#service-message-preview-template').html();
var alertClass = 'none';
switch (style) {
case 'ERROR':
alertClass = 'alert alert-danger';
break;
case 'NOTICE':
alertClass = 'alert alert-warning';
break;
case 'INFO':
alertClass = 'alert alert-info';
break;
case 'SUCCESS':
alertClass = 'alert alert-success';
break;
}
$template = $template.replace('none', alertClass)
.replace('{{TITLE}}', title)
.replace('{{MESSAGE}}', message);
$template = $($template);
if (icon !== '') {
$template.find('i.fa').removeClass('fa').addClass(icon);
}
return $template;
}
function bindEditModal() {
$(document).on('click', '[data-action="edit"]', function() {
var element = serviceMessages[$(this).parent().parent().find('[data-property="id"]').data('value')];
var $modal = $('#service-message-modal');
$modal.find('#preview-pane').html('').end()
.find('input[name="location[]"]').prop('checked', false);
$modal.find('#edit-label').show();
$modal.find('#create-label').hide();
$modal.find('input[name="style"][value="' + (g_styles[element.style]) + '"]').prop('checked', 'checked').end()
.find('input[name="type"][value="' + (element.published ? 0 : 1) + '"]')
.prop('checked', 'checked').end()
.find('input[name="title"]').val(element.title).end()
.find('input[name="id"]').val(element.id).end()
.find('input[name="order"]').val(element.order).end()
.find('select[name="language"]').val(element.language).end();
setIcon(element.icon);
$.each(element.locations, function() {
$modal.find('input[name="location[]"][value="' + this + '"]').prop('checked', 'checked');
});
if ($('input[name="kb_wysiwyg"]').val() === "1") {
tinyMCE.get('content').setContent(element.message);
} else {
$('textarea[name="message"]').val(element.message);
}
$('.tab-pane#sm-contents').addClass('active');
$('.tab-pane#properties').removeClass('active');
$('.nav-tabs > li').removeClass('active');
$('.nav-tabs > li:first').addClass('active');
$modal.modal('show');
});
}
function bindCreateModal() {
$('#create-button').click(function() {
var $modal = $('#service-message-modal');
$modal.find('#edit-label').hide().end()
.find('#create-label').show().end()
.find('input[name="style"][value="0"]').prop('checked', 'checked').end() // "None" style
.find('input[name="type"][value="0"]').prop('checked', 'checked').end() // Published
.find('input[name="title"]').val('').end()
.find('input[name="id"]').val(-1).end()
.find('input[name="order"]').val('').end()
.find('#preview-pane').html('').end()
.find('input[name="location[]"]').prop('checked', false)
.find('select[name="language"]').val('ALL');
setIcon('');
if ($('input[name="kb_wysiwyg"]').val() === "1") {
tinyMCE.get('content').setContent('');
} else {
$('textarea[name="message"]').val('');
}
$('.tab-pane#sm-contents').addClass('active');
$('.tab-pane#properties').removeClass('active');
$('.nav-tabs > li').removeClass('active');
$('.nav-tabs > li:first').addClass('active');
$modal.modal('show');
});
}
function bindFormSubmit() {
$('form#service-message').submit(function(e) {
e.preventDefault();
var heskUrl = $('p#hesk-path').text();
var $modal = $('#service-message-modal');
var styles = [];
styles[0] = "NONE";
styles[1] = "SUCCESS";
styles[2] = "INFO";
styles[3] = "NOTICE";
styles[4] = "ERROR";
var domLocations = $modal.find('input[name="location[]"]:checked');
var locations = [];
$.each(domLocations, function() {
locations.push($(this).val());
});
var data = {
icon: $modal.find('input[name="icon"]').val(),
title: $modal.find('input[name="title"]').val(),
message: getMessage(),
published: $modal.find('input[name="type"]:checked').val() === "0",
style: styles[$modal.find('input[name="style"]:checked').val()],
order: $modal.find('input[name="order"]').val(),
language: $modal.find('select[name="language"]').val(),
locations: locations
};
var url = heskUrl + 'api/index.php/v1/service-messages/';
var method = 'POST';
var serviceMessageId = parseInt($modal.find('input[name="id"]').val());
if (serviceMessageId !== -1) {
url += serviceMessageId;
method = 'PUT';
}
$modal.find('#action-buttons').find('.cancel-button').attr('disabled', 'disabled');
$modal.find('#action-buttons').find('.save-button').attr('disabled', 'disabled');
$.ajax({
method: 'POST',
url: url,
headers: {
'X-Internal-Call': true,
'X-HTTP-Method-Override': method
},
data: JSON.stringify(data),
success: function(data) {
if (serviceMessageId === -1) {
mfhAlert.success(mfhLang.text('sm_added'));
} else {
mfhAlert.success(mfhLang.text('sm_mdf'));
}
$modal.modal('hide');
loadTable();
},
error: function(data) {
mfhAlert.errorWithLog(mfhLang.text('error_saving_updating_sm'), data.responseJSON);
console.error(data);
},
complete: function(data) {
$modal.find('#action-buttons').find('.cancel-button').removeAttr('disabled');
$modal.find('#action-buttons').find('.save-button').removeAttr('disabled');
}
});
});
}
function bindDeleteButton() {
$(document).on('click', '[data-action="delete"]', function() {
$('#overlay').show();
var heskUrl = $('p#hesk-path').text();
var element = serviceMessages[$(this).parent().parent().find('[data-property="id"]').data('value')];
$.ajax({
method: 'POST',
url: heskUrl + 'api/index.php/v1/service-messages/' + element.id,
headers: {
'X-Internal-Call': true,
'X-HTTP-Method-Override': 'DELETE'
},
success: function() {
mfhAlert.success(mfhLang.text('sm_deleted'));
loadTable();
},
error: function(data) {
$('#overlay').hide();
mfhAlert.errorWithLog(mfhLang.text('error_deleting_sm'), data.responseJSON);
console.error(data);
}
});
});
}
function bindSortButtons() {
$(document).on('click', '[data-action="sort"]', function() {
$('#overlay').show();
var heskUrl = $('p#hesk-path').text();
var direction = $(this).data('direction');
var element = serviceMessages[$(this).parent().parent().parent().find('[data-property="id"]').data('value')];
$.ajax({
method: 'POST',
url: heskUrl + 'api/index.php/v1-internal/service-messages/' + element.id + '/sort/' + direction,
headers: { 'X-Internal-Call': true },
success: function() {
loadTable();
},
error: function(data) {
mfhAlert.errorWithLog(mfhLang.text('error_sorting_categories'), data.responseJSON);
console.error(data);
$('#overlay').hide();
}
})
});
}
function bindPreview() {
$('.preview-button').click(function() {
var styles = [];
styles[0] = "NONE";
styles[1] = "SUCCESS";
styles[2] = "INFO";
styles[3] = "NOTICE";
styles[4] = "ERROR";
var $modal = $('#service-message-modal');
var data = {
icon: $modal.find('input[name="icon"]').val(),
title: $modal.find('input[name="title"]').val(),
message: getMessage(),
published: $modal.find('input[name="type"]:checked').val() === "0",
style: styles[$modal.find('input[name="style"]:checked').val()],
order: $modal.find('input[name="order"]').val()
};
var preview = getServiceMessagePreview(data.icon, data.title, data.message, data.style);
$('#preview-pane').html(preview);
});
}
function getMessage() {
if ($('input[name="kb_wysiwyg"]').val() === "1") {
return tinyMCE.get('content').getContent();
}
return $('textarea[name="message"]').val();
}

@ -254,6 +254,13 @@ if (!$show['show']) {
require_once(HESK_PATH . 'inc/header.inc.php');
hesk_kb_header($hesk_settings['kb_link']);
// Service messages
$service_messages = mfh_get_service_messages('CUSTOMER_VIEW_KB_ARTICLE');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
// Update views by 1 - exclude known bots and reloads because of ratings
if (!isset($_GET['rated']) && !hesk_detect_bots()) {
hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "kb_articles` SET `views`=`views`+1 WHERE `id`={$artid}");
@ -406,11 +413,11 @@ if (!$show['show']) {
hesk_kb_header($hesk_settings['kb_link']);
}
// If we are in "Knowledgebase only" mode show system messages
if ($catid == 1 && hesk_check_kb_only(false)) {
// Display service messages on the default category
if ($catid == 1) {
// Service messages
$res = hesk_dbQuery('SELECT `title`, `message`, `style` FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages` WHERE `type`='0' ORDER BY `order` ASC");
while ($sm = hesk_dbFetchAssoc($res)) {
$service_messages = mfh_get_service_messages('CUSTOMER_KB_HOME');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
}

@ -2217,6 +2217,19 @@ $hesklang['audit_unlinked_ticket'] = '%s unlinked ticket %s';
// Added or modified in Mods for HESK 3.3.0
$hesklang['audit_event_created'] = '%s created event';
$hesklang['audit_event_updated'] = '%s updated event';
$hesklang['error_retrieving_sm'] = 'An error occurred retrieving service messages!';
$hesklang['error_saving_updating_sm'] = 'An error occurred creating / saving the service message!';
$hesklang['error_deleting_sm'] = 'An error occurred when trying to delete the service message.';
$hesklang['error_sorting_sm'] = 'An error occurred sorting service messages!';
$hesklang['sm_location'] = 'Location'; // Location for service messages
$hesklang['sm_customer_pages'] = 'Customer Pages';
$hesklang['sm_staff_pages'] = 'Staff Pages';
$hesklang['sm_homepage'] = 'Homepage';
$hesklang['sm_kb_home'] = 'Knowledgebase Home';
$hesklang['sm_view_kb_article'] = 'View Knowledgebase Article';
$hesklang['sm_submit_ticket'] = 'Submit Ticket';
$hesklang['sm_view_ticket'] = 'View Ticket';
$hesklang['sm_login_page'] = 'Login Page';
// DO NOT CHANGE BELOW
if (!defined('IN_SCRIPT')) die('PHP syntax OK!');

@ -215,6 +215,13 @@ if (!$show['show']) {
<?php endif; ?>
<div class="<?php echo $columnWidth; ?>">
<?php
// Service messages
$service_messages = mfh_get_service_messages('CUSTOMER_VIEW_TICKET');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
/* This will handle error, success and notice messages */
hesk_handle_messages();
@ -478,6 +485,12 @@ function print_form()
<?php endif; ?>
<div class="<?php echo $columnWidth; ?>">
<?php
// Service messages
$service_messages = mfh_get_service_messages('CUSTOMER_VIEW_TICKET');
foreach ($service_messages as $sm) {
hesk_service_message($sm);
}
/* This will handle error, success and notice messages */
hesk_handle_messages();
?>

Loading…
Cancel
Save