dbxapp 4.1.3
CMS, Shop, Workflows und modulare Geschäftsanwendungen
Loading...
Searching...
No Matches
dbxContactAdmin.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxContact_admin;
3
4require_once dirname(__DIR__, 2) . '/dbxContact/include/dbxContactConfig.class.php';
5require_once dirname(__DIR__, 2) . '/dbxContact/include/dbxContactTicket.class.php';
6require_once dirname(__DIR__, 2) . '/dbxContact/include/dbxContactContentProvision.class.php';
7
8use dbx\dbxContact\dbxContactConfig;
9use dbx\dbxContact\dbxContactContentProvision;
10use dbx\dbxContact\dbxContactTicket;
11
13
14 private function db() {
15 return dbx()->get_system_obj('dbxDB');
16 }
17
18 private function tpl() {
19 return dbx()->get_system_obj('dbxTPL');
20 }
21
22 private function h($value): string {
23 return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
24 }
25
26 private function alert(string $type, string $message): string {
27 return $this->tpl()->get_tpl('dbx|alert-' . $type, array('msg' => $message));
28 }
29
30 private function schemaReady(): bool {
31 $db = $this->db();
32 $server = 'dbxContact|dbxContact.db3';
33 if (!$db->connect_db_server($server)) {
34 return false;
35 }
36
37 $tables = $db->select_query($server, "SELECT name FROM sqlite_master WHERE type='table' AND name='contact_message' LIMIT 1");
38 if (!is_array($tables) || empty($tables)) {
39 return false;
40 }
41
42 $columns = $db->select_query($server, 'PRAGMA table_info(contact_request)');
43 $names = array();
44 foreach ((array) $columns as $column) {
45 $names[] = (string) ($column['name'] ?? '');
46 }
47
48 return in_array('priority', $names, true)
49 && in_array('last_activity_date', $names, true)
50 && in_array('user_hidden', $names, true);
51 }
52
53 private function statusClass(string $status): string {
54 return array(
55 'open' => 'text-bg-primary',
56 'in_progress' => 'text-bg-info',
57 'waiting_customer' => 'text-bg-warning',
58 'answered' => 'text-bg-success',
59 'closed' => 'text-bg-secondary',
60 )[$status] ?? 'text-bg-light';
61 }
62
63 private function priorityClass(string $priority): string {
64 return array(
65 'low' => 'text-bg-secondary',
66 'normal' => 'text-bg-primary',
67 'high' => 'text-bg-warning',
68 'urgent' => 'text-bg-danger',
69 )[$priority] ?? 'text-bg-light';
70 }
71
72 private function counts(): array {
73 $db = $this->db();
74 return array(
75 'count_open' => max(0, (int) $db->count(dbxContactTicket::DD_TICKET, array('status' => 'open'))),
76 'count_progress' => max(0, (int) $db->count(dbxContactTicket::DD_TICKET, array('status' => 'in_progress'))),
77 'count_waiting' => max(0, (int) $db->count(dbxContactTicket::DD_TICKET, array('status' => 'waiting_customer'))),
78 'count_answered' => max(0, (int) $db->count(dbxContactTicket::DD_TICKET, array('status' => 'answered'))),
79 'count_closed' => max(0, (int) $db->count(dbxContactTicket::DD_TICKET, array('status' => 'closed'))),
80 );
81 }
82
83 private function reportStatusStatsHtml($report): string {
84 $counts = $this->counts();
85 return ''
86 . '<span class="dbx-report-bar-stat ms-3"><span class="badge text-bg-primary">' . $this->h($report->get_fd_message('stat_open')) . ': ' . (int) $counts['count_open'] . '</span></span>'
87 . '<span class="dbx-report-bar-stat"><span class="badge text-bg-info">' . $this->h($report->get_fd_message('stat_progress')) . ': ' . (int) $counts['count_progress'] . '</span></span>'
88 . '<span class="dbx-report-bar-stat"><span class="badge text-bg-warning">' . $this->h($report->get_fd_message('stat_waiting')) . ': ' . (int) $counts['count_waiting'] . '</span></span>'
89 . '<span class="dbx-report-bar-stat"><span class="badge text-bg-success">' . $this->h($report->get_fd_message('stat_answered')) . ': ' . (int) $counts['count_answered'] . '</span></span>'
90 . '<span class="dbx-report-bar-stat"><span class="badge text-bg-secondary">' . $this->h($report->get_fd_message('stat_closed')) . ': ' . (int) $counts['count_closed'] . '</span></span>';
91 }
92
93 private function frame(string $content, string $title = ''): string {
94 $texts = dbx()->get_system_obj('dbxForm');
95 $texts->init('contact-admin-texts', 'dbx|form');
96 $texts->load_fd_messages('dbxContact_admin|rpt-ticket-selection');
97 if ($title === '') {
98 $title = $texts->get_fd_message('frame_title');
99 }
100 $actions = ''
101 . '<a class="btn btn-outline-secondary btn-sm" href="?dbx_modul=dbxContact_admin&dbx_run1=install"><i class="bi bi-database-gear"></i> '
102 . $this->h($texts->get_fd_message('install'))
103 . '</a>'
104 . '<a class="btn btn-primary btn-sm" href="?dbx_modul=dbxContact_admin&dbx_run1=list"><i class="bi bi-arrow-clockwise"></i> '
105 . $this->h($texts->get_fd_message('refresh'))
106 . '</a>';
107
108 return $this->tpl()->get_tpl('dbxContact_admin|ticket-admin-shell', array_merge(
109 $this->counts(),
110 array(
111 'bar_title' => $this->h($title),
112 'bar_icon' => 'bi-life-preserver',
113 'bar_subtitle' => $this->h(
114 $texts->get_fd_message('frame_subtitle')
115 ),
116 'bar_title_pre' => '',
117 'bar_title_heading_attrs' => '',
118 'bar_middle' => '',
119 'bar_extra' => '',
120 'bar_actions' => $actions,
121 'content' => $content,
122 )
123 ));
124 }
125
126 private function install(): string {
127 $dd = dbx()->get_system_obj('dbxDD');
128 $results = array();
129
130 foreach (array('contactMessage') as $name) {
131 $dd->sync_dd_to_db('dbxContact', $name, 'reset');
132 $state = array();
133 for ($i = 0; $i < 100; $i++) {
134 $state = $dd->sync_dd_to_db('dbxContact', $name, 'apply');
135 if (in_array((string) ($state['status'] ?? ''), array('finished', 'error'), true)) {
136 break;
137 }
138 }
139 $results[$name] = $state;
140 }
141
142 foreach ($results as $name => $state) {
143 if ((string) ($state['status'] ?? '') !== 'finished') {
144 return $this->frame($this->alert('danger', 'Installation von ' . $this->h($name) . ' fehlgeschlagen: ' . $this->h($state['message'] ?? 'unbekannt')));
145 }
146 }
147
148 $name = 'contactRequest';
149 $dd->sync_dd_to_db('dbxContact', $name, 'reset');
150 for ($i = 0; $i < 100; $i++) {
151 $state = $dd->sync_dd_to_db('dbxContact', $name, 'force');
152 if (in_array((string) ($state['status'] ?? ''), array('finished', 'error'), true)) {
153 break;
154 }
155 }
156 $results[$name] = $state;
157
158 foreach ($results as $name => $state) {
159 if ((string) ($state['status'] ?? '') !== 'finished') {
160 return $this->frame($this->alert('danger', 'Installation von ' . $this->h($name) . ' fehlgeschlagen: ' . $this->h($state['message'] ?? 'unbekannt')));
161 }
162 }
163
164 $db = $this->db();
165 $rows = $db->select(dbxContactTicket::DD_TICKET, '', '*', 'id', 'ASC', '', 100000, 0, 0);
166 foreach ((array) $rows as $ticket) {
168 if (trim((string) ($ticket['last_activity_date'] ?? '')) === '') {
169 $last = (string) ($ticket['create_date'] ?? date('Y-m-d H:i:s'));
170 $db->update(dbxContactTicket::DD_TICKET, array(
171 'priority' => dbxContactTicket::normalizePriority((string) ($ticket['priority'] ?? 'normal')),
172 'last_activity_date' => $last,
173 ), (int) ($ticket['id'] ?? 0), 0, 1, 1, 0);
174 }
175 }
176 $contentPageId = dbxContactContentProvision::run($db);
177
178 return $this->frame(
179 $this->alert('success', 'Ticket-Datenmodell installiert.')
180 . ($contentPageId > 0
181 ? '<div class="alert alert-info mx-3">Die dbxContent-Seite „Meine Anfragen“ ist unter <a href="meine-anfragen">meine-anfragen</a> verfuegbar.</div>'
182 : '<div class="alert alert-warning mx-3">Die dbxContent-Seite „Meine Anfragen“ konnte nicht automatisch angelegt werden.</div>')
183 . '<div class="p-3"><a class="btn btn-primary" href="?dbx_modul=dbxContact_admin&dbx_run1=list"><i class="bi bi-list-ul"></i> Tickets anzeigen</a></div>'
184 );
185 }
186
187 private function whereForReport(string $search, string $status, string $priority): string {
188 $db = $this->db();
189 $server = $db->get_dd_server(dbxContactTicket::DD_TICKET);
190 $clauses = array();
191
192 if ($status !== 'all' && array_key_exists($status, dbxContactTicket::statuses())) {
193 $clauses[] = "status = '" . $db->escape($status, $server) . "'";
194 }
195 if ($priority !== 'all' && array_key_exists($priority, dbxContactTicket::priorities())) {
196 $clauses[] = "priority = '" . $db->escape($priority, $server) . "'";
197 }
198 if ($search !== '') {
199 $needle = $db->escape($search, $server);
200 $clauses[] = "(subject LIKE '%" . $needle . "%' OR name LIKE '%" . $needle . "%' OR email LIKE '%" . $needle . "%' OR message LIKE '%" . $needle . "%')";
201 }
202
203 return implode(' AND ', $clauses);
204 }
205
206 private function decorateRows(array $rows, $report): array {
207 $out = array();
208 $db = $this->db();
209 foreach ($rows as $row) {
210 $rid = (int) ($row['id'] ?? 0);
211 $status = dbxContactTicket::normalizeStatus((string) ($row['status'] ?? 'open'));
212 $priority = dbxContactTicket::normalizePriority((string) ($row['priority'] ?? 'normal'));
213 $row['status_view'] = '<span class="badge ' . $this->statusClass($status) . '">' . $this->h($report->get_fd_message('status_' . $status, $status)) . '</span>';
214 $row['priority_view'] = '<span class="badge ' . $this->priorityClass($priority) . '">' . $this->h($report->get_fd_message('priority_' . $priority, $priority)) . '</span>';
215 $row['user_view'] = (int) ($row['uid'] ?? 0) > 0
216 ? '#' . (int) $row['uid']
217 : $report->get_fd_message('guest');
218 $row['hidden_view'] = (int) ($row['user_hidden'] ?? 0) === 1
219 ? '<span class="badge text-bg-dark">'
220 . $this->h($report->get_fd_message('user_hidden'))
221 . '</span>'
222 : '';
223 $row['message_count'] = $rid > 0 ? max(0, (int) $db->count(dbxContactTicket::DD_MESSAGE, array('ticket_id' => $rid))) : 0;
224 $row['action'] = $this->tpl()->get_tpl('dbxContact_admin|ticket-row-action', array('rid' => $rid));
225 $out[] = $row;
226 }
227 return $out;
228 }
229
230 private function deleteConfirmText(int $rid, int $messageCount, $source): array {
231 $hasThread = $messageCount > 0;
232
233 return array(
234 'title' => $source->get_fd_message('delete_title'),
235 'question' => $source->format_fd_message(
236 'delete_question',
237 array('id' => $rid)
238 ),
239 'hint' => $hasThread
240 ? $source->format_fd_message(
241 'delete_hint_thread',
242 array('count' => $messageCount)
243 )
244 : $source->get_fd_message('delete_hint_empty'),
245 );
246 }
247
248 public function contactTicketReportRowActionData($report, $data): array {
249 if (!is_array($data) || (string) ($data['type'] ?? '') !== 'delete') {
250 return $data;
251 }
252
253 $rid = (int) ($data['record']['id'] ?? $data['data']['rid'] ?? 0);
254 $messageCount = max(0, (int) ($data['record']['message_count'] ?? 0));
255 $confirm = $this->deleteConfirmText($rid, $messageCount, $report);
256 $data['data']['action'] = '?dbx_modul=dbxContact_admin&dbx_run1=delete&rid=' . $rid;
257 $data['data']['confirm_title'] = $confirm['title'];
258 $data['data']['confirm'] = $confirm['question'];
259 $data['data']['confirm_hint'] = $confirm['hint'];
260 $data['data']['tooltip'] = $report->get_fd_message(
261 'delete_tooltip'
262 );
263
264 return $data;
265 }
266
267 private function listTickets(string $success = '', string $error = '', ?bool $withFrame = null): string {
268 if ($withFrame === null) {
269 $withFrame = (int) dbx()->get_system_var('dbx_ajax', 0, 'int') !== 1;
270 }
271
272 $db = $this->db();
273 $report = dbx()->get_system_obj('dbxReport');
274 $report->init('contact-ticket-report', 'dbxContact_admin|ticket-report');
275 $report->_fd = 'dbxContact_admin|rpt-ticket-selection';
276 $report->load_fd_messages();
278 $report->_action = '?dbx_modul=dbxContact_admin&dbx_run1=list';
279 $report->_pages = true;
280 $report->_create_row_select = false;
281 $report->_create_row_edit = false;
282 $report->_create_row_delete = true;
283 $report->_table_buttons = 'left';
284 $report->_msg_confirm_delete = $report->get_fd_message(
285 'confirm_delete'
286 );
287 $report->_but_pagination = 7;
288 $report->_msg_info = '';
289 $report->add_rep(
290 'report_extra_stats',
291 $this->reportStatusStatsHtml($report)
292 );
293 if ($success !== '') {
294 $report->_msg_success = $report->get_fd_message(
295 $success,
296 $success
297 );
298 }
299 if ($error !== '') {
300 $report->_msg_error = $report->get_fd_message($error, $error);
301 }
302 $report->set_callback_owner($this);
303 $report->set_callback('row_action_data', 'contactTicketReportRowActionData');
304 $report->set_tabel_tpl('tpl_row_delete', 'modul|ticket-row-delete');
305 $report->create_selection_fields('dbxContact_admin|rpt-ticket-selection');
306 $report->add_rep(
307 'bar_title',
308 $report->get_fd_message('report_title')
309 );
310
311 $search = trim((string) $report->get_fld_val('dbx_rwhere', '', 'sqlsearch|max=100'));
312 $status = (string) $report->get_fld_val('dbx_rstatus', 'all', 'parameter');
313 $priority = (string) $report->get_fld_val('dbx_rpriority', 'all', 'parameter');
314 $rowsPerPage = max(10, min(100, (int) $report->get_fld_val('dbx_rrows', 30, 'int')));
315 $position = max(0, (int) $report->get_fld_val('dbx_rpos', 0, 'int'));
316 $sort = (string) $report->get_fld_val('dbx_rsort', 'last_activity_date', 'parameter');
317 $direction = strtoupper((string) $report->get_fld_val('dbx_rdesc', 'DESC', 'parameter')) === 'ASC' ? 'ASC' : 'DESC';
318 if (!in_array($sort, array('last_activity_date', 'create_date', 'id', 'status', 'priority', 'subject'), true)) {
319 $sort = 'last_activity_date';
320 }
321
322 $where = $this->whereForReport($search, $status, $priority);
323 $cols = array('id', 'create_date', 'last_activity_date', 'status', 'priority', 'uid', 'name', 'email', 'subject', 'user_hidden');
324 $report->_rflds = array(
325 'id' => $report->get_fd_message('column_ticket'),
326 'last_activity_date' => $report->get_fd_message(
327 'column_activity'
328 ),
329 'status_view' => $report->get_fd_message('column_status'),
330 'priority_view' => $report->get_fd_message('column_priority'),
331 'user_view' => $report->get_fd_message('column_user'),
332 'name' => $report->get_fd_message('column_name'),
333 'email' => $report->get_fd_message('column_email'),
334 'subject' => $report->get_fd_message('column_subject'),
335 'hidden_view' => '',
336 'action' => $report->get_fd_message('column_action'),
337 );
338 $report->_rpt_format = array(
339 'last_activity_date' => 'php-datetime-usr',
340 'status_view' => 'html',
341 'priority_view' => 'html',
342 'hidden_view' => 'html',
343 'action' => 'html',
344 );
345 $report->_rrows = $rowsPerPage;
346 $report->_rpos = $position;
347 $report->_count_all = $db->count(dbxContactTicket::DD_TICKET, '');
348 $report->_rcount = $db->count(dbxContactTicket::DD_TICKET, $where);
349 $rows = $db->select(dbxContactTicket::DD_TICKET, $where, $cols, $sort . ',id', $direction, '', $rowsPerPage, $position, 0);
350 $report->_rdata = $this->decorateRows(
351 is_array($rows) ? $rows : array(),
352 $report
353 );
354
355 $content = $report->run();
356 return $withFrame ? $this->frame($content) : $content;
357 }
358
359 private function deleteTicketRecord(int $rid): bool {
360 if ($rid <= 0 || !dbxContactTicket::ticket($this->db(), $rid)) {
361 return false;
362 }
363
364 $db = $this->db();
365 $ok = 0;
366
367 if ($db->begin(dbxContactTicket::DD_TICKET) === 1) {
368 $messagesOk = $db->delete(dbxContactTicket::DD_MESSAGE, array('ticket_id' => $rid), 0, 1);
369 if ($messagesOk === 1 || $db->count(dbxContactTicket::DD_MESSAGE, array('ticket_id' => $rid)) === 0) {
370 $ok = $db->delete(dbxContactTicket::DD_TICKET, $rid, 0, 1);
371 }
372 if ($ok === 1) {
373 $db->commit(dbxContactTicket::DD_TICKET);
374 } else {
375 $db->rollback(dbxContactTicket::DD_TICKET);
376 }
377 }
378
379 return $ok === 1;
380 }
381
382 private function timeline(array $ticket, $texts): string {
383 $db = $this->db();
384 dbxContactTicket::ensureInitialMessage($db, $ticket);
385 $html = '';
386
387 foreach (dbxContactTicket::messages($db, (int) $ticket['id'], true) as $message) {
388 $internal = (string) ($message['visibility'] ?? 'public') === 'internal';
389 $authorType = (string) ($message['author_type'] ?? 'system');
390 $statusTo = trim((string) ($message['status_to'] ?? ''));
391 $html .= $this->tpl()->get_tpl('dbxContact_admin|ticket-message', array(
392 'message_class' => $internal ? 'border-warning bg-warning-subtle' : ($authorType === 'admin' ? 'border-primary bg-primary-subtle' : 'bg-body-tertiary'),
393 'message_icon' => $internal ? 'bi-lock' : ($authorType === 'admin' ? 'bi-headset' : ($authorType === 'requester' ? 'bi-person' : 'bi-gear')),
394 'author_label' => $internal
395 ? $texts->get_fd_message('timeline_internal_note')
396 : ($authorType === 'admin'
397 ? $texts->get_fd_message('timeline_support')
398 : ($authorType === 'requester'
399 ? $texts->get_fd_message('timeline_requester')
400 : $texts->get_fd_message('timeline_system'))),
401 'create_date' => $this->h($message['create_date'] ?? ''),
402 'body' => nl2br($this->h($message['body'] ?? '')),
403 'visibility_badge' => $internal
404 ? '<span class="badge text-bg-warning">' . $this->h($texts->get_fd_message('timeline_internal_only')) . '</span>'
405 : '',
406 'status_badge' => $statusTo !== ''
407 ? '<span class="badge ' . $this->statusClass($statusTo) . '">'
408 . $this->h($texts->format_fd_message(
409 'timeline_status',
410 array('status' => $texts->get_fd_message('status_' . $statusTo, $statusTo))
411 ))
412 . '</span>'
413 : '',
414 'mail_badge' => (int) ($message['mail_sent'] ?? 0) === 1
415 ? '<span class="badge text-bg-success"><i class="bi bi-envelope-check"></i> '
416 . $this->h($texts->get_fd_message('timeline_mail_sent'))
417 . '</span>'
418 : '',
419 ));
420 }
421
422 return $html !== ''
423 ? $html
424 : '<div class="text-muted">' . $this->h($texts->get_fd_message('timeline_empty')) . '</div>';
425 }
426
427 private function mailProfileOptions(array $extra = array()): array {
428 $profile = trim((string) dbx()->get_config('dbxContact', 'mail_profile'));
429 if ($profile !== '') {
430 $extra['mail_profile'] = $profile;
431 }
432 return $extra;
433 }
434
435 private function sendReplyMail(array $ticket, string $body): bool {
436 $to = trim((string) ($ticket['email'] ?? ''));
437 if ($to === '') {
438 return false;
439 }
440
441 $from = trim((string) dbx()->get_config('dbxContact', 'mail_from'));
442 $fromName = trim((string) dbx()->get_config('dbxContact', 'mail_from_name'));
443 if (filter_var($from, FILTER_VALIDATE_EMAIL) === false) {
444 return false;
445 }
446 $fromParam = array('email' => $from, 'name' => $fromName);
447 $subject = 'Antwort zu Ticket #' . (int) $ticket['id'] . ': ' . (string) ($ticket['subject'] ?? 'Kontaktanfrage');
448 $html = $this->tpl()->get_tpl('dbxContact|mail-contact-reply', array(
449 'subject' => $this->h($ticket['subject'] ?? ''),
450 'body' => nl2br($this->h($body)),
451 ));
452 $text = "Antwort zu Ticket #" . (int) $ticket['id'] . "\n\n" . $body;
453
454 return (bool) dbx()->send_mail(
455 $fromParam,
456 $to,
457 $subject,
458 $html,
459 'html',
460 array(),
461 $this->mailProfileOptions(array('text' => $text))
462 );
463 }
464
465 private function replyForm(array $ticket): string {
466 $rid = (int) $ticket['id'];
467 $form = dbx()->get_system_obj('dbxForm');
468 $form->init('contact-ticket-reply', 'ticket-reply-form');
469 $form->_dd = dbxContactTicket::DD_MESSAGE;
470 $form->_fd = 'dbxContact_admin|ticket-reply';
471 $form->load_fd_messages();
472 $form->load_fd_messages(
473 'dbxContact_admin|rpt-ticket-selection'
474 );
475 $form->_action = '?dbx_modul=dbxContact_admin&dbx_run1=ticket&rid=' . $rid;
476 $form->_data = array(
477 'status' => dbxContactTicket::normalizeStatus((string) ($ticket['status'] ?? 'open')),
478 'priority' => dbxContactTicket::normalizePriority((string) ($ticket['priority'] ?? 'normal')),
479 'visibility' => 'public',
480 'body' => '',
481 'send_mail' => 1,
482 );
483 $form->add_module_bar(
484 $form->get_fd_message('bar_title'),
485 'bi-reply',
486 $form->get_fd_message('bar_subtitle')
487 );
488 $form->add_rep('bar_title_pre', '');
489 $form->add_rep('bar_title_heading_attrs', '');
490 $form->add_rep('bar_middle', '');
491 $form->add_rep('bar_actions', '');
492 $form->add_rep('bar_extra', '');
493 $form->add_rep('rid', $rid);
494 $messageCount = max(0, (int) $this->db()->count(dbxContactTicket::DD_MESSAGE, array('ticket_id' => $rid)));
495 $confirm = $this->deleteConfirmText(
496 $rid,
497 $messageCount,
498 $form
499 );
500 $form->add_rep(
501 'delete_action',
502 dbx()->action_url(
503 '?dbx_modul=dbxContact_admin&dbx_run1=delete&rid=' . $rid
504 )
505 );
506 $form->add_rep('delete_confirm_title', $confirm['title']);
507 $form->add_rep('delete_confirm', $confirm['question']);
508 $form->add_rep('delete_confirm_hint', $confirm['hint']);
509 $form->_msg_info = $form->get_fd_message('form_info');
510 $form->_msg_error = $form->get_fd_message('validation_error');
511 $form->add_fld('status');
512 $form->add_fld('priority');
513 $form->add_fld('visibility');
514 $form->add_fld('body');
515 $form->add_fld('send_mail');
516
517 if ($form->submit() && !$form->errors()) {
518 $status = dbxContactTicket::normalizeStatus((string) $form->get_post('status', 'answered', 'parameter|max=24'));
519 $priority = dbxContactTicket::normalizePriority((string) $form->get_post('priority', 'normal', 'parameter|max=16'));
520 $visibility = (string) $form->get_post('visibility', 'public', 'parameter|max=16') === 'internal' ? 'internal' : 'public';
521 $body = trim((string) $form->get_post_data('body', '', '*'));
522 $sendMail = (int) $form->get_post('send_mail', 0, 'int') === 1 && $visibility === 'public' && dbxContactConfig::mailOnReply();
523 $oldStatus = dbxContactTicket::normalizeStatus((string) ($ticket['status'] ?? 'open'));
524 $db = $this->db();
525
526 $messageId = dbxContactTicket::addMessage($db, $rid, array(
527 'author_uid' => (int) dbx()->user(),
528 'author_type' => 'admin',
529 'message_type' => $visibility === 'internal' ? 'note' : 'reply',
530 'visibility' => $visibility,
531 'body' => $body,
532 'status_from' => $oldStatus,
533 'status_to' => $status,
534 ));
535
536 if ($messageId > 0) {
537 $values = array(
538 'status' => $status,
539 'priority' => $priority,
540 'assigned_uid' => (int) dbx()->user(),
541 'closed_date' => $status === 'closed' ? date('Y-m-d H:i:s') : '',
542 );
543 dbxContactTicket::touch($db, $rid, $values);
544
545 $mailOk = false;
546 if ($sendMail) {
547 $mailOk = $this->sendReplyMail($ticket, $body);
548 if ($mailOk) {
549 $db->update(dbxContactTicket::DD_MESSAGE, array(
550 'mail_sent' => 1,
551 'mail_sent_date' => date('Y-m-d H:i:s'),
552 ), $messageId, 0, 1, 1, 1);
553 }
554 }
555
556 if ($sendMail && !$mailOk) {
557 $form->_msg_warning = $form->get_fd_message(
558 'mail_warning'
559 );
560 } else {
561 $form->_msg_success = $visibility === 'internal'
562 ? $form->get_fd_message('internal_success')
563 : $form->get_fd_message('reply_success')
564 . ($mailOk
565 ? $form->get_fd_message('mail_success_suffix')
566 : '');
567 }
568 $form->_data['body'] = '';
569 } else {
570 $form->_msg_error = $form->get_fd_message('message_error');
571 }
572 }
573
574 return $form->run();
575 }
576
577 private function ticket(): string {
578 $rid = (int) dbx()->get_modul_var('rid', 0, 'int');
579 $texts = dbx()->get_system_obj('dbxForm');
580 $texts->init('contact-ticket-texts', 'dbx|form');
581 $texts->load_fd_messages('dbxContact_admin|rpt-ticket-selection');
582 $ticket = dbxContactTicket::ticket($this->db(), $rid);
583 if (!$ticket) {
584 return $this->frame(
585 $this->alert(
586 'warning',
587 $texts->get_fd_message('ticket_not_found')
588 )
589 );
590 }
591
592 $replyForm = $this->replyForm($ticket);
593 $ticket = dbxContactTicket::ticket($this->db(), $rid);
594 $status = dbxContactTicket::normalizeStatus((string) ($ticket['status'] ?? 'open'));
595 $priority = dbxContactTicket::normalizePriority((string) ($ticket['priority'] ?? 'normal'));
596 $content = $this->tpl()->get_tpl('dbxContact_admin|ticket-detail', array(
597 'rid' => $rid,
598 'subject' => $this->h($ticket['subject'] ?? ''),
599 'name' => $this->h($ticket['name'] ?? ''),
600 'email' => $this->h($ticket['email'] ?? ''),
601 'email_attr' => $this->h($ticket['email'] ?? ''),
602 'phone' => $this->h(trim((string) ($ticket['phone'] ?? '')) ?: '-'),
603 'create_date' => $this->h($ticket['create_date'] ?? ''),
604 'uid' => (int) ($ticket['uid'] ?? 0) > 0
605 ? '#' . (int) $ticket['uid']
606 : $texts->get_fd_message('guest'),
607 'status_label' => $this->h($texts->get_fd_message('status_' . $status, $status)),
608 'status_class' => $this->statusClass($status),
609 'priority_label' => $this->h($texts->get_fd_message('priority_' . $priority, $priority)),
610 'priority_class' => $this->priorityClass($priority),
611 'user_visibility' => (int) ($ticket['user_hidden'] ?? 0) === 1
612 ? $texts->get_fd_message('user_hidden')
613 : $texts->get_fd_message('user_visible'),
614 'timeline' => $this->timeline($ticket, $texts),
615 'reply_form' => $replyForm,
616 ));
617
618 return $this->frame($content, 'Ticket #' . $rid);
619 }
620
621 private function deleteTicket(): string {
622 $rid = (int) dbx()->get_modul_var('rid', 0, 'int');
623 $withFrame = (int) dbx()->get_system_var('dbx_ajax', 0, 'int') !== 1;
624 $texts = dbx()->get_system_obj('dbxReport');
625 $texts->init('contact-delete-texts', 'dbx|report');
626 $texts->load_fd_messages(
627 'dbxContact_admin|rpt-ticket-selection'
628 );
629 if ($this->deleteTicketRecord($rid)) {
630 return $this->listTickets(
631 $texts->format_fd_message(
632 'delete_success',
633 array('id' => $rid)
634 ),
635 '',
636 $withFrame
637 );
638 }
639
640 return $this->listTickets(
641 '',
642 $texts->format_fd_message(
643 'delete_error',
644 array('id' => $rid)
645 ),
646 $withFrame
647 );
648 }
649
650 public function run() {
651 $run = dbx()->get_modul_var('dbx_run1', 'list', 'parameter');
652 if ($run === 'install') {
653 return $this->install();
654 }
655 if (!$this->schemaReady()) {
656 return $this->alert('warning', 'Das Ticket-Datenmodell ist noch nicht installiert.')
657 . '<div class="p-3"><a class="btn btn-primary" href="?dbx_modul=dbxContact_admin&dbx_run1=install"><i class="bi bi-database-gear"></i> Jetzt installieren</a></div>';
658 }
659 if ($run === 'ticket' || $run === 'reply') {
660 return $this->ticket();
661 }
662 if ($run === 'delete') {
663 return $this->deleteTicket();
664 }
665 return $this->listTickets();
666 }
667}
static normalizeStatus(string $status, string $fallback='open')
static normalizePriority(string $priority, string $fallback='normal')
static ensureInitialMessage($db, array $ticket)
if((string)($formActionPolicy['action'] ?? '') !=='dbxAction.save'||!dbx() ->check_action_token((string)($formActionPolicy['scope'] ?? ''),(string)($formActionRoute['params']['dbx_token'] ?? ''))) $report
action_url(string $url, string $action='', array $bindings=array())
Tokenisiert eine automatisch erkannte zustandsaendernde Route.
Definition dbxApi.php:1270
user($key='id')
Liest einen Wert des aktuellen Benutzers.
Definition dbxApi.php:1305
if(!defined( 'IMG_WEBP')) define( 'IMG_WEBP'
DBX schema administration.
$profile
Definition run.php:6
$results
Definition run_job.php:143