Warning: chmod(): Operation not permitted in /var/www/hopeinstoughton_www/wp-content/plugins/simple-universal-google-analytics/main.php on line 8 [0] in function chmod in /var/www/hopeinstoughton_www/wp-content/plugins/simple-universal-google-analytics/main.php on line 8 [1] in function include_once in /var/www/hopeinstoughton_www/wp-settings.php on line 560 [2] in function require_once in /var/www/hopeinstoughton_www/wp-config.php on line 85 [3] in function require_once in /var/www/hopeinstoughton_www/wp-load.php on line 50 [4] in function require_once in /var/www/hopeinstoughton_www/wp-blog-header.php on line 13 [5] in function require in /var/www/hopeinstoughton_www/index.php on line 17
| Server IP : 94.177.8.99 / Your IP : 216.73.217.165 Web Server : Apache/2.4.58 (Ubuntu) System : Linux aries 6.8.0-134-generic #134-Ubuntu SMP PREEMPT_DYNAMIC Fri Jun 26 18:43:11 UTC 2026 x86_64 User : www-data ( 33) PHP Version : 8.1.34 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/patientapps_support/modules/ticket/model/ |
Upload File : |
<?php
use ZBateson\MailMimeParser\Header\HeaderConsts;
require_once __DIR__ . '/messagebody_model.php';
class Ticket_model extends Database_Model {
protected $ticketnum;
protected $TICKET;
protected $model_customer;
protected $model_project;
protected $model_status;
protected $model_priority;
protected $hostname;
const SYMBOLS = 'BCDFGHJKLMNPQRSTVWXYZ';
const PLACEHOLDER = '# Please type your reply above this line #';
function __construct() {
parent::__construct();
$this->level = isset($_SESSION['current_user']) ? $_SESSION['current_user']['u_level'] : 0;
$this->user_id = isset($_SESSION['current_user']) ? $_SESSION['current_user']['u_id'] : 0;
$this->SETTINGS = $GLOBALS['loader']->LoadModuleSettings(__FILE__);
}
public function AssembleFieldlist($priority) { // assemble the form, put in default values
$fieldlist = [];
// create default fields.
$fieldlist[] = [
'type' => 'string',
'id' => 'name',
'name'=>'name',
'caption' => 'Name',
'required' => TRUE,
];
$fieldlist[] = [
'type' => 'email',
'id' => 'email',
'name'=>'email',
'caption' => 'E-mail',
'required' => TRUE,
];
/* $fieldlist[] = [ // we don't do anything with this yet.
'type' => 'tel',
'id' => 'phone',
'name'=>'phone',
'caption' => 'Phone',
'required' => FALSE,
]; */
$fieldlist[] = [
'type' => 'select',
'id' => 'priority',
'name'=>'priority',
'caption' => 'Priority',
'required' => FALSE,
'options' => $priority,
];
$fieldlist[] = [
'type' => 'string',
'id' => 'subject',
'name' => 'subject',
'caption' => 'Short Description of the Problem',
'required' => FALSE,
];
$fieldlist[] = [
'type' => 'text',
'id' => 'content',
'name'=>'content',
'caption' => 'Please explain the issue you\'re experiencing (with as much detail as possible)',
'required' => TRUE,
];
// add ability to upload attachments
return $fieldlist;
}
public function ModelCustomer() {
if (func_num_args()) {
$this->model_customer = func_get_arg(0);
return $this;
}
return $this->model_customer;
}
public function ModelProject() {
if (func_num_args()) {
$this->model_project = func_get_arg(0);
return $this;
}
return $this->model_project;
}
public function ModelStatus() {
if (func_num_args()) {
$this->model_status = func_get_arg(0);
return $this;
}
return $this->model_status;
}
public function ModelPriority() {
if (func_num_args()) {
$this->model_priority = func_get_arg(0);
return $this;
}
return $this->model_priority;
}
public function Hostname() {
if (func_num_args()) {
$this->hostname = func_get_arg(0);
return $this;
}
return $this->hostname;
}
// add a ticket
public function TicketNumber() {
if (func_num_args()) {
$this->ticketnum = trim(func_get_arg(0));
if ($this->ticketnum == '') {
$this->ticketnum = $this->_allocateNewTicketNum();
}
$this->Load();
return $this;
}
return $this->ticketnum;
}
public function Ticket() {
return $this->TICKET;
}
// member area
private function _allocateNewTicketNum() {
$yy = gmdate('y');
$selection = static::SYMBOLS;
do {
$number = $yy;
for($i = 0; $i < 4; $i++) {
$number .= $selection[mt_rand(0, strlen($selection) - 1)];
}
$data = $this->Query('select t_id from `ticket`')
->where('t_number=%s', $number)
->First();
$found = !$data->empty();
} while ($found);
$this->TableName('ticket')
->InsertOrUpdate([
't_number' => $number,
], [
't_status' => 'closed', // default to closed
't_client' => NULL,
't_created' => time(),
]);
return $number;
}
public function Load() {
$this->TICKET = $this->TableName('ticket')
->Query('select t.*,name_first,name_last,primary_email from `ticket` as t '.
'left join `users` on user_id=t_client')
->where('t_number=%s', $this->ticketnum)
->First();
}
// get Sender email address from pkpCore
public function getSender() {
$settings = $this->SettingGet('config.module_info', NULL);
$replyto = $settings['replyto'];
$settings = $GLOBALS['loader']->LoadModuleSettings('pkpCore');
$automated_sender = $settings['automated_sender'] ?? '';
if (empty($automated_sender)) $automated_sender = $replyto;
return $automated_sender;
}
public function sendEmail_create() {
$this->Load();
$data = [
'ticketnum' => $this->ticketnum,
'link' => $this->hostname . '/t/' . $this->ticketnum,
'auth' => rawurlencode(hook_execute('nonce.create', FALSE, 'ticket', $this->ticketnum, 86400 * 366)),
'placeholder' => static::PLACEHOLDER,
];
$automated_sender = $this->getSender();
$m = new \mailobject();
$sendresult = $m->template('ticket::created')
->to($this->TICKET->primary_email)
->from($automated_sender)
->data($data)
->Send();
$result = [];
if (is_string($sendresult)) {
$result['result'] = 17230;
$result['message'] = $sendresult;
} else {
$result['debug'] = $sendresult;
}
return $result;
}
public function sendEmail_reply($message_id) {
$data = [
'ticketnum' => $this->ticketnum,
'link' => $this->hostname . '/t/' . $this->ticketnum,
'auth' => rawurlencode(hook_execute('nonce.create', FALSE, 'ticket', $this->ticketnum, 86400 * 366)),
'subject' => $this->TICKET->t_subject,
'placeholder' => static::PLACEHOLDER,
];
$msg = new MessageBody;
$message = $msg->MessageId($message_id)
->Load();
$data['text'] = $message->tc_mimetype == 'text/html' ? 0 : 1;
$data['content'] = $message->tc_content;
$automated_sender = $this->getSender();
$m = new \mailobject();
$sendresult = $m->template('ticket::reply')
->to($this->TICKET->primary_email)
->from($automated_sender)
->data($data)
->Send();
$result = [];
if (is_string($sendresult)) {
$result['result'] = 17231;
$result['message'] = $sendresult;
} else {
$result['debug'] = $sendresult;
}
return $result;
}
// when extracting ticket number, it must be from the same email address??
protected function extractTicketNumber($text) {
$possibilities = [];
preg_replace_callback('~\d{2}[' . static::SYMBOLS . ']{3,5}~i', function($m) use (&$possibilities) {
$possibilities[] = $m[0];
}, $text);
if (!count($possibilities)) return FALSE;
$ticket = $this->TableName('ticket')
->Query('select t_id,t_number from `ticket`')
->whereIn('t_number', $possibilities)
->First();
return $ticket->empty() ? FALSE : $ticket->t_number;
}
public function ProcessEmail($message) {
// get the sender email
// add any cc to the ticket
$email = $message->getHeaderValue(HeaderConsts::FROM);
$name = $message->getHeader(HeaderConsts::FROM)->getPersonName();
$subject = $message->getHeaderValue(HeaderConsts::SUBJECT);
$emailid = $message->getHeaderValue(HeaderConsts::MESSAGE_ID);
$text = $message->getHtmlContent();
$contenttype = 'text/html';
if (is_null($text)) {
$text = $message->getTextContent();
$contenttype = 'text/plain';
}
$p = strrpos($text, 'You received this message because you are subscribed to the Google Groups');
if ($p !== FALSE) $text = substr($text, 0, $p);
// get ticket number from subject
$ticketnumber = $this->extractTicketNumber($subject);
$isNew = $ticketnumber === FALSE;
if ($isNew) {
$client_id = $this->createNewTicket($email, $name, $subject);
$ticketnumber = $this->TicketNumber();
} else {
$this->TicketNumber($ticketnumber);
$client_id = $this->TICKET->t_client;
}
$msg = new MessageBody;
$msg->MessageId(NULL)
->MimeType($contenttype);
if (!$isNew) { // if this is a new request, we need to keep everything. Otherwise discard message history
$text = $msg->DropHistory($text, static::PLACEHOLDER);
}
$msg->MessageId(NULL)
->TicketId($this->TICKET->t_id)
->Content($text)
->EmailId($emailid)
->DocType(1)
->Author($client_id);
$message_id = $msg->Save();
// add all the attachments
$attachments = $message->getAllAttachmentParts();
$dir = HOME . '/storage/attachment/';
foreach($attachments as $index => $attachment) {
if (!$index) {
if (!file_exists($dir)) mkdir($dir);
}
$content = $attachment->getBinaryContentStream();
$content_id = $attachment->getHeaderValue('content-id');
if (preg_match('~\<(.*)\>~', $content_id, $match)) {
$content_id = $match[1];
}
$hash = hash('sha256',$content);
// save attachment on filesystem
if (!file_exists("$dir$hash")) {
file_put_contents("$dir/$hash", $content);
}
// save attachment in database
// to do: add thumbnail
$x = $attachment->getHeaderParameter('content-disposition', 'filename');
if (is_null($x)) {
$x = $attachment->getHeaderParameter('content-type', 'name');
}
$this->TableName('ticket_attach')
->InsertOrUpdate([
'ta_conversation' => $message_id,
'ta_hash' => $hash,
], [
'ta_filename' => $x ?? $content_id,
'ta_filesize' => strlen($content),
'ta_contentid' => $content_id,
'ta_mimetype' => $attachment->getHeaderValue(HeaderConsts::CONTENT_TYPE),
]);
$this->DbErr();
}
if ($isNew) {
$this->sendEmail_create();
} else {
$this->SaveField('status', $this->SETTINGS['reply_stat']); // update the status to Open
}
}
public function createNewTicket($email, $name, $subject, $priority = 0, $body = FALSE, $contenttype = 'text/plain') {
$client_id = $this->model_customer->GetClientByEmail($email, $name);
// save ticket - get ticketnumber
$this->TicketNumber(''); // Create a new ticket
$tix = $this->Ticket();
$tix->t_subject = $subject;
$tix->t_priority = $priority;
$tix->t_client = $client_id;
$tix->t_status = $this->SETTINGS['new_stat']; // New
$tix->SaveRecord(['t_number' => $this->TicketNumber()]);
// save message body
if ($body !== FALSE) {
$msg = new MessageBody;
$msg->MessageId(NULL)
->TicketId($this->TICKET->t_id)
->Content($body)
->MimeType($contenttype)
->DocType(1)
->Author($client_id)
->Save();
}
return $client_id;
}
protected function filesize_formatted($size) {
$units = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
$power = $size > 0 ? floor(log($size, 1024)) : 0;
return number_format($size / pow(1024, $power), 2, '.', ',') . ' ' . $units[$power];
}
protected function iconfrommimetype($mime) {
switch(strtolower($mime)) {
case 'image/jpeg':
case 'image/png':
case 'image/gif':
case 'image/vnd.microsoft.icon':
case 'image/bmp':
case 'image/x-icon':
case 'image/svg+xml':
return 'fa-file-image';
case 'video/mp4':
case 'video/mpeg':
case 'video/quicktime':
case 'video/x-msvideo':
case 'video/x-ms-wmv':
return 'fa-file-video';
case 'application/msword':
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
return 'fa-file-word';
case 'application/vnd.ms-excel':
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
return 'fa-file-excel';
case 'application/pdf':
return 'fa-file-pdf';
case 'text/plain':
return 'fa-file-alt';
case 'text/html':
case 'application/xml':
return 'fa-file-code';
default:
return 'fa-file';
}
}
public function getAttachments() {
$tix = $this->Ticket();
$attachments = $this->Query('select distinct ta_hash, ta.* '.
'from ticket_attach as ta '.
'inner join ticket_line as tl on ta.ta_conversation=tl.tc_id ')
->Where('tc_ticket=%d', $tix->t_id)
->GroupBy('ta_hash')
->OrderBy('tc_createdon')
->Get();
// attachments
$attaches = [];
foreach($attachments as $attach) {
$icon = $this->iconfrommimetype($attach->ta_mimetype);
$attaches[] = [
'type' => 'icon',
'filename' => $attach->ta_filename,
'icon' => "<i class=\"far $icon\"></i>",
'size' => $this->filesize_formatted($attach->ta_filesize),
'item' => $attach->ta_conversation,
'hash' => $attach->ta_hash,
];
}
// unsaved attachments
if (isset($_SESSION['upload'][$this->TicketNumber()])) {
foreach($_SESSION['upload'][$this->TicketNumber()] as $hash => $entry) {
$icon = $this->iconfrommimetype($entry['mime'] ?? 'application/octet-stream');
$attaches[] = [
'type' => 'icon',
'draft' => 1,
'filename' => $entry['name'],
'icon' => "<i class=\"far $icon\"></i>",
'size' => $this->filesize_formatted($entry['size']),
'item' => -1,
'hash' => $hash,
];
}
}
if (1) { // only if editable
$attaches[] = [ // space to upload a file
'type' => 'file',
];
}
return $attaches;
}
// retrieve all info
public function AsHandlebars() {
$tix = $this->Ticket();
$this->Query('select t.*, '.
'trim(concat(u.name_first, " ", u.name_last)) as _name '.
'from `ticket_line` as t '.
'left join `users` as u on u.user_id=tc_createdby '
)
->Where('tc_ticket=%d', $tix->t_id);
if ($this->level < 2) {
$this->Where('tc_type <> 0');
}
$content = $this->OrderBy('tc_id')
->Get();
$first = $content[0];
// history
$msg = new MessageBody;
$history = [];
for($i = count($content) - 1; $i >= 1; $i--) {
$msg->ITEM($content[$i]);
$history[] = $msg->AsArray();
}
$msg->ITEM($first);
$data = [
'number' => $this->TicketNumber(),
'subject' => $tix->t_subject,
'desc' => $msg->AsArray(),
'page_title' => $this->TicketNumber() . '. ' . $tix->t_subject,
];
if (count($history)) {
$data['history'] = $history;
}
$attaches = $this->getAttachments();
if (count($attaches)) {
$data['attachment'] = $attaches;
}
// header
$data['project'] = $tix->t_project;
if (is_object($this->model_project)) {
$data['project_desc'] = $this->model_project->AsDescription($tix->t_project);
if ($this->level >= 2) {
$data['list_project'] = $this->model_project->AsHandleBars();
}
}
$data['status'] = $tix->t_status;
if (is_object($this->model_status)) {
$data['status_desc'] = $this->model_status->AsDescription($tix->t_status);
if ($this->level >= 2) {
$data['list_status'] = $this->model_status->AsHandleBars();
}
}
$data['priority'] = $tix->t_priority;
if (is_object($this->model_priority)) {
$data['priority_desc'] = $this->model_priority->AsDescription($tix->t_priority);
}
return $data;
}
protected function LocateTemporaryImage($image_id) {
$data = new \StdClass;
$data->ta_hash = $image_id;
$data->ta_mimetype = 'application/octet-stream';
$data->ta_filename = $image_id;
if (isset($_SESSION['upload'])) {
foreach($_SESSION['upload'] as $ticket => $ticketinfo) {
if (isset($ticketinfo[$image_id])) {
$data->ta_mimetype = $ticketinfo[$image_id]['mime'] ?? 'application/octet-stream';
$data->ta_filename = $ticketinfo[$image_id]['name'];
break;
}
}
}
return $data;
}
public function InlineImage($item_id, $image_id) {
$dir = HOME . '/storage/attachment/';
if ($item_id == -1 && preg_match('~[0-9a-f]{64}~i', $image_id)) { // a temporary image - not yet saved
$dir = $dir = HOME . '/storage/file-upload/';
$data = $this->LocateTemporaryImage($image_id);
} elseif (preg_match('~[0-9a-f]{64}~i', $image_id)) {
$data = $this->Query('select * from `ticket_attach`')
->Where('ta_conversation=%d', $item_id)
->Where('ta_hash=%s', $image_id)
->First();
} else {
$data = $this->Query('select * from `ticket_attach`')
->Where('ta_conversation=%d', $item_id)
->Where('ta_contentid=%s', $image_id)
->First();
}
if (method_exists($data, 'empty') && $data->empty()) {
header('Content-type: text/plain');
return 'Attachment not found';
}
$hash = $data->ta_hash;
if (!file_exists("$dir$hash")) {
header('Content-type: text/plain');
return 'Attachment is missing';
}
header('Content-type: ' . $data->ta_mimetype);
header("Content-disposition: inline; filename=\"{$data->ta_filename}\"");
return file_get_contents("$dir$hash");
}
private function datatableList_yourtickets() {
// $this->user_id
$rows = $this->Query('select * from `ticket`')
->Where('t_client=%d', $this->user_id)
->OrderBy('t_created desc')
->Get();
$output = [];
foreach($rows as $row) {
$output[] = [
'priority' => $row->t_priority,
'date' => gmdate('Y-m-d H:i', $row->t_created),
'ticket' => $row->t_number,
'subject' => $row->t_subject,
'status' => $row->t_status,
];
}
return $output;
}
private function datatableList_new() {
// $this->user_id
$rows = $this->Query('select t.*,name_first,name_last from `ticket` as t '.
'left join `users` on user_id=t_client')
->Where('t_status=%s', 'new')
->OrderBy('t_created desc')
->Get();
$output = [];
foreach($rows as $row) {
$output[] = [
'priority' => $row->t_priority,
'date' => gmdate('Y-m-d H:i', $row->t_created),
'ticket' => $row->t_number,
'subject' => $row->t_subject,
'status' => $row->t_status,
'client' => trim($row->name_first . ' ' . $row->name_last),
];
}
return $output;
}
public function datatableList($action) {
$data = NULL;
switch($action) {
case 'request':
$data = $this->datatableList_yourtickets();
$this->model_status->ApplyStatus($data);
break;
case 'new':
$data = $this->datatableList_new();
$this->model_status->ApplyStatus($data);
break;
case 'open':
$data = [];
break;
}
return [ 'data' => $data ] ;
}
// attach newly uploaded files to the given message,
// or if 0, then to the first.
protected function ConfirmUpload($message_id) {
$tix = $this->Ticket();
if (!$message_id) {
$this->Query('select * from ticket_line')
->Where('tc_ticket=%d', $tix->t_id);
if ($this->level < 2) {
$this->Where('tc_type <> 0');
}
$first = $this->OrderBy('tc_ic')
->Get()
->First();
$message_id = (int) $first->tc_id;
}
// foreach upload - add attachment record
$ticketnum = $this->TicketNumber();
if (isset($_SESSION['upload'][$ticketnum])) {
foreach($_SESSION['upload'][$ticketnum] as $hash => $info) {
// copy to attachments
$source = HOME . '/storage/file-upload/';
$target = HOME . '/storage/attachment/';
copy($source . $hash, $target . $hash);
$this->TableName('ticket_attach')
->InsertOrUpdate([
'ta_conversation' => $message_id,
'ta_hash' => $hash,
], [
'ta_filename' => $info['name'],
'ta_filesize' => $info['size'],
'ta_contentid' => '',
'ta_mimetype' => $info['mime'],
]);
$this->DbErr();
unset($_SESSION['upload'][$ticketnum][$hash]);
}
}
}
public function AddReply($action, $message) {
if ($message != '') {
// action: comment email update
switch($action) {
case 'comment': // staff adding a comment to ticket - client does not see it - ticket status does not change
$msg = new MessageBody;
$msg->MessageId(NULL)
->TicketId($this->TICKET->t_id)
->Content($message)
->MimeType('text/plain')
->DocType(0)
->Author($this->user_id);
$message_id = $msg->Save();
$this->ConfirmUpload($message_id);
return []; // all ok
case 'email': // staff adding message to ticket - client emailed a copy - ticket status changes
$msg = new MessageBody;
$msg->MessageId(NULL)
->TicketId($this->TICKET->t_id)
->Content($message)
->MimeType('text/plain')
->DocType(-1)
->Author($this->user_id);
$message_id = $msg->Save();
$this->SaveUpload($message_id);
$this->ConfirmUpload('status', 'wait');
// send email
$result = $this->sendEmail_reply($message_id);
return $result; // send email, return email result
case 'update': // client adding to ticket. no email, ticket status changes
$msg = new MessageBody;
$msg->MessageId(NULL)
->TicketId($this->TICKET->t_id)
->Content($message)
->MimeType('text/plain')
->DocType(-1)
->Author($this->user_id);
$message_id = $msg->Save();
$this->ConfirmUpload($message_id);
$this->SaveField('status', $this->SETTINGS['reply_stat']);
// send email to CCs?
// send event?
return [];
}
} else {
$this->ConfirmUpload(0); // save attachments to first message
}
}
public function SaveField($name, $value) {
$data = [];
switch($name) {
case 'status':
$data['t_status'] = $value;
break;
case 'project':
$data['t_project'] = $value;
break;
}
$this->TableName('ticket')
->InsertOrUpdate([
't_number' => $this->TicketNumber(),
], $data);
$this->DbErr();
return "1";
}
public function SaveUpload($FILE) {
$dir = HOME . '/storage/file-upload/';
// delete anything older than 1hr
// save file in storage
$content = file_get_contents($FILE['tmp_name']);
$hash = hash('sha256',$content);
file_put_contents($dir . $hash, $content);
// update session - ticket, filename, hash
$ticket = $this->TicketNumber();
$_SESSION['upload'][$ticket][$hash] = [
'name' => $FILE['name'],
'size' => $FILE['size'],
'mime' => $FILE['type'],
];
}
public function RemoveUpload($hash) {
$ticket = $this->TicketNumber();
unset($_SESSION['upload'][$ticket][$hash]);
}
}