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 !62remotes/remote_mirror_8/master
commit
535e48225d
@ -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>
|
||||||
|
<?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">×</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');
|
@ -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…
Reference in New Issue