OpenXE/www/pages/ticket.php

1164 lines
50 KiB
PHP

<?php
/*
* Copyright (c) 2022 OpenXE project
*/
use Xentral\Components\Database\Exception\QueryFailureException;
use Xentral\Modules\Ticket\Task;
class Ticket {
function __construct($app, $intern = false) {
$this->app = $app;
if ($intern)
return;
$this->app->ActionHandlerInit($this);
$this->app->ActionHandler("list", "ticket_list");
$this->app->ActionHandler("create", "ticket_create"); // This automatically adds a "New" button
$this->app->ActionHandler("edit", "ticket_edit");
$this->app->ActionHandler("minidetail", "ticket_minidetail");
$this->app->ActionHandler("text", "ticket_text"); // Output text for iframe display
$this->app->ActionHandler("text_ausgang", "ticket_text_ausgang"); // Output text for iframe display
$this->app->ActionHandler("statusfix", "ticket_statusfix"); // Xentral 20 compatibility set all ticket status to latest ticket_nachricht status
$this->app->ActionHandler("datefix", "ticket_datefix"); // Xentral 20 compatibility set all ticket dates to latest ticket_nachricht date
$this->app->ActionHandler("dateien", "ticket_dateien");
$this->app->DefaultActionHandler("list");
$this->app->ActionHandlerListen($app);
}
public function Install() {
/* Fill out manually later */
}
function ticket_status_icon(string $status) {
return('<img src="./themes/new/images/status_'.$status.'.png" style="margin-right:1px" title="'.$status.'" border="0">');
}
public function TableSearch(&$app, $name, $erlaubtevars) {
function ticket_iconssql() {
return "CONCAT('<img src=\"./themes/new/images/status_',`t`.`status`,'.png\" style=\"margin-right:1px\" title=\"',`t`.`status`,'\" border=\"0\">')";
}
switch ($name) {
case "ticket_list":
$allowed['ticket_list'] = array('list');
$heading = array('','','Ticket #', 'Aktion','Adresse', 'Betreff', 'Tags', 'Verant.', 'Nachr.', 'Status', 'Projekt', 'Men&uuml;');
$width = array('1%','1%','5%', '5%', '5%', '30%', '1%', '5%', '1%', '1%', '1%', '1%');
$findcols = array('t.id','t.id','t.schluessel', 't.zeit', 'a.name', 't.betreff', 't.tags', 'w.warteschlange', 'nachrichten_anz', 't.status', 'p.abkuerzung');
$searchsql = array( 't.schluessel', 't.zeit', 'a.name', 't.betreff','t.notiz', 't.tags', 'w.warteschlange', 't.status', 'p.abkuerzung','(SELECT mail FROM ticket_nachricht tn WHERE tn.ticket = t.schluessel AND tn.versendet <> 1 LIMIT 1)');
$defaultorder = 1;
$defaultorderdesc = 0;
$menu = "<table cellpadding=0 cellspacing=0><tr><td nowrap>" . "<a href=\"index.php?module=ticket&action=edit&id=%value%\"><img src=\"./themes/{$app->Conf->WFconf['defaulttheme']}/images/edit.svg\" border=\"0\"></a>" . "</td></tr></table>";
$timedifference = "if (
TIMESTAMPDIFF(hour, t.zeit, NOW()) < 24,
CONCAT(TIMESTAMPDIFF(hour, t.zeit, NOW()),'h'),
CONCAT(TIMESTAMPDIFF(day, t.zeit, NOW()), 'd ',MOD(TIMESTAMPDIFF(hour, t.zeit, NOW()),24),'h'))";
$dropnbox = "'<img src=./themes/new/images/details_open.png class=details>' AS `open`,
CONCAT('<input type=\"checkbox\" name=\"auswahl[]\" value=\"',t.id,'\" />') AS `auswahl`";
$priobetreff = "if(t.prio!=1,REGEXP_REPLACE(t.betreff, '<[^>]*>+', ''),CONCAT('<b><font color=red>',REGEXP_REPLACE(t.betreff, '<[^>]*>+', ''),'</font></b>'))"; //+ #20230916 XSS
$anzahlnachrichten = "(SELECT COUNT(n.id) FROM ticket_nachricht n WHERE n.ticket = t.schluessel)";
$letztemail = $app->erp->FormatDateTimeShort("(SELECT MAX(n.zeit) FROM ticket_nachricht n WHERE n.ticket = t.schluessel AND n.zeit IS NOT NULL)");
$tagstart = "<li class=\"tag-editor-tag\">";
$tagend = "</li>";
$sql = "SELECT SQL_CALC_FOUND_ROWS
t.id,
".$dropnbox.",
CONCAT('<a href=\"index.php?module=ticket&action=edit&id=',t.id,'\">',t.schluessel,'</a>'),".
$app->erp->FormatDateTimeShort('zeit')." as aktion,
CONCAT(COALESCE(CONCAT(a.name,'<br>'),''),COALESCE((SELECT mail FROM ticket_nachricht tn WHERE tn.ticket = t.schluessel AND tn.versendet <> 1 LIMIT 1),'')) as combiadresse,
CONCAT('<b>',".$priobetreff.",'</b><br/><i>',replace(substring(ifnull(t.notiz,''),1,500),'\n','<br/>'),'</i>'),
CONCAT('<div class=\"ticketoffene\"><ul class=\"tag-editor\">'\n,'".$tagstart."',replace(t.tags,',','".$tagend."<div class=\"tag-editor-spacer\">&nbsp;</div>".$tagstart."'),'".$tagend."','</ul></div>'),
w.warteschlange,
".$anzahlnachrichten." as `nachrichten_anz`,
".ticket_iconssql().",
p.abkuerzung,
t.id
FROM ticket t
LEFT JOIN adresse a ON t.adresse = a.id
LEFT JOIN warteschlangen w ON t.warteschlange = w.label
LEFT JOIN projekt p on t.projekt = p.id";
$where = "1";
// Toggle filters
$this->app->Tpl->Add('JQUERYREADY', "$('#meinetickets').click( function() { fnFilterColumn1( 0 ); } );");
$this->app->Tpl->Add('JQUERYREADY', "$('#prio').click( function() { fnFilterColumn2( 0 ); } );");
$this->app->Tpl->Add('JQUERYREADY', "$('#geschlossene').click( function() { fnFilterColumn3( 0 ); } );");
$this->app->Tpl->Add('JQUERYREADY', "$('#spam').click( function() { fnFilterColumn4( 0 ); } );");
for ($r = 1;$r <= 4;$r++) {
$this->app->Tpl->Add('JAVASCRIPT', '
function fnFilterColumn' . $r . ' ( i )
{
if(oMoreData' . $r . $name . '==1)
oMoreData' . $r . $name . ' = 0;
else
oMoreData' . $r . $name . ' = 1;
$(\'#' . $name . '\').dataTable().fnFilter(
\'\',
i,
0,0
);
}
');
}
$more_data1 = $this->app->Secure->GetGET("more_data1");
if ($more_data1 == 1) {
$where .= " AND t.warteschlange IN (SELECT w.label FROM warteschlangen w WHERE adresse=".$this->app->User->GetAdresse().")"; // Queues of user
} else {
}
$more_data2 = $this->app->Secure->GetGET("more_data2");
if ($more_data2 == 1) {
$where .= " AND t.prio = '1'";
}
else {
}
$more_data3 = $this->app->Secure->GetGET("more_data3");
if ($more_data3 == 1) {
}
else {
$where .= " AND (t.status <> 'abgeschlossen')"; // Exclude and geschlossen
}
$more_data4 = $this->app->Secure->GetGET("more_data4");
if ($more_data4 == 1) {
}
else {
$where .= " AND (t.status <> 'spam')";
}
// END Toggle filters
$moreinfo = true; // Allow drop down details
$menucol = 11; // For moredata
$count = "SELECT count(DISTINCT id) FROM ticket t WHERE $where";
// echo(htmlentities($sql." ".$where));
// $groupby = "";
break;
}
$erg = false;
foreach ($erlaubtevars as $k => $v) {
if (isset($$v)) {
$erg[$v] = $$v;
}
}
return $erg;
}
// Ensure status 'offen' on self-assigned tickets
function ticket_set_self_assigned_status(array $ids) {
$sql = "UPDATE ticket SET status = 'offen'
WHERE
status = 'neu'
AND id IN (".implode(',',$ids).")
AND warteschlange IN (SELECT label FROM warteschlangen WHERE adresse = '".$this->app->User->GetAdresse()."')";
$this->app->DB->Update($sql);
}
function ticket_list() {
// Process multi action
$auswahl = $this->app->Secure->GetPOST('auswahl');
$submit = $this->app->Secure->GetPOST('submit');
$selectedIds = [];
if(!empty($auswahl)) {
foreach($auswahl as $selectedId) {
$selectedId = (int)$selectedId;
if($selectedId > 0) {
$selectedIds[] = $selectedId;
}
}
switch ($submit) {
case 'zuordnen':
$status = $this->app->Secure->GetPOST('status');
$warteschlange = $this->app->Secure->GetPOST('warteschlange');
$sql = "UPDATE ticket SET status = '".$status."', zeit = NOW()";
if ($warteschlange != '') {
$sql .= ", warteschlange = '".explode(" ",$warteschlange)[0]."'";
}
$sql .= " WHERE id IN (".implode(",",$selectedIds).")";
$this->app->DB->Update($sql);
$this->ticket_set_self_assigned_status($selectedIds);
break;
case 'spam_filter':
if($this->app->erp->RechteVorhanden('ticketregeln','create')) {
$sql = "UPDATE ticket SET status = 'spam', zeit = NOW()";
$sql .= " WHERE id IN (".implode(",",$selectedIds).")";
$this->app->DB->Update($sql);
foreach ($selectedIds as $selectedId) {
// Check existing
$sql = "SELECT id FROM ticket_regeln WHERE
empfaenger_email = '' AND
sender_email = (SELECT mailadresse FROM ticket WHERE id = ".$selectedId." LIMIT 1) AND
name = '' AND
betreff = '' AND
spam = 1 AND
aktiv = 1
";
if (!$this->app->DB->Select($sql)) {
$sql = "INSERT IGNORE INTO ticket_regeln (
empfaenger_email,
sender_email,
name,
betreff,
spam,
persoenlich,
prio,
dsgvo,
adresse,
warteschlange,
aktiv
) VALUES (
'',
(SELECT mailadresse FROM ticket WHERE id = ".$selectedId." LIMIT 1),
'',
'',
1,
0,
0,
0,
0,
'',
1
)";
$this->app->DB->Insert($sql);
}
}
}
break;
}
}
// List
$this->app->YUI->TagEditor('taglist', array('width'=>370));
$this->app->Tpl->Add('SCRIPTJAVASCRIPT','<link rel="stylesheet" type="text/css" href="./css/jquery.tag-editor.css">');
$this->app->erp->MenuEintrag("index.php?module=ticket&action=list", "&Uuml;bersicht");
$this->app->erp->MenuEintrag("index.php?module=ticket&action=create", "Neu anlegen");
$this->app->erp->MenuEintrag("index.php", "Zur&uuml;ck");
$this->app->Tpl->Set('STATUS', $this->app->erp->GetStatusTicketSelect('neu'));
$this->app->YUI->AutoComplete("warteschlange","warteschlangename");
if(!$this->app->erp->RechteVorhanden('ticketregeln','create')) {
$this->app->Tpl->Set('SPAM_HIDDEN', 'hidden');
}
$this->app->YUI->TableSearch('TAB1', 'ticket_list', "show", "", "", basename(__FILE__), __CLASS__);
$this->app->Tpl->Parse('PAGE', "ticket_list.tpl");
}
function get_messages_of_ticket($ticket_id, $where, $limit) {
if ($limit) {
$limitsql = " LIMIT ".((int) $limit);
} else {
$limitsql = "";
}
if (empty($ticket_id)) {
$ticket_where = "";
} else {
$ticket_where = " AND t.id = ".$ticket_id;
}
if (empty($where)) {
$where = "1";
}
// $sql = "SELECT n.id, n.betreff, n.verfasser, n.mail, n.mail_cc, n.zeit, n.zeitausgang, n.versendet, n.text, n.verfasser_replyto, mail_replyto, (SELECT GROUP_CONCAT(value SEPARATOR ', ' FROM ticket_header th WHERE th.ticket_nachricht = n.id AND th.type = 'cc') value from) as cc FROM ticket_nachricht n INNER JOIN ticket t ON t.schluessel = n.ticket WHERE (".$where.") AND t.id = ".$ticket_id." ORDER BY n.zeit DESC ".$limitsql;
$sql = "SELECT n.id,
n.betreff,
n.bearbeiter,
n.verfasser,
n.mail,
t.quelle,
".$this->app->erp->FormatDateTimeShort('n.zeit','zeit').",
".$this->app->erp->FormatDateTimeShort('n.zeitausgang','zeitausgang').",
n.versendet,
n.text,
n.textausgang,
n.verfasser_replyto,
n.mail_replyto,
n.mail_cc,
(SELECT GROUP_CONCAT(value SEPARATOR ', ') FROM ticket_header th WHERE th.ticket_nachricht = n.id AND th.type = 'cc') as mail_cc_recipients,
(SELECT GROUP_CONCAT(value SEPARATOR ', ') FROM ticket_header th WHERE th.ticket_nachricht = n.id AND th.type = 'to') as mail_recipients
FROM ticket_nachricht n INNER JOIN ticket t ON t.schluessel = n.ticket
WHERE (".$where.") ".$ticket_where." ORDER BY n.zeit DESC ".$limitsql;
return $this->app->DB->SelectArr($sql);
}
function add_attachments_html($ticket_id, $message_id,$templatepos,$showdelete) {
$file_attachments = $this->app->erp->GetDateiSubjektObjekt('Anhang','Ticket',$message_id);
if (!empty($file_attachments)) {
$this->app->Tpl->Add('NACHRICHT_ANHANG',"<hr style=\"border-style:solid; border-width:1px\">");
foreach ($file_attachments as $file_attachment) {
if ($showdelete) {
$deletetext = '<a href=index.php?module=ticket&action=edit&id='.$ticket_id.'&cmd=deleteattachment'.'&fileid='.$file_attachment.'>'.
'<img src="./themes/' . $this->app->Conf->WFconf['defaulttheme'] . '/images/delete.svg" /></a>';
} else {
$deletetext = "";
}
$attachtext = "";
/* Not implemented -> Attachment of ticket_nachricht to business object is the better option -> implement later
$attachtext = '<a href=index.php?module=dateien&action=edit&id='.$file_attachment.'>'.
'<img src="./themes/' . $this->app->Conf->WFconf['defaulttheme'] . '/images/copy.svg" /></a>'; */
$this->app->Tpl->Add($templatepos,
"<a href=\"index.php?module=dateien&action=send&id=".$file_attachment.
"\">".
htmlentities($this->app->erp->GetDateiName($file_attachment)).
" (".
$this->app->erp->GetDateiSize($file_attachment).
")".
"</a>".
$deletetext.
$attachtext.
"<br>");
}
}
}
function add_attachments_header_html($ticket_id, $templatepos) {
$file_attachments = $this->app->erp->GetDateiSubjektObjekt('%','ticket_header',$ticket_id);
if (!empty($file_attachments)) {
$this->app->Tpl->Add($templatepos,"<tr><td>{|Anh&auml;nge|}:</td><td><div class=\"ticket_attachments\">");
foreach ($file_attachments as $file_attachment) {
$this->app->Tpl->Add($templatepos,
"<a href=\"index.php?module=dateien&action=send&id=".$file_attachment.
"\">".
htmlentities($this->app->erp->GetDateiName($file_attachment)).
" (".
$this->app->erp->GetDateiSize($file_attachment).
")".
"</a>".
"<br>");
}
$this->app->Tpl->Add($templatepos,"</div></td></tr>");
}
}
function add_messages_tpl($messages, $showdrafts) {
// Add Messages now
foreach ($messages as $message) {
$message['betreff'] = strip_tags($message['betreff']); //+ #20230916 XSS
// Clear this first
$this->app->Tpl->Set('NACHRICHT_ANHANG',"");
if (empty($message['betreff'])) {
$message['betreff'] = "...";
}
// Xentral 20 compatibility
if ($message['textausgang'] != '') {
// Sent message
$this->app->Tpl->Set("NACHRICHT_BETREFF",'<a href="index.php?module=ticket&action=text_ausgang&mid='.$message['id'].'" target="_blank">'.htmlentities($message['betreff']).'</a>');
$this->app->Tpl->Set("NACHRICHT_ZEIT",$message['zeitausgang']);
$this->app->Tpl->Set("NACHRICHT_FLOAT","right");
$this->app->Tpl->Set("META_FLOAT","left");
$this->app->Tpl->Set("NACHRICHT_TEXT",$message['textausgang']);
$this->app->Tpl->Set("NACHRICHT_SENDER",htmlentities($message['bearbeiter']));
$this->app->Tpl->Set("NACHRICHT_RECIPIENTS",htmlentities($message['verfasser']." <".$message['mail'].">"));
// $this->app->Tpl->Set("NACHRICHT_TEXT",$message['textausgang']);
$this->app->Tpl->Set("NACHRICHT_TEXT",'<iframe class="ticket_text" src="index.php?module=ticket&action=text_ausgang&mid='.$message['id'].'"></iframe>');
$this->app->Tpl->Parse('MESSAGES', "ticket_nachricht.tpl");
}
if ($message['versendet'] == '1' && empty($message['textausgang'])) { // textausgang is always empty, except for old Xentral 20 tickets
// Sent message
if (is_null($message['zeitausgang'])) {
if (!$showdrafts) {
continue;
}
$this->app->Tpl->Set("NACHRICHT_BETREFF",htmlentities($message['betreff']." (Entwurf)"));
} else {
$this->app->Tpl->Set("NACHRICHT_BETREFF",'<a href="index.php?module=ticket&action=text&mid='.$message['id'].'&insecure=1" target="_blank">'.htmlentities($message['betreff']).'</a>');
}
$this->app->Tpl->Set("NACHRICHT_SENDER",htmlentities($message['verfasser']." <".$message['mail_replyto'].">"));
$this->app->Tpl->Set("NACHRICHT_RECIPIENTS",htmlentities($message['mail']));
$this->app->Tpl->Set("NACHRICHT_CC_RECIPIENTS",htmlentities($message['mail_cc']));
$this->app->Tpl->Set("NACHRICHT_FLOAT","right");
$this->app->Tpl->Set("META_FLOAT","left");
$this->app->Tpl->Set("NACHRICHT_ZEIT",$message['zeitausgang']);
$this->app->Tpl->Set("NACHRICHT_NAME",htmlentities($message['verfasser']));
} else {
// Received message
$this->app->Tpl->Set("NACHRICHT_SENDER",htmlentities($message['verfasser']." <".$message['mail'].">"));
if ($message['mail_recipients'] != '') {
$this->app->Tpl->Set("NACHRICHT_RECIPIENTS",htmlentities($message['mail_recipients']));
}
else {
// Xentral 20 compatibility
$this->app->Tpl->Set("NACHRICHT_RECIPIENTS",htmlentities($message['quelle']));
}
$this->app->Tpl->Set("NACHRICHT_CC_RECIPIENTS",htmlentities($message['mail_cc_recipients']));
$this->app->Tpl->Set("NACHRICHT_BETREFF",'<a href="index.php?module=ticket&action=text&mid='.$message['id'].'&insecure=1" target="_blank">'.htmlentities($message['betreff']).'</a>');
$this->app->Tpl->Set("NACHRICHT_FLOAT","left");
$this->app->Tpl->Set("META_FLOAT","right");
$this->app->Tpl->Set("NACHRICHT_ZEIT",$message['zeit']);
}
// $this->app->Tpl->Set("NACHRICHT_TEXT",$message['text']);
$this->app->Tpl->Set("NACHRICHT_TEXT",'<iframe class="ticket_text" src="index.php?module=ticket&action=text&mid='.$message['id'].'"></iframe>');
$this->add_attachments_html($id,$message['id'],'NACHRICHT_ANHANG',false);
$this->app->Tpl->Parse('MESSAGES', "ticket_nachricht.tpl");
}
}
function ticket_text() {
$secure_html_tags = array(
'<br>',
'<p>',
'<strong>',
'<b>',
'<table>',
'<tr>',
'<td>',
'<style>',
'<ol>',
'<ul>',
'<li>',
'<dd>',
'<dt>',
'<img>'
);
$mid = $this->app->Secure->GetGET('mid');
$insecure = $this->app->Secure->GetGET('insecure');
if (empty($mid)) {
return;
}
$messages = $this->get_messages_of_ticket("", "n.id = ".$mid, NULL);
if (empty($messages)) {
}
$html_start = "<!DOCTYPE html><html>";
$head_start = "<head>";
$security = "";
$style = "<link rel=\"stylesheet\" type=\"text/css\" href=\"./themes/new/css/ticket_iframe.css?v=3\"/>";
$head_end = "</head>";
$html_end = "</html>";
$prepared_text = $messages[0]['text'];
// Adjust cid images
$attachments = $this->app->erp->GetDateiSubjektObjekt('Anhang','Ticket',$mid);
foreach($attachments as $attachment) {
$filename = $this->app->erp->GetDateiName($attachment);
$prepared_text = str_replace($filename,'index.php?module=dateien&action=send&id='.$attachment,$prepared_text);
}
if ($insecure) {
// Add Content Security Policy
} else {
// Add Content Security Policy
$security = "<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self';\" />";
// Strip html tags
$stripped_prepared_text = strip_tags($prepared_text,$secure_html_tags);
if (strlen($stripped_prepared_text) != strlen($prepared_text)) {
$stripped_prepared_text = "<img class=\"eye blink\" src=\"./themes/{$this->app->Conf->WFconf['defaulttheme']}/images/icon-invisible.svg\" alt=\"Einige Elemente wurden durch OpenXE blockiert.\" title=\"Einige Elemente wurden durch OpenXE blockiert.\" border=\"0\">".$stripped_prepared_text;
}
$prepared_text = $stripped_prepared_text;
}
$this->app->Tpl->Set("TEXT",$html_start.$head_start.$security.$style.$head_end.$prepared_text.$html_end);
$this->app->Tpl->Output('ticket_text.tpl');
$this->app->ExitXentral();
}
function ticket_text_ausgang() {
$mid = $this->app->Secure->GetGET('mid');
if (empty($mid)) {
return;
}
$messages = $this->get_messages_of_ticket("", "n.id = ".$mid, NULL);
if (empty($messages)) {
}
$this->app->Tpl->Set("TEXT",$messages[0]['textausgang']);
$this->app->Tpl->Output('ticket_text.tpl');
$this->app->ExitXentral();
}
/**
* @throws NumberGeneratorException
*
* @return string
*/
private function generateRandomTicketNumber(): string
{
$random = rand(300,700);
$loopCounter = 0;
while(true) {
$candidate = sprintf('%s%04d', date('Ymd'), $random++);
if (!$this->app->DB->Select('SELECT id FROM ticket WHERE schluessel = '.$candidate)) {
return($candidate);
}
if ($loopCounter > 99) {
throw new NumberGeneratorException('ticket number generation failed');
}
$loopCounter++;
}
}
function ticket_save_to_db($id, $input) {
// Write to database
// Add checks here
if (empty($id)) {
// New item
$id = 'NULL';
}
if ($input['betreff'] == '') {
$input['betreff'] = "...";
}
$input['projekt'] = $this->app->erp->ReplaceProjekt(true,$input['projekt'],true); // Parameters: Target db?, value, from form?
$input['adresse'] = $this->app->erp->ReplaceAdresse(true,$input['adresse'],true); // Parameters: Target db?, value, from form?
$input['warteschlange'] = explode(" ",$input['warteschlange'])[0]; // Just the label
$input['zeit'] = date('Y-m-d H:i:s', time());
$tags = explode(',',$input['tags']);
// Replace multiple '!' and '?'
foreach ($tags as &$tag) {
$pos = strpos($tag, '?');
if ($pos !== false) {
$tag = substr($tag,0,$pos+1) . str_replace('?','',substr($tag,$pos+1));
}
$tag = preg_replace("/([?!])\\1+/", "$1", $tag);
}
$input['tags'] = implode(',',$tags);
$input['tags'] = str_replace(' ?','?',$input['tags']);
$input['tags'] = str_replace(' !','!',$input['tags']);
$input['tags'] = str_replace('?!','?',$input['tags']);
$input['tags'] = str_replace('!?','?',$input['tags']);
$columns = "id, ";
$values = "$id, ";
$update = "";
$fix = "";
foreach ($input as $key => $value) {
if ($this->app->DB->ColumnExists('ticket',$key)) {
$columns = $columns.$fix.$key;
$values = $values.$fix."'".$value."'";
$update = $update.$fix.$key." = '$value'";
$fix = ", ";
}
}
$sql = "INSERT INTO ticket (".$columns.") VALUES (".$values.") ON DUPLICATE KEY UPDATE ".$update;
$this->app->DB->Update($sql);
$id = $this->app->DB->GetInsertID();
$this->ticket_set_self_assigned_status(array($id));
return($id);
}
function save_draft($id, $input) {
$columns = "id, ";
$values = "$id, ";
$update = "";
$fix = "";
// Translate form to table
$input['betreff'] = $input['email_betreff'];
$input['mail'] = $input['email_an'];
$input['mail_cc'] = $input['email_cc'];
$input['text'] = $input['email_text'];
foreach ($input as $key => $value) {
if ($this->app->DB->ColumnExists('ticket_nachricht',$key)) {
$columns = $columns.$fix.$key;
$values = $values.$fix."'".$value."'";
$update = $update.$fix.$key." = '$value'";
$fix = ", ";
}
}
$sql = "INSERT INTO ticket_nachricht (".$columns.") VALUES (".$values.") ON DUPLICATE KEY UPDATE ".$update;
$this->app->DB->Update($sql);
}
function ticket_create() {
$submit = $this->app->Secure->GetPOST('submit');
$input = $this->GetInput();
$projekt_id = $this->app->User->DefaultProjekt();
$projekt = $this->app->DB->Select("SELECT abkuerzung FROM projekt WHERE id = ".$projekt_id);
if ($submit != '') {
$input['schluessel'] = $this->generateRandomTicketNumber();
$input['kunde'] = $this->app->User->GetName();
$id = $this->ticket_save_to_db($id, $input);
header("Location: index.php?module=ticket&action=edit&id=$id");
exit();
}
$this->app->Tpl->Set('PROJEKT', $projekt);
$this->app->Tpl->Set('STATUSICON', $this->ticket_status_icon('neu')."&nbsp;");
$this->app->YUI->AutoComplete("adresse","adresse");
$this->app->YUI->AutoComplete("projekt","projektname",1);
$this->app->YUI->AutoComplete("status","ticketstatus",1);
$this->app->Tpl->Set('STATUS', $this->app->erp->GetStatusTicketSelect('neu'));
$this->app->YUI->AutoComplete("warteschlange","warteschlangename");
$this->app->Tpl->Parse('PAGE', "ticket_create.tpl");
}
function ticket_menu($id) {
$this->app->erp->MenuEintrag("index.php?module=ticket&action=edit&id=$id", "Details");
$this->app->erp->MenuEintrag("index.php?module=ticket&action=list", "Zur&uuml;ck zur &Uuml;bersicht");
$anzahldateien = $this->app->erp->AnzahlDateien("ticket_header",$id);
if ($anzahldateien > 0) {
$anzahldateien = " (".$anzahldateien.")";
} else {
$anzahldateien="";
}
$this->app->erp->MenuEintrag("index.php?module=ticket&action=dateien&id=$id", "Dateien".$anzahldateien);
}
function ticket_edit() {
$id = $this->app->Secure->GetGET('id');
if (empty($id)) {
return;
}
$this->ticket_menu($id);
$this->app->Tpl->Set('ID', $id);
$id = $this->app->Secure->GetGET('id');
$cmd = $this->app->Secure->GetGET('cmd');
$input = $this->GetInput();
$submit = $this->app->Secure->GetPOST('submit');
$msg = $this->app->erp->base64_url_decode($this->app->Secure->GetGET('msg'));
if ($input['neue_notiz'] != '') {
$input['notiz'] = $this->app->User->GetName()." ".date("d.m.Y H:i").": ".$input['neue_notiz']."\r\n".$input['notiz'];
}
// Always save
if ($submit != '')
{
$this->ticket_save_to_db($id, $input);
$msg = "<div class=\"success\">Die Einstellungen wurden erfolgreich &uuml;bernommen.</div>";
}
// Load values again from database
$sql = "SELECT t.id, t.schluessel, ".$this->app->erp->FormatDateTimeShort("zeit",'zeit').", p.abkuerzung as projekt, t.bearbeiter, t.quelle, t.status, t.prio, t.adresse, t.kunde, CONCAT(w.label,' ',w.warteschlange) as warteschlange, t.mailadresse, t.betreff, t.zugewiesen, t.inbearbeitung, t.inbearbeitung_user, t.firma, t.notiz, t.bitteantworten, t.service, t.kommentar, t.privat, t.dsgvo, t.tags, t.nachrichten_anz, t.id FROM ticket t LEFT JOIN adresse a ON t.adresse = a.id LEFT JOIN projekt p on t.projekt = p.id LEFT JOIN warteschlangen w on t.warteschlange = w.label WHERE t.id=$id";
$ticket_from_db = $this->app->DB->SelectArr($sql)[0];
$ticket_from_db['betreff'] = strip_tags($ticket_from_db['betreff']);
foreach ($ticket_from_db as $key => $value) {
$this->app->Tpl->Set(strtoupper($key), $value);
}
$this->app->Tpl->Set('PRIO', $ticket_from_db['prio']==1?"checked":"");
$this->app->Tpl->Set('STATUSICON', $this->ticket_status_icon($ticket_from_db['status'])."&nbsp;");
$this->app->YUI->AutoComplete("adresse","adresse");
$this->app->Tpl->Set('ADRESSE', $this->app->erp->ReplaceAdresse(false,$ticket_from_db['adresse'],false)); // Convert ID to form display
if ($ticket_from_db['mailadresse'] != "") {
$this->app->Tpl->Set('MAILADRESSE',"&lt;".$ticket_from_db['mailadresse']."&gt;");
}
$this->app->Tpl->Set('ADRESSE_ID',$ticket_from_db['adresse']);
$this->app->YUI->AutoComplete("projekt","projektname",1);
$this->app->YUI->AutoComplete("status","ticketstatus",1);
$this->app->YUI->TagEditor('tags', array('width'=>370));
$this->app->Tpl->Set('STATUS', $this->app->erp->GetStatusTicketSelect($ticket_from_db['status']));
$input['projekt'] = $this->app->erp->ReplaceProjekt(false,$input['projekt'],false); // Parameters: Target db?, value, from form?
$this->app->YUI->AutoComplete("warteschlange","warteschlangename");
// END Header
// Check for draft
$drafted_messages = $this->get_messages_of_ticket($id, "zeitausgang IS NULL AND versendet = '1'",NULL);
if (!empty($drafted_messages)) {
// Draft from form?
if ($submit != '') {
$this->save_draft($drafted_messages[0]['id'],$input);
// Reload
$drafted_messages = $this->get_messages_of_ticket($id, "zeitausgang IS NULL AND versendet = '1'",NULL);
}
// Load the draft for editing
$this->app->Tpl->Set('EMAIL_AN', htmlentities($drafted_messages[0]['mail']));
$this->app->Tpl->Set('EMAIL_CC', htmlentities($drafted_messages[0]['mail_cc']));
$this->app->Tpl->Set('EMAIL_BCC', htmlentities($drafted_messages[0]['mail_bcc']));
$this->app->Tpl->Set('EMAIL_BETREFF', htmlentities($drafted_messages[0]['betreff']));
$this->app->Tpl->Set('EMAIL_TEXT',htmlentities($drafted_messages[0]['text']));
// Show new message dialog
$this->app->Tpl->Set('EMAIL_SENDER', $this->app->erp->GetSelectEmailMitName($dokument['von']));
$this->app->YUI->AutoComplete("email_an","emailname");
$this->app->YUI->AutoComplete("email_cc","emailname");
$this->app->YUI->AutoComplete("email_bcc","emailname");
$this->app->YUI->CkEditor("email_text","internal", null, 'JQUERY');
// Delete attachment from draft
if ($cmd=='deleteattachment') {
$fileid = $this->app->Secure->GetGET('fileid');
// Check if this file is only attached to this draft and nowhere else
$check = $this->app->erp->GetDateiStichwoerter($fileid);
$save_to_delete = true;
foreach ($check as $stichwort) {
if ($stichwort['subjekt'] != 'anhang' || $stichwort['objekt'] != 'Ticket' || $stichwort['parameter'] != $drafted_messages[0]['id']) {
$save_to_delete = false;
break;
}
}
if ($save_to_delete) {
$this->app->erp->DeleteDatei($fileid);
} else {
$msg .= "<div class=\"success\">Fehler beim Löschen der Datei: In Verwendung.</div>";
}
}
// Upload of attachments
if(isset($_FILES['upload']) && is_array($_FILES['upload']))
{
foreach($_FILES['upload']['tmp_name'] as $key => $file)
{
if($file != "")
{
$fileid = $this->app->erp->CreateDatei($_FILES['upload']['name'][$key], $_FILES['upload']['name'][$key], "", "", $_FILES['upload']['tmp_name'][$key], $this->app->User->GetName());
// stichwoerter hinzufuegen
$this->app->erp->AddDateiStichwort($fileid, "anhang", "Ticket", $drafted_messages[0]['id']);
}
}
}
$this->add_attachments_html($id,$drafted_messages[0]['id'],'ANHAENGE',true);
$this->app->Tpl->Parse('NEW_MESSAGE', "ticket_new_message.tpl");
}
// END Draft
// Get all messsages
$messages = $this->get_messages_of_ticket($id, 1, NULL);
$recv_messages = $this->get_messages_of_ticket($id,"n.versendet != 1",NULL);
$an_alle = false;
switch ($submit) {
case 'neue_email_alle':
$an_alle = true;
// break omitted
case 'neue_email':
$senderName = $this->app->User->GetName()." (".$this->app->erp->GetFirmaAbsender().")";
$senderAddress = $this->app->erp->GetFirmaMail();
if (empty($drafted_messages)) {
// Create new message and save it for editing
$this->app->Tpl->Set('EMAIL_AN', htmlentities($recv_messages[0]['mail']));
$to = "";
$cc = "";
if (!empty($recv_messages)) {
if (!str_starts_with(strtoupper($recv_messages[0]['betreff']),"RE:")) {
$betreff = "RE: ".strip_tags($recv_messages[0]['betreff']); //+ #20230916 XSS
}
else {
$betreff = strip_tags($recv_messages[0]['betreff']); //+ #20230916 XSS
}
$to = $recv_messages[0]['mail'];
if ($an_alle) {
$sql = "SELECT GROUP_CONCAT(DISTINCT `value` ORDER BY `value` SEPARATOR ', ') FROM ticket_header th WHERE th.ticket_nachricht = ".$recv_messages[0]['id']." AND `value` <> '".$senderAddress."' AND type='to'";
$to_additional = $this->app->DB->Select($sql);
if (!empty($to_additional)) {
$to .= ", ".$to_additional;
}
$sql = "SELECT GROUP_CONCAT(DISTINCT `value` ORDER BY `value` SEPARATOR ', ') FROM ticket_header th WHERE th.ticket_nachricht = ".$recv_messages[0]['id']." AND `value` <> '".$senderAddress."' AND type='cc'";
$cc = $this->app->DB->Select($sql);
} else {
$cc = null;
}
}
else {
$betreff = $ticket_from_db['betreff'];
$sql = "SELECT email FROM adresse WHERE id =".$ticket_from_db['adresse'];
$to = $this->app->DB->Select($sql);
}
$anschreiben = $this->app->DB->Select("SELECT anschreiben FROM adresse WHERE id='".$ticket_from_db['adresse']."' LIMIT 1");
if($anschreiben=="")
{
$anschreiben = $this->app->erp->Beschriftung("dokument_anschreiben");
}
$anschreiben = $anschreiben.",<br>".$this->app->erp->Grussformel($projekt,$sprache);
$sql = "INSERT INTO `ticket_nachricht` (
`ticket`, `zeit`, `text`, `betreff`, `medium`, `versendet`,
`verfasser`, `mail`,`status`, `verfasser_replyto`, `mail_replyto`,`mail_cc`
) VALUES ('".
$ticket_from_db['schluessel'].
"',NOW(),'".
$this->app->DB->real_escape_string($anschreiben).
"','".
$this->app->DB->real_escape_string($betreff).
"','email','1','".
$this->app->DB->real_escape_string($senderName).
"','".
$this->app->DB->real_escape_string($to).
"','neu','".
$this->app->DB->real_escape_string($senderName).
"','".
$this->app->DB->real_escape_string($senderAddress).
"','".
$this->app->DB->real_escape_string($cc)."');";
$this->app->DB->Insert($sql);
// Show new message dialog
header("Location: index.php?module=ticket&action=edit&id=$id");
$this->app->ExitXentral();
}
break;
case 'entwurfloeschen':
if (!empty($drafted_messages)) {
$sql = "UPDATE ticket_nachricht SET ticket = '' WHERE id=".$drafted_messages[0]['id'];
$this->app->DB->Update($sql);
$msg = $this->app->erp->base64_url_encode("<div class=\"success\">Der Entwurf wurde gel&ouml;scht.</div>");
header("Location: index.php?module=ticket&action=edit&msg=$msg&id=$id");
$this->app->ExitXentral();
}
break;
case 'zitat':
if (!empty($drafted_messages) && !empty($recv_messages)) {
$nl = "<br />";
$citation_info =$recv_messages[0]['zeit']." ".$recv_messages[0]['verfasser']." &lt;".$recv_messages[0]['mail']."&gt;";
$text = $drafted_messages[0]['text'].$nl.$nl.$citation_info.":".$nl."<blockquote type=\"cite\">".$recv_messages[0]['text']."</blockquote>";
$sql = "UPDATE ticket_nachricht SET text='".$this->app->DB->real_escape_string($text)."' WHERE id=".$drafted_messages[0]['id'];
$this->app->DB->Update($sql);
header("Location: index.php?module=ticket&action=edit&id=$id");
$this->app->ExitXentral();
}
break;
case 'absenden':
if (empty($drafted_messages)) {
break;
}
$msg = "";
// Enforce Ticket #
if (!preg_match("/Ticket #[0-9]{12}/i", $drafted_messages[0]['betreff'])) {
$drafted_messages[0]['betreff'].= " Ticket #".$ticket_from_db['schluessel'];
}
// Attachments
$files = $this->app->erp->GetDateiSubjektObjektDateiname('Anhang','Ticket',$drafted_messages[0]['id'],"");
$pattern = '/[a-z0-9_\-\+\.]+@[a-z0-9\-]+\.([a-z]{2,63})(?:\.[a-z]{2,63})?/i';
preg_match_all($pattern, $drafted_messages[0]['mail'], $matches);
$to = $matches[0];
if ($drafted_messages[0]['mail_cc'] != '') {
preg_match_all($pattern, $drafted_messages[0]['mail_cc'], $matches);
$cc = $matches[0];
} else {
$cc = null;
}
$senderName = $this->app->User->GetName()." (".$this->app->erp->GetFirmaAbsender().")";
$senderAddress = $this->app->erp->GetFirmaMail();
// function MailSend($from,$from_name,$to,$to_name,$betreff,$text,$files="",$projekt="",$signature=true,$cc="",$bcc="", $system = false)
if (
$this->app->erp->MailSend(
$senderAddress,
$senderName,
$to,
$to,
htmlentities($drafted_messages[0]['betreff']),
htmlentities($drafted_messages[0]['text']),
$files,
0,
true,
$cc,
'',
true
) != 0
) {
// Update message in ticket_nachricht
$sql = "UPDATE `ticket_nachricht` SET `zeitausgang` = NOW(), `betreff` = '".$this->app->DB->real_escape_string($drafted_messages[0]['betreff'])."', `verfasser` = '$senderName', `verfasser_replyto` = '$senderName', `mail_replyto` = '$senderAddress' WHERE id = ".$drafted_messages[0]['id'];
$this->app->DB->Insert($sql);
$msg .= '<div class="info">Die E-Mail wurde erfolgreich versendet an '.$input['email_an'].'.';
if ($drafted_messages[0]['mail_cc'] != '') {
$msg .= ' (CC: '.$drafted_messages[0]['mail_cc'].')</div>';
}
else {
$msg .= '</div>';
}
header("Location: index.php?module=ticket&action=edit&id=".$id."&msg=".$this->app->erp->base64_url_encode($msg));
}
else {
$msg = '<div class="error">Fehler beim Versenden der E-Mail: '.$this->app->erp->mail_error.'</div>';
}
// Get messsages again
$messages = $this->get_messages_of_ticket($id,1,NULL);
break;
}
$this->add_messages_tpl($messages, false);
$this->add_attachments_header_html($id,'TICKET_ANHANG');
$this->app->Tpl->Set('MESSAGE', $msg);
$this->app->Tpl->Parse('PAGE', "ticket_edit.tpl");
}
function ticket_dateien()
{
$id = $this->app->Secure->GetGET("id");
$this->ticket_menu($id);
$this->app->Tpl->Add('UEBERSCHRIFT'," (Dateien)");
$this->app->YUI->DateiUpload('PAGE',"ticket_header",$id);
}
/**
* Get all paramters from html form and save into $input
*/
public function GetInput(): array {
$input = array();
//$input['EMAIL'] = $this->app->Secure->GetPOST('email');
$input['projekt'] = $this->app->Secure->GetPOST('projekt');
$input['status'] = $this->app->Secure->GetPOST('status');
$input['adresse'] = $this->app->Secure->GetPOST('adresse');
$input['warteschlange'] = $this->app->Secure->GetPOST('warteschlange');
$input['prio'] = !empty($this->app->Secure->GetPOST('prio'))?"1":"0";
$input['neue_notiz'] = $this->app->Secure->GetPOST('neue_notiz');
$input['notiz'] = $this->app->Secure->GetPOST('notiz');
$input['tags'] = $this->app->Secure->GetPOST('tags');
$input['betreff'] = $this->app->Secure->GetPOST('betreff');
$input['email_sender'] = $this->app->Secure->GetPOST('email_sender');
$input['email_an'] = $this->app->Secure->GetPOST('email_an');
$input['email_cc'] = $this->app->Secure->GetPOST('email_cc');
$input['email_bcc'] = $this->app->Secure->GetPOST('email_bcc');
$input['email_betreff'] = $this->app->Secure->GetPOST('email_betreff');
$input['email_text'] = $this->app->Secure->GetPOST('email_text');
return $input;
}
public function ticket_minidetail($parsetarget='',$menu=true) {
$id = $this->app->Secure->GetGET('id');
// Get last 3 messages
$messages = $this->get_messages_of_ticket($id, "1", 3);
if(!empty($messages)) {
$this->add_messages_tpl($messages, true); // With drafts
$render = true;
} else {
}
if($parsetarget=='')
{
if ($render) {
$this->app->Tpl->Output('ticket_minidetail.tpl');
}
$this->app->ExitXentral();
}
if ($render) {
$this->app->Tpl->Parse($parsetarget,'ticket_minidetail.tpl');
}
}
/*
* After import of Xentral 20 ticket system
* Set all ticket status to the status of the latest ticket_nachricht
*/
function ticket_statusfix() {
$confirmed = $this->app->Secure->GetGET('confirmed');
if ($confirmed == "yes") {
$sql = "UPDATE
ticket
SET
STATUS
=ifnull((
SELECT
tn.status
FROM
ticket_nachricht tn
INNER JOIN(
SELECT
ticket,
MAX(zeit) AS lastzeit
FROM
ticket_nachricht
GROUP BY
ticket
) l
ON
tn.ticket = l.ticket AND tn.zeit = l.lastzeit
WHERE
ticket.schluessel = tn.ticket
LIMIT 1
),'abgeschlossen')
WHERE ticket.status = 'neu'";
$this->app->DB->Update($sql);
$this->app->Tpl->Set('TEXT', "Status fix abgeschlossen.");
$this->app->Tpl->Parse('PAGE','ticket_text.tpl');
}
else {
// $this->app->Tpl->Set('TEXT', "This will replace all ticket status with the status of the latest ticket_nachricht. To confirm, press here: ");
$this->app->Tpl->Set('TEXT', "Dieser Assistent ersetzt den Status aller offenen Tickets (Weder abgeschlossen noch Spam) mit dem Status der letzten Nachricht im Ticket. Hier starten: ");
$this->app->Tpl->Add('TEXT', '<a href="index.php?module=ticket&action=statusfix&confirmed=yes"><button>OK</button></a>');
$this->app->Tpl->Parse('PAGE','ticket_text.tpl');
}
}
/*
* After import of Xentral 20 ticket system
* Set all ticket dates to the date of the latest ticket_nachricht
*/
function ticket_datefix() {
$confirmed = $this->app->Secure->GetGET('confirmed');
if ($confirmed == "yes") {
$sql = "UPDATE ticket set zeit =
(SELECT
MAX(zeit) AS lastzeit
FROM
ticket_nachricht
WHERE ticket.schluessel = ticket_nachricht.ticket AND ticket.schluessel
LIMIT 1
)
WHERE ticket.status <> 'abgeschlossen' AND ticket.status <> 'spam'";
$this->app->DB->Update($sql);
$this->app->Tpl->Set('TEXT', "Datum fix abgeschlossen.");
$this->app->Tpl->Parse('PAGE','ticket_text.tpl');
}
else {
// $this->app->Tpl->Set('TEXT', "This will replace all open ticket dates with the date of the latest ticket_nachricht. To confirm, press here: ");
$this->app->Tpl->Set('TEXT', "Dieser Assistent ersetzt das Datum aller offenen Tickets (Weder abgeschlossen noch Spam) mit dem Datum der letzten Nachricht im Ticket. Hier starten: ");
$this->app->Tpl->Add('TEXT', '<a href="index.php?module=ticket&action=datefix&confirmed=yes"><button>OK</button></a>');
$this->app->Tpl->Parse('PAGE','ticket_text.tpl');
}
}
}