Merge branch '3-1-0' into 'master'

3.1.0 Update

Closes #535, #428, #397, #565, #549, #561, #562, #560, #531, #395, #405, #456, #353, #551, #559, #557, #556, #524, #523, #527, #528, #525, #339, and #522

See merge request !62
remotes/remote_mirror_8/master
Mike Koch 7 years ago
commit 535e48225d

7
.gitignore vendored

@ -1,3 +1,8 @@
# Mods for HESK-specific files
api/vendor
api/Tests/integration_test_mfh_settings.php
# HESK Files
admin/admin_suggest_articles.php admin/admin_suggest_articles.php
admin/archive.php admin/archive.php
admin/custom_statuses.php admin/custom_statuses.php
@ -265,7 +270,7 @@ readme.html
robots.txt robots.txt
.idea/ .idea/
attachments/__latest.txt attachments/__latest.txt
attachments /attachments
img/ban.png img/ban.png
img/banned.png img/banned.png
img/ico_tools.png img/ico_tools.png

@ -531,7 +531,7 @@ $modsForHesk_settings = mfh_getSettings();
onclick="javascript:alert('<?php echo hesk_makeJsString($hesklang['settings_intro']) . '\n\n' . hesk_makeJsString($hesklang['all_req']); ?>')"><i onclick="javascript:alert('<?php echo hesk_makeJsString($hesklang['settings_intro']) . '\n\n' . hesk_makeJsString($hesklang['all_req']); ?>')"><i
class="fa fa-question-circle settingsquestionmark"></i></a> class="fa fa-question-circle settingsquestionmark"></i></a>
</h2> </h2>
<form method="post" action="admin_settings_save.php" name="form1" onsubmit="return hesk_checkFields()" <form method="post" enctype="multipart/form-data" action="admin_settings_save.php" name="form1" onsubmit="return hesk_checkFields()"
class="form-horizontal" role="form"> class="form-horizontal" role="form">
<!-- General Settings --> <!-- General Settings -->
@ -3426,27 +3426,6 @@ $modsForHesk_settings = mfh_getSettings();
value="<?php echo $hesk_settings['online_min']; ?>"/> value="<?php echo $hesk_settings['online_min']; ?>"/>
</div> </div>
</div> </div>
<div class="form-group">
<label for="rtl" class="col-sm-4 col-xs-12 control-label">
<span class="label label-primary"
data-toggle="tooltip"
title="<?php echo $hesklang['added_in_mods_for_hesk'] ?>"><?php echo $hesklang['mods_for_hesk_acronym']; ?></span>
<?php echo $hesklang['displayRtl']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
title="<?php echo $hesklang['displayRtl']; ?>"
data-content="<?php echo $hesklang['displayRtlHelp']; ?>"></i>
</label>
<div class="col-sm-8 col-xs-12">
<div class="checkbox">
<label>
<input id="rtl" name="rtl" type="checkbox" <?php if ($modsForHesk_settings['rtl']) {
echo 'checked';
} ?>> <?php echo $hesklang['display_rtl']; ?>
</label>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="show-icons" class="col-sm-4 col-xs-12 control-label"> <label for="show-icons" class="col-sm-4 col-xs-12 control-label">
<span class="label label-primary" <span class="label label-primary"
@ -3527,7 +3506,7 @@ $modsForHesk_settings = mfh_getSettings();
</div> </div>
</div> </div>
<div class="box-body"> <div class="box-body">
<h4>Common Properties</h4> <h4><?php echo $hesklang['common_properties']; ?></h4>
<div class="row"> <div class="row">
<div class="col-sm-6 col-xs-12"> <div class="col-sm-6 col-xs-12">
<div class="form-group"> <div class="form-group">
@ -3548,246 +3527,359 @@ $modsForHesk_settings = mfh_getSettings();
</div> </div>
</div> </div>
<h4>Customer View</h4> <h4><?php echo $hesklang['customer_view']; ?></h4>
<div class="row"> <div class="row">
<div class="col-sm-6 col-xs-12"> <div class="col-sm-6 col-xs-12">
<div class="form-group"> <?php
<label for="navbarBackgroundColor" buildColorSchemeColorpicker('navbarBackgroundColor', 'navbarBackgroundColor', $modsForHesk_settings['navbarBackgroundColor'], 'Help');
class="col-sm-7 col-xs-12 control-label"><?php echo $hesklang['navbarBackgroundColor']; ?> ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
data-placement="left"
title="<?php echo $hesklang['navbarBackgroundColor']; ?>"
data-content="<?php echo $hesklang['navbarBackgroundColorHelp']; ?>"></i>
</label>
<div class="col-sm-5 col-xs-12">
<input type="text" id="navbarBackgroundColor" name="navbarBackgroundColor"
class="form-control"
value="<?php echo $modsForHesk_settings['navbarBackgroundColor']; ?>">
</div>
</div>
</div> </div>
<div class="col-sm-6 col-xs-12"> <div class="col-sm-6 col-xs-12">
<div class="form-group"> <?php
<label for="navbarBrandColor" buildColorSchemeColorpicker('navbarBrandColor', 'navbarBrandColor', $modsForHesk_settings['navbarBrandColor'], 'Help');
class="col-sm-7 col-xs-12 control-label"><?php echo $hesklang['navbarBrandColor']; ?> ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
data-placement="left"
title="<?php echo $hesklang['navbarBrandColor']; ?>"
data-content="<?php echo $hesklang['navbarBrandColorHelp']; ?>"></i>
</label>
<div class="col-sm-5 col-xs-12">
<input type="text" id="navbarBrandColor" name="navbarBrandColor"
class="form-control"
value="<?php echo $modsForHesk_settings['navbarBrandColor']; ?>">
</div>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-xs-12"> <div class="col-sm-6 col-xs-12">
<div class="form-group"> <?php
<label for="navbarBrandHoverColor" buildColorSchemeColorpicker('navbarBrandHoverColor', 'navbarBrandHoverColor', $modsForHesk_settings['navbarBrandHoverColor'], 'Help');
class="col-sm-7 col-xs-12 control-label"><?php echo $hesklang['navbarBrandHoverColor']; ?> ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover" </div>
data-placement="left" <div class="col-sm-6 col-xs-12">
title="<?php echo $hesklang['navbarBrandHoverColor']; ?>" <?php
data-content="<?php echo $hesklang['navbarBrandHoverColorHelp']; ?>"></i> buildColorSchemeColorpicker('navbarItemTextColor', 'navbarItemTextColor', $modsForHesk_settings['navbarItemTextColor'], 'Help');
</label> ?>
</div>
<div class="col-sm-5 col-xs-12"> </div>
<input type="text" id="navbarBrandHoverColor" name="navbarBrandHoverColor" <div class="row">
class="form-control" <div class="col-sm-6 col-xs-12">
value="<?php echo $modsForHesk_settings['navbarBrandHoverColor']; ?>"> <?php
</div> buildColorSchemeColorpicker('navbarItemTextHoverColor', 'navbarItemTextHoverColor', $modsForHesk_settings['navbarItemTextHoverColor'], 'Help');
</div> ?>
</div>
<div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('navbarItemTextSelectedColor', 'navbarItemTextSelectedColor', $modsForHesk_settings['navbarItemTextSelectedColor'], 'Help');
?>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('navbarItemSelectedBackgroundColor', 'navbarItemSelectedBackgroundColor', $modsForHesk_settings['navbarItemSelectedBackgroundColor'], 'Help');
?>
</div> </div>
<div class="col-sm-6 col-xs-12"> <div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('dropdownItemTextColor', 'dropdownItemTextColor', $modsForHesk_settings['dropdownItemTextColor'], 'Help');
?>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('dropdownItemTextHoverColor', 'dropdownItemTextHoverColor', $modsForHesk_settings['dropdownItemTextHoverColor'], 'Help');
?>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('dropdownItemTextHoverBackgroundColor', 'dropdownItemTextHoverBackgroundColor', $modsForHesk_settings['dropdownItemTextHoverBackgroundColor'], 'Help');
?>
</div>
</div>
<h4><?php echo $hesklang['admin_panel']; ?></h4>
<div class="row">
<div class="col-xs-12">
<div class="form-group"> <div class="form-group">
<label for="navbarItemTextColor" <label for="admin-color-scheme"
class="col-sm-7 col-xs-12 control-label"><?php echo $hesklang['navbarItemTextColor']; ?> class="col-sm-3 col-xs-5 control-label"><?php echo $hesklang['color_preset']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover" <i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
data-placement="left" data-placement="left"
title="<?php echo $hesklang['navbarItemTextColor']; ?>" title="<?php echo $hesklang['color_preset']; ?>"
data-content="<?php echo $hesklang['navbarItemTextColorHelp']; ?>"></i> data-content="<?php echo $hesklang['color_preset_help']; ?>"></i>
</label> </label>
<div class="col-sm-5 col-xs-12"> <div class="col-sm-9 col-xs-7">
<input type="text" id="navbarItemTextColor" name="navbarItemTextColor" <select name="admin-color-scheme" id="admin-color-scheme" class="form-control">
class="form-control" <option value="SELECT"><?php echo $hesklang['select_a_preset']; ?></option>
value="<?php echo $modsForHesk_settings['navbarItemTextColor']; ?>"> <option value="blue"><?php echo $hesklang['preset_blue']; ?></option>
<option value="blue-light"><?php echo $hesklang['preset_blue_light']; ?></option>
<option value="yellow"><?php echo $hesklang['preset_yellow']; ?></option>
<option value="yellow-light"><?php echo $hesklang['preset_yellow_light']; ?></option>
<option value="green"><?php echo $hesklang['preset_green']; ?></option>
<option value="green-light"><?php echo $hesklang['preset_green_light']; ?></option>
<option value="purple"><?php echo $hesklang['preset_purple']; ?></option>
<option value="purple-light"><?php echo $hesklang['preset_purple_light']; ?></option>
<option value="red"><?php echo $hesklang['preset_red']; ?></option>
<option value="red-light"><?php echo $hesklang['preset_red_light']; ?></option>
<option value="black"><?php echo $hesklang['preset_black']; ?></option>
<option value="black-light"><?php echo $hesklang['preset_black_light']; ?></option>
</select>
</div> </div>
<script>
$('select[name="admin-color-scheme"]').change(function() {
var val = $(this).val();
if (val === 'SELECT') {
return;
}
var lightTheme = val.match(/.+-light/i);
$('#cpadmin-sidebar-background-color').colorpicker('setValue', lightTheme ? '#f9fafc' : '#222d32');
$('#cpadmin-sidebar-header-background-color').colorpicker('setValue', lightTheme ? '#f9fafc' : '#1a2226');
$('#cpadmin-sidebar-text-color').colorpicker('setValue', lightTheme ? '#444' : '#b8c7ce');
$('#cpadmin-sidebar-header-text-color').colorpicker('setValue', lightTheme ? '#848484' : '#4b646f');
$('#cpadmin-sidebar-text-hover-color').colorpicker('setValue', lightTheme ? '#444' : '#fff');
$('#cpadmin-sidebar-background-hover-color').colorpicker('setValue', lightTheme ? '#f4f4f5' : '#1e282c');
$('input[name="admin-sidebar-font-weight"]').val(lightTheme ? ['bold'] : ['normal']);
$('#cpadmin-navbar-text-color').colorpicker('setValue', '#fff');
$('#cpadmin-navbar-text-hover-color').colorpicker('setValue', '#fff');
$('#cpadmin-navbar-brand-text-color').colorpicker('setValue', '#fff');
$('#cpadmin-navbar-brand-text-hover-color').colorpicker('setValue', '#fff');
if (val.match(/blue.*/i)) {
$('#cpadmin-navbar-background-color').colorpicker('setValue', '#3c8dbc');
$('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#367fa9');
$('#cpadmin-navbar-brand-background-color').colorpicker('setValue', lightTheme ? '#3c8dbc' : '#367fa9');
$('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', lightTheme ? '#3b8ab8' : '#357ca5');
} else if (val.match(/yellow.*/i)) {
$('#cpadmin-navbar-background-color').colorpicker('setValue', '#f39c12');
$('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#da8c10');
$('#cpadmin-navbar-brand-background-color').colorpicker('setValue', lightTheme ? '#f39c12' : '#e08e0b');
$('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', lightTheme ? '#f39a0d' : '#db8b0b');
} else if (val.match(/green.*/i)) {
$('#cpadmin-navbar-background-color').colorpicker('setValue', '#00a65a');
$('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#009551');
$('#cpadmin-navbar-brand-background-color').colorpicker('setValue', lightTheme ? '#00a65a' : '#008d4c');
$('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', lightTheme ? '#00a157' : '#008749');
} else if (val.match(/purple.*/i)) {
$('#cpadmin-navbar-background-color').colorpicker('setValue', '#605ca8');
$('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#565397');
$('#cpadmin-navbar-brand-background-color').colorpicker('setValue', lightTheme ? '#605ca8' : '#555299');
$('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', lightTheme ? '#5d59a6' : '#545096');
} else if (val.match(/red.*/i)) {
$('#cpadmin-navbar-background-color').colorpicker('setValue', '#dd4b39');
$('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#c64333');
$('#cpadmin-navbar-brand-background-color').colorpicker('setValue', lightTheme ? '#dd4b39' : '#d73925');
$('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', lightTheme ? '#dc4735' : '#d33724');
} else {
//-- Black
$('#cpadmin-navbar-background-color').colorpicker('setValue', '#fff');
$('#cpadmin-navbar-background-hover-color').colorpicker('setValue', '#eee');
$('#cpadmin-navbar-brand-background-color').colorpicker('setValue', '#fff');
$('#cpadmin-navbar-brand-background-hover-color').colorpicker('setValue', '#fcfcfc');
}
});
</script>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-xs-12"> <div class="col-sm-6 col-xs-12">
<div class="form-group"> <div class="col-sm-5 col-sm-offset-7 col-xs-12">
<label for="navbarItemTextHoverColor" <h4><?php echo $hesklang['navbar']; ?></h4>
class="col-sm-7 col-xs-12 control-label"><?php echo $hesklang['navbarItemTextHoverColor']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
data-placement="left"
title="<?php echo $hesklang['navbarItemTextHoverColor']; ?>"
data-content="<?php echo $hesklang['navbarItemTextHoverColorHelp']; ?>"></i>
</label>
<div class="col-sm-5 col-xs-12">
<input type="text" id="navbarItemTextHoverColor" name="navbarItemTextHoverColor"
class="form-control"
value="<?php echo $modsForHesk_settings['navbarItemTextHoverColor']; ?>">
</div>
</div> </div>
<?php
buildColorSchemeColorpicker('admin-navbar-background-color', 'background_color', $modsForHesk_settings['admin_navbar_background']);
?>
</div> </div>
<div class="col-sm-6 col-xs-12"> <div class="col-sm-6 col-xs-12">
<div class="form-group"> <div class="col-sm-5 col-sm-offset-7 col-xs-12">
<label for="navbarItemTextSelectedColor" <h4><?php echo $hesklang['navbar_brand']; ?></h4>
class="col-sm-7 col-xs-12 control-label"><?php echo $hesklang['navbarItemTextSelectedColor']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
data-placement="left"
title="<?php echo $hesklang['navbarItemTextSelectedColor']; ?>"
data-content="<?php echo $hesklang['navbarItemTextSelectedColorHelp']; ?>"></i>
</label>
<div class="col-sm-5 col-xs-12">
<input type="text" id="navbarItemTextSelectedColor"
name="navbarItemTextSelectedColor" class="form-control"
value="<?php echo $modsForHesk_settings['navbarItemTextSelectedColor']; ?>">
</div>
</div> </div>
<?php
buildColorSchemeColorpicker('admin-navbar-brand-background-color', 'background_color', $modsForHesk_settings['admin_navbar_brand_background']);
?>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-xs-12"> <div class="col-sm-6 col-xs-12">
<div class="form-group"> <?php
<label for="navbarItemSelectedBackgroundColor" buildColorSchemeColorpicker('admin-navbar-text-color', 'text_color', $modsForHesk_settings['admin_navbar_text']);
class="col-sm-7 col-xs-12 control-label"><?php echo $hesklang['navbarItemSelectedBackgroundColor']; ?> ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover" </div>
data-placement="left" <div class="col-sm-6 col-xs-12">
title="<?php echo $hesklang['navbarItemSelectedBackgroundColor']; ?>" <?php
data-content="<?php echo $hesklang['navbarItemSelectedBackgroundColorHelp']; ?>"></i> buildColorSchemeColorpicker('admin-navbar-brand-text-color', 'text_color', $modsForHesk_settings['admin_navbar_brand_text']);
</label> ?>
</div>
<div class="col-sm-5 col-xs-12"> </div>
<input type="text" id="navbarItemSelectedBackgroundColor" <div class="row">
name="navbarItemSelectedBackgroundColor" class="form-control" <div class="col-sm-6 col-xs-12">
value="<?php echo $modsForHesk_settings['navbarItemSelectedBackgroundColor']; ?>"> <?php
</div> buildColorSchemeColorpicker('admin-navbar-text-hover-color', 'text_hover_color', $modsForHesk_settings['admin_navbar_text_hover']);
?>
</div>
<div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('admin-navbar-brand-text-hover-color', 'text_hover_color', $modsForHesk_settings['admin_navbar_brand_text_hover']);
?>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('admin-navbar-background-hover-color', 'background_hover_color', $modsForHesk_settings['admin_navbar_background_hover']);
?>
</div>
<div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('admin-navbar-brand-background-hover-color', 'background_hover_color', $modsForHesk_settings['admin_navbar_brand_background_hover']);
?>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-xs-12">
<div class="col-sm-5 col-sm-offset-7 col-xs-12">
<h4><?php echo $hesklang['sidebar']; ?></h4>
</div> </div>
<?php
buildColorSchemeColorpicker('admin-sidebar-background-color', 'background_color', $modsForHesk_settings['admin_sidebar_background']);
?>
</div> </div>
<div class="col-sm-6 col-xs-12"> <div class="col-sm-6 col-xs-12">
<div class="form-group"> <div class="col-sm-5 col-sm-offset-7 col-xs-12">
<label for="dropdownItemTextColor" <h4><?php echo $hesklang['sidebar_header']; ?></h4>
class="col-sm-7 col-xs-12 control-label"><?php echo $hesklang['dropdownItemTextColor']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
data-placement="left"
title="<?php echo $hesklang['dropdownItemTextColor']; ?>"
data-content="<?php echo $hesklang['dropdownItemTextColorHelp']; ?>"></i>
</label>
<div class="col-sm-5 col-xs-12">
<input type="text" id="dropdownItemTextColor" name="dropdownItemTextColor"
class="form-control"
value="<?php echo $modsForHesk_settings['dropdownItemTextColor']; ?>">
</div>
</div> </div>
<?php
buildColorSchemeColorpicker('admin-sidebar-header-background-color', 'background_color', $modsForHesk_settings['admin_sidebar_header_background']);
?>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('admin-sidebar-text-color', 'text_color', $modsForHesk_settings['admin_sidebar_text']);
?>
</div>
<div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('admin-sidebar-header-text-color', 'text_color', $modsForHesk_settings['admin_sidebar_header_text']);
?>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('admin-sidebar-text-hover-color', 'text_hover_color', $modsForHesk_settings['admin_sidebar_text_hover']);
?>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-xs-12">
<?php
buildColorSchemeColorpicker('admin-sidebar-background-hover-color', 'background_hover_color', $modsForHesk_settings['admin_sidebar_background_hover']);
?>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-xs-12"> <div class="col-sm-6 col-xs-12">
<div class="form-group"> <div class="form-group">
<label for="dropdownItemTextHoverColor" <label for="admin-sidebar-font-weight"
class="col-sm-7 col-xs-12 control-label"><?php echo $hesklang['dropdownItemTextHoverColor']; ?> class="col-sm-7 col-xs-12 control-label"><?php echo $hesklang['font_weight']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover" <i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
data-placement="left" data-placement="top"
title="<?php echo $hesklang['dropdownItemTextHoverColor']; ?>" title="<?php echo $hesklang['font_weight']; ?>"
data-content="<?php echo $hesklang['dropdownItemTextHoverColorHelp']; ?>"></i> data-content="<?php echo $hesklang['font_weight_help']; ?>"></i>
</label> </label>
<div class="col-sm-5 col-xs-12"> <div class="col-sm-5 col-xs-12 form-inline">
<input type="text" id="dropdownItemTextHoverColor" name="dropdownItemTextHoverColor" <div class="radio">
class="form-control" <label>
value="<?php echo $modsForHesk_settings['dropdownItemTextHoverColor']; ?>"> <input type="radio" name="admin-sidebar-font-weight" value="normal"
<?php echo $modsForHesk_settings['admin_sidebar_font_weight'] == 'normal' ? 'checked' : ''; ?>>
<?php echo $hesklang['normal']; ?>
</label>
</div><br>
<div class="radio">
<label>
<input type="radio" name="admin-sidebar-font-weight" value="bold"
<?php echo $modsForHesk_settings['admin_sidebar_font_weight'] == 'bold' ? 'checked' : ''; ?>>
<?php echo $hesklang['bold']; ?>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<h4><?php echo $hesklang['login_page']; ?></h4>
<div class="row"> <div class="row">
<div class="col-sm-6 col-xs-12"> <div class="col-xs-12">
<div class="form-group"> <div class="form-group">
<label for="dropdownItemTextHoverBackgroundColor" <label for="login-background" class="col-sm-3 col-xs-5 control-label">
class="col-sm-7 col-xs-12 control-label"><?php echo $hesklang['dropdownItemTextHoverBackgroundColor']; ?> <?php echo $hesklang['login_background']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
data-placement="left"
title="<?php echo $hesklang['dropdownItemTextHoverBackgroundColor']; ?>"
data-content="<?php echo $hesklang['dropdownItemTextHoverBackgroundColorHelp']; ?>"></i>
</label> </label>
<div class="col-sm-9 col-xs-7 form-inline">
<div class="col-sm-5 col-xs-12"> <div class="radio">
<input type="text" id="dropdownItemTextHoverBackgroundColor" <label>
name="dropdownItemTextHoverBackgroundColor" class="form-control" <input type="radio" name="login-background"
value="<?php echo $modsForHesk_settings['dropdownItemTextHoverBackgroundColor']; ?>"> data-activate="input[name='login-background-color']" data-deactivate="input[name='login-background-image']"
value="color" <?php if ($modsForHesk_settings['login_background_type'] == 'color') { echo 'checked'; } ?>>
<?php echo $hesklang['solid_color']; ?>
</label>
</div>&nbsp;&nbsp;&nbsp;
<input title="<?php echo $hesklang['login_background_color']; ?>" type="text"
name="login-background-color" class="form-control"
<?php if ($modsForHesk_settings['login_background_type'] == 'image') { echo 'disabled'; } ?>>
<br>
<div class="radio">
<label>
<input type="radio" name="login-background"
data-activate="input[name='login-background-image']" data-deactivate="input[name='login-background-color']"
value="image" <?php if ($modsForHesk_settings['login_background_type'] == 'image') { echo 'checked'; } ?>>
<?php echo $hesklang['image']; ?>
</label>
</div>
<input title="<?php echo $hesklang['login_background_image']; ?>" type="file" name="login-background-image" style="display: inline;vertical-align: bottom" <?php if ($modsForHesk_settings['login_background_type'] == 'color') { echo 'disabled'; } ?>>
<?php if ($modsForHesk_settings['login_background_type'] == 'image'): ?>
<br>
<img src="<?php echo HESK_PATH . $hesk_settings['cache_dir']; ?>/lb_<?php echo $modsForHesk_settings['login_background']; ?>" alt="<?php echo $hesklang['login_background']; ?>" title="<?php echo $hesklang['login_background']; ?>" height="125" width="125" class="push-down-10">
<?php endif; ?>
<script type="text/javascript">
$('input[name="login-background-color"]').colorpicker({
format: 'hex',
color: <?php if ($modsForHesk_settings['login_background_type'] == 'color') { echo "'{$modsForHesk_settings['login_background']}'"; } else { echo 'false'; } ?>
});
</script>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<h4>Admin Panel</h4>
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<div class="form-group"> <div class="form-group">
<label for="admin-color-scheme" <label for="login-box-header" class="col-sm-3 col-xs-5 control-label">
class="col-sm-3 col-xs-5 control-label"><?php echo $hesklang['color_scheme']; ?> <?php echo $hesklang['login_box_header']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
data-placement="left"
title="<?php echo $hesklang['color_scheme']; ?>"
data-content="<?php echo $hesklang['color_scheme_help']; ?>"></i>
</label> </label>
<div class="col-sm-9 col-xs-7 form-inline">
<div class="col-sm-9 col-xs-7"> <div class="radio">
<select name="admin-color-scheme" class="form-control"> <label>
<option value="skin-blue" <input type="radio" name="login-box-header" value="helpdesk-title" data-deactivate="input[name='login-box-header-image']" <?php if ($modsForHesk_settings['login_box_header'] == 'helpdesk-title') { echo 'checked'; } ?>>
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-blue') { echo 'selected'; } ?>>Blue</option> <?php echo $hesklang['hesk_title']; ?>
<option value="skin-blue-light" </label>
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-blue-light') { echo 'selected'; } ?>>Blue (Light)</option> </div><br>
<option value="skin-yellow" <div class="radio">
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-yellow') { echo 'selected'; } ?>>Yellow</option> <label>
<option value="skin-yellow-light" <input type="radio" name="login-box-header" value="image" data-activate="input[name='login-box-header-image']" <?php if ($modsForHesk_settings['login_box_header'] == 'image') { echo 'checked'; } ?>>
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-yellow-light') { echo 'selected'; } ?>>Yellow (Light)</option> <?php echo $hesklang['image']; ?>
<option value="skin-green" </label>
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-green') { echo 'selected'; } ?>>Green</option> <input title="<?php echo $hesklang['login_header_image']; ?>" type="file" name="login-box-header-image" style="display: inline;vertical-align: bottom" <?php if ($modsForHesk_settings['login_box_header'] == 'helpdesk-title') { echo 'disabled'; } ?>>
<option value="skin-green-light" <?php if ($modsForHesk_settings['login_box_header'] == 'image'): ?>
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-green-light') { echo 'selected'; } ?>>Green (Light)</option> <br>
<option value="skin-purple" <img src="<?php echo HESK_PATH . $hesk_settings['cache_dir']; ?>/lbh_<?php echo $modsForHesk_settings['login_box_header_image']; ?>" title="<?php echo $modsForHesk_settings['login_box_header_image']; ?>" alt="<?php echo $modsForHesk_settings['login_box_header_image']; ?>" style="height: 75px" class="push-down-10">
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-purple') { echo 'selected'; } ?>>Purple</option> <?php endif; ?>
<option value="skin-purple-light" </div>
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-purple-light') { echo 'selected'; } ?>>Purple (Light)</option>
<option value="skin-red"
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-red') { echo 'selected'; } ?>>Red</option>
<option value="skin-red-light"
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-red-light') { echo 'selected'; } ?>>Red (Light)</option>
<option value="skin-black"
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-black') { echo 'selected'; } ?>>Black</option>
<option value="skin-black-light"
<?php if ($modsForHesk_settings['admin_color_scheme'] == 'skin-black-light') { echo 'selected'; } ?>>Black (Light)</option>
</select>
</div> </div>
<script>
$('select[name="admin-color-scheme"]').change(function() {
$('body').removeClass('skin-blue')
.removeClass('skin-blue-light')
.removeClass('skin-yellow')
.removeClass('skin-yellow-light')
.removeClass('skin-green')
.removeClass('skin-green-light')
.removeClass('skin-purple')
.removeClass('skin-purple-light')
.removeClass('skin-red')
.removeClass('skin-red-light')
.removeClass('skin-black')
.removeClass('skin-black-light')
.addClass($(this).val());
});
</script>
</div> </div>
</div> </div>
</div> </div>
@ -3811,6 +3903,37 @@ $modsForHesk_settings = mfh_getSettings();
require_once(HESK_PATH . 'inc/footer.inc.php'); require_once(HESK_PATH . 'inc/footer.inc.php');
exit(); exit();
function buildColorSchemeColorpicker($field_name, $label_key, $color, $help_suffix = '_help') {
global $hesklang;
echo '
<div class="form-group">
<label for="admin-navbar-background-color"
class="col-sm-7 col-xs-12 control-label">'. $hesklang[$label_key] . '
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
data-placement="top"
title="' . htmlspecialchars($hesklang[$label_key]) . '"
data-content="' . htmlspecialchars($hesklang[$label_key . $help_suffix]) . '"></i>
</label>
<div class="col-sm-5 col-xs-12">
<div id="cp' . $field_name . '" class="input-group">
<input type="text" id="' . $field_name . '" name="' . $field_name . '"
class="form-control"
value="' . $color . '">
<span class="input-group-addon"><i></i></span>
</div>
</div>
</div>
<script>
$("#cp' . $field_name . '").colorpicker({
color: "' . $color . '",
format: "hex"
});
</script>
';
}
function hesk_checkVersion() function hesk_checkVersion()
{ {
@ -4064,7 +4187,7 @@ $modsForHesk_settings = mfh_getSettings();
background: #fff; background: #fff;
color: black; color: black;
font: 68.8%/1.5 Verdana, Geneva, Arial, Helvetica, sans-serif; font: 68.8%/1.5 Verdana, Geneva, Arial, Helvetica, sans-serif;
text-align: <?php if ($modsForHesk_settings['rtl']) { echo 'right'; } else { echo 'left'; } ?>; text-align: left;
} }
p { p {

@ -39,6 +39,8 @@ hesk_checkPermission('can_manage_settings');
// A security check // A security check
hesk_token_check('POST'); hesk_token_check('POST');
$modsForHesk_settings = mfh_getSettings();
// Demo mode // Demo mode
if (defined('HESK_DEMO')) { if (defined('HESK_DEMO')) {
hesk_process_messages($hesklang['sdemo'], 'admin_settings.php'); hesk_process_messages($hesklang['sdemo'], 'admin_settings.php');
@ -447,7 +449,6 @@ foreach ($postArray as $value) {
} }
// Save the modsForHesk_settings.inc.php file // Save the modsForHesk_settings.inc.php file
$set['rtl'] = empty($_POST['rtl']) ? 0 : 1;
$set['show-icons'] = empty($_POST['show-icons']) ? 0 : 1; $set['show-icons'] = empty($_POST['show-icons']) ? 0 : 1;
$set['custom-field-setting'] = empty($_POST['custom-field-setting']) ? 0 : 1; $set['custom-field-setting'] = empty($_POST['custom-field-setting']) ? 0 : 1;
$set['customer-email-verification-required'] = empty($_POST['email-verification']) ? 0 : 1; $set['customer-email-verification-required'] = empty($_POST['email-verification']) ? 0 : 1;
@ -494,8 +495,115 @@ $set['dropdownItemTextColor'] = hesk_input(hesk_POST('dropdownItemTextColor'));
$set['dropdownItemTextHoverColor'] = hesk_input(hesk_POST('dropdownItemTextHoverColor')); $set['dropdownItemTextHoverColor'] = hesk_input(hesk_POST('dropdownItemTextHoverColor'));
$set['questionMarkColor'] = hesk_input(hesk_POST('questionMarkColor')); $set['questionMarkColor'] = hesk_input(hesk_POST('questionMarkColor'));
$set['dropdownItemTextHoverBackgroundColor'] = hesk_input(hesk_POST('dropdownItemTextHoverBackgroundColor')); $set['dropdownItemTextHoverBackgroundColor'] = hesk_input(hesk_POST('dropdownItemTextHoverBackgroundColor'));
$set['admin_color_scheme'] = hesk_input(hesk_POST('admin-color-scheme')); $set['admin_navbar_background'] = hesk_input(hesk_POST('admin-navbar-background-color'));
mfh_updateSetting('rtl', $set['rtl']); $set['admin_navbar_background_hover'] = hesk_input(hesk_POST('admin-navbar-background-hover-color'));
$set['admin_navbar_brand_background'] = hesk_input(hesk_POST('admin-navbar-brand-background-color'));
$set['admin_navbar_brand_background_hover'] = hesk_input(hesk_POST('admin-navbar-brand-background-hover-color'));
$set['admin_navbar_brand_text'] = hesk_input(hesk_POST('admin-navbar-brand-text-color'));
$set['admin_navbar_brand_text_hover'] = hesk_input(hesk_POST('admin-navbar-brand-text-hover-color'));
$set['admin_navbar_text'] = hesk_input(hesk_POST('admin-navbar-text-color'));
$set['admin_navbar_text_hover'] = hesk_input(hesk_POST('admin-navbar-text-hover-color'));
$set['admin_sidebar_background'] = hesk_input(hesk_POST('admin-sidebar-background-color'));
$set['admin_sidebar_background_hover'] = hesk_input(hesk_POST('admin-sidebar-header-background-color'));
$set['admin_sidebar_font_weight'] = hesk_input(hesk_POST('admin-sidebar-font-weight'));
$set['admin_sidebar_header_background'] = hesk_input(hesk_POST('admin-sidebar-header-background-color'));
$set['admin_sidebar_header_text'] = hesk_input(hesk_POST('admin-sidebar-header-text-color'));
$set['admin_sidebar_text'] = hesk_input(hesk_POST('admin-sidebar-text-color'));
$set['admin_sidebar_text_hover'] = hesk_input(hesk_POST('admin-sidebar-text-hover-color'));
$set['login_background_type'] = hesk_input(hesk_POST('login-background'));
$set['login_box_header'] = hesk_input(hesk_POST('login-box-header'));
$changedBackground = false;
$loadedAttachmentFuncs = false;
if ($set['login_background_type'] == 'color') {
if (file_exists($hesk_settings['cache_dir'] . '/lb_' . $set['login_background'])) {
unlink($hesk_settings['cache_dir'] . '/lb_' . $set['login_background']);
}
$set['login_background'] = hesk_input(hesk_POST('login-background-color'));
if ($set['login_background'] == '') {
$set['login_background'] = '#d2d6de';
}
$changedBackground = true;
} else {
if (!$loadedAttachmentFuncs) {
include(HESK_PATH . 'inc/attachments.inc.php');
include(HESK_PATH . 'inc/posting_functions.inc.php');
$loadedAttachmentFuncs = true;
}
$file_name = hesk_cleanFileName($_FILES['login-background-image']['name']);
if (!empty($_FILES['login-background-image']['name'])) {
$file_size = $_FILES['login-background-image']['size'];
if ($file_size > $hesk_settings['attachments']['max_size']) {
return hesk_fileError(sprintf($hesklang['file_too_large'], $file_name));
}
$ext = strtolower(strrchr($file_name, "."));
if (file_exists($hesk_settings['cache_dir'] . '/lb_' . $modsForHesk_settings['login_background'])) {
unlink($hesk_settings['cache_dir'] . '/lb_' . $modsForHesk_settings['login_background']);
}
$saved_name = 'login-background' . $ext;
$file_to_move = $_FILES['login-background-image']['tmp_name'];
if (!move_uploaded_file($file_to_move, dirname(dirname(__FILE__)) . '/' . $hesk_settings['cache_dir'] . '/lb_' . $saved_name)) {
hesk_error($hesklang['cannot_move_tmp']);
}
$set['login_background'] = $saved_name;
$changedBackground = true;
}
}
$changedLoginImage = false;
if ($set['login_box_header'] == 'image') {
if (!$loadedAttachmentFuncs) {
include(HESK_PATH . 'inc/attachments.inc.php');
include(HESK_PATH . 'inc/posting_functions.inc.php');
$loadedAttachmentFuncs = true;
}
$file_name = hesk_cleanFileName($_FILES['login-box-header-image']['name']);
if (!empty($_FILES['login-box-header-image']['name'])) {
$file_size = $_FILES['login-box-header-image']['size'];
if ($file_size > $hesk_settings['attachments']['max_size']) {
return hesk_fileError(sprintf($hesklang['file_too_large'], $file_name));
}
$ext = strtolower(strrchr($file_name, "."));
if (file_exists($hesk_settings['cache_dir'] . '/lbh_' . $modsForHesk_settings['login_box_header_image'])) {
unlink($hesk_settings['cache_dir'] . '/lbh_' . $modsForHesk_settings['login_box_header_image']);
}
$saved_name = 'login-box-header-image' . $ext;
$file_to_move = $_FILES['login-box-header-image']['tmp_name'];
if (!move_uploaded_file($file_to_move, dirname(dirname(__FILE__)) . '/' . $hesk_settings['cache_dir'] . '/lbh_' . $saved_name)) {
hesk_error($hesklang['cannot_move_tmp']);
}
$set['login_box_header_image'] = $saved_name;
$changedLoginImage = true;
}
} else {
if (file_exists($hesk_settings['cache_dir'] . '/lbh_' . $set['login_box_header_image'])) {
unlink($hesk_settings['cache_dir'] . '/lbh_' . $set['login_box_header_image']);
}
$set['login_box_header_image'] = '';
$changedLoginImage = true;
}
mfh_updateSetting('show_icons', $set['show-icons']); mfh_updateSetting('show_icons', $set['show-icons']);
mfh_updateSetting('custom_field_setting', $set['custom-field-setting']); mfh_updateSetting('custom_field_setting', $set['custom-field-setting']);
mfh_updateSetting('customer_email_verification_required', $set['customer-email-verification-required']); mfh_updateSetting('customer_email_verification_required', $set['customer-email-verification-required']);
@ -521,6 +629,21 @@ mfh_updateSetting('dropdownItemTextColor', $set['dropdownItemTextColor'], true);
mfh_updateSetting('dropdownItemTextHoverColor', $set['dropdownItemTextHoverColor'], true); mfh_updateSetting('dropdownItemTextHoverColor', $set['dropdownItemTextHoverColor'], true);
mfh_updateSetting('questionMarkColor', $set['questionMarkColor'], true); mfh_updateSetting('questionMarkColor', $set['questionMarkColor'], true);
mfh_updateSetting('dropdownItemTextHoverBackgroundColor', $set['dropdownItemTextHoverBackgroundColor'], true); mfh_updateSetting('dropdownItemTextHoverBackgroundColor', $set['dropdownItemTextHoverBackgroundColor'], true);
mfh_updateSetting('admin_navbar_background', $set['admin_navbar_background'], true);
mfh_updateSetting('admin_navbar_background_hover', $set['admin_navbar_background_hover'], true);
mfh_updateSetting('admin_navbar_brand_background', $set['admin_navbar_brand_background'], true);
mfh_updateSetting('admin_navbar_brand_background_hover', $set['admin_navbar_brand_background_hover'], true);
mfh_updateSetting('admin_navbar_brand_text', $set['admin_navbar_brand_text'], true);
mfh_updateSetting('admin_navbar_brand_text_hover', $set['admin_navbar_brand_text_hover'], true);
mfh_updateSetting('admin_navbar_text', $set['admin_navbar_text'], true);
mfh_updateSetting('admin_navbar_text_hover', $set['admin_navbar_text_hover'], true);
mfh_updateSetting('admin_sidebar_background', $set['admin_sidebar_background'], true);
mfh_updateSetting('admin_sidebar_background_hover', $set['admin_sidebar_background_hover'], true);
mfh_updateSetting('admin_sidebar_font_weight', $set['admin_sidebar_font_weight'], true);
mfh_updateSetting('admin_sidebar_header_background', $set['admin_sidebar_header_background'], true);
mfh_updateSetting('admin_sidebar_header_text', $set['admin_sidebar_header_text'], true);
mfh_updateSetting('admin_sidebar_text', $set['admin_sidebar_text'], true);
mfh_updateSetting('admin_sidebar_text_hover', $set['admin_sidebar_text_hover'], true);
mfh_updateSetting('display_user_agent_information', $set['display_user_agent_information']); mfh_updateSetting('display_user_agent_information', $set['display_user_agent_information']);
mfh_updateSetting('navbar_title_url', $set['navbar_title_url'], true); mfh_updateSetting('navbar_title_url', $set['navbar_title_url'], true);
if ($set['use_mailgun'] == 1) { if ($set['use_mailgun'] == 1) {
@ -533,6 +656,16 @@ mfh_updateSetting('first_day_of_week', $set['first_day_of_week'], false);
mfh_updateSetting('default_calendar_view', $set['default_view'], true); mfh_updateSetting('default_calendar_view', $set['default_view'], true);
mfh_updateSetting('admin_color_scheme', $set['admin_color_scheme'], true); mfh_updateSetting('admin_color_scheme', $set['admin_color_scheme'], true);
mfh_updateSetting('login_background_type', $set['login_background_type'], true);
if ($changedBackground) {
mfh_updateSetting('login_background', $set['login_background'], true);
}
mfh_updateSetting('login_box_header', $set['login_box_header'], true);
if ($changedLoginImage) {
mfh_updateSetting('login_box_header_image', $set['login_box_header_image'], true);
}
// Prepare settings file and save it // Prepare settings file and save it
$settings_file_content = '<?php $settings_file_content = '<?php
// Settings file for HESK ' . $set['hesk_version'] . ' // Settings file for HESK ' . $set['hesk_version'] . '

@ -17,7 +17,7 @@ define('WYSIWYG', 1);
define('VALIDATOR', 1); define('VALIDATOR', 1);
define('MFH_PAGE_LAYOUT', 'TOP_AND_SIDE'); define('MFH_PAGE_LAYOUT', 'TOP_AND_SIDE');
define('EXTRA_JS', '<script src="'.HESK_PATH.'internal-api/js/admin-ticket.js"></script>'); define('EXTRA_JS', '<script src="'.HESK_PATH.'internal-api/js/admin-ticket.js"></script><script src="'.HESK_PATH.'js/jquery.dirtyforms.min.js"></script>');
/* Get all the required files and functions */ /* Get all the required files and functions */
require(HESK_PATH . 'hesk_settings.inc.php'); require(HESK_PATH . 'hesk_settings.inc.php');
@ -470,7 +470,7 @@ if (($can_reply || $can_edit) && isset($_POST['childTrackingId'])) {
} }
//-- Check if the ticket is already a child. //-- Check if the ticket is already a child.
$childRs = hesk_dbQuery('SELECT * FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'tickets` WHERE `parent` = ' . intval($ticket['id']) . ' AND `trackid` = \'' . hesk_dbEscape(hesk_POST(['childTrackingId'])) . '\''); $childRs = hesk_dbQuery('SELECT * FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'tickets` WHERE `parent` = ' . intval($ticket['id']) . ' AND `trackid` = \'' . hesk_dbEscape(hesk_POST('childTrackingId')) . '\'');
if (hesk_dbNumRows($childRs) > 0) { if (hesk_dbNumRows($childRs) > 0) {
hesk_process_messages(sprintf($hesklang['is_already_linked'], $_POST['childTrackingId']), 'admin_ticket.php?track=' . $trackingID . '&Refresh=' . mt_rand(10000, 99999), 'NOTICE'); hesk_process_messages(sprintf($hesklang['is_already_linked'], $_POST['childTrackingId']), 'admin_ticket.php?track=' . $trackingID . '&Refresh=' . mt_rand(10000, 99999), 'NOTICE');
} }
@ -1167,7 +1167,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
<?php build_dropzone_markup(true, 'notesFiledrop'); ?> <?php build_dropzone_markup(true, 'notesFiledrop'); ?>
</div> </div>
</div> </div>
<?php display_dropzone_field($hesk_settings['hesk_url'] . '/internal-api/ticket/upload-attachment.php', 'notesFiledrop'); ?> <?php display_dropzone_field(HESK_PATH . 'internal-api/ticket/upload-attachment.php', 'notesFiledrop'); ?>
<div class="text-right"> <div class="text-right">
<i><?php echo $hesklang['nhid']; ?></i>&nbsp; <i><?php echo $hesklang['nhid']; ?></i>&nbsp;
<div class="btn-group"> <div class="btn-group">
@ -1500,6 +1500,22 @@ function hesk_getAdminButtonsInTicket($reply = 0, $white = 1)
$options = $reply ? '' : '<div class="pull-right">'; $options = $reply ? '' : '<div class="pull-right">';
// Resend email notification
$replyDataAttribute = '';
if ($reply) {
$replyDataAttribute = 'data-reply-id="' . $reply['id'] . '"';
}
if ($ticket['email'] !== '') {
$options .= '
<button class="btn btn-default" data-action="resend-email-notification" ' . $replyDataAttribute . ' data-ticket-id="' . $ticket['id'] . '">
<i class="fa fa-envelope navy-blue"></i> ' . $hesklang['resend_email_notification'] . '
</button>
<span id="lang_email_notification_sent" style="display: none">' . $hesklang['email_notification_sent'] . '</span>
<span id="lang_email_notification_resend_failed" style="display: none">' . $hesklang['email_notification_resend_failed'] . '</span>
';
}
/* Edit post */ /* Edit post */
if ($can_edit) { if ($can_edit) {
$tmp = $reply ? '&amp;reply=' . $reply['id'] : ''; $tmp = $reply ? '&amp;reply=' . $reply['id'] : '';
@ -1870,7 +1886,7 @@ function hesk_printReplyForm()
$onsubmit = 'onsubmit="force_stop();return validateRichText(\'message-help-block\', \'message-group\', \'message\', \''.htmlspecialchars($hesklang['this_field_is_required']).'\')"'; $onsubmit = 'onsubmit="force_stop();return validateRichText(\'message-help-block\', \'message-group\', \'message\', \''.htmlspecialchars($hesklang['this_field_is_required']).'\')"';
} }
?> ?>
<form role="form" data-toggle="validator" class="form-horizontal" method="post" action="admin_reply_ticket.php" <form id="reply-form" role="form" data-toggle="validator" class="form-horizontal" method="post" action="admin_reply_ticket.php"
enctype="multipart/form-data" name="form1" <?php echo $onsubmit; ?>> enctype="multipart/form-data" name="form1" <?php echo $onsubmit; ?>>
<?php <?php
@ -1963,7 +1979,7 @@ function hesk_printReplyForm()
</div> </div>
</div> </div>
<?php <?php
display_dropzone_field($hesk_settings['hesk_url'] . '/internal-api/ticket/upload-attachment.php'); display_dropzone_field(HESK_PATH . 'internal-api/ticket/upload-attachment.php');
} }
?> ?>
<div class="form-group"> <div class="form-group">
@ -2046,6 +2062,7 @@ function hesk_printReplyForm()
</div> </div>
</div> </div>
</form> </form>
<script>$('form#reply-form').dirtyForms();</script>
</div> </div>
</div> </div>
<!-- END REPLY FORM --> <!-- END REPLY FORM -->

@ -1,37 +1,9 @@
<?php <?php
/*******************************************************************************
* Title: Help Desk Software HESK
* Version: 2.6.5 from 28th August 2015
* Author: Klemen Stirn
* Website: https://www.hesk.com
********************************************************************************
* COPYRIGHT AND TRADEMARK NOTICE
* Copyright 2005-2015 Klemen Stirn. All Rights Reserved.
* HESK is a registered trademark of Klemen Stirn.
* The HESK may be used and modified free of charge by anyone
* AS LONG AS COPYRIGHT NOTICES AND ALL THE COMMENTS REMAIN INTACT.
* By using this code you agree to indemnify Klemen Stirn from any
* liability that might arise from it's use.
* Selling the code for this program, in part or full, without prior
* written consent is expressly forbidden.
* Using this code, in part or full, to create derivate work,
* new scripts or products is expressly forbidden. Obtain permission
* before redistributing this software over the Internet or in
* any other medium. In all cases copyright and header must remain intact.
* This Copyright is in full effect in any country that has International
* Trade Agreements with the United States of America or
* with the European Union.
* Removing any of the copyright notices without purchasing a license
* is expressly forbidden. To remove HESK copyright notice you must purchase
* a license for this script. For more information on how to obtain
* a license please visit the page below:
* https://www.hesk.com/buy.php
*******************************************************************************/
define('IN_SCRIPT', 1); define('IN_SCRIPT', 1);
define('HESK_PATH', '../'); define('HESK_PATH', '../');
define('PAGE_TITLE', 'ADMIN_SETTINGS'); define('PAGE_TITLE', 'ADMIN_API_SETTINGS');
define('MFH_PAGE_LAYOUT', 'TOP_AND_SIDE'); define('MFH_PAGE_LAYOUT', 'TOP_ONLY');
// Make sure the install folder is deleted // Make sure the install folder is deleted
if (is_dir(HESK_PATH . 'install')) { if (is_dir(HESK_PATH . 'install')) {
@ -120,7 +92,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="active"><a href="#general" data-toggle="tab"><?php echo $hesklang['tab_1']; ?></a></li> <li class="active"><a href="#general" data-toggle="tab"><?php echo $hesklang['tab_1']; ?></a></li>
<li><a href="#user-security" data-toggle="tab"><?php echo $hesklang['user_security']; ?></a></li> <li><a href="#user-security" data-toggle="tab"><?php echo $hesklang['user_security']; ?></a></li>
<li><a href="#" target="_blank"><?php echo $hesklang['api_documentation']; ?> <i class="fa fa-external-link"></i></a></li> <li><a href="https://mods-for-hesk.readme.io/reference" target="_blank"><?php echo $hesklang['api_documentation']; ?> <i class="fa fa-external-link"></i></a></li>
</ul> </ul>
<div class="tab-content summaryList tabPadding"> <div class="tab-content summaryList tabPadding">
<div class="tab-pane fade in active" id="general"> <div class="tab-pane fade in active" id="general">

@ -1,32 +1,15 @@
<?php <?php
/******************************************************************************* /**
* Title: Help Desk Software HESK *
* Version: 2.6.1 from 26th February 2015 * This file is part of HESK - PHP Help Desk Software.
* Author: Klemen Stirn *
* Website: https://www.hesk.com * (c) Copyright Klemen Stirn. All rights reserved.
******************************************************************************** * https://www.hesk.com
* COPYRIGHT AND TRADEMARK NOTICE *
* Copyright 2005-2015 Klemen Stirn. All Rights Reserved. * For the full copyright and license agreement information visit
* HESK is a registered trademark of Klemen Stirn. * https://www.hesk.com/eula.php
* The HESK may be used and modified free of charge by anyone *
* AS LONG AS COPYRIGHT NOTICES AND ALL THE COMMENTS REMAIN INTACT. */
* By using this code you agree to indemnify Klemen Stirn from any
* liability that might arise from it's use.
* Selling the code for this program, in part or full, without prior
* written consent is expressly forbidden.
* Using this code, in part or full, to create derivate work,
* new scripts or products is expressly forbidden. Obtain permission
* before redistributing this software over the Internet or in
* any other medium. In all cases copyright and header must remain intact.
* This Copyright is in full effect in any country that has International
* Trade Agreements with the United States of America or
* with the European Union.
* Removing any of the copyright notices without purchasing a license
* is expressly forbidden. To remove HESK copyright notice you must purchase
* a license for this script. For more information on how to obtain
* a license please visit the page below:
* https://www.hesk.com/buy.php
*******************************************************************************/
define('IN_SCRIPT', 1); define('IN_SCRIPT', 1);
define('HESK_PATH', '../'); define('HESK_PATH', '../');

@ -1,32 +1,4 @@
<?php <?php
/*******************************************************************************
* Title: Help Desk Software HESK
* Version: 2.6.5 from 28th August 2015
* Author: Klemen Stirn
* Website: https://www.hesk.com
********************************************************************************
* COPYRIGHT AND TRADEMARK NOTICE
* Copyright 2005-2015 Klemen Stirn. All Rights Reserved.
* HESK is a registered trademark of Klemen Stirn.
* The HESK may be used and modified free of charge by anyone
* AS LONG AS COPYRIGHT NOTICES AND ALL THE COMMENTS REMAIN INTACT.
* By using this code you agree to indemnify Klemen Stirn from any
* liability that might arise from it's use.
* Selling the code for this program, in part or full, without prior
* written consent is expressly forbidden.
* Using this code, in part or full, to create derivate work,
* new scripts or products is expressly forbidden. Obtain permission
* before redistributing this software over the Internet or in
* any other medium. In all cases copyright and header must remain intact.
* This Copyright is in full effect in any country that has International
* Trade Agreements with the United States of America or
* with the European Union.
* Removing any of the copyright notices without purchasing a license
* is expressly forbidden. To remove HESK copyright notice you must purchase
* a license for this script. For more information on how to obtain
* a license please visit the page below:
* https://www.hesk.com/buy.php
*******************************************************************************/
define('IN_SCRIPT', 1); define('IN_SCRIPT', 1);
define('VALIDATOR', 1); define('VALIDATOR', 1);
@ -61,14 +33,26 @@ if ($modsForHesk_settings['enable_calendar'] == '0') {
// Get categories for the dropdown // Get categories for the dropdown
$order_by = $modsForHesk_settings['category_order_column']; $order_by = $modsForHesk_settings['category_order_column'];
$rs = hesk_dbQuery("SELECT `id`, `name`, `color` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` WHERE `usage` <> 1 ORDER BY `" . hesk_dbEscape($order_by) . "`"); $rs = hesk_dbQuery("SELECT `id`, `name`, `background_color`, `foreground_color`, `display_border_outline`
FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories`
WHERE `usage` <> 1 ORDER BY `" . hesk_dbEscape($order_by) . "`");
$categories = array(); $categories = array();
while ($row = hesk_dbFetchAssoc($rs)) { while ($row = hesk_dbFetchAssoc($rs)) {
if (!$_SESSION['isadmin'] && !in_array($row['id'], $_SESSION['categories'])) { if (!$_SESSION['isadmin'] && !in_array($row['id'], $_SESSION['categories'])) {
continue; continue;
} }
$row['css_style'] = $row['color'] == null ? 'background: white; color: black; border: solid 1px #000;' : 'border: solid 1px ' . $row['color'] . '; background: ' . $row['color']; $row['css_style'] = "background: {$row['background_color']};";
$row['background_volatile'] = 'background-volatile';
if ($row['foreground_color'] != 'AUTO') {
$row['background_volatile'] = '';
$row['css_style'] .= " color: {$row['foreground_color']};";
if ($row['display_border_outline'] == '1') {
$row['css_style'] .= " border: solid 1px {$row['foreground_color']};";
}
}
$categories[] = $row; $categories[] = $row;
} }
@ -85,7 +69,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
<?php foreach ($categories as $category): ?> <?php foreach ($categories as $category): ?>
<li> <li>
<div class="ticket-info"> <div class="ticket-info">
<div class="hide-on-overflow no-wrap event-category background-volatile" <div class="hide-on-overflow no-wrap event-category <?php echo $category['background_volatile']; ?>"
data-select-toggle="category-toggle" data-name="category-toggle" data-category-value="<?php echo $category['id']; ?>" data-select-toggle="category-toggle" data-name="category-toggle" data-category-value="<?php echo $category['id']; ?>"
data-checked="1" data-checked="1"
data-toggle="tooltip" data-toggle="tooltip"
@ -223,7 +207,9 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
echo '<option value="">'.$hesklang['select'].'</option>'; echo '<option value="">'.$hesklang['select'].'</option>';
} }
foreach ($categories as $category): ?> foreach ($categories as $category): ?>
<option value="<?php echo $category['id']; ?>" data-color="<?php echo htmlspecialchars($category['color']); ?>"> <option value="<?php echo $category['id']; ?>" data-background-color="<?php echo htmlspecialchars($category['background_color']); ?>"
data-foreground-color="<?php echo htmlspecialchars($category['foreground_color']); ?>"
data-display-border="<?php echo htmlspecialchars($category['display_border_outline']); ?>">
<?php echo $category['name']; ?> <?php echo $category['name']; ?>
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
@ -393,7 +379,9 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
echo '<option value="">'.$hesklang['select'].'</option>'; echo '<option value="">'.$hesklang['select'].'</option>';
} }
foreach ($categories as $category): ?> foreach ($categories as $category): ?>
<option value="<?php echo $category['id']; ?>" data-color="<?php echo $category['color']; ?>"> <option value="<?php echo $category['id']; ?>" data-background-color="<?php echo htmlspecialchars($category['background_color']); ?>"
data-foreground-color="<?php echo htmlspecialchars($category['foreground_color']); ?>"
data-display-border="<?php echo htmlspecialchars($category['display_border_outline']); ?>">
<?php echo $category['name']; ?> <?php echo $category['name']; ?>
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>

@ -608,7 +608,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
</div> </div>
</div> </div>
<?php <?php
display_dropzone_field($hesk_settings['hesk_url'] . '/internal-api/ticket/upload-attachment.php', display_dropzone_field(HESK_PATH . 'internal-api/ticket/upload-attachment.php',
'filedrop', 'filedrop',
$hesk_settings['attachments']['max_number'] - $number_of_attachments); $hesk_settings['attachments']['max_number'] - $number_of_attachments);
endif; ?> endif; ?>

@ -246,7 +246,7 @@ function do_login()
function print_login() function print_login()
{ {
global $hesk_settings, $hesklang; global $hesk_settings, $hesklang, $modsForHesk_settings;
// Tell header to load reCaptcha API if needed // Tell header to load reCaptcha API if needed
if ($hesk_settings['recaptcha_use'] == 2) if ($hesk_settings['recaptcha_use'] == 2)
@ -269,197 +269,205 @@ function print_login()
?> ?>
<div class="login-box"> <div class="login-box">
<div class="login-logo"> <div class="login-box-container">
<?php echo $hesk_settings['hesk_title']; ?> <div class="login-box-background"></div>
</div> <div class="login-box-body">
<div class="login-box-body"> <div class="loginError">
<div class="loginError"> <?php
<?php /* This will handle error, success and notice messages */
/* This will handle error, success and notice messages */ hesk_handle_messages();
hesk_handle_messages(); ?>
?> </div>
</div> <div class="login-logo">
<h4 class="login-box-msg"> <?php if ($modsForHesk_settings['login_box_header'] == 'image'): ?>
<?php echo $hesklang['staff_login_title']; ?> <img src="<?php echo HESK_PATH . $hesk_settings['cache_dir'] . '/lbh_' . $modsForHesk_settings['login_box_header_image']; ?>"
</h4> style="height: 75px">
<form class="form-horizontal" role="form" action="index.php" method="post" name="form1"> <?php else:
<?php echo $hesk_settings['hesk_title'];
$has_error = ''; endif; ?>
if (in_array('pass',$_SESSION['a_iserror'])) { </div>
$has_error = 'has-error'; <h4 class="login-box-msg">
} <?php echo $hesklang['staff_login_title']; ?>
?> </h4>
<div class="form-group <?php echo $has_error; ?>"> <form class="form-horizontal" role="form" action="index.php" method="post" name="form1">
<label for="user" class="col-sm-4 control-label"> <?php
<?php echo $hesklang['username']; ?> $has_error = '';
</label> if (in_array('pass',$_SESSION['a_iserror'])) {
<div class="col-sm-8"> $has_error = 'has-error';
}
?>
<div class="form-group <?php echo $has_error; ?>">
<label for="user" class="col-sm-4 control-label">
<?php echo $hesklang['username']; ?>
</label>
<div class="col-sm-8">
<?php
if (defined('HESK_USER')) {
$savedUser = HESK_USER;
} else {
$savedUser = hesk_htmlspecialchars(hesk_COOKIE('hesk_username'));
}
$is_1 = '';
$is_2 = '';
$is_3 = '';
$remember_user = hesk_POST('remember_user');
if ($hesk_settings['autologin'] && (isset($_COOKIE['hesk_p']) || $remember_user == 'AUTOLOGIN')) {
$is_1 = 'checked';
} elseif (isset($_COOKIE['hesk_username']) || $remember_user == 'JUSTUSER') {
$is_2 = 'checked';
} else {
$is_3 = 'checked';
}
if ($hesk_settings['list_users']) :
$res = hesk_dbQuery("SELECT `user` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "users` WHERE `active` = '1' ORDER BY `user` ASC");
?>
<select class="form-control" name="user">
<?php
while ($row = hesk_dbFetchAssoc($res)):
$sel = (strtolower($savedUser) == strtolower($row['user'])) ? 'selected' : '';
?>
<option value="<?php echo $row['user']; ?>" <?php echo $sel; ?>>
<?php echo $row['user']; ?>
</option>
<?php endwhile; ?>
</select>
<?php else: ?>
<input class="form-control" type="text" name="user" size="35"
placeholder="<?php echo htmlspecialchars($hesklang['username']); ?>"
value="<?php echo $savedUser; ?>">
<?php endif; ?>
</div>
</div>
<?php
$has_error = '';
if (in_array('pass',$_SESSION['a_iserror'])) {
$has_error = 'has-error';
}
?>
<div class="form-group <?php echo $has_error; ?>">
<label for="pass" class="col-sm-4 control-label">
<?php echo $hesklang['pass']; ?>
</label>
<div class="col-sm-8">
<input type="password" class="form-control" id="pass" name="pass" size="35" placeholder="<?php echo htmlspecialchars($hesklang['pass']); ?>">
</div>
</div>
<?php
if ($hesk_settings['secimg_use'] == 2)
{
// SPAM prevention verified for this session
if (isset($_SESSION['img_a_verified']))
{
echo '<img src="'.HESK_PATH.'img/success.png" width="16" height="16" border="0" alt="" style="vertical-align:text-bottom" /> '.$hesklang['vrfy'];
}
// Not verified yet, should we use Recaptcha?
elseif ($hesk_settings['recaptcha_use'] == 1)
{
?>
<script type="text/javascript">
var RecaptchaOptions = {
theme : '<?php echo ( isset($_SESSION['a_iserror']) && in_array('mysecnum',$_SESSION['a_iserror']) ) ? 'red' : 'white'; ?>',
custom_translations : {
visual_challenge : "<?php echo hesk_slashJS($hesklang['visual_challenge']); ?>",
audio_challenge : "<?php echo hesk_slashJS($hesklang['audio_challenge']); ?>",
refresh_btn : "<?php echo hesk_slashJS($hesklang['refresh_btn']); ?>",
instructions_visual : "<?php echo hesk_slashJS($hesklang['instructions_visual']); ?>",
instructions_context : "<?php echo hesk_slashJS($hesklang['instructions_context']); ?>",
instructions_audio : "<?php echo hesk_slashJS($hesklang['instructions_audio']); ?>",
help_btn : "<?php echo hesk_slashJS($hesklang['help_btn']); ?>",
play_again : "<?php echo hesk_slashJS($hesklang['play_again']); ?>",
cant_hear_this : "<?php echo hesk_slashJS($hesklang['cant_hear_this']); ?>",
incorrect_try_again : "<?php echo hesk_slashJS($hesklang['incorrect_try_again']); ?>",
image_alt_text : "<?php echo hesk_slashJS($hesklang['image_alt_text']); ?>"
}
};
</script>
<?php <?php
if (defined('HESK_USER')) { require_once(HESK_PATH . 'inc/recaptcha/recaptchalib.php');
$savedUser = HESK_USER; echo '<div class="form-group"><div class="col-md-8 col-md-offset-4">';
} else { echo recaptcha_get_html($hesk_settings['recaptcha_public_key'], null, true);
$savedUser = hesk_htmlspecialchars(hesk_COOKIE('hesk_username')); echo '</div></div>';
} }
// Use reCaptcha API v2?
$is_1 = ''; elseif ($hesk_settings['recaptcha_use'] == 2)
$is_2 = ''; {
$is_3 = ''; ?>
<div class="form-group">
$remember_user = hesk_POST('remember_user'); <div class="col-md-8 col-md-offset-4">
<div class="g-recaptcha" data-sitekey="<?php echo $hesk_settings['recaptcha_public_key']; ?>"></div>
if ($hesk_settings['autologin'] && (isset($_COOKIE['hesk_p']) || $remember_user == 'AUTOLOGIN')) { </div>
$is_1 = 'checked'; </div>
} elseif (isset($_COOKIE['hesk_username']) || $remember_user == 'JUSTUSER') { <?php
$is_2 = 'checked';
} else {
$is_3 = 'checked';
} }
// At least use some basic PHP generated image (better than nothing)
else
{
echo '<div class="form-group"><div class="col-md-8 col-md-offset-4">';
$cls = in_array('mysecnum',$_SESSION['a_iserror']) ? ' class="isError" ' : '';
if ($hesk_settings['list_users']) : echo $hesklang['sec_enter'].'<br><br><img src="'.HESK_PATH.'print_sec_img.php?'.rand(10000,99999).'" width="150" height="40" alt="'.$hesklang['sec_img'].'" title="'.$hesklang['sec_img'].'" border="1" name="secimg" style="vertical-align:text-bottom"> '.
$res = hesk_dbQuery("SELECT `user` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "users` WHERE `active` = '1' ORDER BY `user` ASC"); '<a href="javascript:void(0)" onclick="javascript:document.form1.secimg.src=\''.HESK_PATH.'print_sec_img.php?\'+ ( Math.floor((90000)*Math.random()) + 10000);"><img src="'.HESK_PATH.'img/reload.png" height="24" width="24" alt="'.$hesklang['reload'].'" title="'.$hesklang['reload'].'" border="0" style="vertical-align:text-bottom"></a>'.
?> '<br><br><input type="text" name="mysecnum" size="20" maxlength="5" '.$cls.'>';
<select class="form-control" name="user"> echo '</div></div>';
<?php }
while ($row = hesk_dbFetchAssoc($res)): } // End if $hesk_settings['secimg_use'] == 2
$sel = (strtolower($savedUser) == strtolower($row['user'])) ? 'selected' : '';
?>
<option value="<?php echo $row['user']; ?>" <?php echo $sel; ?>>
<?php echo $row['user']; ?>
</option>
<?php endwhile; ?>
</select>
<?php else: ?>
<input class="form-control" type="text" name="user" size="35"
placeholder="<?php echo htmlspecialchars($hesklang['username']); ?>"
value="<?php echo $savedUser; ?>">
<?php endif; ?>
</div>
</div>
<?php
$has_error = '';
if (in_array('pass',$_SESSION['a_iserror'])) {
$has_error = 'has-error';
}
?>
<div class="form-group <?php echo $has_error; ?>">
<label for="pass" class="col-sm-4 control-label">
<?php echo $hesklang['pass']; ?>
</label>
<div class="col-sm-8">
<input type="password" class="form-control" id="pass" name="pass" size="35" placeholder="<?php echo htmlspecialchars($hesklang['pass']); ?>">
</div>
</div>
<?php
if ($hesk_settings['secimg_use'] == 2)
{
// SPAM prevention verified for this session if ($hesk_settings['autologin'])
if (isset($_SESSION['img_a_verified']))
{
echo '<img src="'.HESK_PATH.'img/success.png" width="16" height="16" border="0" alt="" style="vertical-align:text-bottom" /> '.$hesklang['vrfy'];
}
// Not verified yet, should we use Recaptcha?
elseif ($hesk_settings['recaptcha_use'] == 1)
{ {
?> ?>
<script type="text/javascript">
var RecaptchaOptions = {
theme : '<?php echo ( isset($_SESSION['a_iserror']) && in_array('mysecnum',$_SESSION['a_iserror']) ) ? 'red' : 'white'; ?>',
custom_translations : {
visual_challenge : "<?php echo hesk_slashJS($hesklang['visual_challenge']); ?>",
audio_challenge : "<?php echo hesk_slashJS($hesklang['audio_challenge']); ?>",
refresh_btn : "<?php echo hesk_slashJS($hesklang['refresh_btn']); ?>",
instructions_visual : "<?php echo hesk_slashJS($hesklang['instructions_visual']); ?>",
instructions_context : "<?php echo hesk_slashJS($hesklang['instructions_context']); ?>",
instructions_audio : "<?php echo hesk_slashJS($hesklang['instructions_audio']); ?>",
help_btn : "<?php echo hesk_slashJS($hesklang['help_btn']); ?>",
play_again : "<?php echo hesk_slashJS($hesklang['play_again']); ?>",
cant_hear_this : "<?php echo hesk_slashJS($hesklang['cant_hear_this']); ?>",
incorrect_try_again : "<?php echo hesk_slashJS($hesklang['incorrect_try_again']); ?>",
image_alt_text : "<?php echo hesk_slashJS($hesklang['image_alt_text']); ?>"
}
};
</script>
<?php
require_once(HESK_PATH . 'inc/recaptcha/recaptchalib.php');
echo '<div class="form-group"><div class="col-md-8 col-md-offset-4">';
echo recaptcha_get_html($hesk_settings['recaptcha_public_key'], null, true);
echo '</div></div>';
}
// Use reCaptcha API v2?
elseif ($hesk_settings['recaptcha_use'] == 2)
{
?>
<div class="form-group"> <div class="form-group">
<div class="col-md-8 col-md-offset-4"> <div class="col-md-offset-4 col-md-8">
<div class="g-recaptcha" data-sitekey="<?php echo $hesk_settings['recaptcha_public_key']; ?>"></div> <div class="radio">
<label><input type="radio" name="remember_user" value="AUTOLOGIN" <?php echo $is_1; ?>> <?php echo $hesklang['autologin']; ?></label>
</div>
<div class="radio">
<label><input type="radio" name="remember_user" value="JUSTUSER" <?php echo $is_2; ?>> <?php echo $hesklang['just_user']; ?></label>
</div>
<div class="radio">
<label><input type="radio" name="remember_user" value="NOTHANKS" <?php echo $is_3; ?>> <?php echo $hesklang['nothx']; ?></label>
</div>
</div> </div>
</div> </div>
<?php <?php
} }
// At least use some basic PHP generated image (better than nothing)
else else
{ {
echo '<div class="form-group"><div class="col-md-8 col-md-offset-4">'; ?>
$cls = in_array('mysecnum',$_SESSION['a_iserror']) ? ' class="isError" ' : ''; <div class="form-group">
<div class="col-md-offset-4 col-md-8">
echo $hesklang['sec_enter'].'<br><br><img src="'.HESK_PATH.'print_sec_img.php?'.rand(10000,99999).'" width="150" height="40" alt="'.$hesklang['sec_img'].'" title="'.$hesklang['sec_img'].'" border="1" name="secimg" style="vertical-align:text-bottom"> '. <div class="checkbox">
'<a href="javascript:void(0)" onclick="javascript:document.form1.secimg.src=\''.HESK_PATH.'print_sec_img.php?\'+ ( Math.floor((90000)*Math.random()) + 10000);"><img src="'.HESK_PATH.'img/reload.png" height="24" width="24" alt="'.$hesklang['reload'].'" title="'.$hesklang['reload'].'" border="0" style="vertical-align:text-bottom"></a>'. <label><input type="checkbox" name="remember_user" value="JUSTUSER" <?php echo $is_2; ?> /> <?php echo $hesklang['remember_user']; ?></label>
'<br><br><input type="text" name="mysecnum" size="20" maxlength="5" '.$cls.'>'; </div>
echo '</div></div>';
}
} // End if $hesk_settings['secimg_use'] == 2
if ($hesk_settings['autologin'])
{
?>
<div class="form-group">
<div class="col-md-offset-4 col-md-8">
<div class="radio">
<label><input type="radio" name="remember_user" value="AUTOLOGIN" <?php echo $is_1; ?>> <?php echo $hesklang['autologin']; ?></label>
</div>
<div class="radio">
<label><input type="radio" name="remember_user" value="JUSTUSER" <?php echo $is_2; ?>> <?php echo $hesklang['just_user']; ?></label>
</div>
<div class="radio">
<label><input type="radio" name="remember_user" value="NOTHANKS" <?php echo $is_3; ?>> <?php echo $hesklang['nothx']; ?></label>
</div> </div>
</div> </div>
</div> <?php
<?php } // End if $hesk_settings['autologin']
}
else
{
?> ?>
<div class="form-group"> <div class="form-group">
<div class="col-md-offset-4 col-md-8"> <div class="col-md-offset-4 col-md-8">
<div class="checkbox"> <input type="submit" value="<?php echo $hesklang['click_login']; ?>" class="btn btn-default">
<label><input type="checkbox" name="remember_user" value="JUSTUSER" <?php echo $is_2; ?> /> <?php echo $hesklang['remember_user']; ?></label> <input type="hidden" name="a" value="do_login">
</div> <?php
if ( hesk_isREQUEST('goto') && $url=hesk_REQUEST('goto') )
{
echo '<input type="hidden" name="goto" value="'.$url.'">';
}
// Do we allow staff password reset?
if ($hesk_settings['reset_pass'])
{
echo '<br><br><a href="password.php" class="smaller">'.$hesklang['fpass'].'</a>';
}
?>
</div> </div>
</div> </div>
<?php </form>
} // End if $hesk_settings['autologin'] </div>
?>
<div class="form-group">
<div class="col-md-offset-4 col-md-8">
<input type="submit" value="<?php echo $hesklang['click_login']; ?>" class="btn btn-default">
<input type="hidden" name="a" value="do_login">
<?php
if ( hesk_isREQUEST('goto') && $url=hesk_REQUEST('goto') )
{
echo '<input type="hidden" name="goto" value="'.$url.'">';
}
// Do we allow staff password reset?
if ($hesk_settings['reset_pass'])
{
echo '<br><br><a href="password.php" class="smaller">'.$hesklang['fpass'].'</a>';
}
?>
</div>
</div>
</form>
</div> </div>
</div> </div>
<?php <?php

@ -1,32 +1,15 @@
<?php <?php
/******************************************************************************* /**
* Title: Help Desk Software HESK *
* Version: 2.6.8 from 10th August 2016 * This file is part of HESK - PHP Help Desk Software.
* Author: Klemen Stirn *
* Website: https://www.hesk.com * (c) Copyright Klemen Stirn. All rights reserved.
******************************************************************************** * https://www.hesk.com
* COPYRIGHT AND TRADEMARK NOTICE *
* Copyright 2005-2015 Klemen Stirn. All Rights Reserved. * For the full copyright and license agreement information visit
* HESK is a registered trademark of Klemen Stirn. * https://www.hesk.com/eula.php
* The HESK may be used and modified free of charge by anyone *
* AS LONG AS COPYRIGHT NOTICES AND ALL THE COMMENTS REMAIN INTACT. */
* By using this code you agree to indemnify Klemen Stirn from any
* liability that might arise from it's use.
* Selling the code for this program, in part or full, without prior
* written consent is expressly forbidden.
* Using this code, in part or full, to create derivate work,
* new scripts or products is expressly forbidden. Obtain permission
* before redistributing this software over the Internet or in
* any other medium. In all cases copyright and header must remain intact.
* This Copyright is in full effect in any country that has International
* Trade Agreements with the United States of America or
* with the European Union.
* Removing any of the copyright notices without purchasing a license
* is expressly forbidden. To remove HESK copyright notice you must purchase
* a license for this script. For more information on how to obtain
* a license please visit the page below:
* https://www.hesk.com/buy.php
*******************************************************************************/
define('IN_SCRIPT',1); define('IN_SCRIPT',1);
define('HESK_PATH','../'); define('HESK_PATH','../');

@ -107,8 +107,7 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
<div class="box-body"> <div class="box-body">
<form action="manage_categories.php" method="post" role="form" class="form-horizontal" data-toggle="validator"> <form action="manage_categories.php" method="post" role="form" class="form-horizontal" data-toggle="validator">
<div class="form-group"> <div class="form-group">
<p class="col-sm-4 control-label" style="font-size: .87em"> <label for="name" class="col-sm-4 control-label"><?php echo $hesklang['cat_name']; ?></label>
<b><?php echo $hesklang['cat_name']; ?></b> (<?php echo $hesklang['max_chars']; ?>)</p>
<div class="col-sm-8"> <div class="col-sm-8">
<input class="form-control" <input class="form-control"
@ -121,12 +120,12 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
?> ?>
data-error="<?php echo htmlspecialchars($hesklang['enter_cat_name']); ?>" data-error="<?php echo htmlspecialchars($hesklang['enter_cat_name']); ?>"
required> required>
<div class="help-block"><?php echo $hesklang['max_chars']; ?></div>
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="priority" class="col-sm-4 control-label" <label for="priority" class="col-sm-4 control-label"><?php echo $hesklang['def_pri']; ?> <a href="#"
style="font-size: .87em"><?php echo $hesklang['def_pri']; ?> <a href="#"
onclick="alert('<?php echo hesk_makeJsString($hesklang['cat_pri']); ?>')"><i onclick="alert('<?php echo hesk_makeJsString($hesklang['cat_pri']); ?>')"><i
class="fa fa-question-circle settingsquestionmark"></i> </a> </label> class="fa fa-question-circle settingsquestionmark"></i> </a> </label>
@ -149,15 +148,51 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="color" class="col-sm-4 control-label"> <label for="color" class="col-sm-4 control-label">
<?php echo $hesklang['category_color']; ?> <?php echo $hesklang['category_background_color']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover" <i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
title="<?php echo htmlspecialchars($hesklang['category_color']); ?>" title="<?php echo htmlspecialchars($hesklang['category_background_color']); ?>"
data-content="<?php echo htmlspecialchars($hesklang['category_color_help']); ?>"></i> data-content="<?php echo htmlspecialchars($hesklang['category_background_color_help']); ?>"></i>
</label> </label>
<div class="col-sm-8"> <div class="col-sm-8">
<input class="form-control colorpicker-trigger" <input class="form-control colorpicker-trigger"
placeholder="<?php echo htmlspecialchars($hesklang['category_color']); ?>" type="text" placeholder="<?php echo htmlspecialchars($hesklang['category_background_color']); ?>" type="text"
name="color" maxlength="7"> name="background-color" maxlength="7" required>
<div class="help-block with-errors"></div>
</div>
</div>
<div class="form-group">
<label for="color" class="col-sm-4 control-label">
<?php echo $hesklang['category_foreground_color']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
title="<?php echo htmlspecialchars($hesklang['category_foreground_color']); ?>"
data-content="<?php echo htmlspecialchars($hesklang['category_foreground_color_help']); ?>"></i>
</label>
<div class="col-sm-8">
<input class="form-control colorpicker-trigger"
placeholder="<?php echo htmlspecialchars($hesklang['category_foreground_color']); ?>" type="text"
name="foreground-color" maxlength="7">
</div>
</div>
<div class="form-group">
<label for="display-border" class="col-sm-4 control-label">
<?php echo $hesklang['category_display_border']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="htmlpopover"
title="<?php echo htmlspecialchars($hesklang['category_display_border']); ?>"
data-content="<?php echo htmlspecialchars($hesklang['category_display_border_help']); ?>"></i>
</label>
<div class="col-sm-8 form-inline">
<div class="radio">
<label>
<input type="radio" name="display-border" value="1">
<?php echo $hesklang['yes']; ?>
</label>
</div>&nbsp;&nbsp;&nbsp;
<div class="radio">
<label>
<input type="radio" name="display-border" value="0" checked>
<?php echo $hesklang['no']; ?>
</label>
</div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -194,10 +229,12 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
</div> </div>
</div> </div>
</div> </div>
<div class="form-group text-center"> <div class="form-group">
<input type="hidden" name="a" value="new"/> <div class="col-sm-9 col-sm-offset-4">
<input type="hidden" name="token" value="<?php hesk_token_echo(); ?>"/> <input type="hidden" name="a" value="new"/>
<input type="submit" value="<?php echo $hesklang['create_cat']; ?>" class="btn btn-default"/> <input type="hidden" name="token" value="<?php hesk_token_echo(); ?>"/>
<input type="submit" value="<?php echo $hesklang['create_cat']; ?>" class="btn btn-default"/>
</div>
</div> </div>
</form> </form>
</div> </div>
@ -277,12 +314,21 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
} }
$tmp = $i ? 'White' : 'Blue'; $tmp = $i ? 'White' : 'Blue';
$style = ''; $style = 'background: ' . $mycat['background_color'];
if ($mycat['color'] == null) { $backgroundVolatile = 'background-volatile';
$style .= 'color: black; border: solid 1px #000'; if ($mycat['foreground_color'] != 'AUTO') {
} else { $style .= '; color: ' . $mycat['foreground_color'];
$style .= 'background: ' . $mycat['color']; $backgroundVolatile = '';
if ($mycat['display_border_outline']) {
$style .= '; border: solid 1px ' . $mycat['foreground_color'];
}
} }
if ($mycat['foreground_color'] == 'AUTO') {
$mycat['foreground_color'] = '';
}
$i = $i ? 0 : 1; $i = $i ? 0 : 1;
/* Number of tickets and graph width */ /* Number of tickets and graph width */
@ -319,16 +365,18 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
echo ' echo '
<tr data-category-id="' . $mycat['id'] . '" data-name="' . htmlspecialchars($mycat['name']) . '" <tr data-category-id="' . $mycat['id'] . '" data-name="' . htmlspecialchars($mycat['name']) . '"
data-color="'. htmlspecialchars($mycat['color']) . '" data-priority="' . $mycat['priority'] . '" data-foreground-color="' . htmlspecialchars($mycat['foreground_color']) . '"
data-border="' . $mycat['display_border_outline'] . '"
data-background-color="'. htmlspecialchars($mycat['background_color']) . '"
data-priority="' . $mycat['priority'] . '"
data-manager="' . $mycat['manager'] . '" data-usage="'. $mycat['usage'] .'"> data-manager="' . $mycat['manager'] . '" data-usage="'. $mycat['usage'] .'">
<td style="display: none">' . $mycat['id'] . '</td> <td style="display: none">' . $mycat['id'] . '</td>
<td><span class="label background-volatile category-label" style="'.$style.'">' . $mycat['name'] . '</span></td> <td><span class="label ' . $backgroundVolatile . ' category-label" style="'.$style.'">' . $mycat['name'] . '</span></td>
<td width="1" style="white-space: nowrap;">' . $priorities[$mycat['priority']]['formatted'] . '</td> <td width="1" style="white-space: nowrap;">' . $priorities[$mycat['priority']]['formatted'] . '</td>
<td><a href="show_tickets.php?category=' . $mycat['id'] . '&amp;s_all=1&amp;s_my=1&amp;s_ot=1&amp;s_un=1" alt="' . $hesklang['list_tickets_cat'] . '" title="' . $hesklang['list_tickets_cat'] . '">' . $all . '</a></td> <td><a href="show_tickets.php?category=' . $mycat['id'] . '&amp;s_all=1&amp;s_my=1&amp;s_ot=1&amp;s_un=1" alt="' . $hesklang['list_tickets_cat'] . '" title="' . $hesklang['list_tickets_cat'] . '">' . $all . '</a></td>
<td> <td>
<div class="progress" style="width: 160px; margin-bottom: 0" title="' . sprintf($hesklang['perat'], $width_all . '%') . '" data-toggle="tooltip"> <div class="progress" style="width: 160px; margin-bottom: 0" title="' . sprintf($hesklang['perat'], $width_all . '%') . '" data-toggle="tooltip">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: ' . $width_all . '%"> <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: ' . $width_all . '%">
<span class="sr-only">40% Complete (success)</span>
</div> </div>
</div> </div>
</td> </td>
@ -351,7 +399,7 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
'; ';
} }
} }
echo '<a href="javascript:;" class="category-modal-trigger" data-category-id="' . $mycat['id'] . '"><i class="fa fa-pencil icon-link orange" data-toggle="tooltip" title="Edit"></i></a>'; echo '<a href="javascript:;" class="category-modal-trigger" data-category-id="' . $mycat['id'] . '"><i class="fa fa-pencil icon-link orange" data-toggle="tooltip" title="' . $hesklang['edit'] . '"></i></a>';
echo $remove_code . '</td> echo $remove_code . '</td>
</tr> </tr>
'; ';
@ -370,34 +418,71 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
<div class="modal-content"> <div class="modal-content">
<div class="modal-header" style="cursor: move"> <div class="modal-header" style="cursor: move">
<button type="button" class="close cancel-callback" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> <button type="button" class="close cancel-callback" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">Edit Category</h4> <h4 class="modal-title" id="myModalLabel"><?php echo $hesklang['edit_category']; ?></h4>
</div> </div>
<form action="manage_categories.php" class="form-horizontal" data-toggle="validator" method="post"> <form action="manage_categories.php" class="form-horizontal" data-toggle="validator" method="post">
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="form-group"> <div class="form-group">
<label for="name" class="col-sm-3 control-label"><?php echo $hesklang['name']; ?></label> <label for="name" class="col-sm-3 control-label"><?php echo $hesklang['cat_name']; ?></label>
<div class="col-sm-9"> <div class="col-sm-9">
<input type="text" name="name" class="form-control" placeholder="<?php echo $hesklang['name']; ?>" <input type="text" name="name" class="form-control" placeholder="<?php echo $hesklang['cat_name']; ?>"
data-error="<?php echo htmlspecialchars($hesklang['this_field_is_required']); ?>" data-error="<?php echo htmlspecialchars($hesklang['this_field_is_required']); ?>"
required> required>
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="color" class="col-sm-3 control-label"> <label for="background-color" class="col-sm-3 control-label">
<?php echo $hesklang['category_color']; ?> <?php echo $hesklang['category_background_color']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover" <i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
title="<?php echo htmlspecialchars($hesklang['category_color']); ?>" title="<?php echo htmlspecialchars($hesklang['category_background_color']); ?>"
data-content="<?php echo htmlspecialchars($hesklang['category_color_help']); ?>"></i> data-content="<?php echo htmlspecialchars($hesklang['category_background_color_help']); ?>"></i>
</label> </label>
<div class="col-sm-9"> <div class="col-sm-9">
<input type="text" name="color" class="form-control category-colorpicker" <input type="text" name="background-color" class="form-control category-colorpicker"
placeholder="<?php echo $hesklang['category_color']; ?>"> placeholder="<?php echo $hesklang['category_background_color']; ?>"
data-error="<?php echo htmlspecialchars($hesklang['this_field_is_required']); ?>"
required>
<div class="help-block with-errors"></div>
</div>
</div>
<div class="form-group">
<label for="foreground-color" class="col-sm-3 control-label">
<?php echo $hesklang['category_foreground_color']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="popover"
title="<?php echo htmlspecialchars($hesklang['category_foreground_color']); ?>"
data-content="<?php echo htmlspecialchars($hesklang['category_foreground_color_help']); ?>"></i>
</label>
<div class="col-sm-9">
<input type="text" name="foreground-color" class="form-control category-colorpicker"
placeholder="<?php echo $hesklang['category_foreground_color']; ?>">
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
</div> </div>
<div class="form-group">
<label for="display-border" class="col-sm-3 control-label">
<?php echo $hesklang['category_display_border']; ?>
<i class="fa fa-question-circle settingsquestionmark" data-toggle="htmlpopover"
title="<?php echo htmlspecialchars($hesklang['category_display_border']); ?>"
data-content="<?php echo htmlspecialchars($hesklang['category_display_border_help']); ?>"></i>
</label>
<div class="col-sm-9 form-inline">
<div class="radio">
<label>
<input type="radio" name="display-border" value="1">
<?php echo $hesklang['yes']; ?>
</label>
</div>&nbsp;&nbsp;&nbsp;
<div class="radio">
<label>
<input type="radio" name="display-border" value="0" checked>
<?php echo $hesklang['no']; ?>
</label>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="priority" class="col-sm-3 control-label"> <label for="priority" class="col-sm-3 control-label">
<?php echo $hesklang['priority']; ?> <?php echo $hesklang['priority']; ?>
@ -471,7 +556,8 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
var name = tempNameElement.value; var name = tempNameElement.value;
var id = $row.attr('data-category-id'); var id = $row.attr('data-category-id');
var color = $row.attr('data-color'); var backgroundColor = $row.attr('data-background-color');
var foregroundColor = $row.attr('data-foreground-color');
var priority = $row.attr('data-priority'); var priority = $row.attr('data-priority');
var manager = $row.attr('data-manager'); var manager = $row.attr('data-manager');
var usage = $row.attr('data-usage'); var usage = $row.attr('data-usage');
@ -482,29 +568,32 @@ while ($mycat = hesk_dbFetchAssoc($res)) {
.find('select[name="manager"]').val(manager).end() .find('select[name="manager"]').val(manager).end()
.find('input[name="id"]').val(id).end() .find('input[name="id"]').val(id).end()
.find('select[name="usage"]').val(usage).end() .find('select[name="usage"]').val(usage).end()
.find('input[name="color"]').val(color).end(); .find('input[name="background-color"]').val(backgroundColor).end()
.find('input[name="foreground-color"]').val(foregroundColor).end();
var colorpickerOptions = null;
if (color == '') { var colorpickerOptions = {
colorpickerOptions = { format: 'hex',
format: 'hex' color: backgroundColor
}; };
} else { $modal.find('input[name="background-color"]')
colorpickerOptions = {
format: 'hex',
color: color
};
}
$modal.find('input[name="color"]')
.colorpicker(colorpickerOptions).end().modal('show'); .colorpicker(colorpickerOptions).end().modal('show');
if (color == '') { colorpickerOptions = {
$modal.find('input[name="color"]').val(''); format: 'hex'
};
if (foregroundColor != '') {
colorpickerOptions.color = foregroundColor;
} }
$modal.find('input[name="foreground-color"]')
.colorpicker(colorpickerOptions).end().modal('show');
}); });
$('.cancel-callback').click(function() { $('.cancel-callback').click(function() {
$('#edit-category-modal').find('input[name="color"]').val('').colorpicker('destroy').end(); var $editCategoryModal = $('#edit-category-modal');
$editCategoryModal.find('input[name="background-color"]').val('').colorpicker('destroy').end();
$editCategoryModal.find('input[name="foreground-color"]').val('').colorpicker('destroy').end();
}); });
}); });
</script> </script>
@ -629,9 +718,13 @@ function new_cat()
/* Category name */ /* Category name */
$catname = hesk_input(hesk_POST('name'), $hesklang['enter_cat_name'], 'manage_categories.php'); $catname = hesk_input(hesk_POST('name'), $hesklang['enter_cat_name'], 'manage_categories.php');
$color = hesk_POST('color', null); $background_color = hesk_POST('background-color', '#ffffff');
$color = str_replace('#', '', $color); $foreground_color = hesk_POST('foreground-color', '#000000');
$color = $color != null ? "'#" . hesk_dbEscape($color) . "'" : 'NULL'; $display_border = hesk_POST('display-border', 0);
if ($foreground_color == '') {
$foreground_color = 'AUTO';
$display_border = 0;
}
$usage = hesk_POST('usage', 0); $usage = hesk_POST('usage', 0);
@ -647,7 +740,11 @@ function new_cat()
$row = hesk_dbFetchRow($res); $row = hesk_dbFetchRow($res);
$my_order = $row[0] + 10; $my_order = $row[0] + 10;
hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` (`name`,`cat_order`,`autoassign`,`type`, `priority`, `color`, `usage`) VALUES ('" . hesk_dbEscape($catname) . "','" . intval($my_order) . "','" . intval($_SESSION['cat_autoassign']) . "','" . intval($_SESSION['cat_type']) . "','{$_SESSION['cat_priority']}', {$color}, " . intval($usage) . ")"); hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories`
(`name`,`cat_order`,`autoassign`,`type`, `priority`, `background_color`, `foreground_color`, `display_border_outline`, `usage`) VALUES
('" . hesk_dbEscape($catname) . "','" . intval($my_order) . "','" . intval($_SESSION['cat_autoassign']) . "',
'" . intval($_SESSION['cat_type']) . "','{$_SESSION['cat_priority']}', '" . hesk_dbEscape($background_color) . "',
'" . hesk_dbEscape($foreground_color) . "', '" . intval($display_border) . "', " . intval($usage) . ")");
hesk_cleanSessionVars('catname'); hesk_cleanSessionVars('catname');
hesk_cleanSessionVars('cat_autoassign'); hesk_cleanSessionVars('cat_autoassign');
@ -676,9 +773,13 @@ function update_category()
$catname = hesk_input(hesk_POST('name'), $hesklang['cat_ren_name'], $_SERVER['PHP_SELF']); $catname = hesk_input(hesk_POST('name'), $hesklang['cat_ren_name'], $_SERVER['PHP_SELF']);
$_SESSION['catname2'] = $catname; $_SESSION['catname2'] = $catname;
$color = hesk_POST('color', null); $background_color = hesk_POST('background-color', '#ffffff');
$color = str_replace('#', '', $color); $foreground_color = hesk_POST('foreground-color', '#000000');
$color = $color != null ? "'#" . hesk_dbEscape($color) . "'" : 'NULL'; $display_border = hesk_POST('display-border', 0);
if ($foreground_color == '') {
$foreground_color = 'AUTO';
$display_border = 0;
}
$manager = hesk_POST('manager', 0); $manager = hesk_POST('manager', 0);
$priority = hesk_POST('priority', 0); $priority = hesk_POST('priority', 0);
$usage = hesk_POST('usage', 0); $usage = hesk_POST('usage', 0);
@ -687,7 +788,9 @@ function update_category()
hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` SET `name`='" . hesk_dbEscape($catname) . "', hesk_dbQuery("UPDATE `" . hesk_dbEscape($hesk_settings['db_pfix']) . "categories` SET `name`='" . hesk_dbEscape($catname) . "',
`priority` = '" . hesk_dbEscape($priority) . "', `priority` = '" . hesk_dbEscape($priority) . "',
`manager` = " . intval($manager) . ", `manager` = " . intval($manager) . ",
`color` = " . $color . ", `background_color` = '" . hesk_dbEscape($background_color) . "',
`foreground_color` = '" . hesk_dbEscape($foreground_color) . "',
`display_border_outline` = '" . intval($display_border) . "',
`usage` = " . intval($usage) . " `usage` = " . intval($usage) . "
WHERE `id`='" . intval($catid) . "'"); WHERE `id`='" . intval($catid) . "'");
@ -847,6 +950,8 @@ function get_manager($user_id, $user_array) {
return $user['name']; return $user['name'];
} }
} }
return 'Error!';
} }
?> ?>

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

@ -10,6 +10,7 @@ require(HESK_PATH . 'hesk_settings.inc.php');
require(HESK_PATH . 'inc/common.inc.php'); require(HESK_PATH . 'inc/common.inc.php');
require(HESK_PATH . 'inc/admin_functions.inc.php'); require(HESK_PATH . 'inc/admin_functions.inc.php');
require(HESK_PATH . 'inc/mail_functions.inc.php'); require(HESK_PATH . 'inc/mail_functions.inc.php');
require(HESK_PATH . 'inc/custom_fields.inc.php');
hesk_load_database_functions(); hesk_load_database_functions();
hesk_session_start(); hesk_session_start();

@ -1,32 +1,15 @@
<?php <?php
/******************************************************************************* /**
* Title: Help Desk Software HESK *
* Version: 2.6.8 from 10th August 2016 * This file is part of HESK - PHP Help Desk Software.
* Author: Klemen Stirn *
* Website: https://www.hesk.com * (c) Copyright Klemen Stirn. All rights reserved.
******************************************************************************** * https://www.hesk.com
* COPYRIGHT AND TRADEMARK NOTICE *
* Copyright 2005-2015 Klemen Stirn. All Rights Reserved. * For the full copyright and license agreement information visit
* HESK is a registered trademark of Klemen Stirn. * https://www.hesk.com/eula.php
* The HESK may be used and modified free of charge by anyone *
* AS LONG AS COPYRIGHT NOTICES AND ALL THE COMMENTS REMAIN INTACT. */
* By using this code you agree to indemnify Klemen Stirn from any
* liability that might arise from it's use.
* Selling the code for this program, in part or full, without prior
* written consent is expressly forbidden.
* Using this code, in part or full, to create derivate work,
* new scripts or products is expressly forbidden. Obtain permission
* before redistributing this software over the Internet or in
* any other medium. In all cases copyright and header must remain intact.
* This Copyright is in full effect in any country that has International
* Trade Agreements with the United States of America or
* with the European Union.
* Removing any of the copyright notices without purchasing a license
* is expressly forbidden. To remove HESK copyright notice you must purchase
* a license for this script. For more information on how to obtain
* a license please visit the page below:
* https://www.hesk.com/buy.php
*******************************************************************************/
define('IN_SCRIPT',1); define('IN_SCRIPT',1);
define('HESK_PATH','../'); define('HESK_PATH','../');
@ -476,7 +459,7 @@ if (!isset($_SESSION['hide']['new_article']))
<?php build_dropzone_markup(true); ?> <?php build_dropzone_markup(true); ?>
</div> </div>
<?php <?php
display_dropzone_field($hesk_settings['hesk_url'] . '/internal-api/admin/knowledgebase/upload-attachment.php'); display_dropzone_field(HESK_PATH . 'internal-api/admin/knowledgebase/upload-attachment.php');
endif; // End attachments endif; // End attachments
?> ?>
@ -1534,7 +1517,7 @@ function edit_article()
<?php <?php
build_dropzone_markup(true); build_dropzone_markup(true);
display_dropzone_field($hesk_settings['hesk_url'] . '/internal-api/admin/knowledgebase/upload-attachment.php'); display_dropzone_field(HESK_PATH . 'internal-api/admin/knowledgebase/upload-attachment.php');
?> ?>
</div> </div>
<?php endif; //End attachments ?> <?php endif; //End attachments ?>

@ -132,7 +132,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th><?php echo $hesklang['name']; ?></th> <th><?php echo $hesklang['status_name_title']; ?></th>
<th><?php echo $hesklang['closable_question']; ?></th> <th><?php echo $hesklang['closable_question']; ?></th>
<th><?php echo $hesklang['closedQuestionMark']; ?></th> <th><?php echo $hesklang['closedQuestionMark']; ?></th>
<th><?php echo $hesklang['actions']; ?></th> <th><?php echo $hesklang['actions']; ?></th>

@ -1,32 +1,15 @@
<?php <?php
/******************************************************************************* /**
* Title: Help Desk Software HESK *
* Version: 2.6.1 from 26th February 2015 * This file is part of HESK - PHP Help Desk Software.
* Author: Klemen Stirn *
* Website: https://www.hesk.com * (c) Copyright Klemen Stirn. All rights reserved.
******************************************************************************** * https://www.hesk.com
* COPYRIGHT AND TRADEMARK NOTICE *
* Copyright 2005-2015 Klemen Stirn. All Rights Reserved. * For the full copyright and license agreement information visit
* HESK is a registered trademark of Klemen Stirn. * https://www.hesk.com/eula.php
* The HESK may be used and modified free of charge by anyone *
* AS LONG AS COPYRIGHT NOTICES AND ALL THE COMMENTS REMAIN INTACT. */
* By using this code you agree to indemnify Klemen Stirn from any
* liability that might arise from it's use.
* Selling the code for this program, in part or full, without prior
* written consent is expressly forbidden.
* Using this code, in part or full, to create derivate work,
* new scripts or products is expressly forbidden. Obtain permission
* before redistributing this software over the Internet or in
* any other medium. In all cases copyright and header must remain intact.
* This Copyright is in full effect in any country that has International
* Trade Agreements with the United States of America or
* with the European Union.
* Removing any of the copyright notices without purchasing a license
* is expressly forbidden. To remove HESK copyright notice you must purchase
* a license for this script. For more information on how to obtain
* a license please visit the page below:
* https://www.hesk.com/buy.php
*******************************************************************************/
define('IN_SCRIPT', 1); define('IN_SCRIPT', 1);
define('HESK_PATH', '../'); define('HESK_PATH', '../');

@ -879,7 +879,7 @@ $show_quick_help = $show['show'];
</div> </div>
</div> </div>
<?php <?php
display_dropzone_field($hesk_settings['hesk_url'] . '/internal-api/ticket/upload-attachment.php'); display_dropzone_field(HESK_PATH . 'internal-api/ticket/upload-attachment.php');
} }
if (!isset($_SESSION['as_notify'])) { if (!isset($_SESSION['as_notify'])) {

@ -97,6 +97,7 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
<th><?php echo $hesklang['user']; ?></th> <th><?php echo $hesklang['user']; ?></th>
<th><?php echo $hesklang['custom_place']; ?></th> <th><?php echo $hesklang['custom_place']; ?></th>
<th><?php echo $hesklang['message']; ?></th> <th><?php echo $hesklang['message']; ?></th>
<th><?php echo $hesklang['stack_trace_header']; ?></th>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>

@ -0,0 +1,142 @@
<?php
// Responsible for loading in all necessary classes. AKA a poor man's DI solution.
use BusinessLogic\Attachments\AttachmentHandler;
use BusinessLogic\Attachments\AttachmentRetriever;
use BusinessLogic\Categories\CategoryRetriever;
use BusinessLogic\Emails\BasicEmailSender;
use BusinessLogic\Emails\EmailSenderHelper;
use BusinessLogic\Emails\EmailTemplateParser;
use BusinessLogic\Emails\EmailTemplateRetriever;
use BusinessLogic\Emails\MailgunEmailSender;
use BusinessLogic\Navigation\CustomNavElementHandler;
use BusinessLogic\Security\BanRetriever;
use BusinessLogic\Security\UserContextBuilder;
use BusinessLogic\Security\UserToTicketChecker;
use BusinessLogic\Settings\ApiChecker;
use BusinessLogic\Settings\SettingsRetriever;
use BusinessLogic\Statuses\StatusRetriever;
use BusinessLogic\Tickets\Autoassigner;
use BusinessLogic\Tickets\TicketDeleter;
use BusinessLogic\Tickets\TicketEditor;
use BusinessLogic\Tickets\TicketRetriever;
use BusinessLogic\Tickets\TicketCreator;
use BusinessLogic\Tickets\NewTicketValidator;
use BusinessLogic\Tickets\TicketValidators;
use BusinessLogic\Tickets\TrackingIdGenerator;
use BusinessLogic\Tickets\VerifiedEmailChecker;
use DataAccess\Attachments\AttachmentGateway;
use DataAccess\Categories\CategoryGateway;
use DataAccess\Files\FileDeleter;
use DataAccess\Files\FileReader;
use DataAccess\Files\FileWriter;
use DataAccess\Logging\LoggingGateway;
use DataAccess\Navigation\CustomNavElementGateway;
use DataAccess\Security\BanGateway;
use DataAccess\Security\UserGateway;
use DataAccess\Settings\ModsForHeskSettingsGateway;
use DataAccess\Statuses\StatusGateway;
use DataAccess\Tickets\TicketGateway;
use DataAccess\Tickets\VerifiedEmailGateway;
class ApplicationContext {
public $get;
/**
* ApplicationContext constructor.
*/
function __construct() {
$this->get = array();
// Settings
$this->get[ModsForHeskSettingsGateway::class] = new ModsForHeskSettingsGateway();
// API Checker
$this->get[ApiChecker::class] = new ApiChecker($this->get[ModsForHeskSettingsGateway::class]);
// Custom Navigation
$this->get[CustomNavElementGateway::class] = new CustomNavElementGateway();
$this->get[CustomNavElementHandler::class] = new CustomNavElementHandler($this->get[CustomNavElementGateway::class]);
// Logging
$this->get[LoggingGateway::class] = new LoggingGateway();
// Verified Email Checker
$this->get[VerifiedEmailGateway::class] = new VerifiedEmailGateway();
$this->get[VerifiedEmailChecker::class] = new VerifiedEmailChecker($this->get[VerifiedEmailGateway::class]);
// Users
$this->get[UserGateway::class] = new UserGateway();
$this->get[UserContextBuilder::class] = new UserContextBuilder($this->get[UserGateway::class]);
// Categories
$this->get[CategoryGateway::class] = new CategoryGateway();
$this->get[CategoryRetriever::class] = new CategoryRetriever($this->get[CategoryGateway::class]);
// Bans
$this->get[BanGateway::class] = new BanGateway();
$this->get[BanRetriever::class] = new BanRetriever($this->get[BanGateway::class]);
// Statuses
$this->get[StatusGateway::class] = new StatusGateway();
// Email Sender
$this->get[EmailTemplateRetriever::class] = new EmailTemplateRetriever();
$this->get[EmailTemplateParser::class] = new EmailTemplateParser($this->get[StatusGateway::class],
$this->get[CategoryGateway::class],
$this->get[UserGateway::class],
$this->get[EmailTemplateRetriever::class]);
$this->get[BasicEmailSender::class] = new BasicEmailSender();
$this->get[MailgunEmailSender::class] = new MailgunEmailSender();
$this->get[EmailSenderHelper::class] = new EmailSenderHelper($this->get[EmailTemplateParser::class],
$this->get[BasicEmailSender::class],
$this->get[MailgunEmailSender::class]);
// Tickets
$this->get[UserToTicketChecker::class] = new UserToTicketChecker($this->get[UserGateway::class]);
$this->get[TicketGateway::class] = new TicketGateway();
$this->get[TicketRetriever::class] = new TicketRetriever($this->get[TicketGateway::class],
$this->get[UserToTicketChecker::class]);
$this->get[TicketValidators::class] = new TicketValidators($this->get[TicketGateway::class]);
$this->get[TrackingIdGenerator::class] = new TrackingIdGenerator($this->get[TicketGateway::class]);
$this->get[Autoassigner::class] = new Autoassigner($this->get[CategoryGateway::class], $this->get[UserGateway::class]);
$this->get[NewTicketValidator::class] = new NewTicketValidator($this->get[CategoryRetriever::class],
$this->get[BanRetriever::class],
$this->get[TicketValidators::class]);
$this->get[TicketCreator::class] = new TicketCreator($this->get[NewTicketValidator::class],
$this->get[TrackingIdGenerator::class],
$this->get[Autoassigner::class],
$this->get[StatusGateway::class],
$this->get[TicketGateway::class],
$this->get[VerifiedEmailChecker::class],
$this->get[EmailSenderHelper::class],
$this->get[UserGateway::class],
$this->get[ModsForHeskSettingsGateway::class]);
$this->get[FileWriter::class] = new FileWriter();
$this->get[FileReader::class] = new FileReader();
$this->get[FileDeleter::class] = new FileDeleter();
$this->get[AttachmentGateway::class] = new AttachmentGateway();
$this->get[AttachmentHandler::class] = new AttachmentHandler($this->get[TicketGateway::class],
$this->get[AttachmentGateway::class],
$this->get[FileWriter::class],
$this->get[UserToTicketChecker::class],
$this->get[FileDeleter::class]);
$this->get[AttachmentRetriever::class] = new AttachmentRetriever($this->get[AttachmentGateway::class],
$this->get[FileReader::class],
$this->get[TicketGateway::class],
$this->get[UserToTicketChecker::class]);
$this->get[TicketDeleter::class] =
new TicketDeleter($this->get[TicketGateway::class],
$this->get[UserToTicketChecker::class],
$this->get[AttachmentHandler::class]);
$this->get[TicketEditor::class] =
new TicketEditor($this->get[TicketGateway::class], $this->get[UserToTicketChecker::class]);
// Statuses
$this->get[StatusRetriever::class] = new StatusRetriever($this->get[StatusGateway::class]);
// Settings
$this->get[SettingsRetriever::class] = new SettingsRetriever($this->get[ModsForHeskSettingsGateway::class]);
}
}

@ -0,0 +1,21 @@
<?php
namespace BusinessLogic\Attachments;
class Attachment {
/* @var $id int */
public $id;
/* @var $savedName string */
public $savedName;
/* @var $displayName string */
public $displayName;
/* @var $id int */
public $fileSize;
/* @var $downloadCount int */
public $downloadCount;
}

@ -0,0 +1,475 @@
<?php
namespace BusinessLogic\Attachments;
use BusinessLogic\Exceptions\AccessViolationException;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Exceptions\ValidationException;
use BusinessLogic\Security\UserContext;
use BusinessLogic\Security\UserPrivilege;
use BusinessLogic\Security\UserToTicketChecker;
use BusinessLogic\Tickets\Attachment;
use BusinessLogic\Tickets\Ticket;
use BusinessLogic\ValidationModel;
use DataAccess\Attachments\AttachmentGateway;
use DataAccess\Files\FileDeleter;
use DataAccess\Files\FileWriter;
use DataAccess\Tickets\TicketGateway;
class AttachmentHandler {
/* @var $ticketGateway TicketGateway */
private $ticketGateway;
/* @var $attachmentGateway AttachmentGateway */
private $attachmentGateway;
/* @var $fileWriter FileWriter */
private $fileWriter;
/* @var $fileDeleter FileDeleter */
private $fileDeleter;
/* @var $userToTicketChecker UserToTicketChecker */
private $userToTicketChecker;
function __construct($ticketGateway, $attachmentGateway, $fileWriter, $userToTicketChecker, $fileDeleter) {
$this->ticketGateway = $ticketGateway;
$this->attachmentGateway = $attachmentGateway;
$this->fileWriter = $fileWriter;
$this->userToTicketChecker = $userToTicketChecker;
$this->fileDeleter = $fileDeleter;
}
/**
* @param $createAttachmentModel CreateAttachmentForTicketModel
* @param $userContext UserContext
* @param $heskSettings array
* @return TicketAttachment the newly created attachment
* @throws \Exception
*/
function createAttachmentForTicket($createAttachmentModel, $userContext, $heskSettings) {
$this->validate($createAttachmentModel, $heskSettings);
$decodedAttachment = base64_decode($createAttachmentModel->attachmentContents);
$ticket = $this->ticketGateway->getTicketById($createAttachmentModel->ticketId, $heskSettings);
if ($ticket === null) {
throw new ApiFriendlyException("Ticket {$createAttachmentModel->ticketId} not found", "Ticket Not Found", 404);
}
$extraPermissions = $createAttachmentModel->isEditing
? array(UserPrivilege::CAN_EDIT_TICKETS)
: array();
if (!$this->userToTicketChecker->isTicketAccessibleToUser($userContext, $ticket, $heskSettings, $extraPermissions)) {
throw new AccessViolationException("User does not have access to ticket {$ticket->id} being created / edited!");
}
$cleanedFileName = $this->cleanFileName($createAttachmentModel->displayName);
$fileParts = pathinfo($cleanedFileName);
$ticketAttachment = new TicketAttachment();
$ticketAttachment->savedName = $this->generateSavedName($ticket->trackingId,
$cleanedFileName, $fileParts['extension']);
$ticketAttachment->displayName = $cleanedFileName;
$ticketAttachment->ticketTrackingId = $ticket->trackingId;
$ticketAttachment->type = 0;
$ticketAttachment->downloadCount = 0;
$ticketAttachment->fileSize =
$this->fileWriter->writeToFile($ticketAttachment->savedName, $heskSettings['attach_dir'], $decodedAttachment);
$attachmentId = $this->attachmentGateway->createAttachmentForTicket($ticketAttachment, $heskSettings);
$this->updateAttachmentsOnTicket($ticket, $ticketAttachment, $attachmentId, $heskSettings);
$ticketAttachment->id = $attachmentId;
return $ticketAttachment;
}
/**
* Supports deleting attachments from both ticket messages AND replies
*
* @param $ticketId int The ticket ID
* @param $attachmentId int The attachment ID
* @param $userContext UserContext
* @param $heskSettings array
* @throws ApiFriendlyException
* @throws \Exception
*/
function deleteAttachmentFromTicket($ticketId, $attachmentId, $userContext, $heskSettings) {
$ticket = $this->ticketGateway->getTicketById($ticketId, $heskSettings);
if ($ticket === null) {
throw new ApiFriendlyException("Ticket {$ticketId} not found!", "Ticket Not Found", 404);
}
if (!$this->userToTicketChecker->isTicketAccessibleToUser($userContext, $ticket, $heskSettings, array(UserPrivilege::CAN_EDIT_TICKETS))) {
throw new AccessViolationException("User does not have access to ticket {$ticketId} being created / edited!");
}
$indexToRemove = -1;
$attachmentType = AttachmentType::MESSAGE;
$replyId = -1;
for ($i = 0; $i < count($ticket->attachments); $i++) {
$attachment = $ticket->attachments[$i];
if ($attachment->id === $attachmentId) {
$indexToRemove = $i;
$this->fileDeleter->deleteFile($attachment->savedName, $heskSettings['attach_dir']);
$this->attachmentGateway->deleteAttachment($attachment->id, $heskSettings);
}
}
foreach ($ticket->replies as $reply) {
for ($i = 0; $i < count($reply->attachments); $i++) {
$attachment = $reply->attachments[$i];
if ($attachment->id === $attachmentId) {
$indexToRemove = $i;
$replyId = $reply->id;
$attachmentType = AttachmentType::REPLY;
$this->fileDeleter->deleteFile($attachment->savedName, $heskSettings['attach_dir']);
$this->attachmentGateway->deleteAttachment($attachment->id, $heskSettings);
}
}
}
if ($indexToRemove === -1) {
throw new ApiFriendlyException("Attachment not found for ticket or reply! ID: {$attachmentId}", "Attachment not found", 404);
}
if ($attachmentType == AttachmentType::MESSAGE) {
$attachments = $ticket->attachments;
unset($attachments[$indexToRemove]);
$this->ticketGateway->updateAttachmentsForTicket($ticketId, $attachments, $heskSettings);
} else {
$attachments = $ticket->replies[$replyId]->attachments;
unset($attachments[$indexToRemove]);
$this->ticketGateway->updateAttachmentsForReply($replyId, $attachments, $heskSettings);
}
}
/**
* @param $createAttachmentModel CreateAttachmentForTicketModel
* @param $heskSettings array
* @throws ValidationException
*/
private function validate($createAttachmentModel, $heskSettings) {
$errorKeys = array();
if ($createAttachmentModel->attachmentContents === null ||
trim($createAttachmentModel->attachmentContents) === '') {
$errorKeys[] = 'CONTENTS_EMPTY';
}
if (base64_decode($createAttachmentModel->attachmentContents, true) === false) {
$errorKeys[] = 'CONTENTS_NOT_BASE_64';
}
if ($createAttachmentModel->displayName === null ||
trim($createAttachmentModel->displayName === '')) {
$errorKeys[] = 'DISPLAY_NAME_EMPTY';
}
if ($createAttachmentModel->ticketId === null ||
$createAttachmentModel->ticketId < 1) {
$errorKeys[] = 'TICKET_ID_MISSING';
}
$fileParts = pathinfo($createAttachmentModel->displayName);
if (!isset($fileParts['extension']) || !in_array(".{$fileParts['extension']}", $heskSettings['attachments']['allowed_types'])) {
$errorKeys[] = 'EXTENSION_NOT_PERMITTED';
}
$fileContents = base64_decode($createAttachmentModel->attachmentContents);
if (function_exists('mb_strlen')) {
$fileSize = mb_strlen($fileContents, '8bit');
} else {
$fileSize = strlen($fileContents);
}
if ($fileSize > $heskSettings['attachments']['max_size']) {
$errorKeys[] = 'FILE_SIZE_TOO_LARGE';
}
if (count($errorKeys) > 0) {
$validationModel = new ValidationModel();
$validationModel->errorKeys = $errorKeys;
throw new ValidationException($validationModel);
}
}
private function generateSavedName($trackingId, $displayName, $fileExtension) {
$fileExtension = ".{$fileExtension}";
$useChars = 'AEUYBDGHJLMNPQRSTVWXZ123456789';
$tmp = uniqid();
for ($j = 1; $j < 10; $j++) {
$tmp .= $useChars{mt_rand(0, 29)};
}
return substr($trackingId . '_' . md5($tmp . $displayName), 0, 200) . $fileExtension;
}
/**
* @param $displayName string original file name
* @return string The cleaned file name
*/
private function cleanFileName($displayName) {
$filename = str_replace(array('%20', '+'), '-', $displayName);
$filename = preg_replace('/[\s-]+/', '-', $filename);
$filename = $this->removeAccents($filename);
$filename = preg_replace('/[^A-Za-z0-9\.\-_]/', '', $filename);
$filename = trim($filename, '-_');
return $filename;
}
// The following code has been borrowed from Wordpress, and also from posting_functions.inc.php :P
// Credits: http://wordpress.org
private function removeAccents($string)
{
if (!preg_match('/[\x80-\xff]/', $string)) {
return $string;
}
if ($this->seemsUtf8($string)) {
$chars = array(
// Decompositions for Latin-1 Supplement
chr(194) . chr(170) => 'a', chr(194) . chr(186) => 'o',
chr(195) . chr(128) => 'A', chr(195) . chr(129) => 'A',
chr(195) . chr(130) => 'A', chr(195) . chr(131) => 'A',
chr(195) . chr(132) => 'A', chr(195) . chr(133) => 'A',
chr(195) . chr(134) => 'AE', chr(195) . chr(135) => 'C',
chr(195) . chr(136) => 'E', chr(195) . chr(137) => 'E',
chr(195) . chr(138) => 'E', chr(195) . chr(139) => 'E',
chr(195) . chr(140) => 'I', chr(195) . chr(141) => 'I',
chr(195) . chr(142) => 'I', chr(195) . chr(143) => 'I',
chr(195) . chr(144) => 'D', chr(195) . chr(145) => 'N',
chr(195) . chr(146) => 'O', chr(195) . chr(147) => 'O',
chr(195) . chr(148) => 'O', chr(195) . chr(149) => 'O',
chr(195) . chr(150) => 'O', chr(195) . chr(153) => 'U',
chr(195) . chr(154) => 'U', chr(195) . chr(155) => 'U',
chr(195) . chr(156) => 'U', chr(195) . chr(157) => 'Y',
chr(195) . chr(158) => 'TH', chr(195) . chr(159) => 's',
chr(195) . chr(160) => 'a', chr(195) . chr(161) => 'a',
chr(195) . chr(162) => 'a', chr(195) . chr(163) => 'a',
chr(195) . chr(164) => 'a', chr(195) . chr(165) => 'a',
chr(195) . chr(166) => 'ae', chr(195) . chr(167) => 'c',
chr(195) . chr(168) => 'e', chr(195) . chr(169) => 'e',
chr(195) . chr(170) => 'e', chr(195) . chr(171) => 'e',
chr(195) . chr(172) => 'i', chr(195) . chr(173) => 'i',
chr(195) . chr(174) => 'i', chr(195) . chr(175) => 'i',
chr(195) . chr(176) => 'd', chr(195) . chr(177) => 'n',
chr(195) . chr(178) => 'o', chr(195) . chr(179) => 'o',
chr(195) . chr(180) => 'o', chr(195) . chr(181) => 'o',
chr(195) . chr(182) => 'o', chr(195) . chr(184) => 'o',
chr(195) . chr(185) => 'u', chr(195) . chr(186) => 'u',
chr(195) . chr(187) => 'u', chr(195) . chr(188) => 'u',
chr(195) . chr(189) => 'y', chr(195) . chr(190) => 'th',
chr(195) . chr(191) => 'y', chr(195) . chr(152) => 'O',
// Decompositions for Latin Extended-A
chr(196) . chr(128) => 'A', chr(196) . chr(129) => 'a',
chr(196) . chr(130) => 'A', chr(196) . chr(131) => 'a',
chr(196) . chr(132) => 'A', chr(196) . chr(133) => 'a',
chr(196) . chr(134) => 'C', chr(196) . chr(135) => 'c',
chr(196) . chr(136) => 'C', chr(196) . chr(137) => 'c',
chr(196) . chr(138) => 'C', chr(196) . chr(139) => 'c',
chr(196) . chr(140) => 'C', chr(196) . chr(141) => 'c',
chr(196) . chr(142) => 'D', chr(196) . chr(143) => 'd',
chr(196) . chr(144) => 'D', chr(196) . chr(145) => 'd',
chr(196) . chr(146) => 'E', chr(196) . chr(147) => 'e',
chr(196) . chr(148) => 'E', chr(196) . chr(149) => 'e',
chr(196) . chr(150) => 'E', chr(196) . chr(151) => 'e',
chr(196) . chr(152) => 'E', chr(196) . chr(153) => 'e',
chr(196) . chr(154) => 'E', chr(196) . chr(155) => 'e',
chr(196) . chr(156) => 'G', chr(196) . chr(157) => 'g',
chr(196) . chr(158) => 'G', chr(196) . chr(159) => 'g',
chr(196) . chr(160) => 'G', chr(196) . chr(161) => 'g',
chr(196) . chr(162) => 'G', chr(196) . chr(163) => 'g',
chr(196) . chr(164) => 'H', chr(196) . chr(165) => 'h',
chr(196) . chr(166) => 'H', chr(196) . chr(167) => 'h',
chr(196) . chr(168) => 'I', chr(196) . chr(169) => 'i',
chr(196) . chr(170) => 'I', chr(196) . chr(171) => 'i',
chr(196) . chr(172) => 'I', chr(196) . chr(173) => 'i',
chr(196) . chr(174) => 'I', chr(196) . chr(175) => 'i',
chr(196) . chr(176) => 'I', chr(196) . chr(177) => 'i',
chr(196) . chr(178) => 'IJ', chr(196) . chr(179) => 'ij',
chr(196) . chr(180) => 'J', chr(196) . chr(181) => 'j',
chr(196) . chr(182) => 'K', chr(196) . chr(183) => 'k',
chr(196) . chr(184) => 'k', chr(196) . chr(185) => 'L',
chr(196) . chr(186) => 'l', chr(196) . chr(187) => 'L',
chr(196) . chr(188) => 'l', chr(196) . chr(189) => 'L',
chr(196) . chr(190) => 'l', chr(196) . chr(191) => 'L',
chr(197) . chr(128) => 'l', chr(197) . chr(129) => 'L',
chr(197) . chr(130) => 'l', chr(197) . chr(131) => 'N',
chr(197) . chr(132) => 'n', chr(197) . chr(133) => 'N',
chr(197) . chr(134) => 'n', chr(197) . chr(135) => 'N',
chr(197) . chr(136) => 'n', chr(197) . chr(137) => 'N',
chr(197) . chr(138) => 'n', chr(197) . chr(139) => 'N',
chr(197) . chr(140) => 'O', chr(197) . chr(141) => 'o',
chr(197) . chr(142) => 'O', chr(197) . chr(143) => 'o',
chr(197) . chr(144) => 'O', chr(197) . chr(145) => 'o',
chr(197) . chr(146) => 'OE', chr(197) . chr(147) => 'oe',
chr(197) . chr(148) => 'R', chr(197) . chr(149) => 'r',
chr(197) . chr(150) => 'R', chr(197) . chr(151) => 'r',
chr(197) . chr(152) => 'R', chr(197) . chr(153) => 'r',
chr(197) . chr(154) => 'S', chr(197) . chr(155) => 's',
chr(197) . chr(156) => 'S', chr(197) . chr(157) => 's',
chr(197) . chr(158) => 'S', chr(197) . chr(159) => 's',
chr(197) . chr(160) => 'S', chr(197) . chr(161) => 's',
chr(197) . chr(162) => 'T', chr(197) . chr(163) => 't',
chr(197) . chr(164) => 'T', chr(197) . chr(165) => 't',
chr(197) . chr(166) => 'T', chr(197) . chr(167) => 't',
chr(197) . chr(168) => 'U', chr(197) . chr(169) => 'u',
chr(197) . chr(170) => 'U', chr(197) . chr(171) => 'u',
chr(197) . chr(172) => 'U', chr(197) . chr(173) => 'u',
chr(197) . chr(174) => 'U', chr(197) . chr(175) => 'u',
chr(197) . chr(176) => 'U', chr(197) . chr(177) => 'u',
chr(197) . chr(178) => 'U', chr(197) . chr(179) => 'u',
chr(197) . chr(180) => 'W', chr(197) . chr(181) => 'w',
chr(197) . chr(182) => 'Y', chr(197) . chr(183) => 'y',
chr(197) . chr(184) => 'Y', chr(197) . chr(185) => 'Z',
chr(197) . chr(186) => 'z', chr(197) . chr(187) => 'Z',
chr(197) . chr(188) => 'z', chr(197) . chr(189) => 'Z',
chr(197) . chr(190) => 'z', chr(197) . chr(191) => 's',
// Decompositions for Latin Extended-B
chr(200) . chr(152) => 'S', chr(200) . chr(153) => 's',
chr(200) . chr(154) => 'T', chr(200) . chr(155) => 't',
// Euro Sign
chr(226) . chr(130) . chr(172) => 'E',
// GBP (Pound) Sign
chr(194) . chr(163) => '',
// Vowels with diacritic (Vietnamese)
// unmarked
chr(198) . chr(160) => 'O', chr(198) . chr(161) => 'o',
chr(198) . chr(175) => 'U', chr(198) . chr(176) => 'u',
// grave accent
chr(225) . chr(186) . chr(166) => 'A', chr(225) . chr(186) . chr(167) => 'a',
chr(225) . chr(186) . chr(176) => 'A', chr(225) . chr(186) . chr(177) => 'a',
chr(225) . chr(187) . chr(128) => 'E', chr(225) . chr(187) . chr(129) => 'e',
chr(225) . chr(187) . chr(146) => 'O', chr(225) . chr(187) . chr(147) => 'o',
chr(225) . chr(187) . chr(156) => 'O', chr(225) . chr(187) . chr(157) => 'o',
chr(225) . chr(187) . chr(170) => 'U', chr(225) . chr(187) . chr(171) => 'u',
chr(225) . chr(187) . chr(178) => 'Y', chr(225) . chr(187) . chr(179) => 'y',
// hook
chr(225) . chr(186) . chr(162) => 'A', chr(225) . chr(186) . chr(163) => 'a',
chr(225) . chr(186) . chr(168) => 'A', chr(225) . chr(186) . chr(169) => 'a',
chr(225) . chr(186) . chr(178) => 'A', chr(225) . chr(186) . chr(179) => 'a',
chr(225) . chr(186) . chr(186) => 'E', chr(225) . chr(186) . chr(187) => 'e',
chr(225) . chr(187) . chr(130) => 'E', chr(225) . chr(187) . chr(131) => 'e',
chr(225) . chr(187) . chr(136) => 'I', chr(225) . chr(187) . chr(137) => 'i',
chr(225) . chr(187) . chr(142) => 'O', chr(225) . chr(187) . chr(143) => 'o',
chr(225) . chr(187) . chr(148) => 'O', chr(225) . chr(187) . chr(149) => 'o',
chr(225) . chr(187) . chr(158) => 'O', chr(225) . chr(187) . chr(159) => 'o',
chr(225) . chr(187) . chr(166) => 'U', chr(225) . chr(187) . chr(167) => 'u',
chr(225) . chr(187) . chr(172) => 'U', chr(225) . chr(187) . chr(173) => 'u',
chr(225) . chr(187) . chr(182) => 'Y', chr(225) . chr(187) . chr(183) => 'y',
// tilde
chr(225) . chr(186) . chr(170) => 'A', chr(225) . chr(186) . chr(171) => 'a',
chr(225) . chr(186) . chr(180) => 'A', chr(225) . chr(186) . chr(181) => 'a',
chr(225) . chr(186) . chr(188) => 'E', chr(225) . chr(186) . chr(189) => 'e',
chr(225) . chr(187) . chr(132) => 'E', chr(225) . chr(187) . chr(133) => 'e',
chr(225) . chr(187) . chr(150) => 'O', chr(225) . chr(187) . chr(151) => 'o',
chr(225) . chr(187) . chr(160) => 'O', chr(225) . chr(187) . chr(161) => 'o',
chr(225) . chr(187) . chr(174) => 'U', chr(225) . chr(187) . chr(175) => 'u',
chr(225) . chr(187) . chr(184) => 'Y', chr(225) . chr(187) . chr(185) => 'y',
// acute accent
chr(225) . chr(186) . chr(164) => 'A', chr(225) . chr(186) . chr(165) => 'a',
chr(225) . chr(186) . chr(174) => 'A', chr(225) . chr(186) . chr(175) => 'a',
chr(225) . chr(186) . chr(190) => 'E', chr(225) . chr(186) . chr(191) => 'e',
chr(225) . chr(187) . chr(144) => 'O', chr(225) . chr(187) . chr(145) => 'o',
chr(225) . chr(187) . chr(154) => 'O', chr(225) . chr(187) . chr(155) => 'o',
chr(225) . chr(187) . chr(168) => 'U', chr(225) . chr(187) . chr(169) => 'u',
// dot below
chr(225) . chr(186) . chr(160) => 'A', chr(225) . chr(186) . chr(161) => 'a',
chr(225) . chr(186) . chr(172) => 'A', chr(225) . chr(186) . chr(173) => 'a',
chr(225) . chr(186) . chr(182) => 'A', chr(225) . chr(186) . chr(183) => 'a',
chr(225) . chr(186) . chr(184) => 'E', chr(225) . chr(186) . chr(185) => 'e',
chr(225) . chr(187) . chr(134) => 'E', chr(225) . chr(187) . chr(135) => 'e',
chr(225) . chr(187) . chr(138) => 'I', chr(225) . chr(187) . chr(139) => 'i',
chr(225) . chr(187) . chr(140) => 'O', chr(225) . chr(187) . chr(141) => 'o',
chr(225) . chr(187) . chr(152) => 'O', chr(225) . chr(187) . chr(153) => 'o',
chr(225) . chr(187) . chr(162) => 'O', chr(225) . chr(187) . chr(163) => 'o',
chr(225) . chr(187) . chr(164) => 'U', chr(225) . chr(187) . chr(165) => 'u',
chr(225) . chr(187) . chr(176) => 'U', chr(225) . chr(187) . chr(177) => 'u',
chr(225) . chr(187) . chr(180) => 'Y', chr(225) . chr(187) . chr(181) => 'y',
// Vowels with diacritic (Chinese, Hanyu Pinyin)
chr(201) . chr(145) => 'a',
// macron
chr(199) . chr(149) => 'U', chr(199) . chr(150) => 'u',
// acute accent
chr(199) . chr(151) => 'U', chr(199) . chr(152) => 'u',
// caron
chr(199) . chr(141) => 'A', chr(199) . chr(142) => 'a',
chr(199) . chr(143) => 'I', chr(199) . chr(144) => 'i',
chr(199) . chr(145) => 'O', chr(199) . chr(146) => 'o',
chr(199) . chr(147) => 'U', chr(199) . chr(148) => 'u',
chr(199) . chr(153) => 'U', chr(199) . chr(154) => 'u',
// grave accent
chr(199) . chr(155) => 'U', chr(199) . chr(156) => 'u',
);
$string = strtr($string, $chars);
} else {
// Assume ISO-8859-1 if not UTF-8
$chars['in'] = chr(128) . chr(131) . chr(138) . chr(142) . chr(154) . chr(158)
. chr(159) . chr(162) . chr(165) . chr(181) . chr(192) . chr(193) . chr(194)
. chr(195) . chr(196) . chr(197) . chr(199) . chr(200) . chr(201) . chr(202)
. chr(203) . chr(204) . chr(205) . chr(206) . chr(207) . chr(209) . chr(210)
. chr(211) . chr(212) . chr(213) . chr(214) . chr(216) . chr(217) . chr(218)
. chr(219) . chr(220) . chr(221) . chr(224) . chr(225) . chr(226) . chr(227)
. chr(228) . chr(229) . chr(231) . chr(232) . chr(233) . chr(234) . chr(235)
. chr(236) . chr(237) . chr(238) . chr(239) . chr(241) . chr(242) . chr(243)
. chr(244) . chr(245) . chr(246) . chr(248) . chr(249) . chr(250) . chr(251)
. chr(252) . chr(253) . chr(255);
$chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy";
$string = strtr($string, $chars['in'], $chars['out']);
$double_chars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254));
$double_chars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th');
$string = str_replace($double_chars['in'], $double_chars['out'], $string);
}
return $string;
}
private function seemsUtf8($str)
{
$length = strlen($str);
for ($i = 0; $i < $length; $i++) {
$c = ord($str[$i]);
if ($c < 0x80) $n = 0; # 0bbbbbbb
elseif (($c & 0xE0) == 0xC0) $n = 1; # 110bbbbb
elseif (($c & 0xF0) == 0xE0) $n = 2; # 1110bbbb
elseif (($c & 0xF8) == 0xF0) $n = 3; # 11110bbb
elseif (($c & 0xFC) == 0xF8) $n = 4; # 111110bb
elseif (($c & 0xFE) == 0xFC) $n = 5; # 1111110b
else return false; # Does not match any model
for ($j = 0; $j < $n; $j++) { # n bytes matching 10bbbbbb follow ?
if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
return false;
}
}
return true;
}
/**
* @param $ticket Ticket
* @param $ticketAttachment TicketAttachment
* @param $attachmentId int
* @param $heskSettings array
*/
private function updateAttachmentsOnTicket($ticket, $ticketAttachment, $attachmentId, $heskSettings) {
$attachments = $ticket->attachments === null ? array() : $ticket->attachments;
$newAttachment = new Attachment();
$newAttachment->savedName = $ticketAttachment->savedName;
$newAttachment->fileName = $ticketAttachment->displayName;
$newAttachment->id = $attachmentId;
$attachments[] = $newAttachment;
$this->ticketGateway->updateAttachmentsForTicket($ticket->id, $attachments, $heskSettings);
}
}

@ -0,0 +1,50 @@
<?php
namespace BusinessLogic\Attachments;
use BusinessLogic\Exceptions\AccessViolationException;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Security\UserToTicketChecker;
use DataAccess\Attachments\AttachmentGateway;
use DataAccess\Files\FileReader;
use DataAccess\Tickets\TicketGateway;
class AttachmentRetriever {
/* @var $attachmentGateway AttachmentGateway */
private $attachmentGateway;
/* @var $fileReader FileReader */
private $fileReader;
/* @var $ticketGateway TicketGateway */
private $ticketGateway;
/* @var $userToTicketChecker UserToTicketChecker */
private $userToTicketChecker;
function __construct($attachmentGateway, $fileReader, $ticketGateway, $userToTicketChecker) {
$this->attachmentGateway = $attachmentGateway;
$this->fileReader = $fileReader;
$this->ticketGateway = $ticketGateway;
$this->userToTicketChecker = $userToTicketChecker;
}
function getAttachmentContentsForTicket($ticketId, $attachmentId, $userContext, $heskSettings) {
$ticket = $this->ticketGateway->getTicketById($ticketId, $heskSettings);
if ($ticket === null) {
throw new ApiFriendlyException("Ticket {$ticketId} not found!", "Ticket Not Found", 404);
}
if ($this->userToTicketChecker->isTicketAccessibleToUser($userContext, $ticket, $heskSettings)) {
throw new AccessViolationException("User does not have access to attachment {$attachmentId}!");
}
$attachment = $this->attachmentGateway->getAttachmentById($attachmentId, $heskSettings);
$contents = base64_encode($this->fileReader->readFromFile(
$attachment->savedName, $heskSettings['attach_dir']));
return $contents;
}
}

@ -0,0 +1,9 @@
<?php
namespace BusinessLogic\Attachments;
class AttachmentType {
const MESSAGE = 0;
const REPLY = 1;
}

@ -0,0 +1,9 @@
<?php
namespace BusinessLogic\Attachments;
class CreateAttachmentForTicketModel extends CreateAttachmentModel {
/* @var $ticketId int */
public $ticketId;
}

@ -0,0 +1,21 @@
<?php
namespace BusinessLogic\Attachments;
class CreateAttachmentModel {
/* @var $savedName string */
public $savedName;
/* @var $displayName string */
public $displayName;
/* @var $id int */
public $fileSize;
/* @var $attachmentContents string */
public $attachmentContents;
/* @var $isEditing bool */
public $isEditing;
}

@ -0,0 +1,12 @@
<?php
namespace BusinessLogic\Attachments;
class TicketAttachment extends Attachment {
/* @var $ticketTrackingId string */
public $ticketTrackingId;
/* @var $type int [use <code>AttachmentType</code>] */
public $type;
}

@ -0,0 +1,63 @@
<?php
namespace BusinessLogic\Categories;
class Category {
/**
* @var int The Categories ID
*/
public $id;
/* @var $name string */
public $name;
/**
* @var int Categories order number
*/
public $catOrder;
/**
* @var bool Tickets autoassigned in this Categories
*/
public $autoAssign;
/**
* @var int The type of Categories (1 = Private, 2 = Public)
*/
public $type;
/**
* @var int The Categories's usage (0 = Tickets and Events, 1 = Tickets, 2 = Events)
*/
public $usage;
/**
* @var string
*/
public $backgroundColor;
/**
* @var string
*/
public $foregroundColor;
/**
* @var bool
*/
public $displayBorder;
/**
* @var int The default Tickets priority
*/
public $priority;
/**
* @var int|null The manager for the Categories, if applicable
*/
public $manager;
/**
* @var bool Indication if the user has access to the Categories
*/
public $accessible;
}

@ -0,0 +1,33 @@
<?php
namespace BusinessLogic\Categories;
use BusinessLogic\Security\UserContext;
use DataAccess\Categories\CategoryGateway;
class CategoryRetriever {
/**
* @var CategoryGateway
*/
private $categoryGateway;
function __construct($categoryGateway) {
$this->categoryGateway = $categoryGateway;
}
/**
* @param $heskSettings array
* @param $userContext UserContext
* @return array
*/
function getAllCategories($heskSettings, $userContext) {
$categories = $this->categoryGateway->getAllCategories($heskSettings);
foreach ($categories as $category) {
$category->accessible = $userContext->admin ||
in_array($category->id, $userContext->categories);
}
return $categories;
}
}

@ -0,0 +1,21 @@
<?php
namespace BusinessLogic\Emails;
class Addressees {
/**
* @var $to string[]
*/
public $to;
/**
* @var $cc string[]|null
*/
public $cc;
/**
* @var $bcc string[]|null
*/
public $bcc;
}

@ -0,0 +1,75 @@
<?php
namespace BusinessLogic\Emails;
use BusinessLogic\Tickets\Attachment;
use BusinessLogic\Tickets\Ticket;
use PHPMailer;
class BasicEmailSender implements EmailSender {
function sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings, $sendAsHtml) {
$mailer = new PHPMailer();
if ($heskSettings['smtp']) {
$mailer->isSMTP();
$mailer->SMTPAuth = true;
if ($heskSettings['smtp_ssl']) {
$mailer->SMTPSecure = "ssl";
} elseif ($heskSettings['smtp_tls']) {
$mailer->SMTPSecure = "tls";
}
$mailer->Host = $heskSettings['smtp_host_name'];
$mailer->Port = $heskSettings['smtp_host_port'];
$mailer->Username = $heskSettings['smtp_user'];
$mailer->Password = $heskSettings['smtp_password'];
}
$mailer->FromName = $heskSettings['noreply_name'] !== null &&
$heskSettings['noreply_name'] !== '' ? $heskSettings['noreply_name'] : '';
$mailer->From = $heskSettings['noreply_mail'];
if ($emailBuilder->to !== null) {
foreach ($emailBuilder->to as $to) {
$mailer->addAddress($to);
}
}
if ($emailBuilder->cc !== null) {
foreach ($emailBuilder->cc as $cc) {
$mailer->addCC($cc);
}
}
if ($emailBuilder->bcc !== null) {
foreach ($emailBuilder->bcc as $bcc) {
$mailer->addBCC($bcc);
}
}
$mailer->Subject = $emailBuilder->subject;
if ($sendAsHtml) {
$mailer->Body = $emailBuilder->htmlMessage;
$mailer->AltBody = $emailBuilder->message;
} else {
$mailer->Body = $emailBuilder->message;
$mailer->isHTML(false);
}
$mailer->Timeout = $heskSettings['smtp_timeout'];
if ($emailBuilder->attachments !== null) {
foreach ($emailBuilder->attachments as $attachment) {
$mailer->addAttachment(__DIR__ . '/../../../' . $heskSettings['attach_dir'] . '/' . $attachment->savedName,
$attachment->fileName);
}
}
if ($mailer->send()) {
return true;
}
return $mailer->ErrorInfo;
}
}

@ -0,0 +1,43 @@
<?php
namespace BusinessLogic\Emails;
use BusinessLogic\Tickets\Attachment;
class EmailBuilder {
/**
* @var $to string[]
*/
public $to;
/**
* @var $cc string[]
*/
public $cc;
/**
* @var $bcc string[]
*/
public $bcc;
/**
* @var $subject string
*/
public $subject;
/**
* @var $message string
*/
public $message;
/**
* @var $htmlMessage string
*/
public $htmlMessage;
/**
* @var $attachments Attachment[]
*/
public $attachments;
}

@ -0,0 +1,21 @@
<?php
namespace BusinessLogic\Emails;
use BusinessLogic\Tickets\Attachment;
use BusinessLogic\Tickets\Ticket;
use PHPMailer;
interface EmailSender {
/**
* Use to send emails
*
* @param $emailBuilder EmailBuilder
* @param $heskSettings array
* @param $modsForHeskSettings array
* @param $sendAsHtml bool
* @return bool|string|\stdClass true if message sent successfully, string for PHPMail/Smtp error, stdClass for Mailgun error
*/
function sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings, $sendAsHtml);
}

@ -0,0 +1,74 @@
<?php
namespace BusinessLogic\Emails;
use BusinessLogic\Tickets\Ticket;
class EmailSenderHelper {
/**
* @var $emailTemplateParser EmailTemplateParser
*/
private $emailTemplateParser;
/**
* @var $basicEmailSender BasicEmailSender
*/
private $basicEmailSender;
/**
* @var $mailgunEmailSender MailgunEmailSender
*/
private $mailgunEmailSender;
function __construct($emailTemplateParser, $basicEmailSender, $mailgunEmailSender) {
$this->emailTemplateParser = $emailTemplateParser;
$this->basicEmailSender = $basicEmailSender;
$this->mailgunEmailSender = $mailgunEmailSender;
}
/**
* @param $templateId int the EmailTemplateRetriever::TEMPLATE_NAME
* @param $language string the language name
* @param $addressees Addressees the addressees. **cc and bcc addresses from custom fields will be added here!**
* @param $ticket Ticket
* @param $heskSettings array
* @param $modsForHeskSettings array
*/
function sendEmailForTicket($templateId, $language, $addressees, $ticket, $heskSettings, $modsForHeskSettings) {
$languageCode = $heskSettings['languages'][$language]['folder'];
$parsedTemplate = $this->emailTemplateParser->getFormattedEmailForLanguage($templateId, $languageCode,
$ticket, $heskSettings, $modsForHeskSettings);
$emailBuilder = new EmailBuilder();
$emailBuilder->subject = $parsedTemplate->subject;
$emailBuilder->message = $parsedTemplate->message;
$emailBuilder->htmlMessage = $parsedTemplate->htmlMessage;
$emailBuilder->to = $addressees->to;
$emailBuilder->cc = $addressees->cc;
$emailBuilder->bcc = $addressees->bcc;
foreach ($heskSettings['custom_fields'] as $k => $v) {
$number = intval(str_replace('custom', '', $k));
if ($v['use'] && $v['type'] == 'email' && !empty($ticket->customFields[$number])) {
if ($v['value']['email_type'] == 'cc') {
$emailBuilder->cc[] = $ticket->customFields[$number];
} elseif ($v['value']['email_type'] == 'bcc') {
$emailBuilder->bcc[] = $ticket->customFields[$number];
}
}
}
if ($modsForHeskSettings['attachments']) {
$emailBuilder->attachments = $ticket->attachments;
}
if ($modsForHeskSettings['use_mailgun']) {
$this->mailgunEmailSender->sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings, $modsForHeskSettings['html_emails']);
} else {
$this->basicEmailSender->sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings, $modsForHeskSettings['html_emails']);
}
}
}

@ -0,0 +1,27 @@
<?php
namespace BusinessLogic\Emails;
class EmailTemplate {
/**
* @var $languageKey string
*/
public $languageKey;
/**
* @var $fileName string
*/
public $fileName;
/**
* @var $forStaff bool
*/
public $forStaff;
function __construct($forStaff, $fileName, $languageKey = null) {
$this->languageKey = $languageKey === null ? $fileName : $languageKey;
$this->fileName = $fileName;
$this->forStaff = $forStaff;
}
}

@ -0,0 +1,331 @@
<?php
namespace BusinessLogic\Emails;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Exceptions\EmailTemplateNotFoundException;
use BusinessLogic\Exceptions\InvalidEmailTemplateException;
use BusinessLogic\Statuses\DefaultStatusForAction;
use BusinessLogic\Tickets\Ticket;
use Core\Constants\Priority;
use DataAccess\Categories\CategoryGateway;
use DataAccess\Security\UserGateway;
use DataAccess\Statuses\StatusGateway;
class EmailTemplateParser {
/**
* @var $statusGateway StatusGateway
*/
private $statusGateway;
/**
* @var $categoryGateway CategoryGateway
*/
private $categoryGateway;
/**
* @var $userGateway UserGateway
*/
private $userGateway;
/**
* @var $emailTemplateRetriever EmailTemplateRetriever
*/
private $emailTemplateRetriever;
function __construct($statusGateway, $categoryGateway, $userGateway, $emailTemplateRetriever) {
$this->statusGateway = $statusGateway;
$this->categoryGateway = $categoryGateway;
$this->userGateway = $userGateway;
$this->emailTemplateRetriever = $emailTemplateRetriever;
}
/**
* @param $templateId int
* @param $languageCode string
* @param $ticket Ticket
* @param $heskSettings array
* @param $modsForHeskSettings array
* @return ParsedEmailProperties
* @throws InvalidEmailTemplateException
*/
function getFormattedEmailForLanguage($templateId, $languageCode, $ticket, $heskSettings, $modsForHeskSettings) {
global $hesklang;
$emailTemplate = $this->emailTemplateRetriever->getTemplate($templateId);
if ($emailTemplate === null) {
throw new InvalidEmailTemplateException($templateId);
}
$template = self::getFromFileSystem($emailTemplate->fileName, $languageCode, false);
$htmlTemplate = self::getFromFileSystem($emailTemplate->fileName, $languageCode, true);
$subject = $hesklang[$emailTemplate->languageKey];
$fullLanguageName = null;
foreach ($heskSettings['languages'] as $key => $value) {
if ($value['folder'] === $languageCode) {
$fullLanguageName = $key;
break;
}
}
if ($fullLanguageName === null) {
throw new \Exception("Language code {$languageCode} did not return any valid HESK languages!");
}
$subject = $this->parseSubject($subject, $ticket, $fullLanguageName, $heskSettings);
$message = $this->parseMessage($template, $ticket, $fullLanguageName, $emailTemplate->forStaff, $heskSettings, $modsForHeskSettings, false);
$htmlMessage = $this->parseMessage($htmlTemplate, $ticket, $fullLanguageName, $emailTemplate->forStaff, $heskSettings, $modsForHeskSettings, true);
return new ParsedEmailProperties($subject, $message, $htmlMessage);
}
/**
* @param $template string
* @param $language string
* @param $html bool
* @return string The template
* @throws EmailTemplateNotFoundException If the template was not found in the filesystem for the provided language
*/
private function getFromFileSystem($template, $language, $html)
{
$htmlFolder = $html ? 'html/' : '';
/* Get email template */
$file = "language/{$language}/emails/{$htmlFolder}{$template}.txt";
$absoluteFilePath = __DIR__ . '/../../../' . $file;
if (file_exists($absoluteFilePath)) {
return file_get_contents($absoluteFilePath);
} else {
throw new EmailTemplateNotFoundException($template, $language);
}
}
/**
* @param $subjectTemplate string
* @param $ticket Ticket
* @param $language string
* @param $heskSettings array
* @return string
* @throws \Exception if common.inc.php isn't loaded
*/
private function parseSubject($subjectTemplate, $ticket, $language, $heskSettings) {
global $hesklang;
if (!function_exists('hesk_msgToPlain')) {
throw new \Exception("common.inc.php not loaded!");
}
if ($ticket === null) {
return $subjectTemplate;
}
// Status name and category name
$defaultStatus = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::NEW_TICKET, $heskSettings);
$statusName = $defaultStatus->localizedNames[$language];
$category = $this->categoryGateway->getAllCategories($heskSettings)[$ticket->categoryId];
switch ($ticket->priorityId) {
case Priority::CRITICAL:
$priority = $hesklang['critical'];
break;
case Priority::HIGH:
$priority = $hesklang['high'];
break;
case Priority::MEDIUM:
$priority = $hesklang['medium'];
break;
case Priority::LOW:
$priority = $hesklang['low'];
break;
default:
$priority = 'PRIORITY NOT FOUND';
break;
}
// Special tags
$subject = str_replace('%%SUBJECT%%', $ticket->subject, $subjectTemplate);
$subject = str_replace('%%TRACK_ID%%', $ticket->trackingId, $subject);
$subject = str_replace('%%CATEGORY%%', $category->id, $subject);
$subject = str_replace('%%PRIORITY%%', $priority, $subject);
$subject = str_replace('%%STATUS%%', $statusName, $subject);
return $subject;
}
/**
* @param $messageTemplate string
* @param $ticket Ticket
* @param $language string
* @param $heskSettings array
* @return string
* @throws \Exception if common.inc.php isn't loaded
*/
private function parseMessage($messageTemplate, $ticket, $language, $admin, $heskSettings, $modsForHeskSettings, $html) {
global $hesklang;
if (!function_exists('hesk_msgToPlain')) {
throw new \Exception("common.inc.php not loaded!");
}
if ($ticket === null) {
return $messageTemplate;
}
$heskSettings['site_title'] = hesk_msgToPlain($heskSettings['site_title'], 1);
// Is email required to view ticket (for customers only)?
$heskSettings['e_param'] = $heskSettings['email_view_ticket'] ? '&e=' . rawurlencode(implode(';', $ticket->email)) : '';
/* Generate the ticket URLs */
$trackingURL = $heskSettings['hesk_url'];
$trackingURL .= $admin ? '/' . $heskSettings['admin_dir'] . '/admin_ticket.php' : '/ticket.php';
$trackingURL .= '?track=' . $ticket->trackingId . ($admin ? '' : $heskSettings['e_param']) . '&Refresh=' . rand(10000, 99999);
// Status name and category name
$defaultStatus = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::NEW_TICKET, $heskSettings);
$statusName = hesk_msgToPlain($defaultStatus->localizedNames[$language]);
$category = hesk_msgToPlain($this->categoryGateway->getAllCategories($heskSettings)[$ticket->categoryId]->name);
$owner = hesk_msgToPlain($this->userGateway->getUserById($ticket->ownerId, $heskSettings)->name);
switch ($ticket->priorityId) {
case Priority::CRITICAL:
$priority = $hesklang['critical'];
break;
case Priority::HIGH:
$priority = $hesklang['high'];
break;
case Priority::MEDIUM:
$priority = $hesklang['medium'];
break;
case Priority::LOW:
$priority = $hesklang['low'];
break;
default:
$priority = 'PRIORITY NOT FOUND';
break;
}
// Special tags
$msg = str_replace('%%NAME%%', $ticket->name, $messageTemplate);
$msg = str_replace('%%SUBJECT%%', $ticket->subject, $msg);
$msg = str_replace('%%TRACK_ID%%', $ticket->trackingId, $msg);
$msg = str_replace('%%TRACK_URL%%', $trackingURL, $msg);
$msg = str_replace('%%SITE_TITLE%%', $heskSettings['site_title'], $msg);
$msg = str_replace('%%SITE_URL%%', $heskSettings['site_url'], $msg);
$msg = str_replace('%%CATEGORY%%', $category, $msg);
$msg = str_replace('%%PRIORITY%%', $priority, $msg);
$msg = str_replace('%%OWNER%%', $owner, $msg);
$msg = str_replace('%%STATUS%%', $statusName, $msg);
$msg = str_replace('%%EMAIL%%', implode(';',$ticket->email), $msg);
$msg = str_replace('%%CREATED%%', $ticket->dateCreated, $msg);
$msg = str_replace('%%UPDATED%%', $ticket->lastChanged, $msg);
$msg = str_replace('%%ID%%', $ticket->id, $msg);
/* All custom fields */
for ($i=1; $i<=50; $i++) {
$k = 'custom'.$i;
if (isset($heskSettings['custom_fields'][$k]) && isset($ticket->customFields[$i])) {
$v = $heskSettings['custom_fields'][$k];
switch ($v['type']) {
case 'checkbox':
$ticket->customFields[$i] = str_replace("<br>","\n",$ticket->customFields[$i]);
break;
case 'date':
$ticket->customFields[$i] = hesk_custom_date_display_format($ticket->customFields[$i], $v['value']['date_format']);
break;
}
$msg = str_replace('%%'.strtoupper($k).'%%',stripslashes($ticket->customFields[$i]),$msg);
} else {
$msg = str_replace('%%'.strtoupper($k).'%%','',$msg);
}
}
// Is message tag in email template?
if (strpos($msg, '%%MESSAGE%%') !== false) {
// Replace message
if ($html) {
$htmlMessage = html_entity_decode($ticket->message);
$htmlMessage = nl2br($htmlMessage);
$msg = str_replace('%%MESSAGE%%', $htmlMessage, $msg);
} else {
$plainTextMessage = $ticket->message;
$messageHtml = $ticket->usesHtml;
if (count($ticket->replies) > 0) {
$lastReply = end($ticket->replies);
$messageHtml = $lastReply->usesHtml;
}
if ($messageHtml) {
if (!function_exists('convert_html_to_text')) {
require(__DIR__ . '/../../../inc/html2text/html2text.php');
}
$plainTextMessage = convert_html_to_text($plainTextMessage);
$plainTextMessage = fix_newlines($plainTextMessage);
}
$msg = str_replace('%%MESSAGE%%', $plainTextMessage, $msg);
}
// Add direct links to any attachments at the bottom of the email message
if ($heskSettings['attachments']['use'] && isset($ticket->attachments) && count($ticket->attachments) > 0) {
if (!$modsForHeskSettings['attachments']) {
if ($html) {
$msg .= "<br><br><br>" . $hesklang['fatt'];
} else {
$msg .= "\n\n\n" . $hesklang['fatt'];
}
foreach ($ticket->attachments as $attachment) {
if ($html) {
$msg .= "<br><br>{$attachment->fileName}<br>";
} else {
$msg .= "\n\n{$attachment->fileName}\n";
}
$msg .= "{$heskSettings['hesk_url']}/download_attachment.php?att_id={$attachment->id}&track={$ticket->trackingId}{$heskSettings['e_param']}";
}
}
}
// For customer notifications: if we allow email piping/pop 3 fetching and
// stripping quoted replies add an "reply above this line" tag
if (!$admin && ($heskSettings['email_piping'] || $heskSettings['pop3']) && $heskSettings['strip_quoted']) {
$msg = $hesklang['EMAIL_HR'] . "\n\n" . $msg;
}
} elseif (strpos($msg, '%%MESSAGE_NO_ATTACHMENTS%%') !== false) {
if ($html) {
$htmlMessage = nl2br($ticket->message);
$msg = str_replace('%%MESSAGE_NO_ATTACHMENTS%%', $htmlMessage, $msg);
} else {
$plainTextMessage = $ticket->message;
$messageHtml = $ticket->usesHtml;
if (count($ticket->replies) > 0) {
$lastReply = end($ticket->replies);
$messageHtml = $lastReply->usesHtml;
}
if ($messageHtml) {
if (!function_exists('convert_html_to_text')) {
require(__DIR__ . '/../../../inc/html2text/html2text.php');
}
$plainTextMessage = convert_html_to_text($plainTextMessage);
$plainTextMessage = fix_newlines($plainTextMessage);
}
$msg = str_replace('%%MESSAGE_NO_ATTACHMENTS%%', $plainTextMessage, $msg);
}
}
return $msg;
}
}

@ -0,0 +1,65 @@
<?php
namespace BusinessLogic\Emails;
class EmailTemplateRetriever {
/**
* @var $validTemplates EmailTemplate[]
*/
private $validTemplates;
function __construct() {
$this->validTemplates = array();
$this->initializeArray();
}
const FORGOT_TICKET_ID = 0;
const NEW_REPLY_BY_STAFF = 1;
const NEW_TICKET = 2;
const VERIFY_EMAIL = 3;
const TICKET_CLOSED = 4;
const CATEGORY_MOVED = 5;
const NEW_REPLY_BY_CUSTOMER = 6;
const NEW_TICKET_STAFF = 7;
const TICKET_ASSIGNED_TO_YOU = 8;
const NEW_PM = 9;
const NEW_NOTE = 10;
const RESET_PASSWORD = 11;
const CALENDAR_REMINDER = 12;
const OVERDUE_TICKET = 13;
function initializeArray() {
if (count($this->validTemplates) > 0) {
//-- Map already built
return;
}
$this->validTemplates[self::FORGOT_TICKET_ID] = new EmailTemplate(false, 'forgot_ticket_id');
$this->validTemplates[self::NEW_REPLY_BY_STAFF] = new EmailTemplate(false, 'new_reply_by_staff');
$this->validTemplates[self::NEW_TICKET] = new EmailTemplate(false, 'new_ticket', 'ticket_received');
$this->validTemplates[self::VERIFY_EMAIL] = new EmailTemplate(false, 'verify_email');
$this->validTemplates[self::TICKET_CLOSED] = new EmailTemplate(false, 'ticket_closed');
$this->validTemplates[self::CATEGORY_MOVED] = new EmailTemplate(true, 'category_moved');
$this->validTemplates[self::NEW_REPLY_BY_CUSTOMER] = new EmailTemplate(true, 'new_reply_by_customer');
$this->validTemplates[self::NEW_TICKET_STAFF] = new EmailTemplate(true, 'new_ticket_staff');
$this->validTemplates[self::TICKET_ASSIGNED_TO_YOU] = new EmailTemplate(true, 'ticket_assigned_to_you');
$this->validTemplates[self::NEW_PM] = new EmailTemplate(true, 'new_pm');
$this->validTemplates[self::NEW_NOTE] = new EmailTemplate(true, 'new_note');
$this->validTemplates[self::RESET_PASSWORD] = new EmailTemplate(true, 'reset_password');
$this->validTemplates[self::CALENDAR_REMINDER] = new EmailTemplate(true, 'reset_password');
$this->validTemplates[self::OVERDUE_TICKET] = new EmailTemplate(true, 'overdue_ticket');
}
/**
* @param $templateId
* @return EmailTemplate|null
*/
function getTemplate($templateId) {
if (isset($this->validTemplates[$templateId])) {
return $this->validTemplates[$templateId];
}
return null;
}
}

@ -0,0 +1,71 @@
<?php
namespace BusinessLogic\Emails;
use BusinessLogic\Tickets\Attachment;
use BusinessLogic\Tickets\Ticket;
use Mailgun\Mailgun;
class MailgunEmailSender implements EmailSender {
function sendEmail($emailBuilder, $heskSettings, $modsForHeskSettings, $sendAsHtml) {
$mailgunArray = array();
$mailgunArray['from'] = $heskSettings['noreply_mail']; // Email Address
if ($heskSettings['noreply_name'] !== null && $heskSettings['noreply_name'] !== '') {
$mailgunArray['from'] = "{$heskSettings['noreply_name']} <{$heskSettings['noreply_mail']}>"; // Name and address
}
$mailgunArray['to'] = implode(',', $emailBuilder->to);
if ($emailBuilder->cc !== null) {
$mailgunArray['cc'] = implode(',', $emailBuilder->cc);
}
if ($emailBuilder->bcc !== null) {
$mailgunArray['bcc'] = implode(',', $emailBuilder->bcc);
}
$mailgunArray['subject'] = $emailBuilder->subject;
$mailgunArray['text'] = $emailBuilder->message;
if ($sendAsHtml) {
$mailgunArray['html'] = $emailBuilder->htmlMessage;
}
$mailgunAttachments = array();
if ($emailBuilder->attachments !== null) {
foreach ($emailBuilder->attachments as $attachment) {
$mailgunAttachments[] = array(
'remoteName' => $attachment->fileName,
'filePath' => __DIR__ . '/../../../' . $heskSettings['attach_dir'] . '/' . $attachment->savedName
);
}
}
$result = $this->sendMessage($mailgunArray, $mailgunAttachments, $modsForHeskSettings);
if (isset($result->http_response_code)
&& $result->http_response_code === 200) {
return true;
}
return $result;
}
private function sendMessage($mailgunArray, $attachments, $modsForHeskSettings) {
$messageClient = new Mailgun($modsForHeskSettings['mailgun_api_key']);
$mailgunAttachments = array();
if (count($attachments) > 0) {
$mailgunAttachments = array(
'attachment' => $attachments
);
}
$result = $messageClient->sendMessage($modsForHeskSettings['mailgun_domain'], $mailgunArray, $mailgunAttachments);
return $result;
}
}

@ -0,0 +1,33 @@
<?php
/**
* Created by PhpStorm.
* User: mkoch
* Date: 2/28/2017
* Time: 9:36 PM
*/
namespace BusinessLogic\Emails;
class ParsedEmailProperties {
function __construct($subject, $message, $htmlMessage) {
$this->subject = $subject;
$this->message = $message;
$this->htmlMessage = $htmlMessage;
}
/**
* @var $subject string
*/
public $subject;
/**
* @var $message string
*/
public $message;
/**
* @var $htmlMessage string
*/
public $htmlMessage;
}

@ -0,0 +1,10 @@
<?php
namespace BusinessLogic\Exceptions;
class AccessViolationException extends ApiFriendlyException {
function __construct($message) {
parent::__construct($message, 'Access Exception', 403);
}
}

@ -0,0 +1,25 @@
<?php
namespace BusinessLogic\Exceptions;
use Exception;
class ApiFriendlyException extends Exception {
public $title;
public $httpResponseCode;
/**
* ApiFriendlyException constructor.
* @param string $message
* @param string $title
* @param int $httpResponseCode
*/
function __construct($message, $title, $httpResponseCode) {
$this->title = $title;
$this->httpResponseCode = $httpResponseCode;
parent::__construct($message);
}
}

@ -0,0 +1,17 @@
<?php
/**
* Created by PhpStorm.
* User: mkoch
* Date: 2/22/2017
* Time: 10:00 PM
*/
namespace BusinessLogic\Exceptions;
class EmailTemplateNotFoundException extends ApiFriendlyException {
function __construct($emailTemplate, $language) {
parent::__construct(sprintf("The email template '%s' was not found for the language '%s'", $emailTemplate, $language),
'Email Template Not Found!', 400);
}
}

@ -0,0 +1,10 @@
<?php
namespace BusinessLogic\Exceptions;
class InternalUseOnlyException extends ApiFriendlyException {
function __construct() {
parent::__construct("This endpoint can only be used internally", "Internal Use Only", 401);
}
}

@ -0,0 +1,12 @@
<?php
namespace BusinessLogic\Exceptions;
class InvalidAuthenticationTokenException extends ApiFriendlyException {
public function __construct() {
parent::__construct('The X-Auth-Token is invalid. The token must be for an active helpdesk user.',
'Security Exception',
401);
}
}

@ -0,0 +1,16 @@
<?php
/**
* Created by PhpStorm.
* User: mkoch
* Date: 2/23/2017
* Time: 8:13 PM
*/
namespace BusinessLogic\Exceptions;
class InvalidEmailTemplateException extends ApiFriendlyException {
function __construct($template) {
parent::__construct(sprintf("The email template '%s' is invalid", $template), 'Invalid Email Template', 400);
}
}

@ -0,0 +1,11 @@
<?php
namespace BusinessLogic\Exceptions;
class MissingAuthenticationTokenException extends ApiFriendlyException {
function __construct() {
parent::__construct("An 'X-Auth-Token' is required for all requests",
'Security Exception',
400);
}
}

@ -0,0 +1,16 @@
<?php
/**
* Created by PhpStorm.
* User: cokoch
* Date: 5/2/2017
* Time: 12:28 PM
*/
namespace BusinessLogic\Exceptions;
class SessionNotActiveException extends ApiFriendlyException {
function __construct() {
parent::__construct("You must be logged in to call internal API methods", "Authentication Required", 401);
}
}

@ -0,0 +1,21 @@
<?php
namespace BusinessLogic\Exceptions;
use BusinessLogic\ValidationModel;
use Exception;
class ValidationException extends ApiFriendlyException {
/**
* ValidationException constructor.
* @param ValidationModel $validationModel The validation model
* @throws Exception If the validationModel's errorKeys is empty
*/
function __construct($validationModel) {
if (count($validationModel->errorKeys) === 0) {
throw new Exception('Tried to throw a ValidationException, but the validation model was valid or had 0 error keys!');
}
parent::__construct(implode(",", $validationModel->errorKeys), "Validation Failed. Error keys are available in the message section.", 400);
}
}

@ -0,0 +1,29 @@
<?php
namespace BusinessLogic;
class Helpers {
static function getHeader($key) {
$headers = getallheaders();
$uppercaseHeaders = array();
foreach ($headers as $header => $value) {
$uppercaseHeaders[strtoupper($header)] = $value;
}
return isset($uppercaseHeaders[$key])
? $uppercaseHeaders[$key]
: NULL;
}
static function hashToken($token) {
return hash('sha512', $token);
}
static function safeArrayGet($array, $key) {
return $array !== null && array_key_exists($key, $array)
? $array[$key]
: null;
}
}

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

@ -0,0 +1,70 @@
<?php
namespace BusinessLogic\Navigation;
// TODO Test!
use BusinessLogic\Exceptions\ApiFriendlyException;
use DataAccess\Navigation\CustomNavElementGateway;
class CustomNavElementHandler {
/* @var $customNavElementGateway CustomNavElementGateway */
private $customNavElementGateway;
function __construct($customNavElementGateway) {
$this->customNavElementGateway = $customNavElementGateway;
}
function getAllCustomNavElements($heskSettings) {
return $this->customNavElementGateway->getAllCustomNavElements($heskSettings);
}
function getCustomNavElement($id, $heskSettings) {
$elements = $this->getAllCustomNavElements($heskSettings);
foreach ($elements as $element) {
if ($element->id === intval($id)) {
return output($element);
}
}
throw new ApiFriendlyException("Custom nav element {$id} not found!", "Element Not Found", 404);
}
function deleteCustomNavElement($id, $heskSettings) {
$this->customNavElementGateway->deleteCustomNavElement($id, $heskSettings);
$this->customNavElementGateway->resortAllElements($heskSettings);
}
function saveCustomNavElement($element, $heskSettings) {
$this->customNavElementGateway->saveCustomNavElement($element, $heskSettings);
}
function createCustomNavElement($element, $heskSettings) {
$element = $this->customNavElementGateway->createCustomNavElement($element, $heskSettings);
$this->customNavElementGateway->resortAllElements($heskSettings);
return $element;
}
function sortCustomNavElement($elementId, $direction, $heskSettings) {
/* @var $element CustomNavElement */
$elements = $this->customNavElementGateway->getAllCustomNavElements($heskSettings);
$elementToChange = null;
foreach ($elements as $element) {
if ($element->id === intval($elementId)) {
$elementToChange = $element;
}
}
if ($direction === Direction::UP) {
$elementToChange->sort -= 15;
} else {
$elementToChange->sort += 15;
}
$this->customNavElementGateway->saveCustomNavElement($elementToChange, $heskSettings);
$this->customNavElementGateway->resortAllElements($heskSettings);
}
}

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

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

@ -0,0 +1,52 @@
<?php
namespace BusinessLogic\Security;
use DataAccess\Security\BanGateway;
class BanRetriever {
/**
* @var BanGateway
*/
private $banGateway;
function __construct($banGateway) {
$this->banGateway = $banGateway;
}
/**
* @param $email
* @param $heskSettings
* @return bool
*/
function isEmailBanned($email, $heskSettings) {
$bannedEmails = $this->banGateway->getEmailBans($heskSettings);
foreach ($bannedEmails as $bannedEmail) {
if ($bannedEmail->email === $email) {
return true;
}
}
return false;
}
/**
* @param $ip int the IP address, converted beforehand using ip2long()
* @param $heskSettings
* @return bool
*/
function isIpAddressBanned($ip, $heskSettings) {
$bannedIps = $this->banGateway->getIpBans($heskSettings);
foreach ($bannedIps as $bannedIp) {
if ($bannedIp->ipFrom <= $ip && $bannedIp->ipTo >= $ip) {
return true;
}
}
return false;
}
}

@ -0,0 +1,26 @@
<?php
namespace BusinessLogic\Security;
class BannedEmail {
/**
* @var int
*/
public $id;
/**
* @var string
*/
public $email;
/**
* @var int|null The user who banned the email, or null if the user was deleted
*/
public $bannedById;
/**
* @var string
*/
public $dateBanned;
}

@ -0,0 +1,36 @@
<?php
namespace BusinessLogic\Security;
class BannedIp {
/**
* @var int
*/
public $id;
/**
* @var int the lower bound of the IP address range
*/
public $ipFrom;
/**
* @var int the upper bound of the IP address range
*/
public $ipTo;
/**
* @var string the display of the IP ban to be shown to the user
*/
public $ipDisplay;
/**
* @var int|null The user who banned the IP, or null if the user was deleted
*/
public $bannedById;
/**
* @var string
*/
public $dateBanned;
}

@ -0,0 +1,110 @@
<?php
namespace BusinessLogic\Security;
class UserContext {
/* @var $id int */
public $id;
/* @var $username string */
public $username;
/* @var $admin bool */
public $admin;
/* @var $name string */
public $name;
/* @var $email string */
public $email;
/* @var $signature string */
public $signature;
/* @var $language string|null */
public $language;
/* @var $categories int[] */
public $categories;
/* @var $permissions string[] */
public $permissions;
/* @var UserContextPreferences */
public $preferences;
/* @var UserContextNotifications */
public $notificationSettings;
/* @var $autoAssign bool */
public $autoAssign;
/* @var $ratingNegative int */
public $ratingNegative;
/* @var $ratingPositive int */
public $ratingPositive;
/* @var $rating float */
public $rating;
/* @var $totalNumberOfReplies int */
public $totalNumberOfReplies;
/* @var $active bool */
public $active;
/**
* Builds a user context based on the current session. **The session must be active!**
* @param $dataRow array the $_SESSION superglobal or the hesk_users result set
* @return UserContext the built user context
*/
static function fromDataRow($dataRow) {
$userContext = new UserContext();
$userContext->id = intval($dataRow['id']);
$userContext->username = $dataRow['user'];
$userContext->admin = boolval($dataRow['isadmin']);
$userContext->name = $dataRow['name'];
$userContext->email = $dataRow['email'];
$userContext->signature = $dataRow['signature'];
$userContext->language = $dataRow['language'];
if (is_array($dataRow['categories'])) {
$userContext->categories = $dataRow['categories'];
} else {
$userContext->categories = explode(',', $dataRow['categories']);
}
$userContext->permissions = explode(',', $dataRow['heskprivileges']);
$userContext->autoAssign = boolval($dataRow['autoassign']);
$userContext->ratingNegative = intval($dataRow['ratingneg']);
$userContext->ratingPositive = intval($dataRow['ratingpos']);
$userContext->rating = floatval($dataRow['rating']);
$userContext->totalNumberOfReplies = intval($dataRow['replies']);
$userContext->active = boolval($dataRow['active']);
$preferences = new UserContextPreferences();
$preferences->afterReply = intval($dataRow['afterreply']);
$preferences->autoStartTimeWorked = boolval($dataRow['autostart']);
$preferences->autoreload = intval($dataRow['autoreload']);
$preferences->defaultNotifyCustomerNewTicket = boolval($dataRow['notify_customer_new']);
$preferences->defaultNotifyCustomerReply = boolval($dataRow['notify_customer_reply']);
$preferences->showSuggestedKnowledgebaseArticles = boolval($dataRow['show_suggested']);
$preferences->defaultCalendarView = intval($dataRow['default_calendar_view']);
$preferences->defaultTicketView = $dataRow['default_list'];
$userContext->preferences = $preferences;
$notifications = new UserContextNotifications();
$notifications->newUnassigned = boolval($dataRow['notify_new_unassigned']);
$notifications->newAssignedToMe = boolval($dataRow['notify_new_my']);
$notifications->replyUnassigned = boolval($dataRow['notify_reply_unassigned']);
$notifications->replyToMe = boolval($dataRow['notify_reply_my']);
$notifications->ticketAssignedToMe = boolval($dataRow['notify_assigned']);
$notifications->privateMessage = boolval($dataRow['notify_pm']);
$notifications->noteOnTicketAssignedToMe = boolval($dataRow['notify_note']);
$notifications->noteOnTicketNotAssignedToMe = boolval($dataRow['notify_note_unassigned']);
$notifications->overdueTicketUnassigned = boolval($dataRow['notify_overdue_unassigned']);
$userContext->notificationSettings = $notifications;
return $userContext;
}
}

@ -0,0 +1,87 @@
<?php
namespace BusinessLogic\Security;
use BusinessLogic\Exceptions\InvalidAuthenticationTokenException;
use BusinessLogic\Exceptions\MissingAuthenticationTokenException;
use BusinessLogic\Helpers;
use DataAccess\Security\UserGateway;
class UserContextBuilder {
/**
* @var UserGateway
*/
private $userGateway;
function __construct($userGateway) {
$this->userGateway = $userGateway;
}
function buildUserContext($authToken, $heskSettings) {
$NULL_OR_EMPTY_STRING = 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e';
$hashedToken = Helpers::hashToken($authToken);
if ($hashedToken === $NULL_OR_EMPTY_STRING) {
throw new MissingAuthenticationTokenException();
}
$userRow = $this->userGateway->getUserForAuthToken($hashedToken, $heskSettings);
if ($userRow === null) {
throw new InvalidAuthenticationTokenException();
}
return UserContext::fromDataRow($userRow);
}
/**
* Builds a user context based on the current session. **The session must be active!**
* @param $dataRow array the $_SESSION superglobal or the hesk_users result set
* @return UserContext the built user context
*/
function fromDataRow($dataRow) {
$userContext = new UserContext();
$userContext->id = $dataRow['id'];
$userContext->username = $dataRow['user'];
$userContext->admin = $dataRow['isadmin'];
$userContext->name = $dataRow['name'];
$userContext->email = $dataRow['email'];
$userContext->signature = $dataRow['signature'];
$userContext->language = $dataRow['language'];
$userContext->categories = explode(',', $dataRow['categories']);
$userContext->permissions = explode(',', $dataRow['heskprivileges']);
$userContext->autoAssign = $dataRow['autoassign'];
$userContext->ratingNegative = $dataRow['ratingneg'];
$userContext->ratingPositive = $dataRow['ratingpos'];
$userContext->rating = $dataRow['rating'];
$userContext->totalNumberOfReplies = $dataRow['replies'];
$userContext->active = $dataRow['active'];
$preferences = new UserContextPreferences();
$preferences->afterReply = $dataRow['afterreply'];
$preferences->autoStartTimeWorked = $dataRow['autostart'];
$preferences->autoreload = $dataRow['autoreload'];
$preferences->defaultNotifyCustomerNewTicket = $dataRow['notify_customer_new'];
$preferences->defaultNotifyCustomerReply = $dataRow['notify_customer_reply'];
$preferences->showSuggestedKnowledgebaseArticles = $dataRow['show_suggested'];
$preferences->defaultCalendarView = $dataRow['default_calendar_view'];
$preferences->defaultTicketView = $dataRow['default_list'];
$userContext->preferences = $preferences;
$notifications = new UserContextNotifications();
$notifications->newUnassigned = $dataRow['notify_new_unassigned'];
$notifications->newAssignedToMe = $dataRow['notify_new_my'];
$notifications->replyUnassigned = $dataRow['notify_reply_unassigned'];
$notifications->replyToMe = $dataRow['notify_reply_my'];
$notifications->ticketAssignedToMe = $dataRow['notify_assigned'];
$notifications->privateMessage = $dataRow['notify_pm'];
$notifications->noteOnTicketAssignedToMe = $dataRow['notify_note'];
$notifications->noteOnTicketNotAssignedToMe = $dataRow['notify_note_unassigned'];
$notifications->overdueTicketUnassigned = $dataRow['notify_overdue_unassigned'];
$userContext->notificationSettings = $notifications;
return $userContext;
}
}

@ -0,0 +1,16 @@
<?php
namespace BusinessLogic\Security;
class UserContextNotifications {
public $newUnassigned;
public $newAssignedToMe;
public $replyUnassigned;
public $replyToMe;
public $ticketAssignedToMe;
public $privateMessage;
public $noteOnTicketAssignedToMe;
public $noteOnTicketNotAssignedToMe;
public $overdueTicketUnassigned;
}

@ -0,0 +1,15 @@
<?php
namespace BusinessLogic\Security;
class UserContextPreferences {
public $afterReply;
public $autoStartTimeWorked;
public $autoreload;
public $defaultNotifyCustomerNewTicket;
public $defaultNotifyCustomerReply;
public $showSuggestedKnowledgebaseArticles;
public $defaultCalendarView;
public $defaultTicketView;
}

@ -0,0 +1,17 @@
<?php
/**
* Created by PhpStorm.
* User: mkoch
* Date: 3/12/2017
* Time: 12:11 PM
*/
namespace BusinessLogic\Security;
class UserPrivilege {
const CAN_VIEW_TICKETS = 'can_view_tickets';
const CAN_REPLY_TO_TICKETS = 'can_reply_tickets';
const CAN_EDIT_TICKETS = 'can_edit_tickets';
const CAN_DELETE_TICKETS = 'can_del_tickets';
}

@ -0,0 +1,50 @@
<?php
namespace BusinessLogic\Security;
use BusinessLogic\Tickets\Ticket;
use DataAccess\Security\UserGateway;
class UserToTicketChecker {
/* @var $userGateway UserGateway */
private $userGateway;
function __construct($userGateway) {
$this->userGateway = $userGateway;
}
/**
* @param $user UserContext
* @param $ticket Ticket
* @param $heskSettings array
* @param $extraPermissions UserPrivilege[] additional privileges the user needs besides CAN_VIEW_TICKETS (if not an admin)
* for this to return true
* @return bool
*/
function isTicketAccessibleToUser($user, $ticket, $heskSettings, $extraPermissions = array()) {
if ($user->admin === true) {
return true;
}
if (!in_array($ticket->categoryId, $user->categories)) {
return false;
}
$categoryManagerId = $this->userGateway->getManagerForCategory($ticket->categoryId, $heskSettings);
if ($user->id === $categoryManagerId) {
return true;
}
$extraPermissions[] = UserPrivilege::CAN_VIEW_TICKETS;
foreach ($extraPermissions as $permission) {
if (!in_array($permission, $user->permissions)) {
return false;
}
}
return true;
}
}

@ -0,0 +1,21 @@
<?php
namespace BusinessLogic\Settings;
use DataAccess\Settings\ModsForHeskSettingsGateway;
class ApiChecker {
/* @var $modsForHeskSettingsGateway ModsForHeskSettingsGateway */
private $modsForHeskSettingsGateway;
function __construct($modsForHeskSettingsGateway) {
$this->modsForHeskSettingsGateway = $modsForHeskSettingsGateway;
}
function isApiEnabled($heskSettings) {
$modsForHeskSettings = $this->modsForHeskSettingsGateway->getAllSettings($heskSettings);
return intval($modsForHeskSettings['public_api']) === 1;
}
}

@ -0,0 +1,86 @@
<?php
namespace BusinessLogic\Settings;
// TODO Test!
use DataAccess\Settings\ModsForHeskSettingsGateway;
class SettingsRetriever {
/* @var $modsForHeskSettingsGateway ModsForHeskSettingsGateway */
private $modsForHeskSettingsGateway;
function __construct($modsForHeskSettingsGateway) {
$this->modsForHeskSettingsGateway = $modsForHeskSettingsGateway;
}
private static $settingsToNotReturn = array(
'webmaster_email',
'noreply_email',
'noreply_name',
'db_.*',
'admin_dir',
'attach_dir',
'cache_dir',
'autoclose',
'autologin',
'autoassign',
'secimg_.*',
'recaptcha_.*',
'question_.*',
'attempt_.*',
'reset_pass',
'x_frame_opt',
'force_ssl',
'imap.*',
'smtp.*',
'email_piping',
'pop3.*',
'loop.*',
'email_providers',
'notify_.*',
'alink',
'submit_notice',
'online',
'online_min',
'modsForHeskVersion',
'use_mailgun',
'mailgun.*',
'kb_attach_dir',
'public_api',
'custom_fields',
'hesk_version',
'hesk_license',
);
/**
* @param $heskSettings array
* @return array
*/
function getAllSettings($heskSettings) {
$modsForHeskSettings = $this->modsForHeskSettingsGateway->getAllSettings($heskSettings);
$settingsToReturn = array();
foreach ($heskSettings as $key => $value) {
if ($this->isPermittedKey($key)) {
$settingsToReturn[$key] = $value;
}
}
foreach ($modsForHeskSettings as $key => $value) {
if ($this->isPermittedKey($key)) {
$settingsToReturn[$key] = $value;
}
}
return $settingsToReturn;
}
private function isPermittedKey($key) {
foreach (self::$settingsToNotReturn as $setting) {
if (preg_match("/{$setting}/", $key)) {
return false;
}
}
return true;
}
}

@ -0,0 +1,11 @@
<?php
namespace BusinessLogic\Statuses;
class Closable {
const YES = "yes";
const STAFF_ONLY = "sonly";
const CUSTOMERS_ONLY = "conly";
const NO = "no";
}

@ -0,0 +1,30 @@
<?php
namespace BusinessLogic\Statuses;
class DefaultStatusForAction {
const NEW_TICKET = "IsNewTicketStatus";
const CLOSED_STATUS = "IsClosed";
const CLOSED_BY_CLIENT = "IsClosedByClient";
const CUSTOMER_REPLY = "IsCustomerReplyStatus";
const CLOSED_BY_STAFF = "IsStaffClosedOption";
const REOPENED_BY_STAFF = "IsStaffReopenedStatus";
const DEFAULT_STAFF_REPLY = "IsDefaultStaffReplyStatus";
const LOCKED_TICKET = "LockedTicketStatus";
const AUTOCLOSE_STATUS = "IsAutocloseOption";
static function getAll() {
return array(
self::NEW_TICKET,
self::CLOSED_STATUS,
self::CLOSED_BY_CLIENT,
self::CUSTOMER_REPLY,
self::CLOSED_BY_STAFF,
self::REOPENED_BY_STAFF,
self::DEFAULT_STAFF_REPLY,
self::LOCKED_TICKET,
self::AUTOCLOSE_STATUS
);
}
}

@ -0,0 +1,72 @@
<?php
namespace BusinessLogic\Statuses;
class Status {
static function fromDatabase($row, $languageRs) {
$status = new Status();
$status->id = intval($row['ID']);
$status->textColor = $row['TextColor'];
$status->defaultActions = array();
foreach (DefaultStatusForAction::getAll() as $defaultStatus) {
$status = self::addDefaultStatusIfSet($status, $row, $defaultStatus);
}
$status->closable = $row['Closable'];
$localizedLanguages = array();
while ($languageRow = hesk_dbFetchAssoc($languageRs)) {
$localizedLanguages[$languageRow['language']] = $languageRow['text'];
}
$status->localizedNames = $localizedLanguages;
$status->sort = intval($row['sort']);
return $status;
}
/**
* @param $status Status
* @param $row array
* @param $key string
* @return Status
*/
private static function addDefaultStatusIfSet($status, $row, $key) {
if ($row[$key]) {
$status->defaultActions[] = $key;
}
return $status;
}
/**
* @var $id int
*/
public $id;
/**
* @var $textColor string
*/
public $textColor;
/**
* @var $defaultActions DefaultStatusForAction[]
*/
public $defaultActions;
/**
* @var $closable Closable
*/
public $closable;
/**
* @var $sort int
*/
public $sort;
/**
* @var $name string[]
*/
public $localizedNames;
}

@ -0,0 +1,20 @@
<?php
namespace BusinessLogic\Statuses;
use DataAccess\Statuses\StatusGateway;
// TODO Test!
class StatusRetriever {
/* @var $statusGateway StatusGateway */
private $statusGateway;
function __construct($statusGateway) {
$this->statusGateway = $statusGateway;
}
function getAllStatuses($heskSettings) {
return $this->statusGateway->getStatuses($heskSettings);
}
}

@ -0,0 +1,21 @@
<?php
namespace BusinessLogic\Tickets;
class Attachment {
/**
* @var int
*/
public $id;
/**
* @var string
*/
public $fileName;
/**
* @var string
*/
public $savedName;
}

@ -0,0 +1,47 @@
<?php
namespace BusinessLogic\Tickets;
use BusinessLogic\Security\UserContext;
use BusinessLogic\Security\UserPrivilege;
use DataAccess\Categories\CategoryGateway;
use DataAccess\Security\UserGateway;
class Autoassigner {
/* @var $categoryGateway CategoryGateway */
private $categoryGateway;
/* @var $userGateway UserGateway */
private $userGateway;
function __construct($categoryGateway, $userGateway) {
$this->categoryGateway = $categoryGateway;
$this->userGateway = $userGateway;
}
/**
* @param $categoryId int
* @param $heskSettings array
* @return UserContext the user who is assigned, or null if no user should be assigned
*/
function getNextUserForTicket($categoryId, $heskSettings) {
if (!$heskSettings['autoassign']) {
return null;
}
$potentialUsers = $this->userGateway->getUsersByNumberOfOpenTicketsForAutoassign($heskSettings);
foreach ($potentialUsers as $potentialUser) {
if ($potentialUser->admin ||
(in_array($categoryId, $potentialUser->categories) &&
in_array(UserPrivilege::CAN_VIEW_TICKETS, $potentialUser->permissions) &&
in_array(UserPrivilege::CAN_REPLY_TO_TICKETS, $potentialUser->permissions))) {
return $potentialUser;
}
}
return null;
}
}

@ -0,0 +1,80 @@
<?php
namespace BusinessLogic\Tickets;
class CreateTicketByCustomerModel {
/**
* @var string
*/
public $name;
/**
* @var string
*/
public $email;
/**
* @var integer
*/
public $priority;
/**
* @var integer
*/
public $category;
/**
* @var string
*/
public $subject;
/**
* @var string
*/
public $message;
/**
* @var bool
*/
public $html;
/**
* @var array
*/
public $customFields;
/**
* @var string[]|null The latitude/longitude pair, or relevant error code (E-#)
*/
public $location;
/**
* @var int[]|null
*/
public $suggestedKnowledgebaseArticleIds;
/**
* @var string|null
*/
public $userAgent;
/**
* @var int[]|null
*/
public $screenResolution;
/**
* @var int|null
*/
public $ipAddress;
/**
* @var string
*/
public $language;
/**
* @var $sendEmailToCustomer bool
*/
public $sendEmailToCustomer;
}

@ -0,0 +1,17 @@
<?php
namespace BusinessLogic\Tickets;
class CreatedTicketModel {
/* @var $ticket Ticket */
public $ticket;
/* @var $emailVerified bool */
public $emailVerified;
function __construct($ticket, $emailVerified) {
$this->ticket = $ticket;
$this->emailVerified = $emailVerified;
}
}

@ -0,0 +1,18 @@
<?php
namespace BusinessLogic\Tickets\CustomFields;
class CustomFieldValidator {
static function isCustomFieldInCategory($customFieldId, $categoryId, $staff, $heskSettings) {
$customField = $heskSettings['custom_fields']["custom{$customFieldId}"];
if (!$customField['use'] ||
(!$staff && $customField['use'] === 2)) {
return false;
}
return count($customField['category']) === 0 ||
in_array($categoryId, $customField['category']);
}
}

@ -0,0 +1,30 @@
<?php
namespace BusinessLogic\Tickets;
class EditTicketModel {
/* @var $id int */
public $id;
/* @var $language string */
public $language;
/* @var $subject string */
public $subject;
/* @var $name string */
public $name;
/* @var $emails string */
public $email;
/* @var $message string */
public $message;
/* @var $html bool */
public $html;
/* @var $customFields string[] */
public $customFields;
}

@ -0,0 +1,12 @@
<?php
namespace BusinessLogic\Tickets\Exceptions;
use Exception;
class UnableToGenerateTrackingIdException extends Exception {
public function __construct() {
parent::__construct("Error generating a unique ticket ID.");
}
}

@ -0,0 +1,132 @@
<?php
namespace BusinessLogic\Tickets;
use BusinessLogic\Categories\CategoryRetriever;
use BusinessLogic\Security\BanRetriever;
use BusinessLogic\Tickets\CustomFields\CustomFieldValidator;
use BusinessLogic\ValidationModel;
use BusinessLogic\Validators;
use Core\Constants\CustomField;
class NewTicketValidator {
/**
* @var $categoryRetriever CategoryRetriever
*/
private $categoryRetriever;
/**
* @var $banRetriever BanRetriever
*/
private $banRetriever;
/**
* @var $ticketValidators TicketValidators
*/
private $ticketValidators;
function __construct($categoryRetriever, $banRetriever, $ticketValidators) {
$this->categoryRetriever = $categoryRetriever;
$this->banRetriever = $banRetriever;
$this->ticketValidators = $ticketValidators;
}
/**
* @param $ticketRequest CreateTicketByCustomerModel
* @param $heskSettings array HESK settings
* @return ValidationModel If errorKeys is empty, validation successful. Otherwise invalid ticket
*/
function validateNewTicketForCustomer($ticketRequest, $heskSettings, $userContext) {
$TICKET_PRIORITY_CRITICAL = 0;
$validationModel = new ValidationModel();
if ($ticketRequest->name === NULL || $ticketRequest->name == '') {
$validationModel->errorKeys[] = 'NO_NAME';
}
if (!Validators::validateEmail($ticketRequest->email, $heskSettings['multi_eml'], false)) {
$validationModel->errorKeys[] = 'INVALID_OR_MISSING_EMAIL';
}
$categoryId = intval($ticketRequest->category);
if ($categoryId < 1) {
$validationModel->errorKeys[] = 'NO_CATEGORY';
} else {
$categoryExists = array_key_exists($categoryId, $this->categoryRetriever->getAllCategories($heskSettings, $userContext));
if (!$categoryExists) {
$validationModel->errorKeys[] = 'CATEGORY_DOES_NOT_EXIST';
}
}
//-- TODO assert priority exists
if ($heskSettings['cust_urgency'] && intval($ticketRequest->priority) === $TICKET_PRIORITY_CRITICAL) {
$validationModel->errorKeys[] = 'CRITICAL_PRIORITY_FORBIDDEN';
}
if ($heskSettings['require_subject'] === 1 &&
($ticketRequest->subject === NULL || $ticketRequest->subject === '')) {
$validationModel->errorKeys[] = 'SUBJECT_REQUIRED';
}
if ($heskSettings['require_message'] === 1 &&
($ticketRequest->message === NULL || $ticketRequest->message === '')) {
$validationModel->errorKeys[] = 'MESSAGE_REQUIRED';
}
foreach ($heskSettings['custom_fields'] as $key => $value) {
$customFieldNumber = intval(str_replace('custom', '', $key));
//TODO test this
if (!array_key_exists($customFieldNumber, $ticketRequest->customFields)) {
continue;
}
if ($value['use'] == 1 && CustomFieldValidator::isCustomFieldInCategory($customFieldNumber, intval($ticketRequest->category), false, $heskSettings)) {
$custom_field_value = $ticketRequest->customFields[$customFieldNumber];
if (empty($custom_field_value)) {
$validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::NO_VALUE";
continue;
}
switch($value['type']) {
case CustomField::DATE:
if (!preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $custom_field_value)) {
$validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::INVALID_DATE';
} else {
// Actually validate based on range
$date = strtotime($custom_field_value . ' t00:00:00');
$dmin = strlen($value['value']['dmin']) ? strtotime($value['value']['dmin'] . ' t00:00:00') : false;
$dmax = strlen($value['value']['dmax']) ? strtotime($value['value']['dmax'] . ' t00:00:00') : false;
if ($dmin && $dmin > $date) {
$validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::DATE_BEFORE_MIN::MIN:' . date('Y-m-d', $dmin) . '::ENTERED:' . date('Y-m-d', $date);
} elseif ($dmax && $dmax < $date) {
$validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::DATE_AFTER_MAX::MAX:' . date('Y-m-d', $dmax) . '::ENTERED:' . date('Y-m-d', $date);
}
}
break;
case CustomField::EMAIL:
if (!Validators::validateEmail($custom_field_value, $value['value']['multiple'], false)) {
$validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::INVALID_EMAIL";
}
break;
}
}
}
if ($this->banRetriever->isEmailBanned($ticketRequest->email, $heskSettings)) {
$validationModel->errorKeys[] = 'EMAIL_BANNED';
}
if ($this->ticketValidators->isCustomerAtMaxTickets($ticketRequest->email, $heskSettings)) {
$validationModel->errorKeys[] = 'EMAIL_AT_MAX_OPEN_TICKETS';
}
if ($ticketRequest->language === null ||
$ticketRequest->language === '') {
$validationModel->errorKeys[] = 'MISSING_LANGUAGE';
}
return $validationModel;
}
}

@ -0,0 +1,62 @@
<?php
/**
* Created by PhpStorm.
* User: mkoch
* Date: 2/28/2017
* Time: 9:17 PM
*/
namespace BusinessLogic\Tickets;
class Reply {
/**
* @var $id int
*/
public $id;
/**
* @var $ticketId int
*/
public $ticketId;
/**
* @var $replierName string
*/
public $replierName;
/**
* @var $message string
*/
public $message;
/**
* @var $dateCreated string
*/
public $dateCreated;
/**
* @var $attachments Attachment[]
*/
public $attachments;
/**
* @var $staffId int|null
*/
public $staffId;
/**
* @var $rating int|null
*/
public $rating;
/**
* @var $isRead bool
*/
public $isRead;
/**
* @var $usesHtml bool
*/
public $usesHtml;
}

@ -0,0 +1,14 @@
<?php
/**
* Created by PhpStorm.
* User: mkoch
* Date: 2/20/2017
* Time: 10:03 PM
*/
namespace BusinessLogic\Tickets;
class StageTicket extends Ticket {
//-- Nothing here, just an indicator that it is a StageTicket and not a regular Ticket
}

@ -0,0 +1,359 @@
<?php
namespace BusinessLogic\Tickets;
class Ticket {
static function fromDatabaseRow($row, $linkedTicketsRs, $repliesRs, $heskSettings) {
$ticket = new Ticket();
$ticket->id = intval($row['id']);
$ticket->trackingId = $row['trackid'];
$ticket->name = $row['name'];
if ($row['email'] !== null) {
$emails = str_replace(';', ',', $row['email']);
$emails = explode(',', strtolower($emails));
$ticket->email = array_filter($emails);
}
$ticket->categoryId = intval($row['category']);
$ticket->priorityId = intval($row['priority']);
$ticket->subject = $row['subject'];
$ticket->message = $row['message'];
$ticket->dateCreated = $row['dt'];
$ticket->lastChanged = $row['lastchange'];
$ticket->firstReplyDate = $row['firstreply'];
$ticket->closedDate = $row['closedat'];
if (trim($row['articles']) !== '') {
$suggestedArticles = explode(',', $row['articles']);
$articlesAsInts = array();
foreach ($suggestedArticles as $article) {
$articlesAsInts[] = intval($article);
}
$ticket->suggestedArticles = $articlesAsInts;
}
$ticket->ipAddress = $row['ip'];
$ticket->language = $row['language'];
$ticket->statusId = intval($row['status']);
$ticket->openedBy = intval($row['openedby']);
$ticket->firstReplyByUserId = $row['firstreplyby'] === null ? null : intval($row['firstreplyby']);
$ticket->closedByUserId = $row['closedby'] === null ? null : intval($row['closedby']);
$ticket->numberOfReplies = intval($row['replies']);
$ticket->numberOfStaffReplies = intval($row['staffreplies']);
$ticket->ownerId = intval($row['owner']);
$ticket->timeWorked = $row['time_worked'];
$ticket->lastReplyBy = intval($row['lastreplier']);
$ticket->lastReplier = $row['replierid'] === null ? null : intval($row['replierid']);
$ticket->archived = intval($row['archive']) === 1;
$ticket->locked = intval($row['locked']) === 1;
if (trim($row['attachments']) !== '') {
$attachments = explode(',', $row['attachments']);
$attachmentArray = array();
foreach ($attachments as $attachment) {
if (trim($attachment) === '') {
continue;
}
$attachmentRow = explode('#', $attachment);
$attachmentModel = new Attachment();
$attachmentModel->id = $attachmentRow[0];
$attachmentModel->fileName = $attachmentRow[1];
$attachmentModel->savedName = $attachmentRow[2];
$attachmentArray[] = $attachmentModel;
}
$ticket->attachments = $attachmentArray;
}
if (trim($row['merged']) !== '') {
$ticket->mergedTicketIds = explode(',', $row['merged']);
}
$ticket->auditTrailHtml = $row['history'];
$ticket->customFields = array();
foreach ($heskSettings['custom_fields'] as $key => $value) {
if ($value['use'] && hesk_is_custom_field_in_category($key, intval($ticket->categoryId))) {
$ticket->customFields[str_replace('custom', '', $key)] = $row[$key];
}
}
while ($linkedTicketsRow = hesk_dbFetchAssoc($linkedTicketsRs)) {
$ticket->linkedTicketIds[] = $linkedTicketsRow['id'];
}
if ($row['latitude'] !== '' && $row['longitude'] !== '') {
$ticket->location = array();
$ticket->location[0] = $row['latitude'];
$ticket->location[1] = $row['longitude'];
}
$ticket->usesHtml = intval($row['html']) === 1;
if ($row['user_agent'] !== null && trim($row['user_agent']) !== '') {
$ticket->userAgent = $row['user_agent'];
}
if ($row['screen_resolution_height'] !== null && $row['screen_resolution_width'] !== null){
$ticket->screenResolution = array();
$ticket->screenResolution[0] = $row['screen_resolution_width'];
$ticket->screenResolution[1] = $row['screen_resolution_height'];
}
$ticket->dueDate = $row['due_date'];
$ticket->dueDateOverdueEmailSent = $row['overdue_email_sent'] !== null && intval($row['overdue_email_sent']) === 1;
$replies = array();
while ($replyRow = hesk_dbFetchAssoc($repliesRs)) {
$reply = new Reply();
$reply->id = $replyRow['id'];
$reply->ticketId = $replyRow['replyto'];
$reply->replierName = $replyRow['name'];
$reply->message = $replyRow['message'];
$reply->dateCreated = $replyRow['dt'];
if (trim($replyRow['attachments']) !== '') {
$attachments = explode(',', $replyRow['attachments']);
$attachmentArray = array();
foreach ($attachments as $attachment) {
if (trim($attachment) === '') {
continue;
}
$attachmentRow = explode('#', $attachment);
$attachmentModel = new Attachment();
$attachmentModel->id = $attachmentRow[0];
$attachmentModel->fileName = $attachmentRow[1];
$attachmentModel->savedName = $attachmentRow[2];
$attachmentArray[] = $attachmentModel;
}
$reply->attachments = $attachmentArray;
}
$reply->staffId = $replyRow['staffid'] > 0 ? $replyRow['staffid'] : null;
$reply->rating = $replyRow['rating'];
$reply->isRead = $replyRow['read'] === '1';
$reply->usesHtml = $replyRow['html'] === '1';
$replies[$reply->id] = $reply;
}
$ticket->replies = $replies;
return $ticket;
}
/**
* @var int
*/
public $id;
/**
* @var string
*/
public $trackingId;
/**
* @var string
*/
public $name;
/**
* @var array|null
*/
public $email;
/**
* @var int
*/
public $categoryId;
/**
* @var int
*/
public $priorityId;
/**
* @var string|null
*/
public $subject;
/**
* @var string|null
*/
public $message;
/**
* @var string
*/
public $dateCreated;
/**
* @var string
*/
public $lastChanged;
/**
* @var string|null
*/
public $firstReplyDate;
/**
* @var string|null
*/
public $closedDate;
/**
* @var int[]
*/
public $suggestedArticles = array();
/**
* @var string
*/
public $ipAddress;
/**
* @var string|null
*/
public $language;
/**
* @var int
*/
public $statusId;
/**
* @var int
*/
public $openedBy;
/**
* @var int|null
*/
public $firstReplyByUserId;
/**
* @var int|null
*/
public $closedByUserId;
/**
* @var int
*/
public $numberOfReplies;
/**
* @var int
*/
public $numberOfStaffReplies;
/**
* @var int|null
*/
public $ownerId;
/**
* @var string
*/
public $timeWorked;
/**
* @var int
*/
public $lastReplyBy;
/**
* @var int|null
*/
public $lastReplier;
/**
* @var bool
*/
public $archived;
/**
* @var bool
*/
public $locked;
/**
* @var Attachment[]
*/
public $attachments = array();
function getAttachmentsForDatabase() {
$attachmentArray = array();
if ($this->attachments !== null) {
foreach ($this->attachments as $attachment) {
$attachmentArray[] = $attachment->id . '#' . $attachment->fileName . '#' . $attachment->savedName;
}
}
return implode(',', $attachmentArray);
}
/**
* @var int[]
*/
public $mergedTicketIds = array();
/**
* @var string
*/
public $auditTrailHtml;
/**
* @var string[]
*/
public $customFields;
/**
* @var int[]
*/
public $linkedTicketIds = array();
/**
* @var float[]|null
*/
public $location;
/**
* @var bool
*/
public $usesHtml;
/**
* @var string|null
*/
public $userAgent;
/**
* 0 => width
* 1 => height
*
* @var int[]|null
*/
public $screenResolution;
/**
* @var string|null
*/
public $dueDate;
/**
* @var bool|null
*/
public $dueDateOverdueEmailSent;
/**
* @var Reply[]
*/
public $replies = array();
}

@ -0,0 +1,194 @@
<?php
namespace BusinessLogic\Tickets;
use BusinessLogic\Emails\Addressees;
use BusinessLogic\Emails\EmailSenderHelper;
use BusinessLogic\Emails\EmailTemplateRetriever;
use BusinessLogic\Exceptions\ValidationException;
use BusinessLogic\Statuses\DefaultStatusForAction;
use DataAccess\Security\UserGateway;
use DataAccess\Settings\ModsForHeskSettingsGateway;
use DataAccess\Statuses\StatusGateway;
use DataAccess\Tickets\TicketGateway;
class TicketCreator {
/**
* @var $newTicketValidator NewTicketValidator
*/
private $newTicketValidator;
/**
* @var $trackingIdGenerator TrackingIdGenerator
*/
private $trackingIdGenerator;
/**
* @var $autoassigner Autoassigner
*/
private $autoassigner;
/**
* @var $statusGateway StatusGateway
*/
private $statusGateway;
/**
* @var $ticketGateway TicketGateway
*/
private $ticketGateway;
/**
* @var $verifiedEmailChecker VerifiedEmailChecker
*/
private $verifiedEmailChecker;
/**
* @var $emailSenderHelper EmailSenderHelper
*/
private $emailSenderHelper;
/**
* @var $userGateway UserGateway
*/
private $userGateway;
/* @var $modsForHeskSettingsGateway ModsForHeskSettingsGateway */
private $modsForHeskSettingsGateway;
function __construct($newTicketValidator, $trackingIdGenerator, $autoassigner, $statusGateway, $ticketGateway,
$verifiedEmailChecker, $emailSenderHelper, $userGateway, $modsForHeskSettingsGateway) {
$this->newTicketValidator = $newTicketValidator;
$this->trackingIdGenerator = $trackingIdGenerator;
$this->autoassigner = $autoassigner;
$this->statusGateway = $statusGateway;
$this->ticketGateway = $ticketGateway;
$this->verifiedEmailChecker = $verifiedEmailChecker;
$this->emailSenderHelper = $emailSenderHelper;
$this->userGateway = $userGateway;
$this->modsForHeskSettingsGateway = $modsForHeskSettingsGateway;
}
/**
* Ticket attachments are <b>NOT</b> handled here!
*
* @param $ticketRequest CreateTicketByCustomerModel
* @param $heskSettings array HESK settings
* @param $userContext
* @return CreatedTicketModel The newly created ticket along with if the email is verified or not
* @throws ValidationException When a required field in $ticket_request is missing
* @throws \Exception When the default status for new tickets is not found
*/
function createTicketByCustomer($ticketRequest, $heskSettings, $userContext) {
$modsForHeskSettings = $this->modsForHeskSettingsGateway->getAllSettings($heskSettings);
$validationModel = $this->newTicketValidator->validateNewTicketForCustomer($ticketRequest, $heskSettings, $userContext);
if (count($validationModel->errorKeys) > 0) {
// Validation failed
throw new ValidationException($validationModel);
}
$emailVerified = true;
if ($modsForHeskSettings['customer_email_verification_required']) {
$emailVerified = $this->verifiedEmailChecker->isEmailVerified($ticketRequest->email, $heskSettings);
}
// Create the ticket
$ticket = $emailVerified
? new Ticket()
: new StageTicket();
$ticket->trackingId = $this->trackingIdGenerator->generateTrackingId($heskSettings);
if ($heskSettings['autoassign']) {
$ticket->ownerId = $this->autoassigner->getNextUserForTicket($ticketRequest->category, $heskSettings)->id;
}
// Transform one-to-one properties
$ticket->name = $ticketRequest->name;
$ticket->email = $ticketRequest->email;
$ticket->priorityId = $ticketRequest->priority;
$ticket->categoryId = $ticketRequest->category;
$ticket->subject = $ticketRequest->subject;
$ticket->message = $ticketRequest->message;
$ticket->usesHtml = $ticketRequest->html;
$ticket->customFields = $ticketRequest->customFields;
$ticket->location = $ticketRequest->location;
$ticket->suggestedArticles = $ticketRequest->suggestedKnowledgebaseArticleIds;
$ticket->userAgent = $ticketRequest->userAgent;
$ticket->screenResolution = $ticketRequest->screenResolution;
$ticket->ipAddress = $ticketRequest->ipAddress;
$ticket->language = $ticketRequest->language;
$status = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::NEW_TICKET, $heskSettings);
if ($status === null) {
throw new \Exception("Could not find the default status for a new ticket!");
}
$ticket->statusId = $status->id;
$ticketGatewayGeneratedFields = $this->ticketGateway->createTicket($ticket, $emailVerified, $heskSettings);
$ticket->dateCreated = $ticketGatewayGeneratedFields->dateCreated;
$ticket->lastChanged = $ticketGatewayGeneratedFields->dateModified;
$ticket->archived = false;
$ticket->locked = false;
$ticket->id = $ticketGatewayGeneratedFields->id;
$ticket->openedBy = 0;
$ticket->numberOfReplies = 0;
$ticket->numberOfStaffReplies = 0;
$ticket->timeWorked = '00:00:00';
$ticket->lastReplier = 0;
$addressees = new Addressees();
$addressees->to = $this->getAddressees($ticket->email);
if ($ticketRequest->sendEmailToCustomer && $emailVerified) {
$this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_TICKET, $ticketRequest->language, $addressees, $ticket, $heskSettings, $modsForHeskSettings);
} else if ($modsForHeskSettings['customer_email_verification_required'] && !$emailVerified) {
$this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::VERIFY_EMAIL, $ticketRequest->language, $addressees, $ticket, $heskSettings, $modsForHeskSettings);
}
if ($ticket->ownerId !== null) {
$owner = $this->userGateway->getUserById($ticket->ownerId, $heskSettings);
if ($owner->notificationSettings->newAssignedToMe) {
$addressees = new Addressees();
$addressees->to = array($owner->email);
$this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::TICKET_ASSIGNED_TO_YOU, $ticketRequest->language, $addressees, $ticket, $heskSettings, $modsForHeskSettings);
}
} else {
// TODO Test
$usersToBeNotified = $this->userGateway->getUsersForNewTicketNotification($heskSettings);
foreach ($usersToBeNotified as $user) {
if ($user->admin || in_array($ticket->categoryId, $user->categories)) {
$this->sendEmailToStaff($user, $ticket, $heskSettings, $modsForHeskSettings);
}
}
}
return new CreatedTicketModel($ticket, $emailVerified);
}
private function getAddressees($emailAddress) {
if ($emailAddress === null) {
return null;
}
$emails = str_replace(';', ',', $emailAddress);
return explode(',', $emails);
}
private function sendEmailToStaff($user, $ticket, $heskSettings, $modsForHeskSettings) {
$addressees = new Addressees();
$addressees->to = array($user->email);
$language = $user->language !== null && trim($user->language) !== ''
? $user->language
: $heskSettings['language'];
$this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_TICKET_STAFF, $language,
$addressees, $ticket, $heskSettings, $modsForHeskSettings);
}
}

@ -0,0 +1,59 @@
<?php
namespace BusinessLogic\Tickets;
use BusinessLogic\Attachments\AttachmentHandler;
use BusinessLogic\Exceptions\AccessViolationException;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Security\UserPrivilege;
use BusinessLogic\Security\UserToTicketChecker;
use DataAccess\Tickets\TicketGateway;
class TicketDeleter {
/* @var $ticketGateway TicketGateway */
private $ticketGateway;
/* @var $userToTicketChecker UserToTicketChecker */
private $userToTicketChecker;
/* @var $attachmentHandler AttachmentHandler */
private $attachmentHandler;
function __construct($ticketGateway, $userToTicketChecker, $attachmentHandler) {
$this->ticketGateway = $ticketGateway;
$this->userToTicketChecker = $userToTicketChecker;
$this->attachmentHandler = $attachmentHandler;
}
function deleteTicket($ticketId, $userContext, $heskSettings) {
$ticket = $this->ticketGateway->getTicketById($ticketId, $heskSettings);
if ($ticket === null) {
throw new ApiFriendlyException("Ticket {$ticketId} not found!", "Ticket Not Found", 404);
}
if (!$this->userToTicketChecker->isTicketAccessibleToUser($userContext, $ticket, $heskSettings,
array(UserPrivilege::CAN_DELETE_TICKETS))) {
throw new AccessViolationException("User does not have access to ticket {$ticketId}");
}
foreach ($ticket->attachments as $attachment) {
$this->attachmentHandler->deleteAttachmentFromTicket($ticketId, $attachment->id, $userContext, $heskSettings);
}
foreach ($ticket->replies as $reply) {
foreach ($reply->attachments as $attachment) {
$this->attachmentHandler->deleteAttachmentFromTicket($ticketId, $attachment->id, $userContext, $heskSettings);
}
}
$this->ticketGateway->deleteReplyDraftsForTicket($ticketId, $heskSettings);
$this->ticketGateway->deleteRepliesForTicket($ticketId, $heskSettings);
$this->ticketGateway->deleteNotesForTicket($ticketId, $heskSettings);
$this->ticketGateway->deleteTicket($ticketId, $heskSettings);
}
}

@ -0,0 +1,137 @@
<?php
namespace BusinessLogic\Tickets;
use BusinessLogic\Exceptions\AccessViolationException;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Exceptions\ValidationException;
use BusinessLogic\Security\UserContext;
use BusinessLogic\Security\UserPrivilege;
use BusinessLogic\Security\UserToTicketChecker;
use BusinessLogic\Tickets\CustomFields\CustomFieldValidator;
use BusinessLogic\ValidationModel;
use BusinessLogic\Validators;
use Core\Constants\CustomField;
use DataAccess\Tickets\TicketGateway;
class TicketEditor {
/* @var $ticketGateway TicketGateway */
private $ticketGateway;
/* @var $userToTicketChecker UserToTicketChecker */
private $userToTicketChecker;
function __construct($ticketGateway, $userToTicketChecker) {
$this->ticketGateway = $ticketGateway;
$this->userToTicketChecker = $userToTicketChecker;
}
/**
* @param $editTicketModel EditTicketModel
* @param $userContext UserContext
* @param $heskSettings array
* @throws ApiFriendlyException When the ticket isn't found for the ID
* @throws \Exception When the user doesn't have access to the ticket
*/
// TODO Unit Tests
function editTicket($editTicketModel, $userContext, $heskSettings) {
$ticket = $this->ticketGateway->getTicketById($editTicketModel->id, $heskSettings);
if ($ticket === null) {
throw new ApiFriendlyException("Ticket with ID {$editTicketModel->id} not found!", "Ticket not found", 404);
}
if (!$this->userToTicketChecker->isTicketAccessibleToUser($userContext, $ticket, $heskSettings, array(UserPrivilege::CAN_EDIT_TICKETS))) {
throw new AccessViolationException("User does not have access to ticket {$editTicketModel->id}");
}
$this->validate($editTicketModel, $ticket->categoryId, $heskSettings);
$ticket->name = $editTicketModel->name;
$ticket->email = $editTicketModel->email;
$ticket->subject = $editTicketModel->subject;
$ticket->message = $editTicketModel->message;
$ticket->customFields = $editTicketModel->customFields;
$this->ticketGateway->updateBasicTicketInfo($ticket, $heskSettings);
}
/**
* @param $editTicketModel EditTicketModel
* @param $categoryId int
* @param $heskSettings array
* @throws ValidationException When validation fails
*/
private function validate($editTicketModel, $categoryId, $heskSettings) {
$validationModel = new ValidationModel();
if ($editTicketModel->name === null || trim($editTicketModel->name) === '') {
$validationModel->errorKeys[] = 'NO_NAME';
}
if (!Validators::validateEmail($editTicketModel->email, $heskSettings['multi_eml'], false)) {
$validationModel->errorKeys[] = 'INVALID_OR_MISSING_EMAIL';
}
if ($heskSettings['require_subject'] === 1 &&
($editTicketModel->subject === NULL || $editTicketModel->subject === '')) {
$validationModel->errorKeys[] = 'SUBJECT_REQUIRED';
}
if ($heskSettings['require_message'] === 1 &&
($editTicketModel->message === NULL || $editTicketModel->message === '')) {
$validationModel->errorKeys[] = 'MESSAGE_REQUIRED';
}
foreach ($heskSettings['custom_fields'] as $key => $value) {
$customFieldNumber = intval(str_replace('custom', '', $key));
//TODO test this
if ($editTicketModel->customFields === null || !array_key_exists($customFieldNumber, $editTicketModel->customFields)) {
continue;
}
if ($value['use'] == 1 && CustomFieldValidator::isCustomFieldInCategory($customFieldNumber, intval($categoryId), false, $heskSettings)) {
$custom_field_value = $editTicketModel->customFields[$customFieldNumber];
if (empty($custom_field_value)) {
$validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::NO_VALUE";
continue;
}
switch($value['type']) {
case CustomField::DATE:
if (!preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $custom_field_value)) {
$validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::INVALID_DATE';
} else {
// Actually validate based on range
$date = strtotime($custom_field_value . ' t00:00:00');
$dmin = strlen($value['value']['dmin']) ? strtotime($value['value']['dmin'] . ' t00:00:00') : false;
$dmax = strlen($value['value']['dmax']) ? strtotime($value['value']['dmax'] . ' t00:00:00') : false;
if ($dmin && $dmin > $date) {
$validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::DATE_BEFORE_MIN::MIN:' . date('Y-m-d', $dmin) . '::ENTERED:' . date('Y-m-d', $date);
} elseif ($dmax && $dmax < $date) {
$validationModel->errorKeys[] = 'CUSTOM_FIELD_' . $customFieldNumber . '_INVALID::DATE_AFTER_MAX::MAX:' . date('Y-m-d', $dmax) . '::ENTERED:' . date('Y-m-d', $date);
}
}
break;
case CustomField::EMAIL:
if (!Validators::validateEmail($custom_field_value, $value['value']['multiple'], false)) {
$validationModel->errorKeys[] = "CUSTOM_FIELD_{$customFieldNumber}_INVALID::INVALID_EMAIL";
}
break;
}
}
}
if ($editTicketModel->language === null ||
$editTicketModel->language === '') {
$validationModel->errorKeys[] = 'MISSING_LANGUAGE';
}
if (count($validationModel->errorKeys) > 0) {
throw new ValidationException($validationModel);
}
}
}

@ -0,0 +1,10 @@
<?php
namespace BusinessLogic\Tickets;
class TicketGatewayGeneratedFields {
public $id;
public $dateCreated;
public $dateModified;
}

@ -0,0 +1,77 @@
<?php
namespace BusinessLogic\Tickets;
use BusinessLogic\Exceptions\AccessViolationException;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Exceptions\ValidationException;
use BusinessLogic\Security\UserToTicketChecker;
use BusinessLogic\ValidationModel;
use DataAccess\Tickets\TicketGateway;
class TicketRetriever {
/**
* @var $ticketGateway TicketGateway
*/
private $ticketGateway;
/* @var $userToTicketChecker UserToTicketChecker */
private $userToTicketChecker;
function __construct($ticketGateway, $userToTicketChecker) {
$this->ticketGateway = $ticketGateway;
$this->userToTicketChecker = $userToTicketChecker;
}
//TODO Properly test
function getTicketById($id, $heskSettings, $userContext) {
$ticket = $this->ticketGateway->getTicketById($id, $heskSettings);
if ($ticket === null) {
throw new ApiFriendlyException("Ticket {$id} not found!", "Ticket Not Found", 404);
}
if (!$this->userToTicketChecker->isTicketAccessibleToUser($userContext, $ticket, $heskSettings)) {
throw new AccessViolationException("User does not have access to ticket {$id}!");
}
return $ticket;
}
function getTicketByTrackingIdAndEmail($trackingId, $emailAddress, $heskSettings) {
$this->validate($trackingId, $emailAddress, $heskSettings);
$ticket = $this->ticketGateway->getTicketByTrackingId($trackingId, $heskSettings);
if ($ticket === null) {
$ticket = $this->ticketGateway->getTicketByMergedTrackingId($trackingId, $heskSettings);
if ($ticket === null) {
return null;
}
}
if ($heskSettings['email_view_ticket'] && !in_array($emailAddress, $ticket->email)) {
throw new ApiFriendlyException("Email '{$emailAddress}' entered in for ticket '{$trackingId}' does not match!",
"Email Does Not Match", 400);
}
return $ticket;
}
private function validate($trackingId, $emailAddress, $heskSettings) {
$validationModel = new ValidationModel();
if ($trackingId === null || trim($trackingId) === '') {
$validationModel->errorKeys[] = 'MISSING_TRACKING_ID';
}
if ($heskSettings['email_view_ticket'] && ($emailAddress === null || trim($emailAddress) === '')) {
$validationModel->errorKeys[] = 'EMAIL_REQUIRED_AND_MISSING';
}
if (count($validationModel->errorKeys) > 0) {
throw new ValidationException($validationModel);
}
}
}

@ -0,0 +1,30 @@
<?php
namespace BusinessLogic\Tickets;
use DataAccess\Tickets\TicketGateway;
class TicketValidators {
/**
* @var $ticketGateway TicketGateway
*/
private $ticketGateway;
function __construct($ticketGateway) {
$this->ticketGateway = $ticketGateway;
}
/**
* @param $customerEmail string The email address
* @param $heskSettings array HESK Settings
* @return bool true if the user is maxed out on open tickets, false otherwise
*/
function isCustomerAtMaxTickets($customerEmail, $heskSettings) {
if ($heskSettings['max_open'] === 0) {
return false;
}
return count($this->ticketGateway->getTicketsByEmail($customerEmail, $heskSettings)) >= $heskSettings['max_open'];
}
}

@ -0,0 +1,137 @@
<?php
namespace BusinessLogic\Tickets;
use BusinessLogic\Tickets\Exceptions\UnableToGenerateTrackingIdException;
use DataAccess\Tickets\TicketGateway;
class TrackingIdGenerator {
/**
* @var $ticketGateway TicketGateway
*/
private $ticketGateway;
function __construct($ticketGateway) {
$this->ticketGateway = $ticketGateway;
}
/**
* @param $heskSettings array
* @return string
* @throws UnableToGenerateTrackingIdException
*/
function generateTrackingId($heskSettings) {
$acceptableCharacters = 'AEUYBDGHJLMNPQRSTVWXZ123456789';
/* Generate raw ID */
$trackingId = '';
/* Let's avoid duplicate ticket ID's, try up to 3 times */
for ($i = 1; $i <= 3; $i++) {
for ($i = 0; $i < 10; $i++) {
$trackingId .= $acceptableCharacters[mt_rand(0, 29)];
}
$trackingId = $this->formatTrackingId($trackingId);
/* Check for duplicate IDs */
$goodId = !$this->ticketGateway->doesTicketExist($trackingId, $heskSettings);
if ($goodId) {
return $trackingId;
}
/* A duplicate ID has been found! Let's try again (up to 2 more) */
$trackingId = '';
}
/* No valid tracking ID, try one more time with microtime() */
$trackingId = $acceptableCharacters[mt_rand(0, 29)];
$trackingId .= $acceptableCharacters[mt_rand(0, 29)];
$trackingId .= $acceptableCharacters[mt_rand(0, 29)];
$trackingId .= $acceptableCharacters[mt_rand(0, 29)];
$trackingId .= $acceptableCharacters[mt_rand(0, 29)];
$trackingId .= substr(microtime(), -5);
/* Format the ID to the correct shape and check wording */
$trackingId = $this->formatTrackingId($trackingId);
$goodId = !$this->ticketGateway->doesTicketExist($trackingId, $heskSettings);
if ($goodId) {
return $trackingId;
}
throw new UnableToGenerateTrackingIdException();
}
/**
* @param $id string
* @return string
*/
private function formatTrackingId($id) {
$acceptableCharacters = 'AEUYBDGHJLMNPQRSTVWXZ123456789';
$replace = $acceptableCharacters[mt_rand(0, 29)];
$replace .= mt_rand(1, 9);
$replace .= $acceptableCharacters[mt_rand(0, 29)];
/*
Remove 3 letter bad words from ID
Possiblitiy: 1:27,000
*/
$remove = array(
'ASS',
'CUM',
'FAG',
'FUK',
'GAY',
'SEX',
'TIT',
'XXX',
);
$id = str_replace($remove, $replace, $id);
/*
Remove 4 letter bad words from ID
Possiblitiy: 1:810,000
*/
$remove = array(
'ANAL',
'ANUS',
'BUTT',
'CAWK',
'CLIT',
'COCK',
'CRAP',
'CUNT',
'DICK',
'DYKE',
'FART',
'FUCK',
'JAPS',
'JERK',
'JIZZ',
'KNOB',
'PISS',
'POOP',
'SHIT',
'SLUT',
'SUCK',
'TURD',
// Also, remove words that are known to trigger mod_security
'WGET',
);
$replace .= mt_rand(1, 9);
$id = str_replace($remove, $replace, $id);
/* Format the ID string into XXX-XXX-XXXX format for easier readability */
$id = $id[0] . $id[1] . $id[2] . '-' . $id[3] . $id[4] . $id[5] . '-' . $id[6] . $id[7] . $id[8] . $id[9];
return $id;
}
}

@ -0,0 +1,27 @@
<?php
/**
* Created by PhpStorm.
* User: cokoch
* Date: 2/20/2017
* Time: 12:40 PM
*/
namespace BusinessLogic\Tickets;
use DataAccess\Tickets\VerifiedEmailGateway;
class VerifiedEmailChecker {
/**
* @var $verifiedEmailGateway VerifiedEmailGateway
*/
private $verifiedEmailGateway;
function __construct($verifiedEmailGateway) {
$this->verifiedEmailGateway = $verifiedEmailGateway;
}
function isEmailVerified($emailAddress, $heskSettings) {
return $this->verifiedEmailGateway->isEmailVerified($emailAddress, $heskSettings);
}
}

@ -0,0 +1,14 @@
<?php
namespace BusinessLogic;
class ValidationModel {
/**
* @var array
*/
public $errorKeys;
function __construct() {
$this->errorKeys = [];
}
}

@ -0,0 +1,123 @@
<?php
namespace BusinessLogic;
class Validators {
/**
* @param string $address - the email address
* @param array $multiple_emails - true if HESK (or custom field) supports multiple emails
* @param bool $return_emails (def. true): return the email address(es). Otherwise a boolean is returned
*
* @return mixed|null|string - array if multiple valid emails, null if no email and not required, string if valid email
*/
static function validateEmail($address, $multiple_emails, $return_emails = true) {
/* Allow multiple emails to be used? */
if ($multiple_emails) {
/* Make sure the format is correct */
$address = preg_replace('/\s/', '', $address);
$address = str_replace(';', ',', $address);
/* Check if addresses are valid */
$all = explode(',', $address);
foreach ($all as $k => $v) {
if (!self::isValidEmail($v)) {
unset($all[$k]);
}
}
/* If at least one is found return the value */
if (count($all)) {
if ($return_emails) {
return implode(',', $all);
}
return true;
} elseif (!$return_emails) {
return false;
}
} else {
/* Make sure people don't try to enter multiple addresses */
$address = str_replace(strstr($address, ','), '', $address);
$address = str_replace(strstr($address, ';'), '', $address);
$address = trim($address);
/* Valid address? */
if (self::isValidEmail($address)) {
if ($return_emails) {
return $address;
}
return true;
} else {
return false;
}
}
//-- We shouldn't get here
return false;
} // END hesk_validateEmail()
/**
* @param $email
* @return bool
*/
static function isValidEmail($email) {
/* Check for header injection attempts */
if (preg_match("/\r|\n|%0a|%0d/i", $email)) {
return false;
}
/* Does it contain an @? */
$atIndex = strrpos($email, "@");
if ($atIndex === false) {
return false;
}
/* Get local and domain parts */
$domain = substr($email, $atIndex + 1);
$local = substr($email, 0, $atIndex);
$localLen = strlen($local);
$domainLen = strlen($domain);
/* Check local part length */
if ($localLen < 1 || $localLen > 64) {
return false;
}
/* Check domain part length */
if ($domainLen < 1 || $domainLen > 254) {
return false;
}
/* Local part mustn't start or end with a dot */
if ($local[0] == '.' || $local[$localLen - 1] == '.') {
return false;
}
/* Local part mustn't have two consecutive dots*/
if (strpos($local, '..') !== false) {
return false;
}
/* Check domain part characters */
if (!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)) {
return false;
}
/* Domain part mustn't have two consecutive dots */
if (strpos($domain, '..') !== false) {
return false;
}
/* Character not valid in local part unless local part is quoted */
if (!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\", "", $local))) /* " */ {
if (!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\", "", $local))) /* " */ {
return false;
}
}
/* All tests passed, email seems to be OK */
return true;
} // END hesk_isValidEmail()
}

@ -0,0 +1,70 @@
<?php
namespace Controllers\Attachments;
use BusinessLogic\Attachments\AttachmentHandler;
use BusinessLogic\Attachments\AttachmentRetriever;
use BusinessLogic\Attachments\CreateAttachmentForTicketModel;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Helpers;
use BusinessLogic\Security\UserToTicketChecker;
use Controllers\JsonRetriever;
class StaffTicketAttachmentsController {
function get($ticketId, $attachmentId) {
global $hesk_settings, $applicationContext, $userContext;
$this->verifyAttachmentsAreEnabled($hesk_settings);
/* @var $attachmentRetriever AttachmentRetriever */
$attachmentRetriever = $applicationContext->get[AttachmentRetriever::class];
$contents = $attachmentRetriever->getAttachmentContentsForTicket($ticketId, $attachmentId, $userContext, $hesk_settings);
output(array('contents' => $contents));
}
private function verifyAttachmentsAreEnabled($heskSettings) {
if (!$heskSettings['attachments']['use']) {
throw new ApiFriendlyException('Attachments are disabled on this server', 'Attachments Disabled', 404);
}
}
function post($ticketId) {
global $hesk_settings, $applicationContext, $userContext;
$this->verifyAttachmentsAreEnabled($hesk_settings);
/* @var $attachmentHandler AttachmentHandler */
$attachmentHandler = $applicationContext->get[AttachmentHandler::class];
$createAttachmentForTicketModel = $this->createModel(JsonRetriever::getJsonData(), $ticketId);
$createdAttachment = $attachmentHandler->createAttachmentForTicket(
$createAttachmentForTicketModel, $userContext, $hesk_settings);
return output($createdAttachment, 201);
}
private function createModel($json, $ticketId) {
$model = new CreateAttachmentForTicketModel();
$model->attachmentContents = Helpers::safeArrayGet($json, 'data');
$model->displayName = Helpers::safeArrayGet($json, 'displayName');
$model->isEditing = Helpers::safeArrayGet($json, 'isEditing');
$model->ticketId = $ticketId;
return $model;
}
function delete($ticketId, $attachmentId) {
global $applicationContext, $hesk_settings, $userContext;
/* @var $attachmentHandler AttachmentHandler */
$attachmentHandler = $applicationContext->get[AttachmentHandler::class];
$attachmentHandler->deleteAttachmentFromTicket($ticketId, $attachmentId, $userContext, $hesk_settings);
return http_response_code(204);
}
}

@ -0,0 +1,31 @@
<?php
namespace Controllers\Categories;
use BusinessLogic\Categories\CategoryRetriever;
use BusinessLogic\Exceptions\ApiFriendlyException;
class CategoryController {
function get($id) {
$categories = self::getAllCategories();
if (!isset($categories[$id])) {
throw new ApiFriendlyException("Category {$id} not found!", "Category Not Found", 404);
}
output($categories[$id]);
}
static function printAllCategories() {
output(self::getAllCategories());
}
private static function getAllCategories() {
global $hesk_settings, $applicationContext, $userContext;
/* @var $categoryRetriever CategoryRetriever */
$categoryRetriever = $applicationContext->get[CategoryRetriever::class];
return $categoryRetriever->getAllCategories($hesk_settings, $userContext);
}
}

@ -0,0 +1,23 @@
<?php
namespace Controllers;
use BusinessLogic\Exceptions\InternalUseOnlyException;
use BusinessLogic\Helpers;
abstract class InternalApiController {
static function staticCheckForInternalUseOnly() {
$tokenHeader = Helpers::getHeader('X-AUTH-TOKEN');
if ($tokenHeader !== null && trim($tokenHeader) !== '') {
throw new InternalUseOnlyException();
}
}
function checkForInternalUseOnly() {
$tokenHeader = Helpers::getHeader('X-AUTH-TOKEN');
if ($tokenHeader !== null && trim($tokenHeader) !== '') {
throw new InternalUseOnlyException();
}
}
}

@ -0,0 +1,16 @@
<?php
namespace Controllers;
class JsonRetriever {
/**
* Support POST, PUT, and PATCH request (and possibly more)
*
* @return mixed
*/
static function getJsonData() {
$json = file_get_contents('php://input');
return json_decode($json, true);
}
}

@ -0,0 +1,101 @@
<?php
namespace Controllers\Navigation;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Helpers;
use BusinessLogic\Navigation\CustomNavElement;
use BusinessLogic\Navigation\CustomNavElementHandler;
use Controllers\InternalApiController;
use Controllers\JsonRetriever;
class CustomNavElementController extends InternalApiController {
static function getAll() {
global $applicationContext, $hesk_settings;
self::staticCheckForInternalUseOnly();
/* @var $handler CustomNavElementHandler */
$handler = $applicationContext->get[CustomNavElementHandler::class];
output($handler->getAllCustomNavElements($hesk_settings));
}
static function sort($id, $direction) {
global $applicationContext, $hesk_settings;
self::staticCheckForInternalUseOnly();
/* @var $handler CustomNavElementHandler */
$handler = $applicationContext->get[CustomNavElementHandler::class];
$handler->sortCustomNavElement(intval($id), $direction, $hesk_settings);
}
function get($id) {
global $applicationContext, $hesk_settings;
$this->checkForInternalUseOnly();
/* @var $handler CustomNavElementHandler */
$handler = $applicationContext->get[CustomNavElementHandler::class];
output($handler->getCustomNavElement($id, $hesk_settings));
}
function post() {
global $applicationContext, $hesk_settings;
$this->checkForInternalUseOnly();
/* @var $handler CustomNavElementHandler */
$handler = $applicationContext->get[CustomNavElementHandler::class];
$data = JsonRetriever::getJsonData();
$element = $handler->createCustomNavElement($this->buildElementModel($data), $hesk_settings);
return output($element, 201);
}
function put($id) {
global $applicationContext, $hesk_settings;
$this->checkForInternalUseOnly();
/* @var $handler CustomNavElementHandler */
$handler = $applicationContext->get[CustomNavElementHandler::class];
$data = JsonRetriever::getJsonData();
$handler->saveCustomNavElement($this->buildElementModel($data, $id), $hesk_settings);
return http_response_code(204);
}
function delete($id) {
global $applicationContext, $hesk_settings;
$this->checkForInternalUseOnly();
/* @var $handler CustomNavElementHandler */
$handler = $applicationContext->get[CustomNavElementHandler::class];
$handler->deleteCustomNavElement($id, $hesk_settings);
return http_response_code(204);
}
private function buildElementModel($data, $id = null) {
$element = new CustomNavElement();
$element->id = $id;
$element->place = intval(Helpers::safeArrayGet($data, 'place'));
$element->fontIcon = Helpers::safeArrayGet($data, 'fontIcon');
$element->imageUrl = Helpers::safeArrayGet($data, 'imageUrl');
$element->text = Helpers::safeArrayGet($data, 'text');
$element->subtext = Helpers::safeArrayGet($data, 'subtext');
$element->url = Helpers::safeArrayGet($data, 'url');
$element->sort = intval(Helpers::safeArrayGet($data, 'sort'));
return $element;
}
}

@ -0,0 +1,17 @@
<?php
namespace Controllers\Settings;
use BusinessLogic\Settings\SettingsRetriever;
class SettingsController {
function get() {
global $applicationContext, $hesk_settings, $modsForHesk_settings;
/* @var $settingsRetriever SettingsRetriever */
$settingsRetriever = $applicationContext->get[SettingsRetriever::class];
output($settingsRetriever->getAllSettings($hesk_settings, $modsForHesk_settings));
}
}

@ -0,0 +1,17 @@
<?php
namespace Controllers\Statuses;
use BusinessLogic\Statuses\StatusRetriever;
class StatusController {
function get() {
global $applicationContext, $hesk_settings;
/* @var $statusRetriever StatusRetriever */
$statusRetriever = $applicationContext->get[StatusRetriever::class];
output($statusRetriever->getAllStatuses($hesk_settings));
}
}

@ -0,0 +1,71 @@
<?php
namespace Controllers\Tickets;
use BusinessLogic\Helpers;
use BusinessLogic\Tickets\CreateTicketByCustomerModel;
use BusinessLogic\Tickets\TicketCreator;
use BusinessLogic\Tickets\TicketRetriever;
use BusinessLogic\ValidationModel;
use Controllers\JsonRetriever;
class CustomerTicketController {
function get() {
global $applicationContext, $hesk_settings;
$trackingId = isset($_GET['trackingId']) ? $_GET['trackingId'] : null;
$emailAddress = isset($_GET['email']) ? $_GET['email'] : null;
/* @var $ticketRetriever TicketRetriever */
$ticketRetriever = $applicationContext->get[TicketRetriever::class];
output($ticketRetriever->getTicketByTrackingIdAndEmail($trackingId, $emailAddress, $hesk_settings));
}
function post() {
global $applicationContext, $hesk_settings, $userContext;
/* @var $ticketCreator TicketCreator */
$ticketCreator = $applicationContext->get[TicketCreator::class];
$jsonRequest = JsonRetriever::getJsonData();
$ticket = $ticketCreator->createTicketByCustomer($this->buildTicketRequestFromJson($jsonRequest), $hesk_settings, $userContext);
return output($ticket->ticket, $ticket->emailVerified ? 201 : 202);
}
/**
* @param $json array
* @return CreateTicketByCustomerModel
*/
private function buildTicketRequestFromJson($json) {
$ticketRequest = new CreateTicketByCustomerModel();
$ticketRequest->name = Helpers::safeArrayGet($json, 'name');
$ticketRequest->email = Helpers::safeArrayGet($json, 'email');
$ticketRequest->category = Helpers::safeArrayGet($json, 'category');
$ticketRequest->priority = Helpers::safeArrayGet($json, 'priority');
$ticketRequest->subject = Helpers::safeArrayGet($json, 'subject');
$ticketRequest->message = Helpers::safeArrayGet($json, 'message');
$ticketRequest->html = Helpers::safeArrayGet($json, 'html');
$ticketRequest->location = Helpers::safeArrayGet($json, 'location');
$ticketRequest->suggestedKnowledgebaseArticleIds = Helpers::safeArrayGet($json, 'suggestedArticles');
$ticketRequest->userAgent = Helpers::safeArrayGet($json, 'userAgent');
$ticketRequest->screenResolution = Helpers::safeArrayGet($json, 'screenResolution');
$ticketRequest->ipAddress = Helpers::safeArrayGet($json, 'ip');
$ticketRequest->language = Helpers::safeArrayGet($json, 'language');
$ticketRequest->sendEmailToCustomer = true;
$ticketRequest->customFields = array();
$jsonCustomFields = Helpers::safeArrayGet($json, 'customFields');
if ($jsonCustomFields !== null && !empty($jsonCustomFields)) {
foreach ($jsonCustomFields as $key => $value) {
$ticketRequest->customFields[intval($key)] = $value;
}
}
return $ticketRequest;
}
}

@ -0,0 +1,72 @@
<?php
namespace Controllers\Tickets;
use BusinessLogic\Emails\Addressees;
use BusinessLogic\Emails\EmailSenderHelper;
use BusinessLogic\Emails\EmailTemplate;
use BusinessLogic\Emails\EmailTemplateRetriever;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Tickets\TicketRetriever;
use Controllers\InternalApiController;
use DataAccess\Settings\ModsForHeskSettingsGateway;
class ResendTicketEmailToCustomerController extends InternalApiController {
function get($ticketId) {
global $applicationContext, $userContext, $hesk_settings;
$this->checkForInternalUseOnly();
/* @var $ticketRetriever TicketRetriever */
$ticketRetriever = $applicationContext->get[TicketRetriever::class];
$ticket = $ticketRetriever->getTicketById($ticketId, $hesk_settings, $userContext);
/* @var $modsForHeskSettingsGateway ModsForHeskSettingsGateway */
$modsForHeskSettingsGateway = $applicationContext->get[ModsForHeskSettingsGateway::class];
$modsForHeskSettings = $modsForHeskSettingsGateway->getAllSettings($hesk_settings);
/* @var $emailSender EmailSenderHelper */
$emailSender = $applicationContext->get[EmailSenderHelper::class];
$language = $ticket->language;
if ($language === null) {
$language = $hesk_settings['language'];
}
if ($ticket === null) {
throw new ApiFriendlyException("Ticket {$ticketId} not found!", "Ticket Not Found", 404);
}
$reply = null;
$emailTemplate = EmailTemplateRetriever::NEW_TICKET;
if (isset($_GET['replyId'])) {
$replyId = $_GET['replyId'];
$emailTemplate = EmailTemplateRetriever::NEW_REPLY_BY_STAFF;
foreach ($ticket->replies as $ticketReply) {
if ($ticketReply->id === $replyId) {
$reply = $ticketReply;
break;
}
}
if ($reply === null) {
throw new ApiFriendlyException("Reply {$replyId} not found on ticket {$ticketId}!", "Reply Not Found", 404);
}
// Copy over reply properties onto the Ticket
$ticket->lastReplier = $reply->replierName;
$ticket->message = $reply->message;
$ticket->attachments = $reply->attachments;
}
$addressees = new Addressees();
$addressees->to = $ticket->email;
$emailSender->sendEmailForTicket($emailTemplate, $language, $addressees, $ticket, $hesk_settings, $modsForHeskSettings);
http_response_code(204);
}
}

@ -0,0 +1,68 @@
<?php
namespace Controllers\Tickets;
use BusinessLogic\Helpers;
use BusinessLogic\Tickets\EditTicketModel;
use BusinessLogic\Tickets\TicketDeleter;
use BusinessLogic\Tickets\TicketEditor;
use BusinessLogic\Tickets\TicketRetriever;
use Controllers\JsonRetriever;
class StaffTicketController {
function get($id) {
global $applicationContext, $userContext, $hesk_settings;
/* @var $ticketRetriever TicketRetriever */
$ticketRetriever = $applicationContext->get[TicketRetriever::class];
output($ticketRetriever->getTicketById($id, $hesk_settings, $userContext));
}
function delete($id) {
global $applicationContext, $userContext, $hesk_settings;
/* @var $ticketDeleter TicketDeleter */
$ticketDeleter = $applicationContext->get[TicketDeleter::class];
$ticketDeleter->deleteTicket($id, $userContext, $hesk_settings);
http_response_code(204);
}
function put($id) {
global $applicationContext, $userContext, $hesk_settings;
/* @var $ticketEditor TicketEditor */
$ticketEditor = $applicationContext->get[TicketEditor::class];
$jsonRequest = JsonRetriever::getJsonData();
$ticketEditor->editTicket($this->getEditTicketModel($id, $jsonRequest), $userContext, $hesk_settings);
http_response_code(204);
return;
}
private function getEditTicketModel($id, $jsonRequest) {
$editTicketModel = new EditTicketModel();
$editTicketModel->id = $id;
$editTicketModel->language = Helpers::safeArrayGet($jsonRequest, 'language');
$editTicketModel->name = Helpers::safeArrayGet($jsonRequest, 'name');
$editTicketModel->subject = Helpers::safeArrayGet($jsonRequest, 'subject');
$editTicketModel->message = Helpers::safeArrayGet($jsonRequest, 'message');
$editTicketModel->html = Helpers::safeArrayGet($jsonRequest, 'html');
$editTicketModel->email = Helpers::safeArrayGet($jsonRequest, 'email');
$jsonCustomFields = Helpers::safeArrayGet($jsonRequest, 'customFields');
if ($jsonCustomFields !== null && !empty($jsonCustomFields)) {
foreach ($jsonCustomFields as $key => $value) {
$editTicketModel->customFields[intval($key)] = $value;
}
}
return $editTicketModel;
}
}

@ -0,0 +1,16 @@
<?php
namespace Core\Constants;
class CustomField {
const RADIO = 'radio';
const SELECT = 'select';
const CHECKBOX = 'checkbox';
const TEXTAREA = 'textarea';
const DATE = 'date';
const EMAIL = 'email';
const HIDDEN = 'hidden';
const READONLY = 'readonly';
const TEXT = 'text';
}

@ -0,0 +1,11 @@
<?php
namespace Core\Constants;
class Priority {
const CRITICAL = 0;
const HIGH = 1;
const MEDIUM = 2;
const LOW = 3;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save