Merge branch '340-post-customer-reply' into '3-4-0'

Add Customer Reply POST API Endpoint

See merge request mike-koch/Mods-for-HESK!97
master^2
Mike Koch 6 years ago
commit 4a73524c32

@ -18,11 +18,11 @@ class MailgunEmailSender extends \BaseClass implements EmailSender {
$mailgunArray['to'] = implode(',', $emailBuilder->to);
if ($emailBuilder->cc !== null) {
if ($emailBuilder->cc !== null && count($emailBuilder->cc) > 0) {
$mailgunArray['cc'] = implode(',', $emailBuilder->cc);
}
if ($emailBuilder->bcc !== null) {
if ($emailBuilder->bcc !== null && count($emailBuilder->bcc) > 0) {
$mailgunArray['bcc'] = implode(',', $emailBuilder->bcc);
}
@ -55,7 +55,9 @@ class MailgunEmailSender extends \BaseClass implements EmailSender {
}
private function sendMessage($mailgunArray, $attachments, $modsForHeskSettings) {
$messageClient = new Mailgun($modsForHeskSettings['mailgun_api_key']);
$ssl = !defined('NO_MAILGUN_SSL');
$messageClient = new Mailgun($modsForHeskSettings['mailgun_api_key'], 'api.mailgun.net', 'v2', $ssl);
$mailgunAttachments = array();
if (count($attachments) > 0) {

@ -34,4 +34,154 @@ class Helpers extends \BaseClass {
static function heskHtmlSpecialCharsDecode($in) {
return str_replace(array('&amp;', '&lt;', '&gt;', '&quot;'), array('&', '<', '>', '"'), $in);
}
static function heskMakeUrl($text, $class = '', $shortenLinks = true) {
if (!defined('MAGIC_URL_EMAIL')) {
define('MAGIC_URL_EMAIL', 1);
define('MAGIC_URL_FULL', 2);
define('MAGIC_URL_LOCAL', 3);
define('MAGIC_URL_WWW', 4);
}
$class = ($class) ? ' class="' . $class . '"' : '';
// matches a xxxx://aaaaa.bbb.cccc. ...
$text = preg_replace_callback(
'#(^|[\n\t (>.])(' . "[a-z][a-z\d+]*:/{2}(?:(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\])(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?" . ')#iu',
function($matches) use ($class, $shortenLinks) {
return self::makeClickableCallback(MAGIC_URL_FULL, $matches[1], $matches[2], '', $class, $shortenLinks);
},
$text
);
// matches a "www.xxxx.yyyy[/zzzz]" kinda lazy URL thing
$text = preg_replace_callback(
'#(^|[\n\t (>])(' . "www\.(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})+(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?" . ')#iu',
function($matches) use ($class, $shortenLinks) {
return self::makeClickableCallback(MAGIC_URL_WWW, $matches[1], $matches[2], '', $class, $shortenLinks);
},
$text
);
// matches an email address
$text = preg_replace_callback(
'/(^|[\n\t (>])(' . '((?:[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\w\!\#$\%\'\*\+\-\/\=\?\^\`{\|\}\~]|&amp;)+)@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,63})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)' . ')/iu',
function($matches) use ($class, $shortenLinks) {
return self::makeClickableCallback(MAGIC_URL_EMAIL, $matches[1], $matches[2], '', $class, $shortenLinks);
},
$text
);
return $text;
}
static function makeClickableCallback($type, $whitespace, $url, $relative_url, $class, $shortenLinks)
{
global $hesk_settings;
$orig_url = $url;
$orig_relative = $relative_url;
$append = '';
$url = htmlspecialchars_decode($url);
$relative_url = htmlspecialchars_decode($relative_url);
// make sure no HTML entities were matched
$chars = array('<', '>', '"');
$split = false;
foreach ($chars as $char) {
$next_split = strpos($url, $char);
if ($next_split !== false) {
$split = ($split !== false) ? min($split, $next_split) : $next_split;
}
}
if ($split !== false) {
// an HTML entity was found, so the URL has to end before it
$append = substr($url, $split) . $relative_url;
$url = substr($url, 0, $split);
$relative_url = '';
} else if ($relative_url) {
// same for $relative_url
$split = false;
foreach ($chars as $char) {
$next_split = strpos($relative_url, $char);
if ($next_split !== false) {
$split = ($split !== false) ? min($split, $next_split) : $next_split;
}
}
if ($split !== false) {
$append = substr($relative_url, $split);
$relative_url = substr($relative_url, 0, $split);
}
}
// if the last character of the url is a punctuation mark, exclude it from the url
$last_char = ($relative_url) ? $relative_url[strlen($relative_url) - 1] : $url[strlen($url) - 1];
switch ($last_char) {
case '.':
case '?':
case '!':
case ':':
case ',':
$append = $last_char;
if ($relative_url) {
$relative_url = substr($relative_url, 0, -1);
} else {
$url = substr($url, 0, -1);
}
break;
// set last_char to empty here, so the variable can be used later to
// check whether a character was removed
default:
$last_char = '';
break;
}
$short_url = ($hesk_settings['short_link'] && strlen($url) > 70 && $shortenLinks) ? substr($url, 0, 54) . ' ... ' . substr($url, -10) : $url;
switch ($type) {
case MAGIC_URL_LOCAL:
$tag = 'l';
$relative_url = preg_replace('/[&?]sid=[0-9a-f]{32}$/', '', preg_replace('/([&?])sid=[0-9a-f]{32}&/', '$1', $relative_url));
$url = $url . '/' . $relative_url;
$text = $relative_url;
// this url goes to http://domain.tld/path/to/board/ which
// would result in an empty link if treated as local so
// don't touch it and let MAGIC_URL_FULL take care of it.
if (!$relative_url) {
return $whitespace . $orig_url . '/' . $orig_relative; // slash is taken away by relative url pattern
}
break;
case MAGIC_URL_FULL:
$tag = 'm';
$text = $short_url;
break;
case MAGIC_URL_WWW:
$tag = 'w';
$url = 'http://' . $url;
$text = $short_url;
break;
case MAGIC_URL_EMAIL:
$tag = 'e';
$text = $short_url;
$url = 'mailto:' . $url;
break;
}
$url = htmlspecialchars($url);
$text = htmlspecialchars($text);
$append = htmlspecialchars($append);
$html = "$whitespace<a href=\"$url\" target=\"blank\" $class>$text</a>$append";
return $html;
} // END make_clickable_callback()
}

@ -0,0 +1,13 @@
<?php
namespace BusinessLogic\Tickets;
class CreateReplyRequest {
public $ticketId;
public $trackingId;
public $emailAddress;
public $replyMessage;
public $hasHtml;
public $ipAddress;
}

@ -0,0 +1,13 @@
<?php
namespace BusinessLogic\Tickets;
class CustomerCreatedReplyModel {
public $id;
public $ticketId;
public $replierName;
public $message;
public $dateCreated;
public $html;
}

@ -0,0 +1,145 @@
<?php
namespace BusinessLogic\Tickets;
use BusinessLogic\Emails\Addressees;
use BusinessLogic\Emails\EmailSenderHelper;
use BusinessLogic\Emails\EmailTemplateRetriever;
use BusinessLogic\Exceptions\ApiFriendlyException;
use BusinessLogic\Exceptions\ValidationException;
use BusinessLogic\Helpers;
use BusinessLogic\Security\UserContext;
use BusinessLogic\Statuses\Closable;
use BusinessLogic\Statuses\DefaultStatusForAction;
use BusinessLogic\ValidationModel;
use DataAccess\AuditTrail\AuditTrailGateway;
use DataAccess\Security\LoginGateway;
use DataAccess\Security\UserGateway;
use DataAccess\Statuses\StatusGateway;
use DataAccess\Tickets\ReplyGateway;
use DataAccess\Tickets\TicketGateway;
class ReplyCreator extends \BaseClass {
private $statusGateway;
private $ticketGateway;
private $emailSenderHelper;
private $userGateway;
private $auditTrailGateway;
private $loginGateway;
private $replyGateway;
public function __construct(StatusGateway $statusGateway,
TicketGateway $ticketGateway,
EmailSenderHelper $emailSenderHelper,
UserGateway $userGateway,
AuditTrailGateway $auditTrailGateway,
LoginGateway $loginGateway,
ReplyGateway $replyGateway) {
$this->statusGateway = $statusGateway;
$this->ticketGateway = $ticketGateway;
$this->emailSenderHelper = $emailSenderHelper;
$this->userGateway = $userGateway;
$this->auditTrailGateway = $auditTrailGateway;
$this->loginGateway = $loginGateway;
$this->replyGateway = $replyGateway;
}
/**
* @param $replyRequest CreateReplyRequest
* @param $heskSettings array
* @param $modsForHeskSettings array
* @throws ApiFriendlyException
* @throws \Exception
*/
function createReplyByCustomer($replyRequest, $heskSettings, $modsForHeskSettings) {
$ticket = $this->ticketGateway->getTicketByTrackingId($replyRequest->trackingId, $heskSettings);
if ($ticket === null) {
throw new ApiFriendlyException("Ticket with tracking ID {$replyRequest->trackingId} not found.",
"Ticket not found", 404);
}
$validationModel = new ValidationModel();
if ($replyRequest->replyMessage === null || trim($replyRequest->replyMessage) === '') {
$validationModel->errorKeys[] = 'MESSAGE_REQUIRED';
}
if ($heskSettings['email_view_ticket']) {
if ($replyRequest->emailAddress === null || trim($replyRequest->emailAddress) === '') {
$validationModel->errorKeys[] = 'EMAIL_REQUIRED';
} elseif (!in_array($replyRequest->emailAddress, $ticket->email)) {
$validationModel->errorKeys[] = 'EMAIL_NOT_FOUND_ON_TICKET';
}
}
if (count($validationModel->errorKeys) > 0) {
throw new ValidationException($validationModel);
}
if ($modsForHeskSettings['rich_text_for_tickets_for_customers']) {
$replyRequest->replyMessage = Helpers::heskMakeUrl($replyRequest->replyMessage);
$replyRequest->replyMessage = nl2br($replyRequest->replyMessage);
}
if ($this->loginGateway->isIpLockedOut($replyRequest->ipAddress, $heskSettings)) {
throw new ApiFriendlyException("The IP address entered has been locked out of the system for {$heskSettings['attempt_banmin']} minutes because of too many login failures",
"Locked Out",
403);
}
if ($this->ticketGateway->areRepliesBeingFlooded($replyRequest->ticketId, $replyRequest->ipAddress, $heskSettings)) {
throw new ApiFriendlyException("You have been locked out of the system for {$heskSettings['attempt_banmin']} minutes because of too many replies to a ticket.",
"Locked Out",
403);
}
// If staff hasn't replied yet, don't change the status; otherwise set it to the status for customer replies
$currentStatus = $this->statusGateway->getStatusById($ticket->statusId, $heskSettings);
if ($currentStatus->closable === Closable::YES || $currentStatus->closable === Closable::CUSTOMERS_ONLY) {
$customerReplyStatus = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::CUSTOMER_REPLY, $heskSettings);
$defaultNewTicketStatus = $this->statusGateway->getStatusForDefaultAction(DefaultStatusForAction::NEW_TICKET, $heskSettings);
$ticket->statusId = $ticket->statusId === $defaultNewTicketStatus->id ?
$defaultNewTicketStatus->id :
$customerReplyStatus->id;
}
$this->ticketGateway->updateMetadataForReply($ticket->id, $ticket->statusId, $heskSettings);
$createdReply = $this->replyGateway->insertReply($ticket->id, $ticket->name, $replyRequest->replyMessage, $replyRequest->hasHtml, $heskSettings);
//-- Changing the ticket message to the reply's
$ticket->message = $replyRequest->replyMessage;
$addressees = new Addressees();
if ($ticket->ownerId !== null && $ticket->ownerId !== 0) {
$owner = $this->userGateway->getUserById($ticket->ownerId, $heskSettings);
if ($owner->notificationSettings->replyToMe) {
$addressees->to[] = $owner->email;
$language = $owner->language === null ? $heskSettings['language'] : $owner->language;
$this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_REPLY_BY_CUSTOMER,
$language,
$addressees,
$ticket,
$heskSettings,
$modsForHeskSettings);
}
} else {
$users = $this->userGateway->getUsersForUnassignedReplyNotification($heskSettings);
foreach ($users as $user) {
$addressees->to[] = $user->email;
$language = $user->language === null ? $heskSettings['language'] : $user->language;
$this->emailSenderHelper->sendEmailForTicket(EmailTemplateRetriever::NEW_REPLY_BY_CUSTOMER,
$language,
$addressees,
$ticket,
$heskSettings,
$modsForHeskSettings);
}
}
return $createdReply;
}
}

@ -0,0 +1,40 @@
<?php
namespace Controllers\Tickets;
use BusinessLogic\Helpers;
use BusinessLogic\Tickets\CreateReplyRequest;
use BusinessLogic\Tickets\ReplyCreator;
use Controllers\JsonRetriever;
use DataAccess\Settings\ModsForHeskSettingsGateway;
class CustomerReplyController extends \BaseClass {
function post($ticketId) {
global $applicationContext, $hesk_settings;
$jsonRequest = JsonRetriever::getJsonData();
$createReplyByCustomerModel = new CreateReplyRequest();
$createReplyByCustomerModel->id = $ticketId;
$createReplyByCustomerModel->emailAddress = Helpers::safeArrayGet($jsonRequest, 'email');
$createReplyByCustomerModel->trackingId = Helpers::safeArrayGet($jsonRequest, 'trackingId');
$createReplyByCustomerModel->replyMessage = Helpers::safeArrayGet($jsonRequest, 'message');
$createReplyByCustomerModel->hasHtml = Helpers::safeArrayGet($jsonRequest, 'html');
$createReplyByCustomerModel->ipAddress = Helpers::safeArrayGet($jsonRequest, 'ip');
if ($createReplyByCustomerModel->ipAddress === null) {
$createReplyByCustomerModel->ipAddress = hesk_getClientIP();
}
/* @var $modsForHeskSettingsGateway ModsForHeskSettingsGateway */
$modsForHeskSettingsGateway = $applicationContext->get(ModsForHeskSettingsGateway::clazz());
$modsForHesk_settings = $modsForHeskSettingsGateway->getAllSettings($hesk_settings);
/* @var $replyCreator ReplyCreator */
$replyCreator = $applicationContext->get(ReplyCreator::clazz());
$createdReply = $replyCreator->createReplyByCustomer($createReplyByCustomerModel, $hesk_settings, $modsForHesk_settings);
return output($createdReply, 201);
}
}

@ -0,0 +1,24 @@
<?php
namespace DataAccess\Security;
use DataAccess\CommonDao;
class LoginGateway extends CommonDao {
function isIpLockedOut($ipAddress, $heskSettings) {
$this->init();
$rs = hesk_dbQuery("SELECT `number` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "logins`
WHERE `ip` = '" . hesk_dbEscape($ipAddress) . "'
AND `last_attempt` IS NOT NULL
AND DATE_ADD(`last_attempt`, INTERVAL ".intval($heskSettings['attempt_banmin'])." MINUTE ) > NOW() LIMIT 1");
$result = hesk_dbNumRows($rs) == 1 &&
hesk_dbResult($rs) >= $heskSettings['attempt_limit'];
$this->close();
return $result;
}
}

@ -100,6 +100,21 @@ class UserGateway extends CommonDao {
return $users;
}
function getUsersForUnassignedReplyNotification($heskSettings) {
$this->init();
$rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "users` WHERE `notify_reply_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();

@ -53,4 +53,22 @@ class StatusGateway extends CommonDao {
return $statuses;
}
function getStatusById($id, $heskSettings) {
$this->init();
$metaRs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "statuses` WHERE `ID` = " . $id);
$status = null;
if ($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;
}
}

@ -0,0 +1,33 @@
<?php
namespace DataAccess\Tickets;
use BusinessLogic\Tickets\CustomerCreatedReplyModel;
use DataAccess\CommonDao;
class ReplyGateway extends CommonDao {
function insertReply($ticketId, $name, $message, $html, $heskSettings) {
$this->init();
hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($heskSettings['db_pfix']) . "replies` (`replyto`,`name`,`message`,`dt`,`attachments`, `html`)
VALUES ({$ticketId},'" . hesk_dbEscape($name) . "','" . hesk_dbEscape($message) . "',NOW(),'','" . $html . "')");
$customerCreatedReplyModel = new CustomerCreatedReplyModel();
$id = hesk_dbInsertID();
$rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "replies` WHERE `id` = " . intval($id));
$row = hesk_dbFetchAssoc($rs);
$customerCreatedReplyModel->id = $row['id'];
$customerCreatedReplyModel->message = $row['message'];
$customerCreatedReplyModel->ticketId = $row['replyto'];
$customerCreatedReplyModel->dateCreated = hesk_date($row['dt'], true);
$customerCreatedReplyModel->html = $row['html'] === '1';
$customerCreatedReplyModel->replierName = $row['name'];
$this->close();
return $customerCreatedReplyModel;
}
}

@ -312,8 +312,8 @@ class TicketGateway extends CommonDao {
$generatedFields = new TicketGatewayGeneratedFields();
$generatedFields->id = $id;
$generatedFields->dateCreated = $row['dt'];
$generatedFields->dateModified = $row['lastchange'];
$generatedFields->dateCreated = hesk_date($row['dt'], true);
$generatedFields->dateModified = hesk_date($row['lastchange'], true);
$this->close();
@ -454,4 +454,34 @@ class TicketGateway extends CommonDao {
$this->close();
}
function areRepliesBeingFlooded($id, $ip, $heskSettings) {
$this->init();
$result = false;
$res = hesk_dbQuery("SELECT `staffid` FROM `" . hesk_dbEscape($heskSettings['db_pfix']) . "replies` WHERE `replyto`='{$id}' AND `dt` > DATE_SUB(NOW(), INTERVAL 10 MINUTE) ORDER BY `id` ASC");
if (hesk_dbNumRows($res) > 0) {
$sequential_customer_replies = 0;
while ($tmp = hesk_dbFetchAssoc($res)) {
$sequential_customer_replies = $tmp['staffid'] ? 0 : $sequential_customer_replies + 1;
}
if ($sequential_customer_replies > 10) {
hesk_dbQuery("INSERT INTO `".hesk_dbEscape($heskSettings['db_pfix'])."logins` (`ip`, `number`) VALUES ('".hesk_dbEscape($ip)."', ".intval($heskSettings['attempt_limit'] + 1).")");
$result = true;
}
}
$this->close();
return $result;
}
function updateMetadataForReply($id, $status, $heskSettings) {
$this->init();
hesk_dbQuery("UPDATE `" . hesk_dbEscape($heskSettings['db_pfix']) . "tickets` SET `lastchange`=NOW(), `status`='{$status}', `replies`=`replies`+1, `lastreplier`='0' WHERE `id`='{$id}'");
$this->close();
}
}

@ -199,6 +199,7 @@ Link::all(array(
'/v1-internal/categories/{i}/sort/{s}' => action(\Controllers\Categories\CategoryController::clazz() . '::sort', array(RequestMethod::POST), SecurityHandler::INTERNAL),
// Tickets
'/v1/tickets' => action(\Controllers\Tickets\CustomerTicketController::clazz(), RequestMethod::all(), SecurityHandler::OPEN),
'/v1/tickets/{i}/replies' => action(\Controllers\Tickets\CustomerReplyController::clazz(), array(RequestMethod::POST), SecurityHandler::OPEN),
// Tickets - Staff
'/v1/staff/tickets/{i}' => action(\Controllers\Tickets\StaffTicketController::clazz(), RequestMethod::all()),
'/v1/staff/tickets/{i}/due-date' => action(\Controllers\Tickets\StaffTicketController::clazz() . '::updateDueDate', array(RequestMethod::PATCH), SecurityHandler::INTERNAL_OR_AUTH_TOKEN),

Loading…
Cancel
Save