commit
e21c26689a
@ -0,0 +1,133 @@
|
||||
<?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\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\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;
|
||||
|
||||
function __construct() {
|
||||
$this->get = array();
|
||||
|
||||
// Settings
|
||||
$this->get[ModsForHeskSettingsGateway::class] = new ModsForHeskSettingsGateway();
|
||||
|
||||
// API Checker
|
||||
$this->get[ApiChecker::class] = new ApiChecker($this->get[ModsForHeskSettingsGateway::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,53 @@
|
||||
<?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? The color of the Categories
|
||||
*/
|
||||
public $color;
|
||||
|
||||
/**
|
||||
* @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,330 @@
|
||||
<?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($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%%', $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 = nl2br($ticket->message);
|
||||
$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,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,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,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,106 @@
|
||||
<?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 = $dataRow['id'];
|
||||
$userContext->username = $dataRow['user'];
|
||||
$userContext->admin = $dataRow['isadmin'] === '1';
|
||||
$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,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,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,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,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;
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SQLException extends Exception {
|
||||
/**
|
||||
* @var $failingQuery string
|
||||
*/
|
||||
public $failingQuery;
|
||||
|
||||
function __construct($failingQuery) {
|
||||
$this->failingQuery = $failingQuery;
|
||||
|
||||
parent::__construct('A SQL exception occurred. Check the logs for more information.');
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
function print_error($title, $message, $response_code = 500) {
|
||||
require_once(__DIR__ . '/output.php');
|
||||
|
||||
$error = array();
|
||||
$error['type'] = 'ERROR';
|
||||
$error['title'] = $title;
|
||||
$error['message'] = $message;
|
||||
|
||||
print output($error, $response_code);
|
||||
return;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
function output($data, $status_code = 200) {
|
||||
http_response_code($status_code);
|
||||
header('Content-Type: application/json');
|
||||
print json_encode($data);
|
||||
return http_response_code($status_code);
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Attachments;
|
||||
|
||||
|
||||
use BusinessLogic\Attachments\Attachment;
|
||||
use BusinessLogic\Attachments\TicketAttachment;
|
||||
use DataAccess\CommonDao;
|
||||
|
||||
class AttachmentGateway extends CommonDao {
|
||||
|
||||
/**
|
||||
* @param $attachment TicketAttachment
|
||||
* @param $heskSettings array
|
||||
* @return int The inserted attachment ID
|
||||
*/
|
||||
function createAttachmentForTicket($attachment, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "attachments`
|
||||
(`ticket_id`, `note_id`, `saved_name`, `real_name`, `size`, `type`, `download_count`)
|
||||
VALUES ('" . hesk_dbEscape($attachment->ticketTrackingId) . "', NULL, '" . hesk_dbEscape($attachment->savedName) . "',
|
||||
'" . hesk_dbEscape($attachment->displayName) . "', " . intval($attachment->fileSize) . ", '" . intval($attachment->type) . "', 0)");
|
||||
|
||||
$attachmentId = hesk_dbInsertID();
|
||||
|
||||
$this->close();
|
||||
|
||||
return $attachmentId;
|
||||
}
|
||||
|
||||
function getAttachmentById($id, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT *
|
||||
FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "attachments`
|
||||
WHERE `att_id` = " . intval($id));
|
||||
|
||||
if (hesk_dbNumRows($rs) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = hesk_dbFetchAssoc($rs);
|
||||
|
||||
$attachment = new Attachment();
|
||||
$attachment->id = $row['att_id'];
|
||||
$attachment->savedName = $row['saved_name'];
|
||||
$attachment->displayName = $row['real_name'];
|
||||
$attachment->downloadCount = $row['download_count'];
|
||||
$attachment->fileSize = $row['size'];
|
||||
|
||||
$this->close();
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
function deleteAttachment($attachmentId, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "attachments`
|
||||
WHERE `att_id` = " . intval($attachmentId));
|
||||
|
||||
$this->close();
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Categories;
|
||||
|
||||
use BusinessLogic\Categories\Category;
|
||||
use DataAccess\CommonDao;
|
||||
use Exception;
|
||||
|
||||
class CategoryGateway extends CommonDao {
|
||||
/**
|
||||
* @param $hesk_settings
|
||||
* @return Category[]
|
||||
*/
|
||||
function getAllCategories($hesk_settings) {
|
||||
$this->init();
|
||||
|
||||
$sql = 'SELECT * FROM `' . hesk_dbEscape($hesk_settings['db_pfix']) . 'categories`';
|
||||
|
||||
$response = hesk_dbQuery($sql);
|
||||
|
||||
$results = array();
|
||||
while ($row = hesk_dbFetchAssoc($response)) {
|
||||
$category = new Category();
|
||||
|
||||
$category->id = intval($row['id']);
|
||||
$category->name = $row['name'];
|
||||
$category->catOrder = intval($row['cat_order']);
|
||||
$category->autoAssign = $row['autoassign'] == 1;
|
||||
$category->type = intval($row['type']);
|
||||
$category->usage = intval($row['usage']);
|
||||
$category->color = $row['color'];
|
||||
$category->priority = intval($row['priority']);
|
||||
$category->manager = intval($row['manager']) == 0 ? NULL : intval($row['manager']);
|
||||
$results[$category->id] = $category;
|
||||
}
|
||||
|
||||
$this->close();
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess;
|
||||
|
||||
|
||||
use Exception;
|
||||
|
||||
class CommonDao {
|
||||
/**
|
||||
* @throws Exception if the database isn't properly configured
|
||||
*/
|
||||
function init() {
|
||||
if (!function_exists('hesk_dbConnect')) {
|
||||
throw new Exception('Database not loaded!');
|
||||
}
|
||||
hesk_dbConnect();
|
||||
}
|
||||
|
||||
function close() {
|
||||
hesk_dbClose();
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Files;
|
||||
|
||||
|
||||
use BusinessLogic\Exceptions\ApiFriendlyException;
|
||||
|
||||
class FileDeleter {
|
||||
function deleteFile($name, $folder) {
|
||||
$path = __DIR__ . "/../../../{$folder}/{$name}";
|
||||
if (!file_exists($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@unlink($path);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Files;
|
||||
|
||||
|
||||
use BusinessLogic\Exceptions\ApiFriendlyException;
|
||||
|
||||
class FileReader {
|
||||
/**
|
||||
* @param $name string - The file name (including extension)
|
||||
* @param $folder - The folder name (relative to the ROOT of the helpdesk)
|
||||
* @returns string - The contents of the file to write
|
||||
* @throws \Exception When the file fails to save
|
||||
*/
|
||||
function readFromFile($name, $folder) {
|
||||
// __DIR__ === '/{ROOT}/api/DataAccess/Files
|
||||
$location = __DIR__ . "/../../../{$folder}/{$name}";
|
||||
|
||||
if (!file_exists($location)) {
|
||||
throw new ApiFriendlyException("The file '{$name}' does not exist on the server", "File Not Found", 404);
|
||||
}
|
||||
|
||||
$fileContents = file_get_contents($location);
|
||||
|
||||
if ($fileContents === false) {
|
||||
throw new \Exception("Failed to read the file!");
|
||||
}
|
||||
|
||||
return $fileContents;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Files;
|
||||
|
||||
|
||||
class FileWriter {
|
||||
/**
|
||||
* @param $name string - The file name (including extension)
|
||||
* @param $folder - The folder name (relative to the ROOT of the helpdesk)
|
||||
* @param $contents string - The contents of the file to write
|
||||
* @return int The file size, in bytes
|
||||
* @throws \Exception When the file fails to save
|
||||
*/
|
||||
function writeToFile($name, $folder, $contents) {
|
||||
// __DIR__ === '/{ROOT}/api/DataAccess/Files
|
||||
$location = __DIR__ . "/../../../{$folder}/{$name}";
|
||||
$fileSize = file_put_contents($location, $contents);
|
||||
|
||||
if ($fileSize === false) {
|
||||
throw new \Exception("Failed to save the file!");
|
||||
}
|
||||
|
||||
return $fileSize;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Logging;
|
||||
|
||||
|
||||
use BusinessLogic\Security\UserContext;
|
||||
use DataAccess\CommonDao;
|
||||
|
||||
class LoggingGateway extends CommonDao {
|
||||
function logDebug($location, $message, $stackTrace, $userContext, $heskSettings) {
|
||||
return $this->log(Severity::DEBUG, $location, $message, $stackTrace, $userContext, $heskSettings);
|
||||
}
|
||||
|
||||
function logInfo($location, $message, $stackTrace, $userContext, $heskSettings) {
|
||||
return $this->log(Severity::INFO, $location, $message, $stackTrace, $userContext, $heskSettings);
|
||||
}
|
||||
|
||||
function logWarning($location, $message, $stackTrace, $userContext, $heskSettings) {
|
||||
return $this->log(Severity::WARNING, $location, $message, $stackTrace, $userContext, $heskSettings);
|
||||
}
|
||||
|
||||
function logError($location, $message, $stackTrace, $userContext, $heskSettings) {
|
||||
return $this->log(Severity::ERROR, $location, $message, $stackTrace, $userContext, $heskSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $severity int (from Severity)
|
||||
* @param $location string
|
||||
* @param $message string
|
||||
* @param $userContext UserContext
|
||||
* @param $heskSettings array
|
||||
* @return int|null|string The inserted ID, or null on failure.
|
||||
*/
|
||||
private function log($severity, $location, $message, $stackTrace, $userContext, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "logging` (`username`, `message`, `severity`, `location`, `timestamp`, `stack_trace`)
|
||||
VALUES ('" . hesk_dbEscape($userContext->username) . "',
|
||||
'" . hesk_dbEscape(addslashes($message)) . "',
|
||||
" . intval($severity) . ",
|
||||
'" . hesk_dbEscape(addslashes($location)) . "',
|
||||
NOW(),
|
||||
'" . hesk_dbEscape(addslashes($stackTrace)) . "')");
|
||||
|
||||
$insertedId = hesk_dbInsertID();
|
||||
|
||||
$this->close();
|
||||
|
||||
return $insertedId;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Logging;
|
||||
|
||||
|
||||
class Severity {
|
||||
const DEBUG = 0;
|
||||
const INFO = 1;
|
||||
const WARNING = 2;
|
||||
const ERROR = 3;
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Security;
|
||||
|
||||
|
||||
use BusinessLogic\Security\BannedEmail;
|
||||
use BusinessLogic\Security\BannedIp;
|
||||
use DataAccess\CommonDao;
|
||||
use Exception;
|
||||
|
||||
class BanGateway extends CommonDao {
|
||||
|
||||
/**
|
||||
* @param $heskSettings
|
||||
* @return BannedEmail[]
|
||||
*/
|
||||
function getEmailBans($heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT `bans`.`id` AS `id`, `bans`.`email` AS `email`,
|
||||
`users`.`id` AS `banned_by`, `bans`.`dt` AS `dt`
|
||||
FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "banned_emails` AS `bans`
|
||||
LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` AS `users`
|
||||
ON `bans`.`banned_by` = `users`.`id`
|
||||
AND `users`.`active` = '1'");
|
||||
|
||||
$bannedEmails = array();
|
||||
|
||||
while ($row = hesk_dbFetchAssoc($rs)) {
|
||||
$bannedEmail = new BannedEmail();
|
||||
$bannedEmail->id = intval($row['id']);
|
||||
$bannedEmail->email = $row['email'];
|
||||
$bannedEmail->bannedById = $row['banned_by'] === null ? null : intval($row['banned_by']);
|
||||
$bannedEmail->dateBanned = $row['dt'];
|
||||
|
||||
$bannedEmails[$bannedEmail->id] = $bannedEmail;
|
||||
}
|
||||
|
||||
$this->close();
|
||||
|
||||
return $bannedEmails;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $heskSettings
|
||||
* @return BannedIp[]
|
||||
*/
|
||||
function getIpBans($heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT `bans`.`id` AS `id`, `bans`.`ip_from` AS `ip_from`,
|
||||
`bans`.`ip_to` AS `ip_to`, `bans`.`ip_display` AS `ip_display`,
|
||||
`users`.`id` AS `banned_by`, `bans`.`dt` AS `dt`
|
||||
FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "banned_ips` AS `bans`
|
||||
LEFT JOIN `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` AS `users`
|
||||
ON `bans`.`banned_by` = `users`.`id`
|
||||
AND `users`.`active` = '1'");
|
||||
|
||||
$bannedIps = array();
|
||||
|
||||
while ($row = hesk_dbFetchAssoc($rs)) {
|
||||
$bannedIp = new BannedIp();
|
||||
$bannedIp->id = intval($row['id']);
|
||||
$bannedIp->ipFrom = intval($row['ip_from']);
|
||||
$bannedIp->ipTo = intval($row['ip_to']);
|
||||
$bannedIp->ipDisplay = $row['ip_display'];
|
||||
$bannedIp->bannedById = $row['banned_by'] === null ? null : intval($row['banned_by']);
|
||||
$bannedIp->dateBanned = $row['dt'];
|
||||
|
||||
$bannedIps[$bannedIp->id] = $bannedIp;
|
||||
}
|
||||
|
||||
$this->close();
|
||||
|
||||
return $bannedIps;
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
namespace DataAccess\Security;
|
||||
|
||||
|
||||
use BusinessLogic\Security\UserContext;
|
||||
use BusinessLogic\Security\UserContextBuilder;
|
||||
use DataAccess\CommonDao;
|
||||
use Exception;
|
||||
|
||||
class UserGateway extends CommonDao {
|
||||
/**
|
||||
* @param $hashedToken string The pre-hashed token from Helpers::hashToken
|
||||
* @param $heskSettings
|
||||
* @return array|null User ResultSet if an active user for the token is found, null otherwise
|
||||
*/
|
||||
function getUserForAuthToken($hashedToken, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `id` = (
|
||||
SELECT `user_id`
|
||||
FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "user_api_tokens`
|
||||
WHERE `token` = '" . hesk_dbEscape($hashedToken) . "'
|
||||
) AND `active` = '1'");
|
||||
|
||||
if (hesk_dbNumRows($rs) === 0) {
|
||||
$this->close();
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = hesk_dbFetchAssoc($rs);
|
||||
|
||||
$this->close();
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
function getUserById($id, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `id` = " . intval($id));
|
||||
|
||||
if (hesk_dbNumRows($rs) === 0) {
|
||||
$this->close();
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = UserContext::fromDataRow(hesk_dbFetchAssoc($rs));
|
||||
|
||||
$this->close();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $heskSettings array
|
||||
* @return UserContext[]
|
||||
*/
|
||||
function getUsersByNumberOfOpenTicketsForAutoassign($heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT `t1`.*,
|
||||
(SELECT COUNT(*) FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets`
|
||||
WHERE `owner`=`t1`.`id`
|
||||
AND `status` IN (
|
||||
SELECT `ID` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "statuses`
|
||||
WHERE `IsClosed` = 0
|
||||
)
|
||||
) AS `open_tickets`
|
||||
FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` AS `t1`
|
||||
WHERE `t1`.`autoassign` = '1' ORDER BY `open_tickets` ASC, RAND()");
|
||||
|
||||
$users = array();
|
||||
|
||||
while ($row = hesk_dbFetchAssoc($rs)) {
|
||||
$user = UserContext::fromDataRow($row);
|
||||
$users[] = $user;
|
||||
}
|
||||
|
||||
$this->close();
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $heskSettings array
|
||||
* @return UserContext[]
|
||||
*/
|
||||
function getUsersForNewTicketNotification($heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `notify_new_unassigned` = '1' AND `active` = '1'");
|
||||
|
||||
$users = array();
|
||||
while ($row = hesk_dbFetchAssoc($rs)) {
|
||||
$users[] = UserContext::fromDataRow($row);
|
||||
}
|
||||
|
||||
$this->close();
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
function getManagerForCategory($categoryId, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users`
|
||||
WHERE `id` = (
|
||||
SELECT `manager`
|
||||
FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "categories`
|
||||
WHERE `id` = " . intval($categoryId) . ")");
|
||||
|
||||
if (hesk_dbNumRows($rs) === 0) {
|
||||
$this->close();
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = UserContext::fromDataRow(hesk_dbFetchAssoc($rs));
|
||||
|
||||
$this->close();
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Settings;
|
||||
|
||||
|
||||
use DataAccess\CommonDao;
|
||||
|
||||
class ModsForHeskSettingsGateway extends CommonDao {
|
||||
function getAllSettings($heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT `Key`, `Value` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "settings` WHERE `Key` <> 'modsForHeskVersion'");
|
||||
|
||||
$settings = array();
|
||||
while ($row = hesk_dbFetchAssoc($rs)) {
|
||||
$settings[$row['Key']] = $row['Value'];
|
||||
}
|
||||
|
||||
$this->close();
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Statuses;
|
||||
|
||||
|
||||
use BusinessLogic\Statuses\Status;
|
||||
use DataAccess\CommonDao;
|
||||
|
||||
class StatusGateway extends CommonDao {
|
||||
|
||||
/**
|
||||
* @param $defaultAction string
|
||||
* @return Status
|
||||
*/
|
||||
function getStatusForDefaultAction($defaultAction, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$metaRs = hesk_dbQuery('SELECT * FROM `' . hesk_dbEscape($heskSettings['db_pfix']) . 'statuses`
|
||||
WHERE `' . $defaultAction . '` = 1');
|
||||
if (hesk_dbNumRows($metaRs) === 0) {
|
||||
return null;
|
||||
}
|
||||
$row = hesk_dbFetchAssoc($metaRs);
|
||||
|
||||
$languageRs = hesk_dbQuery('SELECT * FROM `' . hesk_dbEscape($heskSettings['db_pfix']) . 'text_to_status_xref`
|
||||
WHERE `status_id` = ' . intval($row['ID']));
|
||||
|
||||
$status = Status::fromDatabase($row, $languageRs);
|
||||
|
||||
$this->close();
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $heskSettings array
|
||||
* @return Status[]
|
||||
*/
|
||||
function getStatuses($heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$metaRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "statuses`");
|
||||
|
||||
$statuses = array();
|
||||
while ($row = hesk_dbFetchAssoc($metaRs)) {
|
||||
$languageRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "text_to_status_xref`
|
||||
WHERE `status_id` = " . intval($row['ID']));
|
||||
|
||||
$statuses[] = Status::fromDatabase($row, $languageRs);
|
||||
}
|
||||
|
||||
$this->close();
|
||||
|
||||
return $statuses;
|
||||
}
|
||||
}
|
@ -0,0 +1,361 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Tickets;
|
||||
|
||||
|
||||
use BusinessLogic\Attachments\AttachmentType;
|
||||
use BusinessLogic\Tickets\Attachment;
|
||||
use BusinessLogic\Tickets\Ticket;
|
||||
use BusinessLogic\Tickets\TicketGatewayGeneratedFields;
|
||||
use DataAccess\CommonDao;
|
||||
|
||||
class TicketGateway extends CommonDao {
|
||||
/**
|
||||
* @param $id int
|
||||
* @param $heskSettings array
|
||||
* @return Ticket|null
|
||||
*/
|
||||
function getTicketById($id, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `id` = " . intval($id));
|
||||
|
||||
if (hesk_dbNumRows($rs) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = hesk_dbFetchAssoc($rs);
|
||||
$linkedTicketsRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `parent` = " . intval($id));
|
||||
|
||||
$repliesRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "replies` WHERE `replyto` = " . intval($id) . " ORDER BY `id` ASC");
|
||||
|
||||
$ticket = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $repliesRs, $heskSettings);
|
||||
|
||||
$this->close();
|
||||
|
||||
return $ticket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $emailAddress string
|
||||
* @param $heskSettings array
|
||||
* @return array|null
|
||||
*/
|
||||
function getTicketsByEmail($emailAddress, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets`
|
||||
WHERE `email` = '" . hesk_dbEscape($emailAddress) . "'");
|
||||
|
||||
if (hesk_dbNumRows($rs) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tickets = array();
|
||||
|
||||
while ($row = hesk_dbFetchAssoc($rs)) {
|
||||
$linkedTicketsRs =
|
||||
hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `parent` = " . intval($row['id']));
|
||||
$repliesRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "replies` WHERE `replyto` = " . intval($row['id']) . " ORDER BY `id` ASC");
|
||||
|
||||
$tickets[] = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $repliesRs, $heskSettings);
|
||||
}
|
||||
|
||||
$this->close();
|
||||
|
||||
return $tickets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $trackingId string
|
||||
* @param $heskSettings array
|
||||
* @return bool
|
||||
*/
|
||||
function doesTicketExist($trackingId, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT 1 FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets`
|
||||
WHERE `trackid` = '" . hesk_dbEscape($trackingId) . "'");
|
||||
|
||||
$ticketExists = hesk_dbNumRows($rs) > 0;
|
||||
|
||||
$this->close();
|
||||
|
||||
return $ticketExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $trackingId string
|
||||
* @param $heskSettings array
|
||||
* @return Ticket|null
|
||||
*/
|
||||
function getTicketByTrackingId($trackingId, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `trackid` = " . intval($trackingId));
|
||||
if (hesk_dbNumRows($rs) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = hesk_dbFetchAssoc($rs);
|
||||
$linkedTicketsRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `parent` = " . intval($trackingId));
|
||||
$repliesRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "replies` WHERE `replyto` = " . intval($row['id']) . " ORDER BY `id` ASC");
|
||||
|
||||
$ticket = Ticket::fromDatabaseRow($row, $linkedTicketsRs, $repliesRs, $heskSettings);
|
||||
|
||||
$this->close();
|
||||
|
||||
return $ticket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $trackingId string
|
||||
* @param $heskSettings array
|
||||
* @return Ticket|null
|
||||
*/
|
||||
function getTicketByMergedTrackingId($trackingId, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT `trackid` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `merged` LIKE '%#" . hesk_dbEscape($trackingId) . "#%'");
|
||||
if (hesk_dbNumRows($rs) === 0) {
|
||||
return null;
|
||||
}
|
||||
$row = hesk_dbFetchAssoc($rs);
|
||||
$actualTrackingId = $row['trackid'];
|
||||
|
||||
$this->close();
|
||||
|
||||
return $this->getTicketByTrackingId($actualTrackingId, $heskSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $ticket Ticket
|
||||
* @param $isEmailVerified
|
||||
* @param $heskSettings
|
||||
* @return TicketGatewayGeneratedFields
|
||||
*/
|
||||
function createTicket($ticket, $isEmailVerified, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$dueDate = $ticket->dueDate ? "'{$ticket->dueDate}'" : "NULL";
|
||||
// Prepare SQL for custom fields
|
||||
$customWhere = '';
|
||||
$customWhat = '';
|
||||
|
||||
for ($i=1; $i<=50; $i++)
|
||||
{
|
||||
$customWhere .= ", `custom{$i}`";
|
||||
$customWhat .= ", '" . (isset($ticket->customFields[$i]) ? hesk_dbEscape($ticket->customFields[$i]) : '') . "'";
|
||||
}
|
||||
|
||||
$suggestedArticles = 'NULL';
|
||||
if ($ticket->suggestedArticles !== null && !empty($ticket->suggestedArticles)) {
|
||||
$suggestedArticles = "'" .implode(',', $ticket->suggestedArticles) . "'";
|
||||
}
|
||||
|
||||
$latitude = $ticket->location !== null
|
||||
&& isset($ticket->location[0])
|
||||
&& $ticket->location[0] !== null ? $ticket->location[0] : 'E-0';
|
||||
$longitude = $ticket->location !== null
|
||||
&& isset($ticket->location[1])
|
||||
&& $ticket->location[1] !== null ? $ticket->location[1] : 'E-0';
|
||||
$userAgent = $ticket->userAgent !== null ? $ticket->userAgent : '';
|
||||
$screenResolutionWidth = $ticket->screenResolution !== null
|
||||
&& isset($ticket->screenResolution[0])
|
||||
&& $ticket->screenResolution[0] !== null ? intval($ticket->screenResolution[0]) : 'NULL';
|
||||
$screenResolutionHeight = $ticket->screenResolution !== null
|
||||
&& isset($ticket->screenResolution[1])
|
||||
&& $ticket->screenResolution[1] !== null ? intval($ticket->screenResolution[1]) : 'NULL';
|
||||
|
||||
$ipAddress = $ticket->ipAddress !== null
|
||||
&& $ticket->ipAddress !== '' ? $ticket->ipAddress : '';
|
||||
|
||||
$tableName = $isEmailVerified ? 'tickets' : 'stage_tickets';
|
||||
|
||||
$sql = "INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . $tableName ."`
|
||||
(
|
||||
`trackid`,
|
||||
`name`,
|
||||
`email`,
|
||||
`category`,
|
||||
`priority`,
|
||||
`subject`,
|
||||
`message`,
|
||||
`dt`,
|
||||
`lastchange`,
|
||||
`articles`,
|
||||
`ip`,
|
||||
`language`,
|
||||
`openedby`,
|
||||
`owner`,
|
||||
`attachments`,
|
||||
`merged`,
|
||||
`status`,
|
||||
`latitude`,
|
||||
`longitude`,
|
||||
`html`,
|
||||
`user_agent`,
|
||||
`screen_resolution_height`,
|
||||
`screen_resolution_width`,
|
||||
`due_date`,
|
||||
`history`
|
||||
{$customWhere}
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
'" . hesk_dbEscape($ticket->trackingId) . "',
|
||||
'" . hesk_dbEscape($ticket->name) . "',
|
||||
'" . hesk_dbEscape($ticket->email) . "',
|
||||
'" . intval($ticket->categoryId) . "',
|
||||
'" . intval($ticket->priorityId) . "',
|
||||
'" . hesk_dbEscape($ticket->subject) . "',
|
||||
'" . hesk_dbEscape($ticket->message) . "',
|
||||
NOW(),
|
||||
NOW(),
|
||||
" . $suggestedArticles . ",
|
||||
'" . hesk_dbEscape($ipAddress) . "',
|
||||
'" . hesk_dbEscape($ticket->language) . "',
|
||||
'" . intval($ticket->openedBy) . "',
|
||||
'" . intval($ticket->ownerId) . "',
|
||||
'" . hesk_dbEscape($ticket->getAttachmentsForDatabase()) . "',
|
||||
'',
|
||||
" . intval($ticket->statusId) . ",
|
||||
'" . hesk_dbEscape($latitude) . "',
|
||||
'" . hesk_dbEscape($longitude) . "',
|
||||
'" . hesk_dbEscape($ticket->usesHtml) . "',
|
||||
'" . hesk_dbEscape($userAgent) . "',
|
||||
" . hesk_dbEscape($screenResolutionHeight) . ",
|
||||
" . hesk_dbEscape($screenResolutionWidth) . ",
|
||||
{$dueDate},
|
||||
'" . hesk_dbEscape($ticket->auditTrailHtml) . "'
|
||||
{$customWhat}
|
||||
)
|
||||
";
|
||||
|
||||
hesk_dbQuery($sql);
|
||||
$id = hesk_dbInsertID();
|
||||
|
||||
$rs = hesk_dbQuery('SELECT `dt`, `lastchange` FROM `' . hesk_dbEscape($heskSettings['db_pfix']) . $tableName .'` WHERE `id` = ' . intval($id));
|
||||
$row = hesk_dbFetchAssoc($rs);
|
||||
|
||||
$generatedFields = new TicketGatewayGeneratedFields();
|
||||
$generatedFields->id = $id;
|
||||
$generatedFields->dateCreated = $row['dt'];
|
||||
$generatedFields->dateModified = $row['lastchange'];
|
||||
|
||||
$this->close();
|
||||
|
||||
return $generatedFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $ticketId int
|
||||
* @param $attachments Attachment[]
|
||||
* @param $heskSettings array
|
||||
*
|
||||
* Crappy logic that should just be pulled from the attachments table, but using for backwards compatibility
|
||||
*/
|
||||
function updateAttachmentsForTicket($ticketId, $attachments, $heskSettings) {
|
||||
$this->init();
|
||||
$this->updateAttachmentsFor($ticketId, $attachments, AttachmentType::MESSAGE, $heskSettings);
|
||||
$this->close();
|
||||
}
|
||||
|
||||
private function updateAttachmentsFor($id, $attachments, $attachmentType, $heskSettings) {
|
||||
$attachmentStrings = array();
|
||||
foreach ($attachments as $attachment) {
|
||||
$attachmentStrings[] = "{$attachment->id}#{$attachment->fileName}#{$attachment->savedName}";
|
||||
}
|
||||
$attachmentStringToSave = implode(',', $attachmentStrings);
|
||||
|
||||
$tableName = $attachmentType == AttachmentType::MESSAGE ? 'tickets' : 'replies';
|
||||
|
||||
hesk_dbQuery("UPDATE `" . hesk_dbEscape($heskSettings['db_pfix']) . $tableName . "`
|
||||
SET `attachments` = '" . hesk_dbEscape($attachmentStringToSave) . "'
|
||||
WHERE `id` = " . intval($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $replyId int
|
||||
* @param $attachments Attachment[]
|
||||
* @param $heskSettings array
|
||||
*
|
||||
* Crappy logic that should just be pulled from the attachments table, but using for backwards compatibility
|
||||
*/
|
||||
function updateAttachmentsForReply($replyId, $attachments, $heskSettings) {
|
||||
$this->init();
|
||||
$this->updateAttachmentsFor($replyId, $attachments, AttachmentType::REPLY, $heskSettings);
|
||||
$this->close();
|
||||
}
|
||||
|
||||
function deleteRepliesForTicket($ticketId, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "replies` WHERE `replyto` = " . intval($ticketId));
|
||||
|
||||
$this->close();
|
||||
}
|
||||
|
||||
function deleteReplyDraftsForTicket($ticketId, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "reply_drafts` WHERE `ticket`=" . intval($ticketId));
|
||||
|
||||
$this->close();
|
||||
}
|
||||
|
||||
function deleteNotesForTicket($ticketId, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "notes` WHERE `ticket`='" . intval($ticketId) . "'");
|
||||
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $ticketId int
|
||||
* @param $heskSettings array
|
||||
*/
|
||||
function deleteTicket($ticketId, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` WHERE `id` = " . intval($ticketId));
|
||||
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $ticket Ticket
|
||||
* @param $heskSettings array
|
||||
*/
|
||||
function updateBasicTicketInfo($ticket, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
// Escaped vars
|
||||
$subject = hesk_dbEscape($ticket->subject);
|
||||
$message = hesk_dbEscape($ticket->message);
|
||||
$language = hesk_dbEscape($ticket->language);
|
||||
$name = hesk_dbEscape($ticket->name);
|
||||
$email = hesk_dbEscape($ticket->email);
|
||||
|
||||
// Prepare SQL for custom fields
|
||||
$customSql = '';
|
||||
|
||||
for ($i=1; $i<=50; $i++)
|
||||
{
|
||||
$customSql .= ", `custom{$i}` = '" . (isset($ticket->customFields[$i]) ? hesk_dbEscape($ticket->customFields[$i]) : '') . "'";
|
||||
}
|
||||
|
||||
hesk_dbQuery("UPDATE `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets`
|
||||
SET `subject` = '{$subject}',
|
||||
`message` = '{$message}',
|
||||
`language` = '{$language}',
|
||||
`name` = '{$name}',
|
||||
`email` = '{$email}',
|
||||
`html` = " . ($ticket->usesHtml ? 1 : 0) . ",
|
||||
{$customSql}
|
||||
WHERE `id` = " . intval($ticket->id));
|
||||
|
||||
$this->close();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace DataAccess\Tickets;
|
||||
|
||||
|
||||
use DataAccess\CommonDao;
|
||||
|
||||
class VerifiedEmailGateway extends CommonDao {
|
||||
function isEmailVerified($emailAddress, $heskSettings) {
|
||||
$this->init();
|
||||
|
||||
$rs = hesk_dbQuery("SELECT 1 FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "verified_emails` WHERE `Email` = '" . hesk_dbEscape($emailAddress) . "'");
|
||||
|
||||
return hesk_dbNumRows($rs) > 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @class Main class of the Link router that helps you create and deploy routes
|
||||
*/
|
||||
class Link
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array A collection of the routes originally passed into all function. Used by static function route
|
||||
*/
|
||||
private static $routes = array();
|
||||
|
||||
/**
|
||||
* @var array A collection of functions that are executed before a route completion ( valid for all routes ), aka universal before functions
|
||||
*/
|
||||
private static $beforeFuncs = array();
|
||||
|
||||
/**
|
||||
* @var array A collection of function that are executed after a route completion ( valid for all routes ), aka universal after functions
|
||||
*/
|
||||
private static $afterFuncs = array();
|
||||
|
||||
/**
|
||||
* Static function of the class Link that deploys the route according to the passed handler and path
|
||||
*
|
||||
* @param array $routes An array of combination of the path and its handler, that are final deployed for a particular url
|
||||
*/
|
||||
public static function all( $routes )
|
||||
{
|
||||
|
||||
/* Call all functions that are to be executed before routing */
|
||||
foreach( self::$beforeFuncs as $beforeFunc ) {
|
||||
if( $beforeFunc[1] ) {
|
||||
call_user_func_array( $beforeFunc[0] , $beforeFunc[1] );
|
||||
} else {
|
||||
call_user_func( $beforeFunc[0] );
|
||||
}
|
||||
}
|
||||
|
||||
self::$routes = $routes;
|
||||
$method = strtolower($_SERVER['REQUEST_METHOD']);
|
||||
$path = '/';
|
||||
$handler = null;
|
||||
$matched = array();
|
||||
if ( !empty ( $_SERVER['PATH_INFO'] ) ) {
|
||||
$path = $_SERVER['PATH_INFO'];
|
||||
} else if ( !empty ( $_SERVER['REQUEST_URI'] ) ) {
|
||||
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
}
|
||||
|
||||
if ( isset($routes[$path] ) ) {
|
||||
if( is_array( $routes[$path] ) ) {
|
||||
$handler = $routes[$path][0];
|
||||
} else {
|
||||
$handler = $routes[$path];
|
||||
}
|
||||
} else if ( $routes ) {
|
||||
|
||||
$regex = array(
|
||||
'/{i}/',
|
||||
'/{s}/',
|
||||
'/{a}/'
|
||||
);
|
||||
|
||||
$replacements = array(
|
||||
'([\d]+)' ,
|
||||
'([a-zA-Z]+)',
|
||||
'([\w-]+)'
|
||||
);
|
||||
|
||||
foreach ( $routes as $routePath => $routeDesc ){
|
||||
$routePath = preg_replace( $regex, $replacements, $routePath );
|
||||
if( preg_match( '#^/?' . $routePath . '/?$#', $path, $matches ) ){
|
||||
if( is_array( $routeDesc ) ) {
|
||||
$handler = $routeDesc[0];
|
||||
if( isset( $routeDesc[2] )) {
|
||||
$middleware = $routeDesc[2];
|
||||
}
|
||||
}
|
||||
else
|
||||
$handler = $routeDesc;
|
||||
$matched = $matches;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset( $matched[0] );
|
||||
|
||||
if( isset($middleware) ){
|
||||
$newMatched = self::callFunction( $middleware, $matched, $method );
|
||||
/* If new wildcard param are there pass them to main handler */
|
||||
if( $newMatched ) {
|
||||
self::callFunction( $handler, $newMatched, $method );
|
||||
} else {
|
||||
self::callFunction( $handler, $matched, $method );
|
||||
}
|
||||
} else {
|
||||
self::callFunction( $handler, $matched, $method );
|
||||
}
|
||||
|
||||
/* Call all the function that are to be executed after routing */
|
||||
foreach( self::$afterFuncs as $afterFunc )
|
||||
if( $afterFunc[1] ) {
|
||||
call_user_func_array( $afterFunc[0] , $afterFunc[1] );
|
||||
} else {
|
||||
call_user_func( $afterFunc[0] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static function that helps you generate links effortlessly and pass parameters to them, thus enabling to generate dynamic links
|
||||
*
|
||||
* @param string $name name of the route for which the link has to be generated
|
||||
* @param array $params An array of parameters that are replaced in the route if it contains wildcards
|
||||
* For e.g. if route is /name/{i}/{a} and parameters passed are 1, aps then link generated will be /name/1/aps
|
||||
*/
|
||||
public static function route( $name, $params = array() )
|
||||
{
|
||||
$href = null;
|
||||
foreach ( self::$routes as $routePath => $routeDesc ) {
|
||||
if( is_array( $routeDesc ) ){
|
||||
if( $name == $routeDesc[1] ){
|
||||
$href = $routePath;
|
||||
for( $i = 0; $i < count($params); $i++){
|
||||
$href = preg_replace('#{(.*?)}#', $params[$i], $href, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static function to handle cases when route is not found, call handler of 404 if defined else
|
||||
* sends a 404 header
|
||||
*/
|
||||
public static function handle404()
|
||||
{
|
||||
/* Call '404' route if it exists */
|
||||
if( isset ( self::$routes['404'] ) ) {
|
||||
call_user_func( self::$routes['404'] );
|
||||
} else {
|
||||
header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static function to handle both middlewares' call and main handler's call.
|
||||
*
|
||||
* @param array|string $handler Handler that will handle the routes call or middleware
|
||||
* @param array $matched The parameters that we get from the route wildcard
|
||||
* @return array $newParams The parameters return in the case of middleware if you intend to
|
||||
* the wildcards that were originally passed, this newParams will
|
||||
* be next passed to main handler
|
||||
*/
|
||||
public static function callFunction( $handler , $matched, $method )
|
||||
{
|
||||
if ( $handler ) {
|
||||
if ( is_callable( $handler ) ) {
|
||||
$newParams = call_user_func_array( $handler, $matched ) ;
|
||||
} else {
|
||||
|
||||
/* Check if class exists in the case user is using RESTful pattern */
|
||||
|
||||
if( class_exists( $handler ) ) {
|
||||
$instanceOfHandler = new $handler(); // Won't work in case of middleware since we aren't using RESTful in that
|
||||
} else {
|
||||
print_r('Class or function ' . $handler . ' not found');
|
||||
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
|
||||
die();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self::handle404();
|
||||
}
|
||||
|
||||
if( isset( $instanceOfHandler ) ) {
|
||||
if( method_exists( $instanceOfHandler, $method ) ) {
|
||||
$newParams = call_user_func_array( array( $instanceOfHandler, $method ), $matched );
|
||||
}
|
||||
}
|
||||
if( isset( $newParams ) && $newParams ) {
|
||||
return $newParams;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static function to add functions that are to be excuted before each routing, must be called before Link::all
|
||||
*
|
||||
* @param string $funcName Name of the funtion to be called upon before
|
||||
* @param array $params Array of parameters that are to be passed to before function, can be null but if not
|
||||
* it must be an array
|
||||
*/
|
||||
public static function before( $funcName, $params = null )
|
||||
{
|
||||
array_push( self::$beforeFuncs, [ $funcName, $params ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static function to add functions that are to be excuted after each routing, must be called before Link::all
|
||||
*
|
||||
* @param string $funcName Name of the funtion to be called upon after
|
||||
* @param array $params Array of parameters that are to be passed to after function, can be null but if not
|
||||
* it must be an array
|
||||
*/
|
||||
public static function after( $funcName, $params = null )
|
||||
{
|
||||
array_push( self::$afterFuncs, [ $funcName, $params ]);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
class AutoLoader {
|
||||
|
||||
static private $classNames = array();
|
||||
|
||||
/**
|
||||
* Store the filename (sans extension) & full path of all ".php" files found
|
||||
*/
|
||||
public static function registerDirectory($dirName) {
|
||||
|
||||
$di = new DirectoryIterator($dirName);
|
||||
foreach ($di as $file) {
|
||||
|
||||
if ($file->isDir() && !$file->isLink() && !$file->isDot()) {
|
||||
// recurse into directories other than a few special ones
|
||||
self::registerDirectory($file->getPathname());
|
||||
} elseif (substr($file->getFilename(), -4) === '.php') {
|
||||
// save the class name / path of a .php file found
|
||||
$className = substr($file->getFilename(), 0, -4);
|
||||
AutoLoader::registerClass($className, $file->getPathname());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function registerClass($className, $fileName) {
|
||||
AutoLoader::$classNames[$className] = $fileName;
|
||||
}
|
||||
|
||||
public static function loadClass($className) {
|
||||
if (isset(AutoLoader::$classNames[$className])) {
|
||||
require_once(AutoLoader::$classNames[$className]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
spl_autoload_register(array('AutoLoader', 'loadClass'));
|
@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace BusinessLogic\Attachments;
|
||||
|
||||
|
||||
use BusinessLogic\Exceptions\ValidationException;
|
||||
use BusinessLogic\Security\UserContext;
|
||||
use BusinessLogic\Security\UserPrivilege;
|
||||
use BusinessLogic\Security\UserToTicketChecker;
|
||||
use BusinessLogic\Tickets\Reply;
|
||||
use BusinessLogic\Tickets\Ticket;
|
||||
use DataAccess\Attachments\AttachmentGateway;
|
||||
use DataAccess\Files\FileDeleter;
|
||||
use DataAccess\Files\FileWriter;
|
||||
use DataAccess\Tickets\TicketGateway;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AttachmentHandlerTest extends TestCase {
|
||||
|
||||
/* @var $attachmentHandler AttachmentHandler */
|
||||
private $attachmentHandler;
|
||||
|
||||
/* @var $createAttachmentModel CreateAttachmentForTicketModel */
|
||||
private $createAttachmentForTicketModel;
|
||||
|
||||
/* @var $ticketGateway \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $ticketGateway;
|
||||
|
||||
/* @var $attachmentGateway \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $attachmentGateway;
|
||||
|
||||
/* @var $userToTicketChecker \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $userToTicketChecker;
|
||||
|
||||
/* @var $fileWriter \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $fileWriter;
|
||||
|
||||
/* @var $fileDeleter \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $fileDeleter;
|
||||
|
||||
/* @var $userContext UserContext */
|
||||
private $userContext;
|
||||
|
||||
/* @var $heskSettings array */
|
||||
private $heskSettings;
|
||||
|
||||
protected function setUp() {
|
||||
$this->ticketGateway = $this->createMock(TicketGateway::class);
|
||||
$this->attachmentGateway = $this->createMock(AttachmentGateway::class);
|
||||
$this->fileWriter = $this->createMock(FileWriter::class);
|
||||
$this->fileDeleter = $this->createMock(FileDeleter::class);
|
||||
$this->userToTicketChecker = $this->createMock(UserToTicketChecker::class);
|
||||
$this->heskSettings = array(
|
||||
'attach_dir' => 'attachments',
|
||||
'attachments' => array(
|
||||
'allowed_types' => array('.txt'),
|
||||
'max_size' => 999
|
||||
)
|
||||
);
|
||||
|
||||
$this->attachmentHandler = new AttachmentHandler($this->ticketGateway,
|
||||
$this->attachmentGateway,
|
||||
$this->fileWriter,
|
||||
$this->userToTicketChecker,
|
||||
$this->fileDeleter);
|
||||
$this->createAttachmentForTicketModel = new CreateAttachmentForTicketModel();
|
||||
$this->createAttachmentForTicketModel->attachmentContents = base64_encode('string');
|
||||
$this->createAttachmentForTicketModel->displayName = 'DisplayName.txt';
|
||||
$this->createAttachmentForTicketModel->ticketId = 1;
|
||||
$this->createAttachmentForTicketModel->type = AttachmentType::MESSAGE;
|
||||
$this->userContext = new UserContext();
|
||||
}
|
||||
|
||||
function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsNull() {
|
||||
//-- Arrange
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
$this->createAttachmentForTicketModel->attachmentContents = null;
|
||||
|
||||
//-- Assert
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessageRegExp('/CONTENTS_EMPTY/');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsEmpty() {
|
||||
//-- Arrange
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
$this->createAttachmentForTicketModel->attachmentContents = '';
|
||||
|
||||
//-- Assert
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessageRegExp('/CONTENTS_EMPTY/');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testThatValidateThrowsAnExceptionWhenTheAttachmentBodyIsInvalidBase64() {
|
||||
//-- Arrange
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
$this->createAttachmentForTicketModel->attachmentContents = 'invalid base 64';
|
||||
|
||||
//-- Assert
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessageRegExp('/CONTENTS_NOT_BASE_64/');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testThatValidateThrowsAnExceptionWhenTheDisplayNameIsNull() {
|
||||
//-- Arrange
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
$this->createAttachmentForTicketModel->displayName = null;
|
||||
|
||||
//-- Assert
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessageRegExp('/DISPLAY_NAME_EMPTY/');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testThatValidateThrowsAnExceptionWhenTheDisplayNameIsEmpty() {
|
||||
//-- Arrange
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
$this->createAttachmentForTicketModel->displayName = '';
|
||||
|
||||
//-- Assert
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessageRegExp('/DISPLAY_NAME_EMPTY/');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testThatValidateThrowsAnExceptionWhenTheTicketIdIsNull() {
|
||||
//-- Arrange
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
$this->createAttachmentForTicketModel->ticketId = null;
|
||||
|
||||
//-- Assert
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessageRegExp('/TICKET_ID_MISSING/');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testThatValidateThrowsAnExceptionWhenTheTicketIdIsANonPositiveInteger() {
|
||||
//-- Arrange
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
$this->createAttachmentForTicketModel->ticketId = 0;
|
||||
|
||||
//-- Assert
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessageRegExp('/TICKET_ID_MISSING/');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testThatValidateThrowsAnExceptionWhenTheFileExtensionIsNotPermitted() {
|
||||
//-- Arrange
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
$this->heskSettings['attachments']['allowed_types'] = array('.gif');
|
||||
$this->createAttachmentForTicketModel->ticketId = 0;
|
||||
|
||||
//-- Assert
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessageRegExp('/EXTENSION_NOT_PERMITTED/');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testThatValidateThrowsAnExceptionWhenTheFileSizeIsLargerThanMaxPermitted() {
|
||||
//-- Arrange
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
$this->createAttachmentForTicketModel->attachmentContents = base64_encode("msg");
|
||||
$this->heskSettings['attachments']['max_size'] = 1;
|
||||
|
||||
//-- Assert
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessageRegExp('/FILE_SIZE_TOO_LARGE/');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testItSavesATicketWithTheProperProperties() {
|
||||
//-- Arrange
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
$this->createAttachmentForTicketModel->ticketId = 1;
|
||||
$ticket = new Ticket();
|
||||
$ticket->trackingId = 'ABC-DEF-1234';
|
||||
$this->ticketGateway->method('getTicketById')->with(1, $this->anything())->willReturn($ticket);
|
||||
|
||||
$ticketAttachment = new TicketAttachment();
|
||||
$ticketAttachment->displayName = $this->createAttachmentForTicketModel->displayName;
|
||||
$ticketAttachment->ticketTrackingId = $ticket->trackingId;
|
||||
$ticketAttachment->type = 0;
|
||||
$ticketAttachment->downloadCount = 0;
|
||||
$ticketAttachment->id = 50;
|
||||
|
||||
$this->attachmentGateway->method('createAttachmentForTicket')->willReturn(50);
|
||||
|
||||
|
||||
//-- Act
|
||||
$actual = $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings);
|
||||
|
||||
//-- Assert
|
||||
self::assertThat($actual->id, self::equalTo(50));
|
||||
self::assertThat($actual->downloadCount, self::equalTo(0));
|
||||
self::assertThat($actual->type, self::equalTo(AttachmentType::MESSAGE));
|
||||
self::assertThat($actual->ticketTrackingId, self::equalTo($ticket->trackingId));
|
||||
self::assertThat($actual->displayName, self::equalTo($this->createAttachmentForTicketModel->displayName));
|
||||
}
|
||||
|
||||
function testItSavesTheFileToTheFileSystem() {
|
||||
//-- Arrange
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
$this->createAttachmentForTicketModel->ticketId = 1;
|
||||
$ticket = new Ticket();
|
||||
$ticket->trackingId = 'ABC-DEF-1234';
|
||||
$this->ticketGateway->method('getTicketById')->with(1, $this->anything())->willReturn($ticket);
|
||||
|
||||
$ticketAttachment = new TicketAttachment();
|
||||
$ticketAttachment->displayName = $this->createAttachmentForTicketModel->displayName;
|
||||
$ticketAttachment->ticketTrackingId = $ticket->trackingId;
|
||||
$ticketAttachment->type = AttachmentType::MESSAGE;
|
||||
$ticketAttachment->downloadCount = 0;
|
||||
$ticketAttachment->id = 50;
|
||||
|
||||
$this->fileWriter->method('writeToFile')->willReturn(1024);
|
||||
$this->attachmentGateway->method('createAttachmentForTicket')->willReturn(50);
|
||||
|
||||
|
||||
//-- Act
|
||||
$actual = $this->attachmentHandler->createAttachmentForTicket($this->createAttachmentForTicketModel, $this->userContext, $this->heskSettings);
|
||||
|
||||
//-- Assert
|
||||
self::assertThat($actual->fileSize, self::equalTo(1024));
|
||||
}
|
||||
|
||||
//-- TODO Test UserToTicketChecker
|
||||
|
||||
function testDeleteThrowsAnExceptionWhenTheUserDoesNotHaveAccessToTheTicket() {
|
||||
//-- Arrange
|
||||
$ticketId = 1;
|
||||
$ticket = new Ticket();
|
||||
$this->ticketGateway->method('getTicketById')
|
||||
->with($ticketId, $this->heskSettings)->willReturn($ticket);
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')
|
||||
->with($this->userContext, $ticket, $this->heskSettings, array(UserPrivilege::CAN_EDIT_TICKETS))
|
||||
->willReturn(false);
|
||||
|
||||
//-- Assert
|
||||
$this->expectException(\Exception::class);
|
||||
$this->expectExceptionMessage("User does not have access to ticket {$ticketId} being created / edited!");
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->deleteAttachmentFromTicket($ticketId, 1, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testDeleteActuallyDeletesTheFile() {
|
||||
//-- Arrange
|
||||
$ticketId = 1;
|
||||
$ticket = new Ticket();
|
||||
$attachment = new Attachment();
|
||||
$attachment->id = 5;
|
||||
$attachment->savedName = 'foobar.txt';
|
||||
$this->heskSettings['attach_dir'] = 'attach-dir';
|
||||
$ticket->attachments = array($attachment);
|
||||
$ticket->replies = array();
|
||||
$this->ticketGateway->method('getTicketById')->willReturn($ticket);
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
|
||||
//-- Assert
|
||||
$this->fileDeleter->expects($this->once())->method('deleteFile')->with('foobar.txt', 'attach-dir');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->deleteAttachmentFromTicket($ticketId, 5, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testDeleteUpdatesTheTicketItselfAndSavesIt() {
|
||||
//-- Arrange
|
||||
$ticketId = 1;
|
||||
$ticket = new Ticket();
|
||||
$ticket->replies = array();
|
||||
$attachment = new Attachment();
|
||||
$attachment->id = 5;
|
||||
$attachment->savedName = 'foobar.txt';
|
||||
$this->heskSettings['attach_dir'] = 'attach-dir';
|
||||
$ticket->attachments = array($attachment);
|
||||
$this->ticketGateway->method('getTicketById')->willReturn($ticket);
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
|
||||
//-- Assert
|
||||
$this->ticketGateway->expects($this->once())->method('updateAttachmentsForTicket');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->deleteAttachmentFromTicket($ticketId, 5, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
|
||||
function testDeleteHandlesReplies() {
|
||||
//-- Arrange
|
||||
$ticketId = 1;
|
||||
$ticket = new Ticket();
|
||||
$reply = new Reply();
|
||||
$reply->id = 10;
|
||||
$attachment = new Attachment();
|
||||
$attachment->id = 5;
|
||||
$attachment->savedName = 'foobar.txt';
|
||||
$this->heskSettings['attach_dir'] = 'attach-dir';
|
||||
$reply->attachments = array($attachment);
|
||||
$ticket->replies = array(10 => $reply);
|
||||
$this->ticketGateway->method('getTicketById')->willReturn($ticket);
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
|
||||
//-- Assert
|
||||
$this->ticketGateway->expects($this->once())->method('updateAttachmentsForReply');
|
||||
|
||||
//-- Act
|
||||
$this->attachmentHandler->deleteAttachmentFromTicket($ticketId, 5, $this->userContext, $this->heskSettings);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace BusinessLogic\Attachments;
|
||||
|
||||
|
||||
use BusinessLogic\Security\UserContext;
|
||||
use BusinessLogic\Security\UserToTicketChecker;
|
||||
use DataAccess\Attachments\AttachmentGateway;
|
||||
use DataAccess\Files\FileReader;
|
||||
use DataAccess\Tickets\TicketGateway;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AttachmentRetrieverTest extends TestCase {
|
||||
/* @var $attachmentGateway \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $attachmentGateway;
|
||||
|
||||
/* @var $fileReader \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $fileReader;
|
||||
|
||||
/* @var $ticketGateway \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $ticketGateway;
|
||||
|
||||
/* @var $userToTicketChecker \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $userToTicketChecker;
|
||||
|
||||
/* @var $attachmentRetriever AttachmentRetriever */
|
||||
private $attachmentRetriever;
|
||||
|
||||
/* @var $heskSettings array */
|
||||
private $heskSettings;
|
||||
|
||||
protected function setUp() {
|
||||
$this->attachmentGateway = $this->createMock(AttachmentGateway::class);
|
||||
$this->fileReader = $this->createMock(FileReader::class);
|
||||
$this->ticketGateway = $this->createMock(TicketGateway::class);
|
||||
$this->userToTicketChecker = $this->createMock(UserToTicketChecker::class);
|
||||
$this->heskSettings = array('attach_dir' => 'attachments');
|
||||
|
||||
$this->attachmentRetriever = new AttachmentRetriever($this->attachmentGateway, $this->fileReader,
|
||||
$this->ticketGateway, $this->userToTicketChecker);
|
||||
|
||||
$this->userToTicketChecker->method('isTicketAccessibleToUser')->willReturn(true);
|
||||
}
|
||||
|
||||
function testItGetsTheMetadataFromTheGateway() {
|
||||
//-- Arrange
|
||||
$attachmentMeta = new Attachment();
|
||||
$attachmentMeta->savedName = '5';
|
||||
$attachmentContents = 'string';
|
||||
$expectedContents = base64_encode($attachmentContents);
|
||||
$this->attachmentGateway->method('getAttachmentById')
|
||||
->with(4, $this->heskSettings)
|
||||
->willReturn($attachmentMeta);
|
||||
$this->fileReader->method('readFromFile')
|
||||
->with('5', $this->heskSettings['attach_dir'])
|
||||
->willReturn($attachmentContents);
|
||||
|
||||
//-- Act
|
||||
$actualContents = $this->attachmentRetriever->getAttachmentContentsForTicket(0, 4, new UserContext(), $this->heskSettings);
|
||||
|
||||
//-- Assert
|
||||
self::assertThat($actualContents, self::equalTo($expectedContents));
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace BusinessLogic\Emails;
|
||||
|
||||
use BusinessLogic\IntegrationTestCaseBase;
|
||||
use BusinessLogic\Tickets\Attachment;
|
||||
|
||||
class BasicEmailSenderIntegrationTest extends IntegrationTestCaseBase {
|
||||
/**
|
||||
* @var $emailSender BasicEmailSender;
|
||||
*/
|
||||
private $emailSender;
|
||||
|
||||
/**
|
||||
* @var $heskSettings array
|
||||
*/
|
||||
private $heskSettings;
|
||||
|
||||
/**
|
||||
* @var $modsForHeskSettings array
|
||||
*/
|
||||
private $modsForHeskSettings;
|
||||
|
||||
/**
|
||||
* @var $attachmentsToPurge string[]
|
||||
*/
|
||||
private $attachmentsToPurge;
|
||||
|
||||
protected function setUp() {
|
||||
global $hesk_settings, $modsForHesk_settings;
|
||||
|
||||
$this->skip();
|
||||
|
||||
if (!defined('IN_SCRIPT')) {
|
||||
define('IN_SCRIPT', 1);
|
||||
}
|
||||
require(__DIR__ . '/../../../../hesk_settings.inc.php');
|
||||
require(__DIR__ . '/../../integration_test_mfh_settings.php');
|
||||
|
||||
$this->emailSender = new BasicEmailSender();
|
||||
$this->heskSettings = $hesk_settings;
|
||||
$this->modsForHeskSettings = $modsForHesk_settings;
|
||||
$this->attachmentsToPurge = array();
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
foreach ($this->attachmentsToPurge as $file) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
function testItCanSendHtmlMail() {
|
||||
//-- Arrange
|
||||
//$hesk_settings['smtp'] = 0 //Uncomment this to use PHPMail
|
||||
$emailBuilder = new EmailBuilder();
|
||||
$emailBuilder->to = array('mfh1@mailinator.com');
|
||||
$emailBuilder->cc = array('mfh2@mailinator.com');
|
||||
$emailBuilder->bcc = array('mfh3@mailinator.com');
|
||||
$emailBuilder->message = "Test PLAIN TEXT message";
|
||||
$emailBuilder->htmlMessage = "Test <b>HTML</b> <i>message</i>";
|
||||
$emailBuilder->subject = "BasicEmailSenderIntegrationTest";
|
||||
|
||||
$attachment = new Attachment();
|
||||
$attachment->id = 1;
|
||||
$attachment->fileName = "file.txt";
|
||||
$attachment->savedName = "test1.txt";
|
||||
$filename1 = __DIR__ . '/../../../../' . $this->heskSettings['attach_dir'] . '/' . $attachment->savedName;
|
||||
file_put_contents($filename1, 'TEST DATA');
|
||||
|
||||
$otherAttachment = new Attachment();
|
||||
$otherAttachment->id = 2;
|
||||
$otherAttachment->fileName = "file2.txt";
|
||||
$otherAttachment->savedName = "test2.txt";
|
||||
$filename2 = __DIR__ . '/../../../../' . $this->heskSettings['attach_dir'] . '/' . $otherAttachment->savedName;
|
||||
file_put_contents($filename2, 'TEST DATA 2');
|
||||
|
||||
$emailBuilder->attachments = array($attachment, $otherAttachment);
|
||||
$this->attachmentsToPurge = array($filename1, $filename2);
|
||||
|
||||
|
||||
//-- Act
|
||||
$result = $this->emailSender->sendEmail($emailBuilder, $this->heskSettings, $this->modsForHeskSettings, true);
|
||||
|
||||
//-- Assert
|
||||
if ($result !== true) {
|
||||
$this->fail($result);
|
||||
}
|
||||
}
|
||||
|
||||
function testItCanSendPlaintextMail() {
|
||||
//-- Arrange
|
||||
//$hesk_settings['smtp'] = 0 //Uncomment this to use PHPMail
|
||||
$emailBuilder = new EmailBuilder();
|
||||
$emailBuilder->to = array('mfh1@mailinator.com');
|
||||
$emailBuilder->cc = array('mfh2@mailinator.com');
|
||||
$emailBuilder->bcc = array('mfh3@mailinator.com');
|
||||
$emailBuilder->message = "Test PLAIN TEXT message";
|
||||
$emailBuilder->subject = "BasicEmailSenderIntegrationTest";
|
||||
|
||||
|
||||
//-- Act
|
||||
$result = $this->emailSender->sendEmail($emailBuilder, $this->heskSettings, $this->modsForHeskSettings, false);
|
||||
|
||||
//-- Assert
|
||||
if ($result !== true) {
|
||||
$this->fail($result);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace BusinessLogic\Emails;
|
||||
|
||||
|
||||
use BusinessLogic\Tickets\Ticket;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class EmailSenderHelperTest extends TestCase {
|
||||
/**
|
||||
* @var $emailTemplateParser \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $emailTemplateParser;
|
||||
|
||||
/**
|
||||
* @var $basicEmailSender \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $basicEmailSender;
|
||||
|
||||
/**
|
||||
* @var $mailgunEmailSender \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $mailgunEmailSender;
|
||||
|
||||
/**
|
||||
* @var $emailSenderHelper EmailSenderHelper
|
||||
*/
|
||||
private $emailSenderHelper;
|
||||
|
||||
/**
|
||||
* @var $heskSettings array
|
||||
*/
|
||||
private $heskSettings;
|
||||
|
||||
/**
|
||||
* @var $modsForHeskSettings array
|
||||
*/
|
||||
private $modsForHeskSettings;
|
||||
|
||||
protected function setUp() {
|
||||
$this->emailTemplateParser = $this->createMock(EmailTemplateParser::class);
|
||||
$this->basicEmailSender = $this->createMock(BasicEmailSender::class);
|
||||
$this->mailgunEmailSender = $this->createMock(MailgunEmailSender::class);
|
||||
$this->heskSettings = array(
|
||||
'languages' => array(
|
||||
'English' => array('folder' => 'en')
|
||||
),
|
||||
'custom_fields' => array()
|
||||
);
|
||||
$this->modsForHeskSettings = array(
|
||||
'attachments' => 0,
|
||||
'use_mailgun' => 0,
|
||||
'html_emails' => 0
|
||||
);
|
||||
|
||||
$this->emailSenderHelper = new EmailSenderHelper($this->emailTemplateParser, $this->basicEmailSender,
|
||||
$this->mailgunEmailSender);
|
||||
}
|
||||
|
||||
function testItParsesTheTemplateForTheTicket() {
|
||||
//-- Arrange
|
||||
$templateId = EmailTemplateRetriever::NEW_NOTE;
|
||||
$languageCode = 'en';
|
||||
$ticket = new Ticket();
|
||||
$this->emailTemplateParser->method('getFormattedEmailForLanguage')->willReturn(new ParsedEmailProperties('Subject', 'Message', 'HTML Message'));
|
||||
|
||||
//-- Assert
|
||||
$this->emailTemplateParser->expects($this->once())
|
||||
->method('getFormattedEmailForLanguage')
|
||||
->with($templateId, $languageCode, $ticket, $this->heskSettings, $this->modsForHeskSettings);
|
||||
|
||||
//-- Act
|
||||
$this->emailSenderHelper->sendEmailForTicket($templateId, 'English', new Addressees(), $ticket, $this->heskSettings, $this->modsForHeskSettings);
|
||||
}
|
||||
|
||||
function testItSendsTheEmailThroughTheMailgunEmailSender() {
|
||||
//-- Arrange
|
||||
$addressees = new Addressees();
|
||||
$addressees->to = ['to@email'];
|
||||
$addressees->cc = ['cc1', 'cc2'];
|
||||
$addressees->bcc = ['bcc1', 'bcc2'];
|
||||
$this->modsForHeskSettings['use_mailgun'] = 1;
|
||||
$this->modsForHeskSettings['html_emails'] = true;
|
||||
|
||||
$expectedEmailBuilder = new EmailBuilder();
|
||||
$expectedEmailBuilder->to = $addressees->to;
|
||||
$expectedEmailBuilder->cc = $addressees->cc;
|
||||
$expectedEmailBuilder->bcc = $addressees->bcc;
|
||||
$expectedEmailBuilder->subject = 'Subject';
|
||||
$expectedEmailBuilder->message = 'Message';
|
||||
$expectedEmailBuilder->htmlMessage = 'HTML Message';
|
||||
|
||||
$this->emailTemplateParser->method('getFormattedEmailForLanguage')->willReturn(new ParsedEmailProperties('Subject', 'Message', 'HTML Message'));
|
||||
|
||||
//-- Assert
|
||||
$this->mailgunEmailSender->expects($this->once())
|
||||
->method('sendEmail')
|
||||
->with($expectedEmailBuilder, $this->heskSettings, $this->modsForHeskSettings, true);
|
||||
|
||||
//-- Act
|
||||
$this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_NOTE, 'English', $addressees, new Ticket(), $this->heskSettings, $this->modsForHeskSettings);
|
||||
}
|
||||
|
||||
function testItSendsTheEmailThroughTheBasicEmailSender() {
|
||||
//-- Arrange
|
||||
$addressees = new Addressees();
|
||||
$addressees->to = ['to@email'];
|
||||
$addressees->cc = ['cc1', 'cc2'];
|
||||
$addressees->bcc = ['bcc1', 'bcc2'];
|
||||
$this->modsForHeskSettings['use_mailgun'] = 0;
|
||||
$this->modsForHeskSettings['html_emails'] = true;
|
||||
|
||||
$expectedEmailBuilder = new EmailBuilder();
|
||||
$expectedEmailBuilder->to = $addressees->to;
|
||||
$expectedEmailBuilder->cc = $addressees->cc;
|
||||
$expectedEmailBuilder->bcc = $addressees->bcc;
|
||||
$expectedEmailBuilder->subject = 'Subject';
|
||||
$expectedEmailBuilder->message = 'Message';
|
||||
$expectedEmailBuilder->htmlMessage = 'HTML Message';
|
||||
|
||||
$this->emailTemplateParser->method('getFormattedEmailForLanguage')->willReturn(new ParsedEmailProperties('Subject', 'Message', 'HTML Message'));
|
||||
|
||||
//-- Assert
|
||||
$this->basicEmailSender->expects($this->once())
|
||||
->method('sendEmail')
|
||||
->with($expectedEmailBuilder, $this->heskSettings, $this->modsForHeskSettings, true);
|
||||
|
||||
//-- Act
|
||||
$this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_NOTE, 'English', $addressees, new Ticket(), $this->heskSettings, $this->modsForHeskSettings);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue