diff --git a/.gitignore b/.gitignore index 44ac5da8..ad0887cb 100644 --- a/.gitignore +++ b/.gitignore @@ -197,52 +197,52 @@ inc/tabs/tabber-minimized.js inc/tabs/tabber.css inc/timer/hesk_timer.js inc/timer/index.htm -inc/tiny_mce/3.5.11/langs/en.js -inc/tiny_mce/3.5.11/license.txt -inc/tiny_mce/3.5.11/themes/advanced/about.htm -inc/tiny_mce/3.5.11/themes/advanced/anchor.htm -inc/tiny_mce/3.5.11/themes/advanced/charmap.htm -inc/tiny_mce/3.5.11/themes/advanced/color_picker.htm -inc/tiny_mce/3.5.11/themes/advanced/editor_template.js -inc/tiny_mce/3.5.11/themes/advanced/image.htm -inc/tiny_mce/3.5.11/themes/advanced/img/colorpicker.jpg -inc/tiny_mce/3.5.11/themes/advanced/img/flash.gif -inc/tiny_mce/3.5.11/themes/advanced/img/icons.gif -inc/tiny_mce/3.5.11/themes/advanced/img/iframe.gif -inc/tiny_mce/3.5.11/themes/advanced/img/pagebreak.gif -inc/tiny_mce/3.5.11/themes/advanced/img/quicktime.gif -inc/tiny_mce/3.5.11/themes/advanced/img/realmedia.gif -inc/tiny_mce/3.5.11/themes/advanced/img/shockwave.gif -inc/tiny_mce/3.5.11/themes/advanced/img/trans.gif -inc/tiny_mce/3.5.11/themes/advanced/img/video.gif -inc/tiny_mce/3.5.11/themes/advanced/img/windowsmedia.gif -inc/tiny_mce/3.5.11/themes/advanced/js/about.js -inc/tiny_mce/3.5.11/themes/advanced/js/anchor.js -inc/tiny_mce/3.5.11/themes/advanced/js/charmap.js -inc/tiny_mce/3.5.11/themes/advanced/js/color_picker.js -inc/tiny_mce/3.5.11/themes/advanced/js/image.js -inc/tiny_mce/3.5.11/themes/advanced/js/link.js -inc/tiny_mce/3.5.11/themes/advanced/js/source_editor.js -inc/tiny_mce/3.5.11/themes/advanced/langs/en.js -inc/tiny_mce/3.5.11/themes/advanced/langs/en_dlg.js -inc/tiny_mce/3.5.11/themes/advanced/link.htm -inc/tiny_mce/3.5.11/themes/advanced/shortcuts.htm -inc/tiny_mce/3.5.11/themes/advanced/skins/default/content.css -inc/tiny_mce/3.5.11/themes/advanced/skins/default/dialog.css -inc/tiny_mce/3.5.11/themes/advanced/skins/default/img/buttons.png -inc/tiny_mce/3.5.11/themes/advanced/skins/default/img/items.gif -inc/tiny_mce/3.5.11/themes/advanced/skins/default/img/menu_arrow.gif -inc/tiny_mce/3.5.11/themes/advanced/skins/default/img/menu_check.gif -inc/tiny_mce/3.5.11/themes/advanced/skins/default/img/progress.gif -inc/tiny_mce/3.5.11/themes/advanced/skins/default/img/tabs.gif -inc/tiny_mce/3.5.11/themes/advanced/skins/default/ui.css -inc/tiny_mce/3.5.11/themes/advanced/source_editor.htm -inc/tiny_mce/3.5.11/tiny_mce.js -inc/tiny_mce/3.5.11/tiny_mce_popup.js -inc/tiny_mce/3.5.11/utils/editable_selects.js -inc/tiny_mce/3.5.11/utils/form_utils.js -inc/tiny_mce/3.5.11/utils/mctabs.js -inc/tiny_mce/3.5.11/utils/validate.js +inc/tiny_mce/3.5.12/langs/en.js +inc/tiny_mce/3.5.12/license.txt +inc/tiny_mce/3.5.12/themes/advanced/about.htm +inc/tiny_mce/3.5.12/themes/advanced/anchor.htm +inc/tiny_mce/3.5.12/themes/advanced/charmap.htm +inc/tiny_mce/3.5.12/themes/advanced/color_picker.htm +inc/tiny_mce/3.5.12/themes/advanced/editor_template.js +inc/tiny_mce/3.5.12/themes/advanced/image.htm +inc/tiny_mce/3.5.12/themes/advanced/img/colorpicker.jpg +inc/tiny_mce/3.5.12/themes/advanced/img/flash.gif +inc/tiny_mce/3.5.12/themes/advanced/img/icons.gif +inc/tiny_mce/3.5.12/themes/advanced/img/iframe.gif +inc/tiny_mce/3.5.12/themes/advanced/img/pagebreak.gif +inc/tiny_mce/3.5.12/themes/advanced/img/quicktime.gif +inc/tiny_mce/3.5.12/themes/advanced/img/realmedia.gif +inc/tiny_mce/3.5.12/themes/advanced/img/shockwave.gif +inc/tiny_mce/3.5.12/themes/advanced/img/trans.gif +inc/tiny_mce/3.5.12/themes/advanced/img/video.gif +inc/tiny_mce/3.5.12/themes/advanced/img/windowsmedia.gif +inc/tiny_mce/3.5.12/themes/advanced/js/about.js +inc/tiny_mce/3.5.12/themes/advanced/js/anchor.js +inc/tiny_mce/3.5.12/themes/advanced/js/charmap.js +inc/tiny_mce/3.5.12/themes/advanced/js/color_picker.js +inc/tiny_mce/3.5.12/themes/advanced/js/image.js +inc/tiny_mce/3.5.12/themes/advanced/js/link.js +inc/tiny_mce/3.5.12/themes/advanced/js/source_editor.js +inc/tiny_mce/3.5.12/themes/advanced/langs/en.js +inc/tiny_mce/3.5.12/themes/advanced/langs/en_dlg.js +inc/tiny_mce/3.5.12/themes/advanced/link.htm +inc/tiny_mce/3.5.12/themes/advanced/shortcuts.htm +inc/tiny_mce/3.5.12/themes/advanced/skins/default/content.css +inc/tiny_mce/3.5.12/themes/advanced/skins/default/dialog.css +inc/tiny_mce/3.5.12/themes/advanced/skins/default/img/buttons.png +inc/tiny_mce/3.5.12/themes/advanced/skins/default/img/items.gif +inc/tiny_mce/3.5.12/themes/advanced/skins/default/img/menu_arrow.gif +inc/tiny_mce/3.5.12/themes/advanced/skins/default/img/menu_check.gif +inc/tiny_mce/3.5.12/themes/advanced/skins/default/img/progress.gif +inc/tiny_mce/3.5.12/themes/advanced/skins/default/img/tabs.gif +inc/tiny_mce/3.5.12/themes/advanced/skins/default/ui.css +inc/tiny_mce/3.5.12/themes/advanced/source_editor.htm +inc/tiny_mce/3.5.12/tiny_mce.js +inc/tiny_mce/3.5.12/tiny_mce_popup.js +inc/tiny_mce/3.5.12/utils/editable_selects.js +inc/tiny_mce/3.5.12/utils/form_utils.js +inc/tiny_mce/3.5.12/utils/mctabs.js +inc/tiny_mce/3.5.12/utils/validate.js inc/treemenu/TreeMenu.php inc/treemenu/index.htm inc/zip/Zip.php diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76921748..8a47c6e9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,6 +6,12 @@ stages: before_script: - bash ci/docker_install.sh > /dev/null +validate:7.2: + image: php:7.2 + stage: validate + script: + - bash ci/php_lint.sh ./ + validate:7.1: image: php:7.1 stage: validate @@ -55,8 +61,8 @@ test:7.1: - cd Tests - phpunit -test:7.0: - image: php:7.0 +test:7.2: + image: php:7.2 stage: test script: - cd api diff --git a/admin/admin_main.php b/admin/admin_main.php index 0d6dcefc..59898d18 100644 --- a/admin/admin_main.php +++ b/admin/admin_main.php @@ -54,7 +54,15 @@ else { ?>
- +

diff --git a/admin/admin_settings.php b/admin/admin_settings.php index 8f9a5115..8fd9fd77 100644 --- a/admin/admin_settings.php +++ b/admin/admin_settings.php @@ -93,19 +93,20 @@ if ($hesk_settings['attachments']['use'] && !defined('HESK_DEMO')) { $tmp = @ini_get('upload_max_filesize'); if ($tmp) { $last = strtoupper(substr($tmp, -1)); + $number = substr($tmp, 0, -1); switch ($last) { case 'K': - $tmp = $tmp * 1024; + $tmp = $number * 1024; break; case 'M': - $tmp = $tmp * 1048576; + $tmp = $number * 1048576; break; case 'G': - $tmp = $tmp * 1073741824; + $tmp = $number * 1073741824; break; default: - $tmp = $tmp; + $tmp = $number; } if ($tmp < $hesk_settings['attachments']['max_size']) { @@ -117,19 +118,20 @@ if ($hesk_settings['attachments']['use'] && !defined('HESK_DEMO')) { $tmp = @ini_get('post_max_size'); if ($tmp) { $last = strtoupper(substr($tmp, -1)); + $number = substr($tmp, 0, -1); switch ($last) { case 'K': - $tmp = $tmp * 1024; + $tmp = $number * 1024; break; case 'M': - $tmp = $tmp * 1048576; + $tmp = $number * 1048576; break; case 'G': - $tmp = $tmp * 1073741824; + $tmp = $number * 1073741824; break; default: - $tmp = $tmp; + $tmp = $number; } if ($tmp < ($hesk_settings['attachments']['max_size'] * $hesk_settings['attachments']['max_number'] + 524288)) { @@ -2220,6 +2222,106 @@ $modsForHesk_settings = mfh_getSettings();

+
+ +
+

+

'; ?> +
+
+

+ + +

+ +
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ +
+ + + +
+
@@ -3272,6 +3374,27 @@ $modsForHesk_settings = mfh_getSettings(); +
+ +
+
+ +
+
+
@@ -334,147 +334,168 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
diff --git a/admin/knowledgebase_private.php b/admin/knowledgebase_private.php index 041da9bd..e2cd0277 100644 --- a/admin/knowledgebase_private.php +++ b/admin/knowledgebase_private.php @@ -121,7 +121,9 @@ function hesk_kb_header($kb_link, $catid=1) '; hesk_kbSearchLarge(1); + echo ''; } // END hesk_kb_header() @@ -214,7 +216,15 @@ function hesk_show_kb_article($artid) ?>
- + '; + $service_messages = mfh_get_service_messages('STAFF_VIEW_KB_ARTICLE'); + foreach ($service_messages as $sm) { + hesk_service_message($sm); + } + echo '
'; + ?>
@@ -397,6 +407,13 @@ function hesk_show_kb_category($catid, $is_search = 0) { { /* Print header */ hesk_kb_header($hesk_settings['kb_link'], $catid); + + echo '
'; + $service_messages = mfh_get_service_messages('STAFF_KB_HOME'); + foreach ($service_messages as $sm) { + hesk_service_message($sm); + } + echo '
'; } ?>
diff --git a/admin/mail.php b/admin/mail.php index 9890aade..38aa2e75 100644 --- a/admin/mail.php +++ b/admin/mail.php @@ -32,7 +32,7 @@ $modsForHesk_settings = mfh_getSettings(); /* List of staff */ $admins = array(); -$res = hesk_dbQuery("SELECT `id`,`name` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "users` ORDER BY `name` ASC"); +$res = hesk_dbQuery("SELECT `id`,`name` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "users` WHERE `active` = '1' ORDER BY `name` ASC"); while ($row = hesk_dbFetchAssoc($res)) { $admins[$row['id']] = $row['name']; } diff --git a/admin/manage_knowledgebase.php b/admin/manage_knowledgebase.php index b126a326..987772e3 100644 --- a/admin/manage_knowledgebase.php +++ b/admin/manage_knowledgebase.php @@ -212,7 +212,8 @@ while (count($kb_cat) > 0) if (isset($node[$up])) { - $node[$my] = &$node[$up]->addItem(new HTML_TreeNode(array('hesk_selected' => $selected, 'text' => $text, 'text_short' => $text_short, 'menu_icons' => $menu_icons, 'hesk_catid' => $cat['id'], 'hesk_select' => 'option'.$j, 'icon' => $icon, 'expandedIcon' => $expandedIcon, 'expanded' => true))); + $HTML_TreeNode[$my] = new HTML_TreeNode(array('hesk_selected' => $selected, 'text' => $text, 'text_short' => $text_short, 'menu_icons' => $menu_icons, 'hesk_catid' => $cat['id'], 'hesk_select' => 'option'.$j, 'icon' => $icon, 'expandedIcon' => $expandedIcon, 'expanded' => true)); + $node[$my] = &$node[$up]->addItem($HTML_TreeNode[$my]); } else { @@ -242,8 +243,11 @@ while (count($kb_cat) > 0) $menu->addItem($node[1]); // Create the presentation class -$treeMenu = & ref_new(new HTML_TreeMenu_DHTML($menu, array('images' => '../img', 'defaultClass' => 'treeMenuDefault', 'isDynamic' => true))); -$listBox = & ref_new(new HTML_TreeMenu_Listbox($menu)); +$HTML_TreeMenu_DHTML = new HTML_TreeMenu_DHTML($menu, array('images' => '../img', 'defaultClass' => 'treeMenuDefault', 'isDynamic' => true)); +$treeMenu = & ref_new($HTML_TreeMenu_DHTML); + +$HTML_TreeMenu_Listbox = new HTML_TreeMenu_Listbox($menu); +$listBox = & ref_new($HTML_TreeMenu_Listbox); /* Hide new article and new category forms by default */ if (!isset($_SESSION['hide'])) @@ -270,6 +274,12 @@ if (!isset($_SESSION['hide']['treemenu'])) addItem(new HTML_TreeNode(array('hesk_parent' => $this_cat['parent'], 'text' => 'Text', 'text_short' => $text_short, 'hesk_catid' => $cat['id'], 'hesk_select' => 'option'.$j, 'icon' => $icon, 'expandedIcon' => $expandedIcon, 'expanded' => true))); + $HTML_TreeNode[$my] = new HTML_TreeNode(array('hesk_parent' => $this_cat['parent'], 'text' => 'Text', 'text_short' => $text_short, 'hesk_catid' => $cat['id'], 'hesk_select' => 'option'.$j, 'icon' => $icon, 'expandedIcon' => $expandedIcon, 'expanded' => true)); + $node[$my] = &$node[$up]->addItem($HTML_TreeNode[$my]); } else { @@ -1377,7 +1388,8 @@ function edit_article() $menu->addItem($node[1]); // Create the presentation class - $listBox = & ref_new(new HTML_TreeMenu_Listbox($menu)); + $HTML_TreeMenu_Listbox = new HTML_TreeMenu_Listbox($menu); + $listBox = & ref_new($HTML_TreeMenu_Listbox); /* Print header */ require_once(HESK_PATH . 'inc/headerAdmin.inc.php'); @@ -1604,6 +1616,9 @@ function manage_category() { foreach ($kb_cat as $k=>$cat) { + if ($cat['id'] == $catid) { + continue; + } if (in_array($cat['parent'],$thislevel)) { @@ -1616,7 +1631,8 @@ function manage_category() { if (isset($node[$up])) { - $node[$my] = &$node[$up]->addItem(new HTML_TreeNode(array('hesk_parent' => $this_cat['parent'], 'text' => 'Text', 'text_short' => $text_short, 'hesk_catid' => $cat['id'], 'hesk_select' => 'option'.$j, 'icon' => $icon, 'expandedIcon' => $expandedIcon, 'expanded' => true))); + $HTML_TreeNode[$my] = new HTML_TreeNode(array('hesk_parent' => $this_cat['parent'], 'text' => 'Text', 'text_short' => $text_short, 'hesk_catid' => $cat['id'], 'hesk_select' => 'option'.$j, 'icon' => $icon, 'expandedIcon' => $expandedIcon, 'expanded' => true)); + $node[$my] = &$node[$up]->addItem($HTML_TreeNode[$my]); } else { @@ -1646,7 +1662,8 @@ function manage_category() { $menu->addItem($node[1]); // Create the presentation class - $listBox = & ref_new(new HTML_TreeMenu_Listbox($menu)); + $HTML_TreeMenu_Listbox = new HTML_TreeMenu_Listbox($menu); + $listBox = & ref_new($HTML_TreeMenu_Listbox); /* Print header */ require_once(HESK_PATH . 'inc/headerAdmin.inc.php'); diff --git a/admin/manage_permission_groups.php b/admin/manage_permission_groups.php index 3fd7eddc..42cdd736 100644 --- a/admin/manage_permission_groups.php +++ b/admin/manage_permission_groups.php @@ -80,14 +80,19 @@ while ($row = hesk_dbFetchAssoc($res)) {
- - - +
+ + + +
+ - - - + + + + + @@ -96,13 +101,13 @@ while ($row = hesk_dbFetchAssoc($res)) { @@ -137,11 +142,9 @@ function createEditModal($template, $features, $categories) { global $hesklang; - $disabled = 'checked="checked" disabled'; $enabledFeatures = array(); $enabledCategories = array(); - if ($template['heskprivileges'] != 'ALL') { - $disabled = ''; + if ($template['heskprivileges'] !== 'ALL') { $enabledFeatures = explode(',', $template['heskprivileges']); $enabledCategories = explode(',', $template['categories']); } @@ -157,6 +160,12 @@ function createEditModal($template, $features, $categories) @@ -334,7 +370,6 @@ function save() WHERE `id` = " . intval($templateId)); $row = hesk_dbFetchAssoc($res); - // Add 'can ban emails' if 'can unban emails' is set (but not added). Same with 'can ban ips' $catArray = hesk_POST_array('categories'); $featArray = hesk_POST_array('features'); @@ -349,6 +384,41 @@ function save() $features = implode(',', $featArray); $name = hesk_POST('name'); + // Only allow users to add what they are allowed to add + // Admins can handle anything + if (!$_SESSION['isadmin']) { + // Update categories based on user visibility + $originalCategories = explode(',', $row['categories']); + $newCategories = array(); + foreach ($originalCategories as $innerCategory) { + if (in_array($innerCategory, $catArray) && in_array($innerCategory, $_SESSION['categories'])) { + $newCategories[] = $innerCategory; + } elseif (!in_array($innerCategory, $catArray) && !in_array($innerCategory, $_SESSION['categories'])) { + // The user can't modify this, so keep it in + $newCategories[] = $innerCategory; + } + // If neither, the user removed it. + } + + // Update features based on user visibility + $originalFeatures = explode(',', $row['heskprivileges']); + $newFeatures = array(); + foreach ($originalFeatures as $innerFeature) { + if (in_array($innerFeature, $featArray) && strpos($_SESSION['heskprivileges'], $innerFeature) !== false) { + $newFeatures[] = $innerFeature; + } elseif (!in_array($innerFeature, $featArray) && strpos($_SESSION['heskprivileges'], $innerFeature) === false) { + // The user can't modify this, so keep it in + $newFeatures[] = $innerFeature; + } + // If neither, the user removed it. + } + + $categories = implode(',', $newCategories); + $features = implode(',', $newFeatures); + } + + + hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "permission_templates` SET `categories` = '" . hesk_dbEscape($categories) . "', `heskprivileges` = '" . hesk_dbEscape($features) . "', `name` = '" . hesk_dbEscape($name) . "' diff --git a/admin/manage_users.php b/admin/manage_users.php index e96aff40..dee9b844 100644 --- a/admin/manage_users.php +++ b/admin/manage_users.php @@ -736,7 +736,7 @@ function hesk_validateUserInfo($pass_required = 1, $redirect_to = './manage_user } } - if (strlen($myuser['signature']) > 1000) { + if (hesk_mb_strlen($myuser['signature']) > 1000) { $hesk_error_buffer .= '
  • ' . $hesklang['signature_long'] . '
  • '; } diff --git a/admin/new_ticket.php b/admin/new_ticket.php index 9128c017..261077a3 100644 --- a/admin/new_ticket.php +++ b/admin/new_ticket.php @@ -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): ?>
    diff --git a/admin/profile.php b/admin/profile.php index d083e798..17596356 100644 --- a/admin/profile.php +++ b/admin/profile.php @@ -170,7 +170,7 @@ function update_profile() $_SESSION['new']['signature'] = hesk_input(hesk_POST('signature')); /* Signature */ - if (strlen($_SESSION['new']['signature']) > 1000) { + if (hesk_mb_strlen($_SESSION['new']['signature']) > 1000) { $hesk_error_buffer .= '
  • ' . $hesklang['signature_long'] . '
  • '; } diff --git a/admin/service_messages.php b/admin/service_messages.php index 580d0012..052e0eb4 100644 --- a/admin/service_messages.php +++ b/admin/service_messages.php @@ -15,6 +15,7 @@ define('IN_SCRIPT', 1); define('HESK_PATH', '../'); define('PAGE_TITLE', 'ADMIN_SERVICE_MESSAGES'); define('MFH_PAGE_LAYOUT', 'TOP_ONLY'); +define('EXTRA_JS', ''); /* Get all the required files and functions */ require(HESK_PATH . 'hesk_settings.inc.php'); @@ -31,26 +32,8 @@ hesk_isLoggedIn(); hesk_checkPermission('can_service_msg'); // Define required constants -define('LOAD_TABS', 1); define('WYSIWYG', 1); -// What should we do? -if ($action = hesk_REQUEST('a')) { - if ($action == 'edit_sm') { - edit_sm(); - } elseif (defined('HESK_DEMO')) { - hesk_process_messages($hesklang['ddemo'], 'service_messages.php', 'NOTICE'); - } elseif ($action == 'new_sm') { - new_sm(); - } elseif ($action == 'save_sm') { - save_sm(); - } elseif ($action == 'order_sm') { - order_sm(); - } elseif ($action == 'remove_sm') { - remove_sm(); - } -} - /* Print header */ require_once(HESK_PATH . 'inc/headerAdmin.inc.php'); @@ -60,652 +43,401 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
    +
    +

    + + +

    +
    -
    - -
    - - - - - - - - - - - - - - - - - - -
      
    -
    - - -
    -
    - 1) { - if ($k == 1) { - ?> - - - - - - - - - - - - - - - - -   -
    - -
    - - -
    -
    -

    -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - - -
    - - - - - - -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - - -
    - data-error="" required> -
    -
    -
    -
    - - -
    - -
    -
    -
    - ' : ''; ?> - - -
    -
    - - -
    -
    -
    -
    -
    -
    - - + + + + + + + + + + + + + +
    +
    + +
    + +var users = [];'; +$usersRs = hesk_dbQuery("SELECT `id`, `name` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "users` WHERE `active` = '1'"); +$users = array(); +while ($row = hesk_dbFetchAssoc($usersRs)) { + $users[] = $row; + echo "users[" . $row['id'] . "] = { + id: ".$row['id'].", + name: '".$row['name']."' + }\n"; +} +echo " +var languages = [];\n"; +foreach ($hesk_settings['languages'] as $key => $value) { + echo "languages[" . json_encode($value['folder']) . "] = " . json_encode($key) . ";\n"; +} +echo ''; +?> + + + 4 || $style < 0) { - $style = 0; - } - - $type = empty($_POST['type']) ? 0 : 1; - $icon = hesk_POST('icon'); - $title = hesk_input(hesk_POST('title')) or $hesk_error_buffer[] = $hesklang['sm_e_title']; - $message = hesk_getHTML(hesk_POST('message')); - - // Clean the HTML code - require(HESK_PATH . 'inc/htmlpurifier/HeskHTMLPurifier.php'); - $purifier = new HeskHTMLPurifier($hesk_settings['cache_dir']); - $message = $purifier->heskPurify($message); - - // Any errors? - if (count($hesk_error_buffer)) { - $_SESSION['edit_sm'] = true; - - $_SESSION['new_sm'] = array( - 'id' => $id, - 'style' => $style, - 'type' => $type, - 'title' => $title, - 'icon' => $icon, - 'message' => hesk_input(hesk_POST('message')), - ); - - $tmp = ''; - foreach ($hesk_error_buffer as $error) { - $tmp .= "
  • $error
  • \n"; - } - $hesk_error_buffer = $tmp; - - $hesk_error_buffer = $hesklang['rfm'] . '

    '; - hesk_process_messages($hesk_error_buffer, 'service_messages.php'); - } - - // Just preview the message? - if (isset($_POST['sm_preview'])) { - $_SESSION['preview_sm'] = true; - $_SESSION['edit_sm'] = true; - - $_SESSION['new_sm'] = array( - 'id' => $id, - 'style' => $style, - 'type' => $type, - 'title' => $title, - 'message' => $message, - 'icon' => $icon, - ); - - header('Location: service_messages.php'); - exit; - } - - // Update the service message in the database - hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages` SET - `author` = '" . intval($_SESSION['id']) . "', - `title` = '" . hesk_dbEscape($title) . "', - `message` = '" . hesk_dbEscape($message) . "', - `style` = '{$style}', - `type` = '{$type}', - `icon` = '{$icon}' - WHERE `id`={$id}"); - - $_SESSION['smord'] = $id; - hesk_process_messages($hesklang['sm_mdf'], 'service_messages.php', 'SUCCESS'); - -} // End save_sm() - - -function edit_sm() -{ - global $hesk_settings, $hesklang; - - // Get service messageID - $id = intval(hesk_GET('id')) or hesk_error($hesklang['sm_e_id']); - - // Get details from the database - $res = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages` WHERE `id`={$id} LIMIT 1"); - if (hesk_dbNumRows($res) != 1) { - hesk_error($hesklang['sm_not_found']); - } - $sm = hesk_dbFetchAssoc($res); - - $_SESSION['new_sm'] = $sm; - $_SESSION['edit_sm'] = true; - -} // End edit_sm() - - -function order_sm() -{ - global $hesk_settings, $hesklang; - - // A security check - hesk_token_check(); - - // Get ID and move parameters - $id = intval(hesk_GET('id')) or hesk_error($hesklang['sm_e_id']); - $move = intval(hesk_GET('move')); - $_SESSION['smord'] = $id; - - // Update article details - hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages` SET `order`=`order`+" . intval($move) . " WHERE `id`={$id}"); - - // Update order of all service messages - update_sm_order(); - - // Finish - header('Location: service_messages.php'); - exit(); - -} // End order_sm() - - -function update_sm_order() -{ - global $hesk_settings, $hesklang; - - // Get list of current service messages - $res = hesk_dbQuery("SELECT `id` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages` ORDER BY `order` ASC"); - - // Update database - $i = 10; - while ($sm = hesk_dbFetchAssoc($res)) { - hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages` SET `order`=" . intval($i) . " WHERE `id`='" . intval($sm['id']) . "'"); - $i += 10; - } - - return true; - -} // END update_sm_order() - - -function remove_sm() -{ - global $hesk_settings, $hesklang; - - // A security check - hesk_token_check(); - - // Get ID - $id = intval(hesk_GET('id')) or hesk_error($hesklang['sm_e_id']); - - // Delete the service message - hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages` WHERE `id`={$id}"); - - // Were we successful? - if (hesk_dbAffectedRows() == 1) { - hesk_process_messages($hesklang['sm_deleted'], './service_messages.php', 'SUCCESS'); - } else { - hesk_process_messages($hesklang['sm_not_found'], './service_messages.php'); - } - -} // End remove_sm() - - -function new_sm() -{ - global $hesk_settings, $hesklang, $listBox; - global $hesk_error_buffer; - - // A security check - # hesk_token_check('POST'); - - $hesk_error_buffer = array(); - - $style = intval(hesk_POST('style', 0)); - if ($style > 4 || $style < 0) { - $style = 0; - } - - $type = empty($_POST['type']) ? 0 : 1; - $icon = hesk_POST('icon'); - $title = hesk_input(hesk_POST('title')) or $hesk_error_buffer[] = $hesklang['sm_e_title']; - $message = hesk_getHTML(hesk_POST('message')); - - // Clean the HTML code - require(HESK_PATH . 'inc/htmlpurifier/HeskHTMLPurifier.php'); - $purifier = new HeskHTMLPurifier($hesk_settings['cache_dir']); - $message = $purifier->heskPurify($message); - - // Any errors? - if (count($hesk_error_buffer)) { - $_SESSION['new_sm'] = array( - 'style' => $style, - 'type' => $type, - 'title' => $title, - 'icon' => $icon, - 'message' => hesk_input(hesk_POST('message')), - ); - - $tmp = ''; - foreach ($hesk_error_buffer as $error) { - $tmp .= "
  • $error
  • \n"; - } - $hesk_error_buffer = $tmp; - - $hesk_error_buffer = $hesklang['rfm'] . '

    '; - hesk_process_messages($hesk_error_buffer, 'service_messages.php'); - } - - // Just preview the message? - if (isset($_POST['sm_preview'])) { - $_SESSION['preview_sm'] = true; - - $_SESSION['new_sm'] = array( - 'style' => $style, - 'type' => $type, - 'title' => $title, - 'icon' => $icon, - 'message' => $message, - ); - - header('Location: service_messages.php'); - exit; - } - - // Get the latest service message order - $res = hesk_dbQuery("SELECT `order` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages` ORDER BY `order` DESC LIMIT 1"); - $row = hesk_dbFetchRow($res); - $my_order = intval($row[0]) + 10; - - // Insert service message into database - hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "service_messages` (`author`,`title`,`message`,`style`,`type`,`order`, `icon`) VALUES ( - '" . intval($_SESSION['id']) . "', - '" . hesk_dbEscape($title) . "', - '" . hesk_dbEscape($message) . "', - '{$style}', - '{$type}', - '{$my_order}', - '{$icon}' - )"); - - $_SESSION['smord'] = hesk_dbInsertID(); - hesk_process_messages($hesklang['sm_added'], 'service_messages.php', 'SUCCESS'); - -} // End new_sm() - ?> diff --git a/api/BusinessLogic/Calendar/AbstractEvent.php b/api/BusinessLogic/Calendar/AbstractEvent.php new file mode 100644 index 00000000..27f2a184 --- /dev/null +++ b/api/BusinessLogic/Calendar/AbstractEvent.php @@ -0,0 +1,22 @@ +calendarGateway = $calendarGateway; + $this->auditTrailGateway = $auditTrailGateway; + } + + public function getEventsForStaff($searchEventsFilter, $heskSettings) { + return $this->calendarGateway->getEventsForStaff($searchEventsFilter, $heskSettings); + } + + /** + * @param $calendarEvent CalendarEvent + * @param $userContext UserContext + * @param $heskSettings array + * @return CalendarEvent + * @throws \Exception If more than one event is returned for the given ID + */ + public function updateEvent($calendarEvent, $userContext, $heskSettings) { + $this->calendarGateway->updateEvent($calendarEvent, $userContext, $heskSettings); + + $this->auditTrailGateway->insertAuditTrailRecord($calendarEvent->id, + AuditTrailEntityType::CALENDAR_EVENT, + 'audit_event_updated', + DateTimeHelpers::heskDate($heskSettings), + array(0 => $userContext->name . ' (' . $userContext->username . ')'), $heskSettings); + + $eventFilter = new SearchEventsFilter(); + $eventFilter->eventId = $calendarEvent->id; + $eventFilter->reminderUserId = $userContext->id; + + $events = $this->calendarGateway->getEventsForStaff($eventFilter, $heskSettings); + + if (count($events) !== 1) { + throw new \Exception("Expected exactly 1 event, found: " . count($events)); + } + + $event = $events[0]; + + return $event; + } + + + /** + * @param $calendarEvent CalendarEvent + * @param $userContext UserContext + * @param $heskSettings array + * @return AbstractEvent + * @throws \Exception + */ + public function createEvent($calendarEvent, $userContext, $heskSettings) { + $this->calendarGateway->createEvent($calendarEvent, $userContext, $heskSettings); + + $eventFilter = new SearchEventsFilter(); + $eventFilter->eventId = $calendarEvent->id; + $eventFilter->reminderUserId = $userContext->id; + + $events = $this->calendarGateway->getEventsForStaff($eventFilter, $heskSettings); + + if (count($events) !== 1) { + throw new \Exception("Expected exactly 1 event, found: " . count($events)); + } + + $event = $events[0]; + + $this->auditTrailGateway->insertAuditTrailRecord($event->id, + AuditTrailEntityType::CALENDAR_EVENT, + 'audit_event_created', + DateTimeHelpers::heskDate($heskSettings), + array(0 => $userContext->name . ' (' . $userContext->username . ')'), $heskSettings); + + return $event; + } + + public function deleteEvent($id, $userContext, $heskSettings) { + $this->calendarGateway->deleteEvent($id, $userContext, $heskSettings); + } + + public function getBusinessHours($heskSettings) { + return $this->calendarGateway->getBusinessHours($heskSettings); + } +} \ No newline at end of file diff --git a/api/BusinessLogic/Calendar/ReminderUnit.php b/api/BusinessLogic/Calendar/ReminderUnit.php new file mode 100644 index 00000000..e45b2169 --- /dev/null +++ b/api/BusinessLogic/Calendar/ReminderUnit.php @@ -0,0 +1,41 @@ +categoryGateway->updateCategory($category, $heskSettings); $this->categoryGateway->resortAllCategories($heskSettings); } + + function getPublicCategories($heskSettings) { + $allCategories = $this->categoryGateway->getAllCategories($heskSettings, $this->modsForHeskSettingsGateway->getAllSettings($heskSettings)); + + $publicCategories = array(); + foreach ($allCategories as $category) { + if ($category->type === 0) { + $publicCategories[] = $category; + } + } + + return $publicCategories; + } } \ No newline at end of file diff --git a/api/BusinessLogic/Emails/Addressees.php b/api/BusinessLogic/Emails/Addressees.php index baa37ae5..1e2a4669 100644 --- a/api/BusinessLogic/Emails/Addressees.php +++ b/api/BusinessLogic/Emails/Addressees.php @@ -12,10 +12,10 @@ class Addressees extends \BaseClass { /** * @var $cc string[]|null */ - public $cc; + public $cc = array(); /** * @var $bcc string[]|null */ - public $bcc; + public $bcc = array(); } \ No newline at end of file diff --git a/api/BusinessLogic/Emails/EmailTemplateParser.php b/api/BusinessLogic/Emails/EmailTemplateParser.php index 13cf48cf..563a993e 100644 --- a/api/BusinessLogic/Emails/EmailTemplateParser.php +++ b/api/BusinessLogic/Emails/EmailTemplateParser.php @@ -5,10 +5,12 @@ namespace BusinessLogic\Emails; use BusinessLogic\Exceptions\EmailTemplateNotFoundException; use BusinessLogic\Exceptions\InvalidEmailTemplateException; +use BusinessLogic\Security\UserContext; use BusinessLogic\Statuses\DefaultStatusForAction; use BusinessLogic\Tickets\Ticket; use Core\Constants\Priority; use DataAccess\Categories\CategoryGateway; +use DataAccess\Logging\LoggingGateway; use DataAccess\Security\UserGateway; use DataAccess\Statuses\StatusGateway; @@ -34,14 +36,21 @@ class EmailTemplateParser extends \BaseClass { */ private $emailTemplateRetriever; + /** + * @var $logger LoggingGateway + */ + private $logger; + function __construct(StatusGateway $statusGateway, CategoryGateway $categoryGateway, UserGateway $userGateway, - EmailTemplateRetriever $emailTemplateRetriever) { + EmailTemplateRetriever $emailTemplateRetriever, + LoggingGateway $loggingGateway) { $this->statusGateway = $statusGateway; $this->categoryGateway = $categoryGateway; $this->userGateway = $userGateway; $this->emailTemplateRetriever = $emailTemplateRetriever; + $this->logger = $loggingGateway; } /** @@ -129,7 +138,17 @@ class EmailTemplateParser extends \BaseClass { // Status name and category name $defaultStatus = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::NEW_TICKET, $heskSettings); - $statusName = $defaultStatus->localizedNames[$language]; + + if (key_exists($language, $defaultStatus->localizedNames)) { + $statusName = $defaultStatus->localizedNames[$language]; + } elseif (key_exists('English', $defaultStatus->localizedNames)) { + $statusName = $defaultStatus->localizedNames['English']; + $this->logger->logWarning('EmailTemplateParser', "No localized status found for status '{$defaultStatus->id}' and language '{$language}'. Defaulted to English.", "", new UserContext(), $heskSettings); + } else { + $statusName = "[ERROR: No localized status found for status '{$defaultStatus->id}']"; + $this->logger->logError('EmailTemplateParser', "No localized status found for status '{$defaultStatus->id}'", "", new UserContext(), $heskSettings); + } + $categories = $this->categoryGateway->getAllCategories($heskSettings, $modsForHeskSettings); $category = null; foreach ($categories as $innerCategory) { diff --git a/api/BusinessLogic/Exceptions/MissingAuthenticationTokenException.php b/api/BusinessLogic/Exceptions/MissingAuthenticationTokenException.php index 596839ff..267a82c0 100644 --- a/api/BusinessLogic/Exceptions/MissingAuthenticationTokenException.php +++ b/api/BusinessLogic/Exceptions/MissingAuthenticationTokenException.php @@ -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); } } \ No newline at end of file diff --git a/api/BusinessLogic/Helpers.php b/api/BusinessLogic/Helpers.php index f2a49c6d..b841fd27 100644 --- a/api/BusinessLogic/Helpers.php +++ b/api/BusinessLogic/Helpers.php @@ -30,4 +30,8 @@ class Helpers extends \BaseClass { static function boolval($val) { return $val == true; } + + static function heskHtmlSpecialCharsDecode($in) { + return str_replace(array('&', '<', '>', '"'), array('&', '<', '>', '"'), $in); + } } \ No newline at end of file diff --git a/api/BusinessLogic/Security/UserContext.php b/api/BusinessLogic/Security/UserContext.php index 3055b4ff..6ce0fba0 100644 --- a/api/BusinessLogic/Security/UserContext.php +++ b/api/BusinessLogic/Security/UserContext.php @@ -57,6 +57,10 @@ class UserContext extends \BaseClass { /* @var $active bool */ public $active; + function isAnonymousUser() { + return $this->id === -1; + } + static function buildAnonymousUser() { $userContext = new UserContext(); $userContext->id = -1; diff --git a/api/BusinessLogic/Security/UserPrivilege.php b/api/BusinessLogic/Security/UserPrivilege.php index 353e8e43..801e1937 100644 --- a/api/BusinessLogic/Security/UserPrivilege.php +++ b/api/BusinessLogic/Security/UserPrivilege.php @@ -15,4 +15,7 @@ class UserPrivilege extends \BaseClass { const CAN_EDIT_TICKETS = 'can_edit_tickets'; const CAN_DELETE_TICKETS = 'can_del_tickets'; 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'; } \ No newline at end of file diff --git a/api/BusinessLogic/ServiceMessages/GetServiceMessagesFilter.php b/api/BusinessLogic/ServiceMessages/GetServiceMessagesFilter.php new file mode 100644 index 00000000..a8f381d6 --- /dev/null +++ b/api/BusinessLogic/ServiceMessages/GetServiceMessagesFilter.php @@ -0,0 +1,12 @@ +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); + } + } +} \ No newline at end of file diff --git a/api/BusinessLogic/ServiceMessages/ServiceMessageLocation.php b/api/BusinessLogic/ServiceMessages/ServiceMessageLocation.php new file mode 100644 index 00000000..7f918bc7 --- /dev/null +++ b/api/BusinessLogic/ServiceMessages/ServiceMessageLocation.php @@ -0,0 +1,34 @@ + 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]; + } +} \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/AuditTrailEntityType.php b/api/BusinessLogic/Tickets/AuditTrailEntityType.php index feea16f6..dafcad42 100644 --- a/api/BusinessLogic/Tickets/AuditTrailEntityType.php +++ b/api/BusinessLogic/Tickets/AuditTrailEntityType.php @@ -5,4 +5,5 @@ namespace BusinessLogic\Tickets; class AuditTrailEntityType extends \BaseClass { const TICKET = 'TICKET'; + const CALENDAR_EVENT = 'CALENDAR_EVENT'; } \ No newline at end of file diff --git a/api/BusinessLogic/Tickets/AuditTrailEvent.php b/api/BusinessLogic/Tickets/AuditTrailEvent.php new file mode 100644 index 00000000..d3063e73 --- /dev/null +++ b/api/BusinessLogic/Tickets/AuditTrailEvent.php @@ -0,0 +1,9 @@ +newTicketValidator = $newTicketValidator; $this->trackingIdGenerator = $trackingIdGenerator; $this->autoassigner = $autoassigner; @@ -81,6 +86,7 @@ class TicketCreator extends \BaseClass { $this->userGateway = $userGateway; $this->modsForHeskSettingsGateway = $modsForHeskSettingsGateway; $this->auditTrailGateway = $auditTrailGateway; + $this->customFieldsGateway = $customFieldsGateway; } /** @@ -162,6 +168,19 @@ class TicketCreator extends \BaseClass { $addressees = new Addressees(); $addressees->to = $ticket->email; + foreach ($ticket->customFields as $key => $value) { + $customField = $this->customFieldsGateway->getCustomField($key, $heskSettings); + if ($customField !== null && + $customField->type === 'email' && + $customField->properties['email_type'] !== 'none') { + if ($customField->properties['email_type'] === 'cc') { + $addressees->cc[] = $value; + } elseif ($customField->properties['email_type'] === 'bcc') { + $addressees->bcc[] = $value; + } + } + } + if ($ticketRequest->sendEmailToCustomer && $emailVerified) { $this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_TICKET, $ticketRequest->language, $addressees, $ticket, $heskSettings, $modsForHeskSettings); } else if ($modsForHeskSettings['customer_email_verification_required'] && !$emailVerified) { diff --git a/api/BusinessLogic/Tickets/TicketEditor.php b/api/BusinessLogic/Tickets/TicketEditor.php index e15b33d9..13eceaa6 100644 --- a/api/BusinessLogic/Tickets/TicketEditor.php +++ b/api/BusinessLogic/Tickets/TicketEditor.php @@ -3,6 +3,7 @@ namespace BusinessLogic\Tickets; +use BusinessLogic\DateTimeHelpers; use BusinessLogic\Exceptions\AccessViolationException; use BusinessLogic\Exceptions\ApiFriendlyException; use BusinessLogic\Exceptions\ValidationException; @@ -13,6 +14,7 @@ use BusinessLogic\Tickets\CustomFields\CustomFieldValidator; use BusinessLogic\ValidationModel; use BusinessLogic\Validators; use Core\Constants\CustomField; +use DataAccess\AuditTrail\AuditTrailGateway; use DataAccess\Tickets\TicketGateway; class TicketEditor extends \BaseClass { @@ -22,10 +24,15 @@ class TicketEditor extends \BaseClass { /* @var $userToTicketChecker UserToTicketChecker */ private $userToTicketChecker; + /* @var $auditTrailGateway AuditTrailGateway */ + private $auditTrailGateway; + function __construct(TicketGateway $ticketGateway, - UserToTicketChecker $userToTicketChecker) { + UserToTicketChecker $userToTicketChecker, + AuditTrailGateway $auditTrailGateway) { $this->ticketGateway = $ticketGateway; $this->userToTicketChecker = $userToTicketChecker; + $this->auditTrailGateway = $auditTrailGateway; } @@ -135,4 +142,67 @@ class TicketEditor extends \BaseClass { throw new ValidationException($validationModel); } } + + /** + * @param $id int + * @param $dueDate string + * @param $userContext UserContext + * @param $heskSettings array + * @return Ticket The updated ticket + */ + function updateDueDate($id, $dueDate, $userContext, $heskSettings) { + $ticket = $this->ticketGateway->getTicketById($id, $heskSettings); + + $this->validateDueDate($ticket, $dueDate, $userContext, $heskSettings); + + $this->ticketGateway->updateTicketDueDate($ticket->id, $dueDate, $heskSettings); + + $event = AuditTrailEvent::DUE_DATE_REMOVED; + $replacementValues = array(0 => $userContext->name . ' (' . $userContext->username . ')'); + if ($dueDate !== null) { + $event = AuditTrailEvent::DUE_DATE_CHANGED; + $replacementValues = array( + 0 => $userContext->name . ' (' . $userContext->username . ')', + 1 => date('Y-m-d H:i:s', strtotime($dueDate)) + ); + } + + $this->auditTrailGateway->insertAuditTrailRecord($ticket->id, + AuditTrailEntityType::TICKET, + $event, + DateTimeHelpers::heskDate($heskSettings), + $replacementValues, + $heskSettings); + + $ticket->dueDate = $dueDate; + + return $ticket; + } + + /** + * @param $ticket Ticket + * @param $dueDate string + * @param $userContext UserContext + * @param $heskSettings array + * @throws ValidationException When validation fails + */ + private function validateDueDate($ticket, $dueDate, $userContext, $heskSettings) { + $validationModel = new ValidationModel(); + + if ($ticket === null) { + $validationModel->errorKeys[] = 'TICKET_MUST_EXIST_FOR_ID'; + } + + if (!$this->userToTicketChecker->isTicketAccessibleToUser($userContext, $ticket, $heskSettings, array(UserPrivilege::CAN_EDIT_TICKETS))) { + $validationModel->errorKeys[] = 'TICKET_MUST_BE_ACCESSIBLE_TO_USER'; + } + + if ($dueDate === false) { + $validationModel->errorKeys[] = 'DUE_DATE_MUST_BE_IN_VALID_FORMAT'; + } + + if (count($validationModel->errorKeys) > 0) { + throw new ValidationException($validationModel); + } + } } \ No newline at end of file diff --git a/api/BusinessLogic/Validators.php b/api/BusinessLogic/Validators.php index e0303434..e33adac7 100644 --- a/api/BusinessLogic/Validators.php +++ b/api/BusinessLogic/Validators.php @@ -19,7 +19,7 @@ class Validators extends \BaseClass { $address = str_replace(';', ',', $address); /* Check if addresses are valid */ - $all = explode(',', $address); + $all = array_unique(explode(',',$address)); foreach ($all as $k => $v) { if (!self::isValidEmail($v)) { unset($all[$k]); diff --git a/api/Controllers/Calendar/CalendarController.php b/api/Controllers/Calendar/CalendarController.php new file mode 100644 index 00000000..df9a1cc3 --- /dev/null +++ b/api/Controllers/Calendar/CalendarController.php @@ -0,0 +1,135 @@ +errorKeys = array('START_AND_END_TIMES_REQUIRED'); + throw new ValidationException($validationModel); + } + + $startTime = $_GET['start']; + $endTime = $_GET['end']; + + /* @var $calendarHandler CalendarHandler */ + $calendarHandler = $applicationContext->get(CalendarHandler::clazz()); + + $searchEventsFilter = new SearchEventsFilter(); + $searchEventsFilter->startTime = $startTime; + $searchEventsFilter->endTime = $endTime; + $searchEventsFilter->reminderUserId = $userContext->id; + + if ($userContext->isAnonymousUser()) { + $searchEventsFilter->includeTicketsAssignedToOthers = false; + $searchEventsFilter->includeUnassignedTickets = false; + $searchEventsFilter->includeTickets = false; + + /* @var $categoryHandler CategoryHandler */ + $categoryHandler = $applicationContext->get(CategoryHandler::clazz()); + + $publicCategories = $categoryHandler->getPublicCategories($hesk_settings); + $ids = array(); + foreach ($publicCategories as $category) { + $ids[] = $category->id; + } + $searchEventsFilter->categories = $ids; + } else { + $searchEventsFilter->includeTicketsAssignedToOthers = in_array(UserPrivilege::CAN_VIEW_ASSIGNED_TO_OTHER, $userContext->permissions); + $searchEventsFilter->includeUnassignedTickets = in_array(UserPrivilege::CAN_VIEW_UNASSIGNED, $userContext->permissions); + $searchEventsFilter->includeTickets = true; + $searchEventsFilter->categories = $userContext->admin ? null : $userContext->categories; + } + + $events = $calendarHandler->getEventsForStaff($searchEventsFilter, $hesk_settings); + + return output($events); + } + + function post() { + /* @var $userContext UserContext */ + /* @var $hesk_settings array */ + global $applicationContext, $hesk_settings, $userContext; + + $json = JsonRetriever::getJsonData(); + + $event = $this->transformJson($json); + + /* @var $calendarHandler CalendarHandler */ + $calendarHandler = $applicationContext->get(CalendarHandler::clazz()); + + return output($calendarHandler->createEvent($event, $userContext, $hesk_settings), 201); + } + + function put($id) { + /* @var $userContext UserContext */ + global $applicationContext, $hesk_settings, $userContext; + + $json = JsonRetriever::getJsonData(); + + $event = $this->transformJson($json, $id); + + /* @var $calendarHandler CalendarHandler */ + $calendarHandler = $applicationContext->get(CalendarHandler::clazz()); + + return output($calendarHandler->updateEvent($event, $userContext, $hesk_settings)); + } + + function delete($id) { + /* @var $userContext UserContext */ + global $applicationContext, $hesk_settings, $userContext; + + /* @var $calendarHandler CalendarHandler */ + $calendarHandler = $applicationContext->get(CalendarHandler::clazz()); + + $calendarHandler->deleteEvent($id, $userContext, $hesk_settings); + + return http_response_code(204); + } + + private function transformJson($json, $id = null) { + $event = new CalendarEvent(); + + $event->id = $id; + $event->startTime = date('Y-m-d H:i:s', strtotime(Helpers::safeArrayGet($json, 'startTime'))); + $event->endTime = date('Y-m-d H:i:s', strtotime(Helpers::safeArrayGet($json, 'endTime'))); + $event->allDay = Helpers::safeArrayGet($json, 'allDay'); + $event->title = Helpers::safeArrayGet($json, 'title'); + $event->location = Helpers::safeArrayGet($json, 'location'); + $event->comments = Helpers::safeArrayGet($json, 'comments'); + $event->categoryId = Helpers::safeArrayGet($json, 'categoryId'); + $event->reminderValue = Helpers::safeArrayGet($json, 'reminderValue'); + $event->reminderUnits = ReminderUnit::getByName(Helpers::safeArrayGet($json, 'reminderUnits')); + + return $event; + } + + static function getBusinessHours() { + global $applicationContext, $hesk_settings; + + $calendarHandler = $applicationContext->get(CalendarHandler::clazz()); + + return output($calendarHandler->getBusinessHours($hesk_settings)); + } +} \ No newline at end of file diff --git a/api/Controllers/ServiceMessages/ServiceMessagesController.php b/api/Controllers/ServiceMessages/ServiceMessagesController.php new file mode 100644 index 00000000..48da1a7d --- /dev/null +++ b/api/Controllers/ServiceMessages/ServiceMessagesController.php @@ -0,0 +1,139 @@ +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); + } +} \ No newline at end of file diff --git a/api/Controllers/Tickets/StaffTicketController.php b/api/Controllers/Tickets/StaffTicketController.php index 023f5352..e049bca5 100644 --- a/api/Controllers/Tickets/StaffTicketController.php +++ b/api/Controllers/Tickets/StaffTicketController.php @@ -4,6 +4,7 @@ namespace Controllers\Tickets; use BusinessLogic\Helpers; +use BusinessLogic\Security\UserContext; use BusinessLogic\Tickets\EditTicketModel; use BusinessLogic\Tickets\TicketDeleter; use BusinessLogic\Tickets\TicketEditor; @@ -45,6 +46,20 @@ class StaffTicketController extends \BaseClass { return; } + static function updateDueDate($id) { + /* @var $userContext UserContext */ + global $applicationContext, $userContext, $hesk_settings; + + /* @var $ticketEditor TicketEditor */ + $ticketEditor = $applicationContext->get(TicketEditor::clazz()); + + $json = JsonRetriever::getJsonData(); + + $dueDate = date('Y-m-d H:i:s', strtotime(Helpers::safeArrayGet($json, 'dueDate'))); + + $ticketEditor->updateDueDate($id, $dueDate, $userContext, $hesk_settings); + } + private function getEditTicketModel($id, $jsonRequest) { $editTicketModel = new EditTicketModel(); $editTicketModel->id = $id; diff --git a/api/Core/Constants/Priority.php b/api/Core/Constants/Priority.php index 39b86313..de361ab3 100644 --- a/api/Core/Constants/Priority.php +++ b/api/Core/Constants/Priority.php @@ -8,4 +8,19 @@ class Priority extends \BaseClass { const HIGH = 1; const MEDIUM = 2; const LOW = 3; + + static function getByValue($value) { + switch ($value) { + case self::CRITICAL: + return 'CRITICAL'; + case self::HIGH: + return 'HIGH'; + case self::MEDIUM: + return 'MEDIUM'; + case self::LOW: + return 'LOW'; + default: + return 'UNKNOWN'; + } + } } \ No newline at end of file diff --git a/api/Core/json_error.php b/api/Core/json_error.php index 1ff438c0..c9ea1b61 100644 --- a/api/Core/json_error.php +++ b/api/Core/json_error.php @@ -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; diff --git a/api/DataAccess/Calendar/CalendarGateway.php b/api/DataAccess/Calendar/CalendarGateway.php new file mode 100644 index 00000000..f0f95885 --- /dev/null +++ b/api/DataAccess/Calendar/CalendarGateway.php @@ -0,0 +1,266 @@ +init(); + + $events = array(); + + // EVENTS + $sql = "SELECT `events`.*, `categories`.`name` AS `category_name`, `categories`.`background_color` AS `background_color`, + `categories`.`foreground_color` AS `foreground_color`, `categories`.`display_border_outline` AS `display_border`, + `reminders`.`amount` AS `reminder_value`, `reminders`.`unit` AS `reminder_unit` + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "calendar_event` AS `events` + INNER JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "categories` AS `categories` + ON `events`.`category` = `categories`.`id` + LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "calendar_event_reminder` AS `reminders` + ON `reminders`.`user_id` = " . intval($searchEventsFilter->reminderUserId) . " + AND `reminders`.`event_id` = `events`.`id` + WHERE 1=1"; + + if ($searchEventsFilter->startTime !== null && $searchEventsFilter->endTime !== null) { + $startTimeSql = "CONVERT_TZ(FROM_UNIXTIME(" . hesk_dbEscape($searchEventsFilter->startTime) . " / 1000), @@session.time_zone, '+00:00')"; + $endTimeSql = "CONVERT_TZ(FROM_UNIXTIME(" . hesk_dbEscape($searchEventsFilter->endTime) . " / 1000), @@session.time_zone, '+00:00')"; + + + $sql .= " AND NOT (`end` < {$startTimeSql} OR `start` > {$endTimeSql}) + AND `categories`.`usage` <> 1"; + } + + if ($searchEventsFilter->eventId !== null) { + $sql .= " AND `events`.`id` = " . intval($searchEventsFilter->eventId); + } + + if (!empty($searchEventsFilter->categories)) { + $categoriesAsString = implode(',', $searchEventsFilter->categories); + $sql .= " AND `events`.`category` IN (" . $categoriesAsString . ")"; + } + + $rs = hesk_dbQuery($sql); + while ($row = hesk_dbFetchAssoc($rs)) { + $event = new CalendarEvent(); + $event->id = intval($row['id']); + $event->startTime = $row['start']; + $event->endTime = $row['end']; + $event->allDay = Helpers::boolval($row['all_day']); + $event->title = $row['name']; + $event->location = $row['location']; + $event->comments = $row['comments']; + $event->categoryId = intval($row['category']); + $event->categoryName = Helpers::heskHtmlSpecialCharsDecode($row['category_name']); + $event->backgroundColor = $row['background_color']; + $event->foregroundColor = $row['foreground_color']; + $event->displayBorder = Helpers::boolval($row['display_border']); + $event->reminderValue = $row['reminder_value'] === null ? null : floatval($row['reminder_value']); + $event->reminderUnits = $row['reminder_unit'] === null ? null : ReminderUnit::getByValue($row['reminder_unit']); + + $auditTrailSql = "SELECT `at`.`id` AS `id`, `at`.`entity_id`, `at`.`language_key`, `at`.`date`, + `values`.`replacement_index`, `values`.`replacement_value` + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "audit_trail` AS `at` + INNER JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "audit_trail_to_replacement_values` AS `values` + ON `at`.`id` = `values`.`audit_trail_id` + WHERE `entity_id` = " . intval($event->id) . " + AND `entity_type` = '" . AuditTrailEntityType::CALENDAR_EVENT . "'"; + $auditTrailRs = hesk_dbQuery($auditTrailSql); + /* @var $auditTrailEntry AuditTrail */ + $auditTrailEntry = null; + while ($row = hesk_dbFetchAssoc($auditTrailRs)) { + if ($auditTrailEntry == null || intval($auditTrailEntry->id) !== intval($row['id'])) { + if ($auditTrailEntry !== null) { + $event->auditTrail[] = $auditTrailEntry; + } + + $auditTrailEntry = new AuditTrail(); + $auditTrailEntry->id = intval($row['id']); + $auditTrailEntry->entityId = intval($row['entity_id']); + $auditTrailEntry->entityType = AuditTrailEntityType::CALENDAR_EVENT; + $auditTrailEntry->languageKey = $row['language_key']; + $auditTrailEntry->date = $row['date']; + $auditTrailEntry->replacementValues = array(); + } + $auditTrailEntry->replacementValues[intval($row['replacement_index'])] = $row['replacement_value']; + } + + if ($auditTrailEntry !== null) { + $event->auditTrail[] = $auditTrailEntry; + } + + $events[] = $event; + } + + // TICKETS + if ($searchEventsFilter->includeTickets) { + $oldTimeSetting = $heskSettings['timeformat']; + $heskSettings['timeformat'] = 'Y-m-d'; + $currentDate = hesk_date(); + $heskSettings['timeformat'] = $oldTimeSetting; + + $sql = "SELECT `tickets`.`id` AS `id`, `trackid`, `subject`, `due_date`, `category`, `categories`.`name` AS `category_name`, `categories`.`background_color` AS `background_color`, + `categories`.`foreground_color` AS `foreground_color`, `categories`.`display_border_outline` AS `display_border`, + CASE WHEN `due_date` < '{$currentDate}' THEN 1 ELSE 0 END AS `overdue`, `owner`.`name` AS `owner_name`, `tickets`.`owner` AS `owner_id`, + `tickets`.`priority` AS `priority`, `text_to_status_xref`.`text` AS `status_name` + FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` AS `tickets` + INNER JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "categories` AS `categories` + ON `categories`.`id` = `tickets`.`category` + AND `categories`.`usage` <> 2 + LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` AS `owner` + ON `tickets`.`owner` = `owner`.`id` + LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "text_to_status_xref` AS `text_to_status_xref` + ON `tickets`.`status` = `text_to_status_xref`.`status_id` + AND `text_to_status_xref`.`language` = '" . hesk_dbEscape($heskSettings['language']) . "' + WHERE `due_date` >= {$startTimeSql} + AND `due_date` <= {$endTimeSql} + AND `status` IN (SELECT `id` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "statuses` WHERE `IsClosed` = 0) + AND (`owner` = " . $searchEventsFilter->reminderUserId; + + if ($searchEventsFilter->includeUnassignedTickets) { + $sql .= " OR `owner` = 0 "; + } + + if ($searchEventsFilter->includeTicketsAssignedToOthers) { + $sql .= " OR `owner` NOT IN (0, " . $searchEventsFilter->reminderUserId . ") "; + } + + $sql .= ")"; + + if (!empty($searchEventsFilter->categories)) { + $categoriesAsString = implode(',', $searchEventsFilter->categories); + $sql .= " AND `tickets`.`category` IN (" . $categoriesAsString . ")"; + } + + $rs = hesk_dbQuery($sql); + while ($row = hesk_dbFetchAssoc($rs)) { + $event = new TicketEvent(); + $event->id = intval($row['id']); + $event->trackingId = $row['trackid']; + $event->subject = $row['subject']; + $event->title = $row['subject']; + $event->startTime = $row['due_date']; + $event->url = $heskSettings['hesk_url'] . '/' . $heskSettings['admin_dir'] . '/admin_ticket.php?track=' . $event->trackingId; + $event->categoryId = intval($row['category']); + $event->categoryName = Helpers::heskHtmlSpecialCharsDecode($row['category_name']); + $event->backgroundColor = $row['background_color']; + $event->foregroundColor = $row['foreground_color']; + $event->displayBorder = Helpers::boolval($row['display_border']); + $event->owner = $row['owner_name']; + $event->priority = Priority::getByValue($row['priority']); + $event->status = $row['status_name']; + + $events[] = $event; + } + } + + $this->close(); + + return $events; + } + + /** + * @param $event CalendarEvent + * @param $userContext UserContext + * @param $heskSettings array + * @return CalendarEvent + */ + public function createEvent($event, $userContext, $heskSettings) { + $this->init(); + + hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "calendar_event` (`start`, `end`, `all_day`, `name`, + `location`, `comments`, `category`) VALUES ('" . hesk_dbEscape($event->startTime) . "', '" . hesk_dbEscape($event->endTime) . "', + '" . ($event->allDay ? 1 : 0) . "', '" . hesk_dbEscape(addslashes($event->title)) . "', + '" . hesk_dbEscape(addslashes($event->location)) . "', '". hesk_dbEscape(addslashes($event->comments)) . "', " . intval($event->categoryId) . ")"); + + $event->id = hesk_dbInsertID(); + + if ($event->reminderValue !== null) { + hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "calendar_event_reminder` (`user_id`, `event_id`, + `amount`, `unit`) VALUES (" . intval($userContext->id) . ", " . intval($event->id) . ", " . intval($event->reminderValue) . ", + " . intval($event->reminderUnits) . ")"); + } + + $this->close(); + } + + /** + * @param $event CalendarEvent + * @param $userContext UserContext + * @param $heskSettings array + */ + public function updateEvent($event, $userContext, $heskSettings) { + $this->init(); + + $sql = "UPDATE `" . hesk_dbEscape($heskSettings['db_pfix']) . "calendar_event` SET `start` = '" . hesk_dbEscape($event->startTime) + . "', `end` = '" . hesk_dbEscape($event->endTime) . "', `all_day` = '" . ($event->allDay ? 1 : 0) . "', `name` = '" + . hesk_dbEscape(addslashes($event->title)) . "', `location` = '" . hesk_dbEscape(addslashes($event->location)) . "', `comments` = '" + . hesk_dbEscape(addslashes($event->comments)) . "', `category` = " . intval($event->categoryId) . " WHERE `id` = " . intval($event->id); + + if ($event->reminderValue !== null) { + $delete_sql = "DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "calendar_event_reminder` WHERE `event_id` = " . intval($event->id) + . " AND `user_id` = " . intval($userContext->id); + hesk_dbQuery($delete_sql); + $insert_sql = "INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "calendar_event_reminder` (`user_id`, `event_id`, + `amount`, `unit`) VALUES (" . intval($userContext->id) . ", " . intval($event->id) . ", " . intval($event->reminderValue) . ", + " . intval($event->reminderUnits) . ")"; + hesk_dbQuery($insert_sql); + } + + hesk_dbQuery($sql); + + $this->close(); + } + + /** + * @param $id int + * @param $userContext UserContext + * @param $heskSettings array + */ + public function deleteEvent($id, $userContext, $heskSettings) { + $this->init(); + + hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "calendar_event_reminder` + WHERE `event_id` = " . intval($id) . " AND `user_id` = " . intval($userContext->id)); + hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "calendar_event` + WHERE `id` = " . intval($id)); + + $this->close(); + } + + public function getBusinessHours($heskSettings) { + $this->init(); + + $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "mfh_calendar_business_hours`"); + $businessHours = array(); + while ($row = hesk_dbFetchAssoc($rs)) { + $businessHour = new BusinessHours(); + $businessHour->dayOfWeek = intval($row['day_of_week']); + $businessHour->startTime = $row['start_time']; + $businessHour->endTime = $row['end_time']; + + $businessHours[] = $businessHour; + } + + $this->close(); + + return $businessHours; + } +} \ No newline at end of file diff --git a/api/DataAccess/CommonDao.php b/api/DataAccess/CommonDao.php index cb3f5b3e..dce640b8 100644 --- a/api/DataAccess/CommonDao.php +++ b/api/DataAccess/CommonDao.php @@ -11,7 +11,7 @@ class CommonDao extends \BaseClass { */ function init() { if (!function_exists('hesk_dbConnect')) { - throw new Exception('Database not loaded!'); + throw new \BaseException('Database not loaded!'); } hesk_dbConnect(); } diff --git a/api/DataAccess/CustomFields/CustomFieldsGateway.php b/api/DataAccess/CustomFields/CustomFieldsGateway.php new file mode 100644 index 00000000..ce26ba45 --- /dev/null +++ b/api/DataAccess/CustomFields/CustomFieldsGateway.php @@ -0,0 +1,26 @@ +init(); + + $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "custom_fields` WHERE `id` = " . intval($id)); + + if ($row = hesk_dbFetchAssoc($rs)) { + $customField = new CustomField(); + $customField->id = $row['id']; + $names = json_decode($row['name'], true); + $customField->name = (isset($names[$heskSettings['language']])) ? $names[$heskSettings['language']] : reset($names); + $customField->type = $row['type']; + $customField->properties = json_decode($row['value'], true); + + return $customField; + } else { + return null; + } + } +} \ No newline at end of file diff --git a/api/DataAccess/ServiceMessages/ServiceMessagesGateway.php b/api/DataAccess/ServiceMessages/ServiceMessagesGateway.php new file mode 100644 index 00000000..e15a593e --- /dev/null +++ b/api/DataAccess/ServiceMessages/ServiceMessagesGateway.php @@ -0,0 +1,182 @@ +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(); + } +} \ No newline at end of file diff --git a/api/DataAccess/Tickets/TicketGateway.php b/api/DataAccess/Tickets/TicketGateway.php index 4c72a9f2..2ea5a84d 100644 --- a/api/DataAccess/Tickets/TicketGateway.php +++ b/api/DataAccess/Tickets/TicketGateway.php @@ -440,4 +440,18 @@ class TicketGateway extends CommonDao { $this->close(); } + + function updateTicketDueDate($id, $dueDate, $heskSettings) { + $this->init(); + + $sqlDueDate = 'NULL'; + if ($dueDate != NULL) { + $sqlDueDate = "'" . date('Y-m-d H:i:s', strtotime($dueDate)) . "'"; + } + + hesk_dbQuery("UPDATE `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` SET `due_date` = {$sqlDueDate} + WHERE `id` = " . intval($id)); + + $this->close(); + } } \ No newline at end of file diff --git a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php index 69d817d6..5269a7b4 100644 --- a/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php +++ b/api/Tests/BusinessLogic/Tickets/TicketCreatorTests/CreateTicketForCustomerTest.php @@ -19,6 +19,7 @@ use BusinessLogic\Tickets\VerifiedEmailChecker; use BusinessLogic\ValidationModel; use Core\Constants\Priority; use DataAccess\AuditTrail\AuditTrailGateway; +use DataAccess\CustomFields\CustomFieldsGateway; use DataAccess\Security\UserGateway; use DataAccess\Settings\ModsForHeskSettingsGateway; use DataAccess\Statuses\StatusGateway; @@ -101,6 +102,9 @@ class CreateTicketTest extends TestCase { /* @var $auditTrailGateway \PHPUnit_Framework_MockObject_MockObject|AuditTrailGateway */ private $auditTrailGateway; + /* @var $customFieldsGateway \PHPUnit_Framework_MockObject_MockObject|CustomFieldsGateway */ + private $customFieldsGateway; + protected function setUp() { $this->ticketGateway = $this->createMock(TicketGateway::clazz()); $this->newTicketValidator = $this->createMock(NewTicketValidator::clazz()); @@ -112,10 +116,11 @@ class CreateTicketTest extends TestCase { $this->userGateway = $this->createMock(UserGateway::clazz()); $this->modsForHeskSettingsGateway = $this->createMock(ModsForHeskSettingsGateway::clazz()); $this->auditTrailGateway = $this->createMock(AuditTrailGateway::clazz()); + $this->customFieldsGateway = $this->createMock(CustomFieldsGateway::clazz()); $this->ticketCreator = new TicketCreator($this->newTicketValidator, $this->trackingIdGenerator, $this->autoassigner, $this->statusGateway, $this->ticketGateway, $this->verifiedEmailChecker, - $this->emailSenderHelper, $this->userGateway, $this->modsForHeskSettingsGateway, $this->auditTrailGateway); + $this->emailSenderHelper, $this->userGateway, $this->modsForHeskSettingsGateway, $this->auditTrailGateway, $this->customFieldsGateway); $this->ticketRequest = new CreateTicketByCustomerModel(); $this->ticketRequest->name = 'Name'; diff --git a/api/autoload.php b/api/autoload.php index 88c675b6..4355e688 100644 --- a/api/autoload.php +++ b/api/autoload.php @@ -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(); diff --git a/api/composer.lock b/api/composer.lock index 1668fba8..f882508e 100644 --- a/api/composer.lock +++ b/api/composer.lock @@ -692,16 +692,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.8.28", + "version": "v2.8.33", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "7fe089232554357efb8d4af65ce209fc6e5a2186" + "reference": "d64be24fc1eba62f9daace8a8918f797fc8e87cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7fe089232554357efb8d4af65ce209fc6e5a2186", - "reference": "7fe089232554357efb8d4af65ce209fc6e5a2186", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d64be24fc1eba62f9daace8a8918f797fc8e87cc", + "reference": "d64be24fc1eba62f9daace8a8918f797fc8e87cc", "shasum": "" }, "require": { @@ -748,7 +748,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-10-01T21:00:16+00:00" + "time": "2018-01-03T07:36:31+00:00" }, { "name": "zendframework/zend-code", @@ -1334,29 +1334,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.1.1", + "version": "4.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2" + "reference": "66465776cfc249844bde6d117abff1d22e06c2da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2d3d238c433cf69caeb4842e97a3223a116f94b2", - "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/66465776cfc249844bde6d117abff1d22e06c2da", + "reference": "66465776cfc249844bde6d117abff1d22e06c2da", "shasum": "" }, "require": { "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/reflection-common": "^1.0.0", "phpdocumentor/type-resolver": "^0.4.0", "webmozart/assert": "^1.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, "autoload": { "psr-4": { "phpDocumentor\\Reflection\\": [ @@ -1375,7 +1381,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-08-30T18:51:59+00:00" + "time": "2017-11-27T17:38:31+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -1426,16 +1432,16 @@ }, { "name": "phpspec/prophecy", - "version": "v1.7.2", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", - "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", "shasum": "" }, "require": { @@ -1447,7 +1453,7 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7" }, "type": "library", "extra": { @@ -1485,20 +1491,20 @@ "spy", "stub" ], - "time": "2017-09-04T11:05:03+00:00" + "time": "2017-11-24T13:59:53+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.2.3", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d" + "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", - "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1", "shasum": "" }, "require": { @@ -1507,14 +1513,13 @@ "php": "^7.0", "phpunit/php-file-iterator": "^1.4.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^2.0", + "phpunit/php-token-stream": "^2.0.1", "sebastian/code-unit-reverse-lookup": "^1.0.1", "sebastian/environment": "^3.0", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, "require-dev": { - "ext-xdebug": "^2.5", "phpunit/phpunit": "^6.0" }, "suggest": { @@ -1523,7 +1528,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.2.x-dev" + "dev-master": "5.3.x-dev" } }, "autoload": { @@ -1538,7 +1543,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1549,20 +1554,20 @@ "testing", "xunit" ], - "time": "2017-11-03T13:47:33+00:00" + "time": "2017-12-06T09:29:45+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.2", + "version": "1.4.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", "shasum": "" }, "require": { @@ -1596,7 +1601,7 @@ "filesystem", "iterator" ], - "time": "2016-10-03T07:40:28+00:00" + "time": "2017-11-27T13:52:08+00:00" }, { "name": "phpunit/php-text-template", @@ -1690,16 +1695,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0" + "reference": "791198a2c6254db10131eecfe8c06670700904db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0", - "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", "shasum": "" }, "require": { @@ -1735,7 +1740,7 @@ "keywords": [ "tokenizer" ], - "time": "2017-08-20T05:47:52+00:00" + "time": "2017-11-27T05:48:46+00:00" }, { "name": "phpunit/phpunit", @@ -1974,16 +1979,16 @@ }, { "name": "sebastian/comparator", - "version": "2.1.0", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1174d9018191e93cb9d719edec01257fc05f8158" + "reference": "11c07feade1d65453e06df3b3b90171d6d982087" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1174d9018191e93cb9d719edec01257fc05f8158", - "reference": "1174d9018191e93cb9d719edec01257fc05f8158", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/11c07feade1d65453e06df3b3b90171d6d982087", + "reference": "11c07feade1d65453e06df3b3b90171d6d982087", "shasum": "" }, "require": { @@ -2034,7 +2039,7 @@ "compare", "equality" ], - "time": "2017-11-03T07:16:52+00:00" + "time": "2018-01-12T06:34:42+00:00" }, { "name": "sebastian/diff", @@ -2488,16 +2493,16 @@ }, { "name": "symfony/console", - "version": "v2.8.28", + "version": "v2.8.33", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853" + "reference": "a4bd0f02ea156cf7b5138774a7ba0ab44d8da4fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f81549d2c5fdee8d711c9ab3c7e7362353ea5853", - "reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853", + "url": "https://api.github.com/repos/symfony/console/zipball/a4bd0f02ea156cf7b5138774a7ba0ab44d8da4fe", + "reference": "a4bd0f02ea156cf7b5138774a7ba0ab44d8da4fe", "shasum": "" }, "require": { @@ -2545,7 +2550,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-10-01T21:00:16+00:00" + "time": "2018-01-03T07:36:31+00:00" }, { "name": "symfony/debug", diff --git a/api/index.php b/api/index.php index 4766701c..ec493347 100644 --- a/api/index.php +++ b/api/index.php @@ -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; @@ -194,6 +201,7 @@ Link::all(array( '/v1/tickets' => action(\Controllers\Tickets\CustomerTicketController::clazz(), RequestMethod::all(), SecurityHandler::OPEN), // Tickets - Staff '/v1/staff/tickets/{i}' => action(\Controllers\Tickets\StaffTicketController::clazz(), RequestMethod::all()), + '/v1/staff/tickets/{i}/due-date' => action(\Controllers\Tickets\StaffTicketController::clazz() . '::updateDueDate', array(RequestMethod::PATCH), SecurityHandler::INTERNAL_OR_AUTH_TOKEN), // Attachments '/v1/tickets/{a}/attachments/{i}' => action(\Controllers\Attachments\PublicAttachmentController::clazz() . '::getRaw', RequestMethod::all()), '/v1/staff/tickets/{i}/attachments' => action(\Controllers\Attachments\StaffTicketAttachmentsController::clazz(), RequestMethod::all()), @@ -202,6 +210,21 @@ Link::all(array( '/v1/statuses' => action(\Controllers\Statuses\StatusController::clazz(), RequestMethod::all()), // Settings '/v1/settings' => action(\Controllers\Settings\SettingsController::clazz(), RequestMethod::all()), + // Calendar + '/v1/calendar/business-hours' => action(\Controllers\Calendar\CalendarController::clazz() . '::getBusinessHours', array(RequestMethod::GET), SecurityHandler::OPEN), + '/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 diff --git a/calendar.php b/calendar.php index bb6a9ee0..64488e4c 100644 --- a/calendar.php +++ b/calendar.php @@ -111,8 +111,20 @@ require_once(HESK_PATH . 'inc/header.inc.php'); +
    -

    -
    \ No newline at end of file +

    + + +

    +

    + \ No newline at end of file diff --git a/inc/admin_functions.inc.php b/inc/admin_functions.inc.php index 1de4b3a7..f2ace1dc 100644 --- a/inc/admin_functions.inc.php +++ b/inc/admin_functions.inc.php @@ -32,6 +32,7 @@ $hesk_settings['possible_ticket_list'] = array( 'staffreplies' => $hesklang['replies'] . ' (' . $hesklang['staff'] . ')', 'lastreplier' => $hesklang['last_replier'], 'time_worked' => $hesklang['ts'], + 'due_date' => $hesklang['due_date'], ); define('HESK_NO_ROBOTS', true); diff --git a/inc/common.inc.php b/inc/common.inc.php index 92fe0aa4..dd47cafa 100644 --- a/inc/common.inc.php +++ b/inc/common.inc.php @@ -185,12 +185,34 @@ function hesk_service_message($sm) ?>
    '; ?> - +
    +

    $v) { if (!hesk_isValidEmail($v)) { unset($all[$k]); @@ -1777,7 +1814,7 @@ function hesk_session_start() return true; } else { global $hesk_settings, $hesklang; - hesk_error("$hesklang[no_session] $hesklang[contact_webmaster] $hesk_settings[webmaster_mail]"); + hesk_error("$hesklang[no_session] $hesklang[contact_webmsater] $hesk_settings[webmaster_mail]"); } } // END hesk_session_start() @@ -2109,6 +2146,31 @@ function mfh_getNumberOfDownloadsForAttachment($att_id, $table = 'attachments') return $rec['download_count']; } +function mfh_getAttachmentFileSize($att_id, $table = 'attachments') { + global $hesk_settings; + + $res = hesk_dbQuery('SELECT `size` FROM `' . hesk_dbEscape($hesk_settings['db_pfix'] . $table) . "` WHERE `att_id` = " . intval($att_id)); + $rec = hesk_dbFetchAssoc($res); + return human_filesize($rec['size']); +} + +function human_filesize($bytes, $decimals = 2) { + global $hesklang; + + $sz = 'BKMGTP'; + $factor = floor((strlen($bytes) - 1) / 3); + + if ($factor < strlen($sz)) { + $factorName = @$sz[$factor]; + if ($factorName !== 'B') { + $factorName .= 'B'; + } + + return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . $factorName; + } + return $hesklang['unknown']; +} + function mfh_getSettings() { global $hesk_settings; diff --git a/inc/custom_fields.inc.php b/inc/custom_fields.inc.php index 7cbd4ef9..bec51b12 100755 --- a/inc/custom_fields.inc.php +++ b/inc/custom_fields.inc.php @@ -66,7 +66,7 @@ function hesk_load_custom_fields($category=0, $use_cache=1) // Name for display in ticket list; punctuation removed and shortened $row['title'] = hesk_remove_punctuation($row['name']); - $row['title'] = strlen($row['title']) > 30 ? substr($row['title'], 0, 30) . '...' : $row['title']; + $row['title'] = hesk_mb_strlen($row['title']) > 30 ? hesk_mb_substr($row['title'], 0, 30) . '...' : $row['title']; // A version with forced punctuation $row['name:'] = in_array(substr($row['name'], -1), array(':', '?', '!', '.') ) ? $row['name'] : $row['name'] . ':'; diff --git a/inc/email_functions.inc.php b/inc/email_functions.inc.php index 5483d121..7be13062 100644 --- a/inc/email_functions.inc.php +++ b/inc/email_functions.inc.php @@ -50,20 +50,19 @@ function hesk_notifyCustomerForVerifyEmail($email_template = 'verify_email', $ac $ccEmails = array(); $bccEmails = array(); - //TODO Update the email custom field to handle this properly - /*foreach ($hesk_settings['custom_fields'] as $k => $v) { + foreach ($hesk_settings['custom_fields'] as $k => $v) { if ($v['use']) { if ($v['type'] == 'email' && !empty($ticket[$k])) { - if ($v['value'] == 'cc') { + if ($v['value']['email_type'] == 'cc') { $emails = explode(',', $ticket[$k]); array_push($ccEmails, $emails); - } elseif ($v['value'] == 'bcc') { + } elseif ($v['value']['email_type'] == 'bcc') { $emails = explode(',', $ticket[$k]); array_push($bccEmails, $emails); } } } - }*/ + } hesk_mail($ticket['email'], $subject, $message, $htmlMessage, $modsForHesk_settings, $ccEmails, $bccEmails, $hasMessage); } @@ -95,18 +94,19 @@ function hesk_notifyCustomer($modsForHesk_settings, $email_template = 'new_ticke $ccEmails = array(); $bccEmails = array(); - //TODO Update the email custom field to handle this properly - /*foreach ($hesk_settings['custom_fields'] as $k => $v) { + foreach ($hesk_settings['custom_fields'] as $k => $v) { if ($v['use']) { - if ($v['type'] == 'email' && !empty($ticket[$k])) { - if ($v['value'] == 'cc') { - array_push($ccEmails, $ticket[$k]); - } elseif ($v['value'] == 'bcc') { - array_push($bccEmails, $ticket[$k]); + if ($v['type'] == 'email' && !empty($ticket[$k]) && isset($v['value']['emails_to_receive'])) { + if ($v['value']['email_type'] == 'cc') { + $emails = explode(',', $ticket[$k]); + array_push($ccEmails, $emails); + } elseif ($v['value']['email_type'] == 'bcc') { + $emails = explode(',', $ticket[$k]); + array_push($bccEmails, $emails); } } } - }*/ + } // Send e-mail hesk_mail($ticket['email'], $subject, $message, $htmlMessage, $modsForHesk_settings, $ccEmails, $bccEmails, $hasMessage); @@ -513,6 +513,7 @@ function hesk_mail($to, $subject, $message, $htmlMessage, $modsForHesk_settings, // Remove duplicate recipients $to_arr = array_unique(explode(',', $to)); + $to_arr = array_values($to_arr); $to = implode(',', $to_arr); // Use PHP's mail function diff --git a/inc/header.inc.php b/inc/header.inc.php index 970fc128..a436e76d 100644 --- a/inc/header.inc.php +++ b/inc/header.inc.php @@ -175,7 +175,7 @@ header('X-UA-Compatible: IE=edge'); /* If page requires WYSIWYG editor include TinyMCE Javascript */ if (defined('WYSIWYG') && $hesk_settings['kb_wysiwyg']) { ?> - + internal-api/js/lang.js?v="> + $hesk_settings['kb_substrart']) { + if (hesk_mb_strlen($txt) > $hesk_settings['kb_substrart']) { // The quick but not 100% accurate way (number of chars displayed may be lower than the limit) - return substr($txt, 0, $hesk_settings['kb_substrart']) . '...'; + return hesk_mb_substr($txt, 0, $hesk_settings['kb_substrart']) . '...'; // If you want a more accurate, but also slower way, use this instead - // return hesk_htmlentities( substr( hesk_html_entity_decode($txt), 0, $hesk_settings['kb_substrart'] ) ) . '...'; + // return hesk_htmlentities( hesk_mb_substr( hesk_html_entity_decode($txt), 0, $hesk_settings['kb_substrart'] ) ) . '...'; } return $txt; diff --git a/inc/pipe_functions.inc.php b/inc/pipe_functions.inc.php index 1a56eca6..758ed9e8 100755 --- a/inc/pipe_functions.inc.php +++ b/inc/pipe_functions.inc.php @@ -366,7 +366,7 @@ function hesk_stripQuotedText($message) foreach ($hesk_settings['languages'] as $language => $settings) { if (($found = strpos($message, $settings['hr'])) !== false) { // "Reply above this line" tag found, strip quoted reply - $message = substr($message, 0, $found); + $message = hesk_mb_substr($message, 0, $found); $message .= "\n" . $hesklang['qrr']; // Set language to the detected language diff --git a/inc/posting_functions.inc.php b/inc/posting_functions.inc.php index 9ab98909..8111bcde 100644 --- a/inc/posting_functions.inc.php +++ b/inc/posting_functions.inc.php @@ -23,7 +23,7 @@ function hesk_newTicket($ticket, $isVerified = true) global $hesk_settings, $hesklang, $hesk_db_link; // Generate a subject if necessary - if (strlen($ticket['subject']) < 1) + if (hesk_mb_strlen($ticket['subject']) < 1) { $ticket['subject'] = sprintf($hesklang['default_subject'], $ticket['name']); } diff --git a/inc/print_tickets.inc.php b/inc/print_tickets.inc.php index 66d2534a..3b4c0f04 100644 --- a/inc/print_tickets.inc.php +++ b/inc/print_tickets.inc.php @@ -45,7 +45,8 @@ LEFT(`message`, 400) AS `message`, `replierid`, `archive`, `locked`, -`merged` +`merged`, +`due_date` "; foreach ($hesk_settings['custom_fields'] as $k => $v) { diff --git a/inc/show_admin_nav.inc.php b/inc/show_admin_nav.inc.php index f6d7de9d..ddb11a74 100644 --- a/inc/show_admin_nav.inc.php +++ b/inc/show_admin_nav.inc.php @@ -53,7 +53,7 @@ $mails = mfh_get_mail_headers_for_dropdown($_SESSION['id'], $hesk_settings, $hes ?>
  • > - > + >
  • $row['name']) { - $row['name'] = (strlen($row['name']) > 30) ? substr($row['name'], 0, 30) . '...' : $row['name']; + $row['name'] = (hesk_mb_strlen($row['name']) > 30) ? hesk_mb_substr($row['name'],0,30) . '...' : $row['name']; $selected = ($row['id'] == $category) ? 'selected="selected"' : ''; $category_options .= ''; } } else { $res2 = hesk_dbQuery('SELECT `id`, `name` FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'categories` WHERE ' . hesk_myCategories('id') . ' ORDER BY `cat_order` ASC'); while ($row = hesk_dbFetchAssoc($res2)) { - $row['name'] = (strlen($row['name']) > 30) ? substr($row['name'], 0, 30) . '...' : $row['name']; + $row['name'] = (hesk_mb_strlen($row['name']) > 30) ? hesk_mb_substr($row['name'],0,30) . '...' : $row['name']; $selected = ($row['id'] == $category) ? 'selected="selected"' : ''; $category_options .= ''; } @@ -436,7 +436,7 @@ $more2 = empty($_GET['more2']) ? 0 : 1; $v['name'] = $hesklang[$v['name']]; } - $v['name'] = (strlen($v['name']) > 30) ? substr($v['name'], 0, 30) . '...' : $v['name']; + $v['name'] = (hesk_mb_strlen($v['name']) > 30) ? hesk_mb_substr($v['name'],0,30) . '...' : $v['name']; echo ''; } } diff --git a/inc/ticket_list.inc.php b/inc/ticket_list.inc.php index 1e871e48..117a6e53 100644 --- a/inc/ticket_list.inc.php +++ b/inc/ticket_list.inc.php @@ -238,13 +238,15 @@ if ($total > 0) { break; case 2: $ticket['priority'] = ''; + $color = $modsForHesk_settings['highlight_ticket_rows_based_on_priority'] ? 'success' : ''; break; default: $ticket['priority'] = ''; + $color = $modsForHesk_settings['highlight_ticket_rows_based_on_priority'] ? 'info' : ''; } // Set message (needed for row title) - $ticket['message'] = $first_line . substr(strip_tags($ticket['message']), 0, 200) . '...'; + $ticket['message'] = $first_line . hesk_mb_substr(strip_tags($ticket['message']),0,200).'...'; // Start ticket row echo ' @@ -373,6 +375,16 @@ if ($total > 0) { echo '' . $ticket['time_worked'] . ''; } + // Print due date + if (hesk_show_column('due_date')) { + $due_date = $hesklang['none']; + if ($ticket['due_date'] != null) { + $due_date = hesk_date($ticket['due_date'], false, true, false); + } + + echo '' . ($ticket['due_date'] == null ? 'NONE' : date('Y-m-d', $due_date)) . ''; + } + // Print custom fields foreach ($hesk_settings['custom_fields'] as $key => $value) { if ($value['use'] && hesk_show_column($key)) { diff --git a/inc/tiny_mce/3.5.11/plugins/autolink/editor_plugin.js b/inc/tiny_mce/3.5.12/plugins/autolink/editor_plugin.js similarity index 100% rename from inc/tiny_mce/3.5.11/plugins/autolink/editor_plugin.js rename to inc/tiny_mce/3.5.12/plugins/autolink/editor_plugin.js diff --git a/inc/tiny_mce/3.5.11/plugins/autolink/editor_plugin_src.js b/inc/tiny_mce/3.5.12/plugins/autolink/editor_plugin_src.js similarity index 100% rename from inc/tiny_mce/3.5.11/plugins/autolink/editor_plugin_src.js rename to inc/tiny_mce/3.5.12/plugins/autolink/editor_plugin_src.js diff --git a/inc/view_attachment_functions.inc.php b/inc/view_attachment_functions.inc.php index 3aee6e88..cd3c3b52 100644 --- a/inc/view_attachment_functions.inc.php +++ b/inc/view_attachment_functions.inc.php @@ -24,6 +24,7 @@ function mfh_listAttachments($attachments = '', $reply = 0, $is_staff) if ($is_staff) { echo '' . $hesklang['download_count'] . ''; } + echo '' . 'File size' . ''; echo '' . $hesklang['action'] . ' '; @@ -72,6 +73,7 @@ function mfh_listAttachments($attachments = '', $reply = 0, $is_staff) if ($is_staff) { echo '' . mfh_getNumberOfDownloadsForAttachment($att_id) . ''; } + echo '' . mfh_getAttachmentFileSize($att_id) . ''; echo '
    '; /* Can edit and delete tickets? */ @@ -279,7 +281,6 @@ function display_dropzone_field($url, $id = 'filedrop', $max_files_override = -1 paramName: 'attachment', url: '" . $url . "', parallelUploads: ".$max_files.", - uploadMultiple: true, maxFiles: ".$max_files.", acceptedFiles: ".json_encode($acceptedFiles).", maxFilesize: ".$size.", // MB diff --git a/index.php b/index.php index e9082980..a5b624a6 100644 --- a/index.php +++ b/index.php @@ -79,7 +79,6 @@ function print_select_category($number_of_categories) ?>
    -

    @@ -322,6 +321,13 @@ function print_add_ticket()

    +
    @@ -892,10 +898,10 @@ function print_add_ticket() echo ''; } + echo ''; if (!empty($v['mfh_description'])) { echo '
    ' . $v['mfh_description'] . '
    '; } - echo ''; echo '
    '; @@ -1315,15 +1321,13 @@ function print_start() 0) - { + $service_messages = mfh_get_service_messages('CUSTOMER_HOME'); + if (count($service_messages) > 0) { ?>
    diff --git a/install/database-validation.php b/install/database-validation.php index 4b2b14ef..d90aa816 100644 --- a/install/database-validation.php +++ b/install/database-validation.php @@ -5,6 +5,59 @@ require(HESK_PATH . 'install/install_functions.inc.php'); require(HESK_PATH . 'hesk_settings.inc.php'); hesk_dbConnect(); + +/* +We have four possible validation scenarios: + +1. Fresh install - the user has never installed Mods for HESK before. Nothing to validate. +2. Installed a really old version - we don't have a previous version to start from. +3. Installed a recent version, but before migrations began - just pull the version # and use the dictionary below. +4. Migration number present in the settings table. Take that number and run with it. + */ + +$tableSql = hesk_dbQuery("SHOW TABLES LIKE '" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings'"); +$startingValidationNumber = 1; +if (hesk_dbNumRows($tableSql) > 0) { + // They have installed at LEAST to version 1.6.0. Just pull the version number OR migration number + $migrationNumberSql = hesk_dbQuery("SELECT `Value` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` WHERE `Key` = 'migrationNumber'"); + if ($migrationRow = hesk_dbFetchAssoc($migrationNumberSql)) { + $startingValidationNumber = intval($migrationRow['Value']); + } else { + $versionSql = hesk_dbQuery("SELECT `Value` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` WHERE `Key` = 'modsForHeskVersion'"); + $versionRow = hesk_dbFetchAssoc($versionSql); + + $migration_map = array( + // Pre-1.4.0 to 1.5.0 did not have a settings table + '1.6.0' => 22, '1.6.1' => 23, '1.7.0' => 27, '2.0.0' => 37, '2.0.1' => 38, '2.1.0' => 39, '2.1.1' => 42, + '2.2.0' => 47, '2.2.1' => 48, '2.3.0' => 68, '2.3.1' => 69, '2.3.2' => 70, '2.4.0' => 86, '2.4.1' => 87, + '2.4.2' => 88, '2.5.0' => 98, '2.5.1' => 99, '2.5.2' => 100, '2.5.3' => 101, '2.5.4' => 102, '2.5.5' => 103, + '2.6.0' => 121, '2.6.1' => 122, '2.6.2' => 125, '2.6.3' => 126, '2.6.4' => 127, '3.0.0 beta 1' => 130, + '3.0.0 RC 1' => 131, '3.0.0' => 132, '3.0.1' => 133, '3.0.2' => 135, '3.0.3' => 136, '3.0.4' => 137, + '3.0.5' => 138, '3.0.6' => 139, '3.0.7' => 140, '3.1.0' => 153, '3.1.1' => 154 + ); + $startingValidationNumber = $migration_map[$versionRow['Value']]; + } +} else { + // migration # => sql for checking + $versionChecks = array( + // 1.5.0 -> users.active + 14 => "SHOW COLUMNS FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "users` LIKE 'active'", + // 1.4.1 -> denied_emails + 11 => "SHOW TABLES LIKE '" . hesk_dbEscape($hesk_settings['db_pfix']) . "denied_emails'", + // 1.4.0 -> denied ips + 9 => "SHOW TABLES LIKE '" . hesk_dbEscape($hesk_settings['db_pfix']) . "denied_ips'", + // Pre-1.4.0 but still something -> statuses + 7 => "SHOW TABLES LIKE '" . hesk_dbEscape($hesk_settings['db_pfix']) . "statuses'" + ); + + foreach ($versionChecks as $migrationNumber => $sql) { + $rs = hesk_dbQuery($sql); + if (hesk_dbNumRows($rs) > 0) { + $startingValidationNumber = $migrationNumber; + break; + } + } +} ?> @@ -26,12 +79,11 @@ hesk_dbConnect();
    +

    Results

    @@ -61,157 +121,192 @@ hesk_dbConnect(); $('#all-good').show()"; - } else { + } elseif ($failed) { echo ""; } + + if ($skipped) { + echo ""; + } ?> @@ -220,39 +315,53 @@ hesk_dbConnect(); 0; - - output_result('Setting Exists: ' . $setting_name, $all_good); - - return $all_good !== false; -} - -function run_table_check($table_name) { - return run_column_check($table_name, '1'); -} - -function run_column_check($table_name, $column_name) { - global $hesk_settings; - - if ($column_name == '1') { - $all_good = run_check('SELECT ' . $column_name . ' FROM `' . $hesk_settings['db_pfix'] . $table_name . '` LIMIT 1'); - - output_result('Table Exists: ' . $table_name, - $all_good); + if ($startingValidationNumber < $minimumValidationNumber) { + $checks = 'SKIPPED'; } else { - $all_good = run_check('SELECT `' . $column_name . '` FROM `' . $hesk_settings['db_pfix'] . $table_name . '` LIMIT 1'); - output_result('Column Exists: ' . $table_name . '.' . $column_name, - $all_good); + $res = run_check("SELECT 1 FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` WHERE `Key` = '{$setting_name}'", false); + $checks = hesk_dbNumRows($res) > 0 ? 'PASS' : 'FAIL'; } - return $all_good !== false; + output_result('Setting Exists: ' . $setting_name, $checks); + + return $checks; } -function run_check($sql) { +function run_table_check($table_name, $minimumValidationNumber) { + + return run_column_check($table_name, '1', $minimumValidationNumber); +} + +function run_column_check($table_name, $column_name, $minimumValidationNumber) { + global $hesk_settings, $startingValidationNumber; + + if ($column_name == '1') { + if ($startingValidationNumber < $minimumValidationNumber) { + $checks = 'SKIPPED'; + } else { + $checks = run_check('SELECT ' . $column_name . ' FROM `' . $hesk_settings['db_pfix'] . $table_name . '` LIMIT 1'); + } + + output_result('Table Exists: ' . $table_name, + $checks); + } else { + if ($startingValidationNumber < $minimumValidationNumber) { + $checks = 'SKIPPED'; + } else { + $checks = run_check('SELECT `' . $column_name . '` FROM `' . $hesk_settings['db_pfix'] . $table_name . '` LIMIT 1'); + } + + output_result('Column Exists: ' . $table_name . '.' . $column_name, + $checks); + } + + return $checks; +} + +function run_check($sql, $returnString = true) { global $hesk_last_query; global $hesk_db_link; if (function_exists('mysqli_connect')) { @@ -261,23 +370,43 @@ function run_check($sql) { } $hesk_last_query = $sql; - return @mysqli_query($hesk_db_link, $sql); + if ($returnString) { + return @mysqli_query($hesk_db_link, $sql) ? 'PASS' : 'FAIL'; + } else { + return @mysqli_query($hesk_db_link, $sql); + } + } else { if (!$hesk_db_link && !hesk_dbConnect()) { return false; } $hesk_last_query = $sql; - return $res = @mysql_query($sql, $hesk_db_link); + if ($returnString) { + return $res = @mysql_query($sql, $hesk_db_link) ? 'PASS' : 'FAIL'; + } else { + return $res = @mysql_query($sql, $hesk_db_link); + } } } -function output_result($change_title, $success) { - $css_color = 'success'; - $text = ' Success'; - if (!$success) { - $css_color = 'danger'; - $text = ' Failure'; +function output_result($change_title, $status) { + switch ($status) { + case 'PASS': + $css_color = 'success'; + $text = ' Success'; + break; + case 'FAIL': + $css_color = 'danger'; + $text = ' Failure'; + break; + case 'SKIPPED': + $css_color = 'default'; + $text = ' Skipped'; + break; + default: + $css_color = 'danger'; + $text = 'WTF?! ' . $status; } $formatted_text = sprintf('%s%s', $change_title, $css_color, $text); diff --git a/install/index.php b/install/index.php index e6199ac3..699e5c9b 100644 --- a/install/index.php +++ b/install/index.php @@ -31,9 +31,9 @@ if (hesk_dbNumRows($tableSql) > 0) { '1.6.0' => 22, '1.6.1' => 23, '1.7.0' => 27, '2.0.0' => 37, '2.0.1' => 38, '2.1.0' => 39, '2.1.1' => 42, '2.2.0' => 47, '2.2.1' => 48, '2.3.0' => 68, '2.3.1' => 69, '2.3.2' => 70, '2.4.0' => 86, '2.4.1' => 87, '2.4.2' => 88, '2.5.0' => 98, '2.5.1' => 99, '2.5.2' => 100, '2.5.3' => 101, '2.5.4' => 102, '2.5.5' => 103, - '2.6.0' => 121, '2.6.1' => 122, '2.6.2' => 125, '2.6.3' => 126, '2.6.4' => 127, '3.0.0' => 132, '3.0.1' => 133, - '3.0.2' => 135, '3.0.3' => 136, '3.0.4' => 137, '3.0.5' => 138, '3.0.6' => 139, '3.0.7' => 140, '3.1.0' => 153, - '3.1.1' => 154 + '2.6.0' => 121, '2.6.1' => 122, '2.6.2' => 125, '2.6.3' => 126, '2.6.4' => 127, '3.0.0 beta 1' => 130, + '3.0.0 RC 1' => 131, '3.0.0' => 132, '3.0.1' => 133, '3.0.2' => 135, '3.0.3' => 136, '3.0.4' => 137, + '3.0.5' => 138, '3.0.6' => 139, '3.0.7' => 140, '3.1.0' => 153, '3.1.1' => 154 ); $startingMigrationNumber = $migration_map[$versionRow['Value']]; } diff --git a/install/install_functions.inc.php b/install/install_functions.inc.php index 8dcae5c0..894bb47e 100644 --- a/install/install_functions.inc.php +++ b/install/install_functions.inc.php @@ -15,8 +15,8 @@ if (!defined('IN_SCRIPT')) {die('Invalid attempt');} // We will be installing this HESK version: -define('HESK_NEW_VERSION','2.7.5'); -define('MODS_FOR_HESK_NEW_VERSION','3.2.4'); +define('HESK_NEW_VERSION','2.7.6'); +define('MODS_FOR_HESK_NEW_VERSION','3.3.0'); define('REQUIRE_PHP_VERSION','5.3.0'); define('REQUIRE_MYSQL_VERSION','5.0.7'); diff --git a/install/js/install-script.js b/install/js/install-script.js index 0e6eda7a..04835fc0 100644 --- a/install/js/install-script.js +++ b/install/js/install-script.js @@ -104,10 +104,10 @@ function executeMigration(startingMigrationNumber, migrationNumber, latestMigrat console.log('latestMigrationNumber: ' + latestMigrationNumber); console.info('---'); if (migrationNumber === latestMigrationNumber || (migrationNumber === startingMigrationNumber && direction === 'down')) { - updateProgressBar(migrationNumber, latestMigrationNumber, direction === 'down', true); + updateProgressBar(migrationNumber - startingMigrationNumber, latestMigrationNumber - startingMigrationNumber, direction === 'down', true); console.log('%c Success! ', 'color: white; background-color: green; font-size: 2em'); } else { - updateProgressBar(migrationNumber, latestMigrationNumber, false, false); + updateProgressBar(migrationNumber - startingMigrationNumber, latestMigrationNumber - startingMigrationNumber, false, false); var newMigrationNumber = direction === 'up' ? migrationNumber + 1 : migrationNumber - 1; executeMigration(startingMigrationNumber, newMigrationNumber, latestMigrationNumber, direction); } @@ -121,7 +121,7 @@ function executeMigration(startingMigrationNumber, migrationNumber, latestMigrat $errorBlock = $('#error-block'); $errorBlock.html($errorBlock.html() + "

    An error occurred! (Error Code: " + migrationNumber + ")
    " + message).show(); - updateProgressBar(migrationNumber, latestMigrationNumber, true, false); + updateProgressBar(migrationNumber - startingMigrationNumber, latestMigrationNumber - startingMigrationNumber, true, false); if (direction === 'up') { // Revert! diff --git a/install/migrations/Pre140/Statuses/AddIntColumnUpDropTableDown.php b/install/migrations/Pre140/Statuses/AddIntColumnUpDropTableDown.php index b6bf841e..c3053741 100644 --- a/install/migrations/Pre140/Statuses/AddIntColumnUpDropTableDown.php +++ b/install/migrations/Pre140/Statuses/AddIntColumnUpDropTableDown.php @@ -7,10 +7,12 @@ use AbstractMigration; class AddIntColumnUpDropTableDown extends AbstractMigration { function up($hesk_settings) { - $this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` ADD COLUMN `status_int` INT NOT NULL DEFAULT 0 AFTER `status`;"); + // We no longer need to do this thanks to HESK 2.7.0 + //$this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` ADD COLUMN `status_int` INT NOT NULL DEFAULT 0 AFTER `status`;"); } function down($hesk_settings) { - $this->executeQuery("DROP TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "statuses`"); + // Moved to migration #2 for clarity + //$this->executeQuery("DROP TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "statuses`"); } } \ No newline at end of file diff --git a/install/migrations/Pre140/Statuses/CreateStatusesTable.php b/install/migrations/Pre140/Statuses/CreateStatusesTable.php index 0d7dbef7..8b2321c2 100644 --- a/install/migrations/Pre140/Statuses/CreateStatusesTable.php +++ b/install/migrations/Pre140/Statuses/CreateStatusesTable.php @@ -23,6 +23,9 @@ class CreateStatusesTable extends \AbstractMigration { } function down($hesk_settings) { - $this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` ADD COLUMN `status_int` INT NOT NULL AFTER `status`;"); + // We no longer need to do this thanks to HESK 2.7.0 + //$this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` ADD COLUMN `status_int` INT NOT NULL AFTER `status`;"); + // Moved from migration #1 + $this->executeQuery("DROP TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "statuses`"); } } \ No newline at end of file diff --git a/install/migrations/Pre140/Statuses/DropOldStatusColumn.php b/install/migrations/Pre140/Statuses/DropOldStatusColumn.php index eb41f6b6..0e6d136a 100644 --- a/install/migrations/Pre140/Statuses/DropOldStatusColumn.php +++ b/install/migrations/Pre140/Statuses/DropOldStatusColumn.php @@ -8,14 +8,16 @@ use AbstractMigration; class DropOldStatusColumn extends AbstractMigration { function up($hesk_settings) { - $this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` DROP COLUMN `status`"); + // We no longer need to do this thanks to HESK 2.7.0 + //$this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` DROP COLUMN `status`"); } function down($hesk_settings) { - $ticketsRS = $this->executeQuery("SELECT `id`, `status_int` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets`;"); - while ($currentResult = hesk_dbFetchAssoc($ticketsRS)) { - - $this->executeQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `status_int` = '" . intval($currentResult['status']) . "' WHERE `id` = " . $currentResult['id']); - } + // We no longer need to do this thanks to HESK 2.7.0 + //$ticketsRS = $this->executeQuery("SELECT `id`, `status_int` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets`;"); + //while ($currentResult = hesk_dbFetchAssoc($ticketsRS)) { + // + // $this->executeQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `status_int` = '" . intval($currentResult['status_int']) . "' WHERE `id` = " . $currentResult['id']); + //} } } \ No newline at end of file diff --git a/install/migrations/Pre140/Statuses/MoveStatusesToNewColumn.php b/install/migrations/Pre140/Statuses/MoveStatusesToNewColumn.php index ce64aced..58037bec 100644 --- a/install/migrations/Pre140/Statuses/MoveStatusesToNewColumn.php +++ b/install/migrations/Pre140/Statuses/MoveStatusesToNewColumn.php @@ -7,14 +7,16 @@ use AbstractMigration; class MoveStatusesToNewColumn extends AbstractMigration { function up($hesk_settings) { - $ticketsRS = $this->executeQuery("SELECT `id`, `status` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets`;"); - while ($currentResult = hesk_dbFetchAssoc($ticketsRS)) { - - $this->executeQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `status_int` = " . $currentResult['status'] . " WHERE `id` = " . $currentResult['id']); - } + // We no longer need to do this thanks to HESK 2.7.0 + //$ticketsRS = $this->executeQuery("SELECT `id`, `status` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets`;"); + //while ($currentResult = hesk_dbFetchAssoc($ticketsRS)) { + // + // $this->executeQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `status_int` = " . $currentResult['status'] . " WHERE `id` = " . $currentResult['id']); + //} } function down($hesk_settings) { - $this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` CHANGE COLUMN `status_int` `status` INT NOT NULL"); + // We no longer need to do this thanks to HESK 2.7.0 + //$this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` CHANGE COLUMN `status_int` `status` INT NOT NULL"); } } \ No newline at end of file diff --git a/install/migrations/Pre140/Statuses/RenameTempColumn.php b/install/migrations/Pre140/Statuses/RenameTempColumn.php index d1eec50f..5695b798 100644 --- a/install/migrations/Pre140/Statuses/RenameTempColumn.php +++ b/install/migrations/Pre140/Statuses/RenameTempColumn.php @@ -6,10 +6,12 @@ namespace Pre140\Statuses; class RenameTempColumn extends \AbstractMigration { function up($hesk_settings) { - $this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` CHANGE COLUMN `status_int` `status` INT NOT NULL"); + // We no longer need to do this thanks to HESK 2.7.0 + //$this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` CHANGE COLUMN `status_int` `status` INT NOT NULL"); } function down($hesk_settings) { - $this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` DROP COLUMN `status`"); + // We no longer need to do this thanks to HESK 2.7.0 + //$this->executeQuery("ALTER TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` DROP COLUMN `status`"); } } \ No newline at end of file diff --git a/install/migrations/core.php b/install/migrations/core.php index 6c7980d8..4a22c4b7 100644 --- a/install/migrations/core.php +++ b/install/migrations/core.php @@ -176,8 +176,8 @@ function getAllMigrations() { //3.0.0 127 => new \v300\MigrateHeskCustomStatuses(), 128 => new \v300\MigrateAutorefreshOption\UpdateFromOldValue(), - 129 => new \v300\MigrateAutorefreshOption\DropOldColumn(), - 130 => new \v300\AddColorSchemeSetting(), + 129 => new \v300\AddColorSchemeSetting(), + 130 => new \v300\MigrateAutorefreshOption\DropOldColumn(), 131 => new LegacyUpdateMigration('3.0.0', '2.6.4'), //3.0.1 132 => new LegacyUpdateMigration('3.0.1', '3.0.0'), @@ -219,5 +219,13 @@ function getAllMigrations() { 162 => new UpdateMigration('3.2.3', '3.2.2', 162), 163 => new UpdateMigration('3.2.4', '3.2.3', 163), 164 => new UpdateMigration('3.2.5', '3.2.4', 164), + // 3.3.0 + 165 => new \v330\ServiceMessagesImprovements\CreateServiceMessageToLocationTable(165), + 166 => new \v330\ServiceMessagesImprovements\UpdateExistingServiceMessagesLocations(166), + 167 => new \v330\ServiceMessagesImprovements\AddLanguageColumnToServiceMessages(167), + 168 => new \v330\CalendarImprovements\AddBusinessHoursTable(168), + 169 => new \v330\CalendarImprovements\InsertDefaultBusinessHours(169), + 170 => new \v330\CalendarImprovements\AddShowStartTimeSetting(170), + 171 => new UpdateMigration('3.3.0', '3.2.5', 171), ); } \ No newline at end of file diff --git a/install/migrations/v240/CreateNewStatusNameTable/InsertTextToStatusXrefValues.php b/install/migrations/v240/CreateNewStatusNameTable/InsertTextToStatusXrefValues.php index b2ef866d..7ec6a0ce 100644 --- a/install/migrations/v240/CreateNewStatusNameTable/InsertTextToStatusXrefValues.php +++ b/install/migrations/v240/CreateNewStatusNameTable/InsertTextToStatusXrefValues.php @@ -17,10 +17,26 @@ class InsertTextToStatusXrefValues extends \AbstractMigration { $oldSetting = $hesk_settings['can_sel_lang']; $hesk_settings['can_sel_lang'] = 1; while ($row = hesk_dbFetchAssoc($statusesRs)) { + $englishText = ''; foreach ($languages as $language => $languageCode) { hesk_setLanguage($language); + + if ($language === 'English') { + if (key_exists($row['Key'], $hesklang)) { + $englishText = $hesklang[$row['Key']]; + } else { + $englishText = $row['Key']; + } + } + + if (key_exists($row['Key'], $hesklang)) { + $textToInsert = $hesklang[$row['Key']]; + } else { + $textToInsert = $englishText; + } + $sql = "INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "text_to_status_xref` (`language`, `text`, `status_id`) - VALUES ('" . hesk_dbEscape($language) . "', '" . hesk_dbEscape($hesklang[$row['Key']]) . "', " . intval($row['ID']) . ")"; + VALUES ('" . hesk_dbEscape($language) . "', '" . hesk_dbEscape($textToInsert) . "', " . intval($row['ID']) . ")"; $this->executeQuery($sql); } } diff --git a/install/migrations/v330/AddHighlightTicketRowsSetting.php b/install/migrations/v330/AddHighlightTicketRowsSetting.php new file mode 100644 index 00000000..192359a7 --- /dev/null +++ b/install/migrations/v330/AddHighlightTicketRowsSetting.php @@ -0,0 +1,16 @@ +executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` (`Key`, `Value`) + VALUES ('highlight_ticket_rows_based_on_priority', '0')"); + } + + function innerDown($hesk_settings) { + $this->executeQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` + WHERE `Key` = 'highlight_ticket_rows_based_on_priority'"); + } +} \ No newline at end of file diff --git a/install/migrations/v330/CalendarImprovements/AddBusinessHoursTable.php b/install/migrations/v330/CalendarImprovements/AddBusinessHoursTable.php new file mode 100644 index 00000000..42483d29 --- /dev/null +++ b/install/migrations/v330/CalendarImprovements/AddBusinessHoursTable.php @@ -0,0 +1,15 @@ +executeQuery("CREATE TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_calendar_business_hours` + (`day_of_week` INT NOT NULL, `start_time` VARCHAR(5) NOT NULL, `end_time` VARCHAR(5) NOT NULL)"); + } + + function innerDown($hesk_settings) { + $this->executeQuery("DROP TABLE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_calendar_business_hours`"); + } +} \ No newline at end of file diff --git a/install/migrations/v330/CalendarImprovements/AddShowStartTimeSetting.php b/install/migrations/v330/CalendarImprovements/AddShowStartTimeSetting.php new file mode 100644 index 00000000..89c0cd81 --- /dev/null +++ b/install/migrations/v330/CalendarImprovements/AddShowStartTimeSetting.php @@ -0,0 +1,17 @@ +executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` (`Key`, `Value`) + VALUES ('calendar_show_start_time', 'true')"); + } + + function innerDown($hesk_settings) { + $this->executeQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "settings` + WHERE `Key` = 'calendar_show_start_time'"); + } +} \ No newline at end of file diff --git a/install/migrations/v330/CalendarImprovements/InsertDefaultBusinessHours.php b/install/migrations/v330/CalendarImprovements/InsertDefaultBusinessHours.php new file mode 100644 index 00000000..970b59ca --- /dev/null +++ b/install/migrations/v330/CalendarImprovements/InsertDefaultBusinessHours.php @@ -0,0 +1,28 @@ +executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_calendar_business_hours` (`day_of_week`, `start_time`, `end_time`) + VALUES (0, '00:00', '23:59')"); + $this->executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_calendar_business_hours` (`day_of_week`, `start_time`, `end_time`) + VALUES (1, '00:00', '23:59')"); + $this->executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_calendar_business_hours` (`day_of_week`, `start_time`, `end_time`) + VALUES (2, '00:00', '23:59')"); + $this->executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_calendar_business_hours` (`day_of_week`, `start_time`, `end_time`) + VALUES (3, '00:00', '23:59')"); + $this->executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_calendar_business_hours` (`day_of_week`, `start_time`, `end_time`) + VALUES (4, '00:00', '23:59')"); + $this->executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_calendar_business_hours` (`day_of_week`, `start_time`, `end_time`) + VALUES (5, '00:00', '23:59')"); + $this->executeQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_calendar_business_hours` (`day_of_week`, `start_time`, `end_time`) + VALUES (6, '00:00', '23:59')"); + } + + function innerDown($hesk_settings) { + $this->executeQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfh_calendar_business_hours`"); + } +} \ No newline at end of file diff --git a/install/migrations/v330/ServiceMessagesImprovements/AddLanguageColumnToServiceMessages.php b/install/migrations/v330/ServiceMessagesImprovements/AddLanguageColumnToServiceMessages.php new file mode 100644 index 00000000..21481b99 --- /dev/null +++ b/install/migrations/v330/ServiceMessagesImprovements/AddLanguageColumnToServiceMessages.php @@ -0,0 +1,17 @@ +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`"); + } +} \ No newline at end of file diff --git a/install/migrations/v330/ServiceMessagesImprovements/CreateServiceMessageToLocationTable.php b/install/migrations/v330/ServiceMessagesImprovements/CreateServiceMessageToLocationTable.php new file mode 100644 index 00000000..0fe7d1ec --- /dev/null +++ b/install/migrations/v330/ServiceMessagesImprovements/CreateServiceMessageToLocationTable.php @@ -0,0 +1,16 @@ +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`"); + } +} \ No newline at end of file diff --git a/install/migrations/v330/ServiceMessagesImprovements/UpdateExistingServiceMessagesLocations.php b/install/migrations/v330/ServiceMessagesImprovements/UpdateExistingServiceMessagesLocations.php new file mode 100644 index 00000000..b9da7cc4 --- /dev/null +++ b/install/migrations/v330/ServiceMessagesImprovements/UpdateExistingServiceMessagesLocations.php @@ -0,0 +1,19 @@ +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`)"); + } +} \ No newline at end of file diff --git a/internal-api/calendar/index.php b/internal-api/calendar/index.php deleted file mode 100644 index 1ccc6011..00000000 --- a/internal-api/calendar/index.php +++ /dev/null @@ -1,29 +0,0 @@ - {$end_time_sql}) AND `categories`.`usage` <> 1"; - - if (!$staff) { - $sql .= " AND `categories`.`type` = '0'"; - } - - $rs = hesk_dbQuery($sql); - - $events = array(); - while ($row = hesk_dbFetchAssoc($rs)) { - // Skip the event if the user does not have access to it - if ($staff && !$_SESSION['isadmin'] && !in_array($row['category'], $_SESSION['categories'])) { - continue; - } - - mfh_log_debug('Calendar', "Creating event with id: {$row['id']}", ''); - - $event['type'] = 'CALENDAR'; - $event['id'] = intval($row['id']); - $event['startTime'] = $row['start']; - $event['endTime'] = $row['end']; - $event['allDay'] = $row['all_day'] ? true : false; - $event['title'] = $row['name']; - $event['location'] = $row['location']; - $event['comments'] = $row['comments']; - $event['categoryId'] = $row['category']; - $event['categoryName'] = $row['category_name']; - $event['backgroundColor'] = $row['background_color']; - $event['foregroundColor'] = $row['foreground_color']; - $event['displayBorder'] = $row['display_border']; - - if ($staff) { - $event['reminderValue'] = $row['reminder_value']; - $event['reminderUnits'] = $row['reminder_unit']; - } - - $events[] = $event; - } - - if ($staff) { - $old_time_setting = $hesk_settings['timeformat']; - $hesk_settings['timeformat'] = 'Y-m-d'; - $current_date = hesk_date(); - $hesk_settings['timeformat'] = $old_time_setting; - - $sql = "SELECT `trackid`, `subject`, `due_date`, `category`, `categories`.`name` AS `category_name`, `categories`.`background_color` AS `background_color`, - `categories`.`foreground_color` AS `foreground_color`, `categories`.`display_border_outline` AS `display_border`, - CASE WHEN `due_date` < '{$current_date}' THEN 1 ELSE 0 END AS `overdue`, `owner`.`name` AS `owner_name`, `tickets`.`owner` AS `owner_id`, - `tickets`.`priority` AS `priority` - FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` AS `tickets` - INNER JOIN `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` AS `categories` - ON `categories`.`id` = `tickets`.`category` - AND `categories`.`usage` <> 2 - LEFT JOIN `" . hesk_dbEscape($hesk_settings['db_pfix']) . "users` AS `owner` - ON `tickets`.`owner` = `owner`.`id` - WHERE `due_date` >= CONVERT_TZ(FROM_UNIXTIME(" . hesk_dbEscape($start) - . " / 1000), @@session.time_zone, '+00:00') - AND `due_date` <= CONVERT_TZ(FROM_UNIXTIME(" . hesk_dbEscape($end) . " / 1000), @@session.time_zone, '+00:00') - AND `status` IN (SELECT `id` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "statuses` WHERE `IsClosed` = 0) "; - - $rs = hesk_dbQuery($sql); - while ($row = hesk_dbFetchAssoc($rs)) { - // Skip the ticket if the user does not have access to it - if (!hesk_checkPermission('can_view_tickets', 0) - || ($row['owner_id'] && $row['owner_id'] != $_SESSION['id'] && !hesk_checkPermission('can_view_ass_others', 0)) - || (!$row['owner_id'] && !hesk_checkPermission('can_view_unassigned', 0))) { - continue; - } - - $event['type'] = 'TICKET'; - $event['trackingId'] = $row['trackid']; - $event['subject'] = $row['subject']; - $event['title'] = $row['subject']; - $event['startTime'] = $row['due_date']; - $event['url'] = $hesk_settings['hesk_url'] . '/' . $hesk_settings['admin_dir'] . '/admin_ticket.php?track=' . $event['trackingId']; - $event['categoryId'] = $row['category']; - $event['categoryName'] = $row['category_name']; - $event['backgroundColor'] = $row['background_color']; - $event['foregroundColor'] = $row['foreground_color']; - $event['displayBorder'] = $row['display_border']; - $event['owner'] = $row['owner_name']; - - $priorities = array( - 0 => $hesklang['critical'], - 1 => $hesklang['high'], - 2 => $hesklang['medium'], - 3 => $hesklang['low'] - ); - $event['priority'] = $priorities[$row['priority']]; - - $events[] = $event; - } - } - - return $events; -} - -function create_event($event, $hesk_settings) { - // Make sure the user can create events in this category - if (!$_SESSION['isadmin'] && !in_array($event['category'], $_SESSION['categories'])) { - print_error('Access Denied', 'You cannot create an event in this category'); - } - - $event['start'] = date('Y-m-d H:i:s', strtotime($event['start'])); - $event['end'] = date('Y-m-d H:i:s', strtotime($event['end'])); - $event['all_day'] = $event['all_day'] ? 1 : 0; - - $sql = "INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "calendar_event` (`start`, `end`, `all_day`, - `name`, `location`, `comments`, `category`) VALUES ( - '" . hesk_dbEscape($event['start']) . "', '" . hesk_dbEscape($event['end']) . "', '" . hesk_dbEscape($event['all_day']) . "', - '" . hesk_dbEscape(addslashes($event['title'])) . "', '" . hesk_dbEscape(addslashes($event['location'])) . "', '" . hesk_dbEscape(addslashes($event['comments'])) . "', - " . intval($event['category']) . ")"; - - hesk_dbQuery($sql); - $event_id = hesk_dbInsertID(); - - if ($event['reminder_amount'] != null) { - $sql = "INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "calendar_event_reminder` (`user_id`, `event_id`, - `amount`, `unit`) VALUES (" . intval($event['reminder_user']) . ", " . intval($event_id) . ", " . intval($event['reminder_amount']) . ", - " . intval($event['reminder_units']) . ")"; - - hesk_dbQuery($sql); - } - - return $event_id; -} - -function update_event($event, $hesk_settings) { - // Make sure the user can edit events in this category - if (!$_SESSION['isadmin'] && !in_array($event['category'], $_SESSION['categories'])) { - print_error('Access Denied', 'You cannot edit an event in this category'); - } - - $event['start'] = date('Y-m-d H:i:s', strtotime($event['start'])); - $event['end'] = date('Y-m-d H:i:s', strtotime($event['end'])); - if ($event['create_ticket_date'] != null) { - $event['create_ticket_date'] = date('Y-m-d H:i:s', strtotime($event['create_ticket_date'])); - } - $event['all_day'] = $event['all_day'] ? 1 : 0; - $event['assign_to'] = $event['assign_to'] != null ? intval($event['assign_to']) : 'NULL'; - - $sql = "UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "calendar_event` SET `start` = '" . hesk_dbEscape($event['start']) - . "', `end` = '" . hesk_dbEscape($event['end']) . "', `all_day` = '" . hesk_dbEscape($event['all_day']) . "', `name` = '" - . hesk_dbEscape(addslashes($event['title'])) . "', `location` = '" . hesk_dbEscape(addslashes($event['location'])) . "', `comments` = '" - . hesk_dbEscape(addslashes($event['comments'])) . "', `category` = " . intval($event['category']) . " WHERE `id` = " . intval($event['id']); - - if ($event['reminder_amount'] != null) { - $delete_sql = "DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "calendar_event_reminder` WHERE `event_id` = " . intval($event['id']) - . " AND `user_id` = " . intval($event['reminder_user']); - hesk_dbQuery($delete_sql); - $insert_sql = "INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "calendar_event_reminder` (`user_id`, `event_id`, - `amount`, `unit`) VALUES (" . intval($event['reminder_user']) . ", " . intval($event['id']) . ", " . intval($event['reminder_amount']) . ", - " . intval($event['reminder_units']) . ")"; - hesk_dbQuery($insert_sql); - } - - hesk_dbQuery($sql); -} - -function delete_event($id, $hesk_settings) { - // Make sure the user can delete events in this category - $categoryRs = hesk_dbQuery('SELECT `category` FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'calendar_event` WHERE `id` = ' . intval($id)); - $category = hesk_dbFetchAssoc($categoryRs); - if (!$_SESSION['isadmin'] && !in_array($category['category'], $_SESSION['categories'])) { - print_error('Access Denied', 'You cannot delete events in this category'); - } - - $sql = "DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "calendar_event` WHERE `id` = " . intval($id); - - hesk_dbQuery($sql); -} - -function update_ticket_due_date($ticket, $hesk_settings) { - $ticket_id_rs = hesk_dbQuery("SELECT `id` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` WHERE `trackid` = '" . hesk_dbEscape($ticket['trackid']) . "'"); - $ticket_id = hesk_dbFetchAssoc($ticket_id_rs); - - $due_date = 'NULL'; - $language_key = 'audit_due_date_removed'; - $audit_array = array(0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')'); - if ($ticket['due_date'] != NULL) { - $audit_array = array( - 0 => $_SESSION['name'] . ' (' . $_SESSION['user'] . ')', - 1 => date('Y-m-d H:i:s', strtotime($ticket['due_date'])) - ); - $due_date = "'" . date('Y-m-d H:i:s', strtotime($ticket['due_date'])) . "'"; - $language_key = 'audit_due_date_changed'; - } - $sql = "UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "tickets` SET `due_date` = {$due_date}, `overdue_email_sent` = '0' - WHERE `trackid` = '" . hesk_dbEscape($ticket['trackid']) . "'"; - - mfh_insert_audit_trail_record($ticket_id['id'], 'TICKET', $language_key, hesk_date(), - $audit_array); - - hesk_dbQuery($sql); -} \ No newline at end of file diff --git a/internal-api/js/service-messages.js b/internal-api/js/service-messages.js new file mode 100644 index 00000000..e75719eb --- /dev/null +++ b/internal-api/js/service-messages.js @@ -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('' + mfhLang.text('no_sm') + ''); + $('#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(); +} \ No newline at end of file diff --git a/js/calendar/mods-for-hesk-calendar-admin-readonly.js b/js/calendar/mods-for-hesk-calendar-admin-readonly.js index 619e5d23..43ce1b43 100644 --- a/js/calendar/mods-for-hesk-calendar-admin-readonly.js +++ b/js/calendar/mods-for-hesk-calendar-admin-readonly.js @@ -12,13 +12,52 @@ $(document).ready(function() { eventLimit: true, timeFormat: 'H:mm', axisFormat: 'H:mm', + displayEventTime: $('#setting_show_start_time').text(), + businessHours: [ + { + dow: [0], + start: $('#business_hours_0_start').text(), + end: $('#business_hours_0_end').text() + }, + { + dow: [1], + start: $('#business_hours_1_start').text(), + end: $('#business_hours_1_end').text() + }, + { + dow: [2], + start: $('#business_hours_2_start').text(), + end: $('#business_hours_2_end').text() + }, + { + dow: [3], + start: $('#business_hours_3_start').text(), + end: $('#business_hours_3_end').text() + }, + { + dow: [4], + start: $('#business_hours_4_start').text(), + end: $('#business_hours_4_end').text() + }, + { + dow: [5], + start: $('#business_hours_5_start').text(), + end: $('#business_hours_5_end').text() + }, + { + dow: [6], + start: $('#business_hours_6_start').text(), + end: $('#business_hours_6_end').text() + } + ], firstDay: $('#setting_first_day_of_week').text(), defaultView: $('#setting_default_view').text().trim(), events: function(start, end, timezone, callback) { $.ajax({ - url: heskPath + 'internal-api/admin/calendar/?start=' + start + '&end=' + end, + url: heskPath + 'api/index.php/v1/calendar/events/staff?start=' + start + '&end=' + end, method: 'GET', dataType: 'json', + headers: { 'X-Internal-Call': true }, success: function(data) { var events = []; $(data).each(function() { @@ -61,7 +100,8 @@ $(document).ready(function() { .find('.popover-owner span').text(event.owner).end() .find('.popover-subject span').text(event.subject).end() .find('.popover-category span').text(event.categoryName).end() - .find('.popover-priority span').text(event.priority); + .find('.popover-priority span').text(event.priority) + .find('.popover-status span').text(event.status).end(); } else { if (event.location === '') { $contents.find('.popover-location').hide(); @@ -124,7 +164,13 @@ $(document).ready(function() { }); function buildEvent(id, dbObject) { - if (dbObject.type == 'TICKET') { + var priorities = []; + priorities['CRITICAL'] = mfhLang.text('critical'); + priorities['HIGH'] = mfhLang.text('high'); + priorities['MEDIUM'] = mfhLang.text('medium'); + priorities['LOW'] = mfhLang.text('low'); + + if (dbObject.type === 'TICKET') { return { title: dbObject.title, subject: dbObject.subject, @@ -140,8 +186,9 @@ function buildEvent(id, dbObject) { categoryName: dbObject.categoryName, className: 'category-' + dbObject.categoryId, owner: dbObject.owner, - priority: dbObject.priority, - fontIconMarkup: getIcon(dbObject) + priority: priorities[dbObject.priority], + fontIconMarkup: getIcon(dbObject), + status: dbObject.status }; } diff --git a/js/calendar/mods-for-hesk-calendar-readonly.js b/js/calendar/mods-for-hesk-calendar-readonly.js index eff85039..72774ce5 100644 --- a/js/calendar/mods-for-hesk-calendar-readonly.js +++ b/js/calendar/mods-for-hesk-calendar-readonly.js @@ -12,11 +12,49 @@ $(document).ready(function() { eventLimit: true, timeFormat: 'H:mm', axisFormat: 'H:mm', + displayEventTime: $('#setting_show_start_time').text(), + businessHours: [ + { + dow: [0], + start: $('#business_hours_0_start').text(), + end: $('#business_hours_0_end').text() + }, + { + dow: [1], + start: $('#business_hours_1_start').text(), + end: $('#business_hours_1_end').text() + }, + { + dow: [2], + start: $('#business_hours_2_start').text(), + end: $('#business_hours_2_end').text() + }, + { + dow: [3], + start: $('#business_hours_3_start').text(), + end: $('#business_hours_3_end').text() + }, + { + dow: [4], + start: $('#business_hours_4_start').text(), + end: $('#business_hours_4_end').text() + }, + { + dow: [5], + start: $('#business_hours_5_start').text(), + end: $('#business_hours_5_end').text() + }, + { + dow: [6], + start: $('#business_hours_6_start').text(), + end: $('#business_hours_6_end').text() + } + ], firstDay: $('#setting_first_day_of_week').text(), defaultView: $('#setting_default_view').text().trim(), events: function(start, end, timezone, callback) { $.ajax({ - url: heskPath + 'internal-api/calendar/?start=' + start + '&end=' + end, + url: heskPath + 'api/index.php/v1/calendar/events/?start=' + start + '&end=' + end, method: 'GET', dataType: 'json', success: function(data) { diff --git a/js/calendar/mods-for-hesk-calendar.js b/js/calendar/mods-for-hesk-calendar.js index 97c4fc7b..08f1b4f4 100644 --- a/js/calendar/mods-for-hesk-calendar.js +++ b/js/calendar/mods-for-hesk-calendar.js @@ -12,13 +12,52 @@ $(document).ready(function() { eventLimit: true, timeFormat: 'H:mm', axisFormat: 'H:mm', + displayEventTime: $('#setting_show_start_time').text() === 'true', + businessHours: [ + { + dow: [0], + start: $('#business_hours_0_start').text(), + end: $('#business_hours_0_end').text() + }, + { + dow: [1], + start: $('#business_hours_1_start').text(), + end: $('#business_hours_1_end').text() + }, + { + dow: [2], + start: $('#business_hours_2_start').text(), + end: $('#business_hours_2_end').text() + }, + { + dow: [3], + start: $('#business_hours_3_start').text(), + end: $('#business_hours_3_end').text() + }, + { + dow: [4], + start: $('#business_hours_4_start').text(), + end: $('#business_hours_4_end').text() + }, + { + dow: [5], + start: $('#business_hours_5_start').text(), + end: $('#business_hours_5_end').text() + }, + { + dow: [6], + start: $('#business_hours_6_start').text(), + end: $('#business_hours_6_end').text() + } + ], firstDay: $('#setting_first_day_of_week').text(), defaultView: $('#setting_default_view').text().trim(), events: function(start, end, timezone, callback) { $.ajax({ - url: heskPath + 'internal-api/admin/calendar/?start=' + start + '&end=' + end, + url: heskPath + 'api/index.php/v1/calendar/events/staff?start=' + start + '&end=' + end, method: 'GET', dataType: 'json', + headers: { 'X-Internal-Call': true }, success: function(data) { var events = []; $(data).each(function() { @@ -51,16 +90,7 @@ $(document).ready(function() { var $contents = $(contents); var format = 'dddd, MMMM Do YYYY'; - var endDate = event.end == null ? event.start : event.end; - - if (event.allDay) { - endDate = event.end.clone(); - endDate.add(-1, 'days'); - } - - if (!event.allDay && event.type !== 'TICKET') { - format += ', HH:mm'; - } + var endDate = event.end === null ? event.start : event.end; if (event.type === 'TICKET') { contents = $('.ticket-popover-template').html(); @@ -74,8 +104,16 @@ $(document).ready(function() { .find('.popover-owner span').text(event.owner).end() .find('.popover-subject span').text(event.subject).end() .find('.popover-category span').text(event.categoryName).end() - .find('.popover-priority span').text(event.priority); + .find('.popover-priority span').text(event.priority).end() + .find('.popover-status span').text(event.status).end(); } else { + if (event.allDay) { + endDate = event.end.clone(); + endDate.add(-1, 'days'); + } else { + format += ', HH:mm'; + } + if (event.location === '') { $contents.find('.popover-location').hide(); } @@ -90,7 +128,7 @@ $(document).ready(function() { var $eventMarkup = $(this); var eventTitle = event.title; - if (event.fontIconMarkup != undefined) { + if (event.fontIconMarkup !== undefined) { eventTitle = event.fontIconMarkup + ' ' + eventTitle; } @@ -161,17 +199,15 @@ $(document).ready(function() { $editForm.find('#delete-button').click(function() { var id = $editForm.find('input[name="id"]').val(); - var data = { - id: id, - action: 'delete' - }; - $.ajax({ method: 'POST', - url: heskPath + 'internal-api/admin/calendar/', - data: data, + url: heskPath + 'api/index.php/v1/calendar/events/staff/' + id, + headers: { + 'X-Internal-Call': true, + 'X-HTTP-Method-Override': 'DELETE' + }, success: function() { - removeFromCalendar(data.id); + removeFromCalendar(id); mfhAlert.success(mfhLang.text('event_deleted')); $('#edit-event-modal').modal('hide'); }, @@ -195,6 +231,9 @@ $(document).ready(function() { dateFormat = 'YYYY-MM-DD HH:mm:ss'; } + var reminderValue = $createForm.find('input[name="reminder-value"]').val(); + var reminderUnits = $createForm.find('select[name="reminder-unit"]').val(); + var data = { title: $createForm.find('input[name="name"]').val(), location: $createForm.find('input[name="location"]').val(), @@ -203,22 +242,23 @@ $(document).ready(function() { allDay: allDay, comments: $createForm.find('textarea[name="comments"]').val(), categoryId: $createForm.find('select[name="category"]').val(), - action: 'create', type: 'CALENDAR', backgroundColor: $createForm.find('select[name="category"] :selected').attr('data-background-color'), foregroundColor: $createForm.find('select[name="category"] :selected').attr('data-foreground-color'), displayBorder: $createForm.find('select[name="category"] :selected').attr('data-display-border'), categoryName: $createForm.find('select[name="category"] :selected').text().trim(), - reminderValue: $createForm.find('input[name="reminder-value"]').val(), - reminderUnits: $createForm.find('select[name="reminder-unit"]').val() + reminderValue: reminderValue === "" ? null : reminderValue, + reminderUnits: reminderValue === "" ? null : reminderUnits }; $.ajax({ method: 'POST', - url: heskPath + 'internal-api/admin/calendar/', - data: data, - success: function(id) { - addToCalendar(id, data, $('#lang_event_created').text()); + url: heskPath + 'api/index.php/v1/calendar/events/staff', + data: JSON.stringify(data), + contentType: 'json', + headers: { 'X-Internal-Call': true }, + success: function(createdEvent) { + addToCalendar(createdEvent.id, data, $('#lang_event_created').text()); $('#create-event-modal').modal('hide'); updateCategoryVisibility(); }, @@ -243,6 +283,9 @@ $(document).ready(function() { dateFormat = 'YYYY-MM-DD HH:mm:ss'; } + var reminderValue = $createForm.find('input[name="reminder-value"]').val(); + var reminderUnits = $createForm.find('select[name="reminder-unit"]').val(); + var data = { id: $form.find('input[name="id"]').val(), title: $form.find('input[name="name"]').val(), @@ -251,21 +294,26 @@ $(document).ready(function() { endTime: moment(end).format(dateFormat), allDay: allDay, comments: $form.find('textarea[name="comments"]').val(), - categoryId: $form.find('select[name="category"]').val(), + categoryId: parseInt($form.find('select[name="category"]').val()), backgroundColor: $form.find('select[name="category"] :selected').attr('data-background-color'), foregroundColor: $form.find('select[name="category"] :selected').attr('data-foreground-color'), displayBorder: $form.find('select[name="category"] :selected').attr('data-display-border'), categoryName: $form.find('select[name="category"] :selected').text().trim(), - action: 'update', - reminderValue: $form.find('input[name="reminder-value"]').val(), - reminderUnits: $form.find('select[name="reminder-unit"]').val() + reminderValue: reminderValue === "" ? null : reminderValue, + reminderUnits: reminderValue === "" ? null : reminderUnits, }; $.ajax({ method: 'POST', - url: heskPath + 'internal-api/admin/calendar/', - data: data, - success: function() { + url: heskPath + 'api/index.php/v1/calendar/events/staff/' + data.id, + data: JSON.stringify(data), + contentType: 'json', + headers: { + 'X-Internal-Call': true, + 'X-HTTP-Method-Override': 'PUT' + }, + success: function(updatedEvent) { + data.auditTrail = updatedEvent.auditTrail; removeFromCalendar(data.id); addToCalendar(data.id, data, $('#lang_event_updated').text()); $('#edit-event-modal').modal('hide'); @@ -290,8 +338,16 @@ function removeFromCalendar(id) { } function buildEvent(id, dbObject) { - if (dbObject.type == 'TICKET') { + var priorities = []; + priorities['CRITICAL'] = mfhLang.text('critical'); + priorities['HIGH'] = mfhLang.text('high'); + priorities['MEDIUM'] = mfhLang.text('medium'); + priorities['LOW'] = mfhLang.text('low'); + + + if (dbObject.type === 'TICKET') { return { + id: id, title: dbObject.title, subject: dbObject.subject, trackingId: dbObject.trackingId, @@ -306,8 +362,9 @@ function buildEvent(id, dbObject) { categoryName: dbObject.categoryName, className: 'category-' + dbObject.categoryId, owner: dbObject.owner, - priority: dbObject.priority, - fontIconMarkup: getIcon(dbObject) + priority: priorities[dbObject.priority], + fontIconMarkup: getIcon(dbObject), + status: dbObject.status }; } @@ -334,7 +391,8 @@ function buildEvent(id, dbObject) { borderColor: parseInt(dbObject.displayBorder) === 1 ? dbObject.foregroundColor : dbObject.backgroundColor, reminderValue: dbObject.reminderValue == null ? '' : dbObject.reminderValue, reminderUnits: dbObject.reminderUnits, - fontIconMarkup: '' + fontIconMarkup: '', + auditTrail: dbObject.auditTrail }; } @@ -379,7 +437,7 @@ function displayCreateModal(date, viewName) { .find('input[name="location"]').val('').end() .find('textarea[name="comments"]').val('').end() .find('select[name="category"]').val($form.find('select[name="category"] option:first-child').val()).end() - .find('select[name="reminder-unit"]').val(0).end() + .find('select[name="reminder-unit"]').val("MINUTE").end() .find('input[name="reminder-value"]').val('').end(); var $modal = $('#create-event-modal'); @@ -454,6 +512,24 @@ function displayEditModal(date) { $form.find('select[name="category"] option[value="' + date.categoryId + '"]').prop('selected', true); + var $auditTrail = $('#edit-history'); + if (date.auditTrail.length === 0) { + $('.nav-tabs[role="tablist"]').hide(); + } else { + $('.nav-tabs[role="tablist"]').show(); + + var $historyTable = $('#history-table'); + $historyTable.html(''); + $.each(date.auditTrail, function() { + var $template = $($('#audit-trail-template').html()); + $template.find('[data-property="date"]').text(this.date).end() + .find('[data-property="description"]').text(vsprintf(mfhLang.text(this.languageKey), this.replacementValues)); + + $historyTable.append($template); + }); + } + $('#edit-modal-tabs').find('a:first').tab('show'); + $('#edit-event-modal').modal('show'); } @@ -477,15 +553,19 @@ function updateCategoryVisibility() { function respondToDragAndDrop(event, delta, revertFunc) { var heskPath = $('p#hesk-path').text(); + if (event.type === 'TICKET') { + var uri = 'api/index.php/v1/staff/tickets/' + event.id + '/due-date'; $.ajax({ method: 'POST', - url: heskPath + 'internal-api/admin/calendar/', - data: { - trackingId: event.trackingId, - action: 'update-ticket', - dueDate: event.start.format('YYYY-MM-DD') + url: heskPath + uri, + headers: { + 'X-Internal-Call': true, + 'X-HTTP-Method-Override': 'PATCH' }, + data: JSON.stringify({ + dueDate: event.start.format('YYYY-MM-DD') + }), success: function() { event.fontIconMarkup = getIcon({ startTime: event.start @@ -526,12 +606,19 @@ function respondToDragAndDrop(event, delta, revertFunc) { reminderValue: event.reminderValue, reminderUnits: event.reminderUnits }; + + var url = heskPath + 'api/index.php/v1/calendar/events/staff/' + event.id; $.ajax({ method: 'POST', - url: heskPath + 'internal-api/admin/calendar/', - data: data, - success: function() { - mfhAlert.success(mfhLang.text('event_updated')); + url: url, + data: JSON.stringify(data), + headers: { + 'X-Internal-Call': true, + 'X-HTTP-Method-Override': 'PUT' + }, + success: function(updatedEvent) { + removeFromCalendar(updatedEvent.id); + addToCalendar(updatedEvent.id, updatedEvent, $('#lang_event_updated').text()); }, error: function() { mfhAlert.error(mfhLang.text('error_updating_event')); diff --git a/js/sprintf.min.js b/js/sprintf.min.js new file mode 100644 index 00000000..11ea3642 --- /dev/null +++ b/js/sprintf.min.js @@ -0,0 +1,3 @@ +/*! sprintf-js v1.1.0 | Copyright (c) 2007-present, Alexandru Marasteanu | BSD-3-Clause */ +!function(e){"use strict";function t(){var e=arguments[0],r=t.cache;return r[e]||(r[e]=t.parse(e)),t.format.call(null,r[e],arguments)}function r(e){return"number"==typeof e?"number":"string"==typeof e?"string":Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}function n(e,t){return t>=0&&t<=7&&i[e]?i[e][t]:Array(t+1).join(e)}var s={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[\+\-]/};t.format=function(e,a){var i,o,l,c,p,f,u,g=1,_=e.length,d="",b=[],h=!0,x="";for(o=0;o<_;o++)if("string"===(d=r(e[o])))b[b.length]=e[o];else if("array"===d){if(c=e[o],c[2])for(i=a[g],l=0;l=0),c[8]){case"b":i=parseInt(i,10).toString(2);break;case"c":i=String.fromCharCode(parseInt(i,10));break;case"d":case"i":i=parseInt(i,10);break;case"j":i=JSON.stringify(i,null,c[6]?parseInt(c[6]):0);break;case"e":i=c[7]?parseFloat(i).toExponential(c[7]):parseFloat(i).toExponential();break;case"f":i=c[7]?parseFloat(i).toFixed(c[7]):parseFloat(i);break;case"g":i=c[7]?parseFloat(i).toPrecision(c[7]):parseFloat(i);break;case"o":i=i.toString(8);break;case"s":i=String(i),i=c[7]?i.substring(0,c[7]):i;break;case"t":i=String(!!i),i=c[7]?i.substring(0,c[7]):i;break;case"T":i=r(i),i=c[7]?i.substring(0,c[7]):i;break;case"u":i=parseInt(i,10)>>>0;break;case"v":i=i.valueOf(),i=c[7]?i.substring(0,c[7]):i;break;case"x":i=parseInt(i,10).toString(16);break;case"X":i=parseInt(i,10).toString(16).toUpperCase()}s.json.test(c[8])?b[b.length]=i:(!s.number.test(c[8])||h&&!c[3]?x="":(x=h?"+":"-",i=i.toString().replace(s.sign,"")),f=c[4]?"0"===c[4]?"0":c[4].charAt(1):" ",u=c[6]-(x+i).length,p=c[6]&&u>0?n(f,u):"",b[b.length]=c[5]?x+i+p:"0"===f?x+p+i:p+x+i)}return b.join("")},t.cache=Object.create(null),t.parse=function(e){for(var t=e,r=[],n=[],a=0;t;){if(null!==(r=s.text.exec(t)))n[n.length]=r[0];else if(null!==(r=s.modulo.exec(t)))n[n.length]="%";else{if(null===(r=s.placeholder.exec(t)))throw new SyntaxError("[sprintf] unexpected placeholder");if(r[2]){a|=1;var i=[],o=r[2],l=[];if(null===(l=s.key.exec(o)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(i[i.length]=l[1];""!==(o=o.substring(l[0].length));)if(null!==(l=s.key_access.exec(o)))i[i.length]=l[1];else{if(null===(l=s.index_access.exec(o)))throw new SyntaxError("[sprintf] failed to parse named argument key");i[i.length]=l[1]}r[2]=i}else a|=2;if(3===a)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");n[n.length]=r}t=t.substring(r[0].length)}return n};var a=function(e,r,n){return n=(r||[]).slice(0),n.splice(0,0,e),t.apply(null,n)},i={0:["","0","00","000","0000","00000","000000","0000000"]," ":[""," "," "," "," "," "," "," "],_:["","_","__","___","____","_____","______","_______"]};"undefined"!=typeof exports&&(exports.sprintf=t,exports.vsprintf=a),void 0!==e&&(e.sprintf=t,e.vsprintf=a,"function"==typeof define&&define.amd&&define(function(){return{sprintf:t,vsprintf:a}}))}("undefined"==typeof window?this:window); +//# sourceMappingURL=sprintf.min.js.map diff --git a/knowledgebase.php b/knowledgebase.php index fdb2a3a4..6e5a4f9c 100644 --- a/knowledgebase.php +++ b/knowledgebase.php @@ -173,7 +173,7 @@ if (!$show['show']) { define('HESK_NO_ROBOTS', 1); /* Print header */ - $hesk_settings['tmp_title'] = $hesklang['sr'] . ': ' . substr(hesk_htmlspecialchars(stripslashes($query)), 0, 20); + $hesk_settings['tmp_title'] = $hesklang['sr'] . ': ' . hesk_mb_substr(hesk_htmlspecialchars(stripslashes($query)),0,20); require_once(HESK_PATH . 'inc/header.inc.php'); hesk_kb_header($hesk_settings['kb_link']); @@ -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); } } diff --git a/language/en/text.php b/language/en/text.php index e55cd0e4..23fdbd79 100644 --- a/language/en/text.php +++ b/language/en/text.php @@ -2214,5 +2214,34 @@ $hesklang['audit_due_date_changed'] = '%s changed due date to %s'; $hesklang['audit_linked_ticket'] = '%s linked ticket %s to this ticket'; $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'; +$hesklang['business_hours'] = 'Business Hours'; +$hesklang['business_hours_help'] = 'Set business hours for the calendar. There is no functional change by setting this, +but times outside of the defined business hours will have a darker gray background for increased visibility.'; +$hesklang['show_event_start_time'] = 'Show event start time in title'; +$hesklang['show_event_start_time_help'] = 'Always show the start time on event titles (unless the event is an all-day event).'; +$hesklang['highlight_ticket_rows_based_on_priority'] = 'Highlight ticket rows based on priority'; +$hesklang['highlight_ticket_rows_based_on_priority_help'] = 'If enabled, each ticket on the tickets page will be highlighted based on priority. If disabled, only * Critical * and High priority tickets will be highlighted.'; +$hesklang['highlight_ticket_rows_based_on_priority_descr'] = 'Highlight all ticket rows based on priority'; +$hesklang['protected_group'] = 'This is a protected group; you cannot change accessible categories / features.'; +$hesklang['emails_to_receive'] = 'Emails to receive'; +$hesklang['emails_sent_to_staff'] = 'Emails sent to staff'; +$hesklang['emails_sent_to_customer'] = 'Emails sent to customer'; + // DO NOT CHANGE BELOW if (!defined('IN_SCRIPT')) die('PHP syntax OK!'); diff --git a/suggest_articles.php b/suggest_articles.php index 13002665..07b84d66 100644 --- a/suggest_articles.php +++ b/suggest_articles.php @@ -59,8 +59,8 @@ if (hesk_isREQUEST('p')) { } $txt = strip_tags($article['content']); - if (strlen($txt) > $hesk_settings['kb_substrart']) { - $txt = substr($txt, 0, $hesk_settings['kb_substrart']) . '...'; + if (hesk_mb_strlen($txt) > $hesk_settings['kb_substrart']) { + $txt = hesk_mb_substr($txt, 0, $hesk_settings['kb_substrart']).'...'; } echo ' diff --git a/ticket.php b/ticket.php index cb7ef11b..1c7e6109 100644 --- a/ticket.php +++ b/ticket.php @@ -65,7 +65,7 @@ $is_form = hesk_SESSION('t_form'); $trackingID = hesk_cleanID('', hesk_SESSION('t_track')); /* Email required to view ticket? */ -$my_email = hesk_getCustomerEmail(1, 't_email'); +$my_email = hesk_getCustomerEmail(1, 't_email', 1); /* Remember email address? */ $do_remember = strlen($do_remember) || strlen(hesk_SESSION('t_remember')) ? ' checked="checked" ' : ''; @@ -215,6 +215,13 @@ if (!$show['show']) {