From 010b8218025115f749d69e6da2d2a8aef8129722 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Fri, 2 Jan 2026 17:34:52 +0000 Subject: [PATCH] wp-multi-ticket.php aktualisiert --- wp-multi-ticket.php | 2662 +++++++++++++++++++++---------------------- 1 file changed, 1331 insertions(+), 1331 deletions(-) diff --git a/wp-multi-ticket.php b/wp-multi-ticket.php index d02e234..b1231f5 100644 --- a/wp-multi-ticket.php +++ b/wp-multi-ticket.php @@ -1,1331 +1,1331 @@ -table_tickets = $wpdb->prefix . 'wmt_tickets'; - $this->table_messages = $wpdb->prefix . 'wmt_messages'; - - register_activation_hook( __FILE__, array( $this, 'create_tables' ) ); - add_action( 'plugins_loaded', array( $this, 'check_db_update' ) ); - - add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); - add_action( 'admin_init', array( $this, 'register_settings' ) ); - - add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widget' ) ); - - add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'load_analytics_scripts' ) ); - - add_shortcode( 'wmt_form', array( $this, 'render_creation_form' ) ); - add_shortcode( 'wmt_view', array( $this, 'render_ticket_view' ) ); - add_shortcode( 'wmt_lookup', array( $this, 'render_lookup_form' ) ); - - add_action( 'init', array( $this, 'handle_guest_creation' ) ); - add_action( 'init', array( $this, 'handle_guest_reply' ) ); - add_action( 'init', array( $this, 'handle_guest_lookup' ) ); - - add_action( 'admin_post_wmt_admin_reply', array( $this, 'handle_admin_post' ) ); - add_action( 'admin_init', array( $this, 'handle_delete_ticket' ) ); - add_action( 'admin_init', array( $this, 'handle_csv_export' ) ); - } - - // *** HIER: korrigierter Hook-Name für die Analytics-Unterseite *** - public function load_analytics_scripts( $hook ) { - // Für Submenu: parent slug wmt_tickets -> Hook: wmt-tickets_page_wmt_analytics - if ( 'wmt-tickets_page_wmt_analytics' !== $hook ) return; - - $local_js = plugin_dir_path( __FILE__ ) . 'chart.js'; - $js_url = plugins_url( 'chart.js', __FILE__ ); - - if ( file_exists( $local_js ) ) { - wp_enqueue_script( 'chartjs', $js_url, array(), '4.4.0', true ); - } - } - - public function enqueue_styles() { - ?> - - get_charset_collate(); - - $sql_tickets = "CREATE TABLE $this->table_tickets ( - id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, - title varchar(255) NOT NULL, - category varchar(100) DEFAULT 'Allgemein', - department varchar(100) DEFAULT NULL, - status varchar(50) DEFAULT 'Offen', - priority varchar(50) DEFAULT 'Mittel', - guest_name varchar(100) NOT NULL, - guest_email varchar(100) NOT NULL, - ticket_hash varchar(64) NOT NULL, - assigned_to bigint(20) UNSIGNED DEFAULT NULL, - created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, - updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY assigned_to (assigned_to) - ) $charset_collate;"; - - $sql_messages = "CREATE TABLE $this->table_messages ( - id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, - ticket_id bigint(20) UNSIGNED NOT NULL, - sender_name varchar(100) NOT NULL, - sender_type varchar(20) NOT NULL, - message longtext DEFAULT NULL, - internal_note text DEFAULT NULL, - file_url varchar(255) DEFAULT NULL, - created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, - PRIMARY KEY (id) - ) $charset_collate;"; - - require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); - dbDelta( $sql_tickets ); - dbDelta( $sql_messages ); - } - - public function check_db_update() { - global $wpdb; - $cols = $wpdb->get_col( "SHOW COLUMNS FROM $this->table_tickets" ); - if( !in_array('ticket_hash', $cols) ) $wpdb->query( "ALTER TABLE $this->table_tickets ADD COLUMN ticket_hash varchar(64) NOT NULL AFTER guest_email" ); - if( !in_array('guest_name', $cols) ) { $wpdb->query( "ALTER TABLE $this->table_tickets ADD COLUMN guest_name varchar(100) NOT NULL" ); $wpdb->query( "ALTER TABLE $this->table_tickets ADD COLUMN guest_email varchar(100) NOT NULL" ); } - if( !in_array('department', $cols) ) $wpdb->query( "ALTER TABLE $this->table_tickets ADD COLUMN department varchar(100) DEFAULT NULL AFTER category" ); - - $mcols = $wpdb->get_col( "SHOW COLUMNS FROM $this->table_messages" ); - if( !in_array('sender_type', $mcols) ) { $wpdb->query( "ALTER TABLE $this->table_messages ADD COLUMN sender_name varchar(100) NOT NULL" ); $wpdb->query( "ALTER TABLE $this->table_messages ADD COLUMN sender_type varchar(20) NOT NULL" ); } - if( !in_array('internal_note', $mcols) ) $wpdb->query( "ALTER TABLE $this->table_messages ADD COLUMN internal_note text DEFAULT NULL AFTER message" ); - if( !in_array('file_url', $mcols) ) $wpdb->query( "ALTER TABLE $this->table_messages ADD COLUMN file_url varchar(255) DEFAULT NULL AFTER internal_note" ); - - $wpdb->query( "ALTER TABLE $this->table_messages MODIFY COLUMN message longtext DEFAULT NULL" ); - } - - public function add_admin_menu() { - add_menu_page( 'Tickets', 'Tickets Pro', 'manage_options', 'wmt_tickets', array( $this, 'render_admin_page' ), 'dashicons-tickets-alt', 30 ); - add_submenu_page( 'wmt_tickets', 'Übersicht', 'Übersicht', 'manage_options', 'wmt_tickets', array( $this, 'render_admin_page' ) ); - add_submenu_page( 'wmt_tickets', 'Analytics', 'Analytics', 'manage_options', 'wmt_analytics', array( $this, 'render_analytics_page' ) ); - add_submenu_page( 'wmt_tickets', 'Einstellungen', 'Einstellungen', 'manage_options', 'wmt_settings', array( $this, 'render_settings_page' ) ); - add_submenu_page( 'wmt_tickets', 'Benachrichtigungen', 'Benachrichtigungen', 'manage_options', 'wmt_notifications', array( $this, 'render_notifications_page' ) ); - } - - public function register_settings() { - register_setting( 'wmt_settings_group', 'wmt_categories' ); - register_setting( 'wmt_settings_group', 'wmt_departments' ); - register_setting( 'wmt_settings_group', 'wmt_priorities' ); - register_setting( 'wmt_settings_group', 'wmt_statuses' ); - register_setting( 'wmt_settings_group', 'wmt_admin_email' ); - register_setting( 'wmt_settings_group', 'wmt_templates' ); - register_setting( 'wmt_settings_group', 'wmt_allowed_filetypes' ); - - register_setting( 'wmt_notifications_group', 'wmt_discord_webhook' ); - register_setting( 'wmt_notifications_group', 'wmt_telegram_token' ); - register_setting( 'wmt_notifications_group', 'wmt_telegram_chat_id' ); - register_setting( 'wmt_notifications_group', 'wmt_new_ticket_notify_users' ); - } - - public function render_analytics_page() { - global $wpdb; - - $total_tickets = $wpdb->get_var("SELECT COUNT(*) FROM $this->table_tickets"); - - if ( $total_tickets == 0 ) { - echo '
'; - echo '
'; - echo '

Noch keine Daten

'; - echo '

Es wurden noch keine Tickets erstellt.

'; - echo 'Zu den Tickets'; - echo '
'; - return; - } - - $open_tickets = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $this->table_tickets WHERE status NOT LIKE %s", '%Geschlossen%')); - $closed_tickets = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $this->table_tickets WHERE status LIKE %s", '%Geschlossen%')); - $new_tickets_30d = $wpdb->get_var("SELECT COUNT(*) FROM $this->table_tickets WHERE created_at >= DATE_SUB(DATE_ADD(NOW(), INTERVAL 2 YEAR), INTERVAL 30 DAY)"); - - $status_data = $wpdb->get_results("SELECT COALESCE(status, 'Unbekannt') as status, COUNT(*) as count FROM $this->table_tickets GROUP BY status"); - - $cat_data = $wpdb->get_results("SELECT COALESCE(category, 'Keine Kategorie') as category, COUNT(*) as count FROM $this->table_tickets GROUP BY category ORDER BY count DESC LIMIT 8"); - - $agent_data = $wpdb->get_results(" - SELECT u.display_name, COUNT(t.id) as count - FROM {$wpdb->users} u - LEFT JOIN $this->table_tickets t ON u.ID = t.assigned_to - GROUP BY u.ID - HAVING count > 0 - ORDER BY count DESC - "); - - $timeline_data = $wpdb->get_results(" - SELECT DATE(created_at) as date, COUNT(*) as count - FROM $this->table_tickets - GROUP BY DATE(created_at) - ORDER BY date ASC - "); - - ?> -
-
-

Analytics Dashboard

- Live Daten -
- -
-
-

Gesamt Tickets

-

-
-
-

Aktiv / Offen

-

-

Benötigt Aufmerksamkeit

-
-
-

Geschlossen

-

-
-
-

Letzte 30 Tage

-

-

Neue Eingänge

-
-
- -
- -
-

Status Verteilung

-
-
- - -
-

Top Kategorien

-
-
- - -
-

Workload pro Agent

-
-
- - -
-

Tickets pro Tag

-
-
-
- -
- - - - - - -
-

Einstellungen

-
- - - - - - - - - - - - -
Kategorien
Kategorie ➔ Abteilung Zuordnung
Prioritäten
Status
Admin E-Mail
Erlaubte Dateiendungen

Kommagetrennt (z.B. pdf, doc, png).

Textbausteine -
- $tpl ): ?> -
- × -
- - -
- -
- -
- -
- -
-
- - - 'display_name' ) ); - $selected_users = get_option( 'wmt_new_ticket_notify_users', array() ); // FIX: Corrected array() - if ( ! is_array( $selected_users ) ) $selected_users = array(); - ?> -
-

Benachrichtigungen

-
- - - - - - - - - - - - - - - - - - -
Discord Webhook URL - -

Wird bei neuem Ticket und neuer Gast-Antwort gesendet.

-
Telegram Bot Token - -
Telegram Chat ID - -

Für Gruppen/Kanäle mit -100 beginnen.

-
Zusätzliche Benachrichtigungen
bei neuem Ticket
- -

Diese User erhalten zusätzlich eine E-Mail bei jedem neuen Ticket (Strg/Cmd klicken für Mehrfachauswahl).

-
- -
-
- get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $this->table_tickets WHERE status NOT LIKE %s", '%Geschlossen%' ) ); - $link = admin_url( 'admin.php?page=wmt_tickets' ); - - echo '
'; - if ( $count > 0 ) { - echo '
' . intval( $count ) . '
'; - echo '
Offene Tickets
'; - } else { - echo '
0
'; - echo '
Keine offenen Tickets
'; - } - echo 'Alle Tickets ansehen'; - echo '
'; - } - - public function render_admin_page() { - if ( isset( $_GET['wmt_print'] ) && $_GET['wmt_print'] == '1' && isset( $_GET['id'] ) ) { - $this->render_print_view( intval( $_GET['id'] ) ); - return; - } - - if ( isset( $_GET['action'] ) && $_GET['action'] === 'edit' && isset( $_GET['id'] ) ) { - $this->render_admin_detail( intval( $_GET['id'] ) ); - return; - } - - if( isset( $_GET['deleted'] ) && $_GET['deleted'] == '1' ) { - echo '

Ticket erfolgreich gelöscht.

'; - } - - $search = isset( $_GET['s'] ) ? sanitize_text_field( $_GET['s'] ) : ''; - $status_filter = isset( $_GET['status_filter'] ) ? sanitize_text_field( $_GET['status_filter'] ) : ''; - - global $wpdb; - $sql = "SELECT * FROM $this->table_tickets WHERE 1=1"; - - if ( $search ) { - $sql .= $wpdb->prepare( " AND (title LIKE %s OR guest_name LIKE %s OR guest_email LIKE %s)", '%' . $wpdb->esc_like( $search ) . '%', '%' . $wpdb->esc_like( $search ) . '%', '%' . $wpdb->esc_like( $search ) . '%' ); - } - - if ( $status_filter ) { - $sql .= $wpdb->prepare( " AND status = %s", $status_filter ); - } - - $sql .= " ORDER BY updated_at DESC"; - $tickets = $wpdb->get_results( $sql ); - - echo '

Ticket Übersicht

'; - - echo '
'; - echo '
'; - echo ''; - echo ''; - echo ''; - echo ''; - echo '
'; - - $export_url = admin_url( 'admin.php?page=wmt_tickets&wmt_action=export' ); - if($search) $export_url = add_query_arg( 's', $search, $export_url ); - echo 'CSV Export'; - echo '
'; - - echo ''; - echo ''; - echo ''; - foreach ( $tickets as $t ) { - $prio_style = strtolower( $t->priority ) === 'hoch' ? 'color: #d63638; font-weight: bold; font-size: 1.1em;' : ''; - $assigned_user = $t->assigned_to ? get_userdata( $t->assigned_to ) : null; - $assigned_name = $assigned_user ? $assigned_user->display_name : '-'; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - } - echo '
IDBetreffKat.Abt.PrioGast InfoStatusZugewiesenAktionen
#' . $t->id . '' . esc_html( $t->title ) . '' . esc_html( $t->category ) . '' . ( $t->department ? esc_html($t->department) : '-' ) . '' . esc_html( $t->priority ) . '' . esc_html( $t->guest_name ) . '
' . esc_html( $t->guest_email ) . '
' . esc_html( $t->status ) . '' . esc_html( $assigned_name ) . ''; - echo 'Bearbeiten '; - $delete_url = wp_nonce_url( admin_url( 'admin.php?page=wmt_tickets&action=wmt_delete&id=' . $t->id ), 'wmt_delete_ticket_' . $t->id ); - echo 'Löschen'; - echo '
'; - } - - private function render_admin_detail( $id ) { - global $wpdb; - $ticket = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE id = %d", $id ) ); - if ( ! $ticket ) return; - - if ( isset( $_GET['msg'] ) && $_GET['msg'] == 'sent' ) { - echo '

Aktualisierung erfolgreich!

'; - } - - $messages = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $this->table_messages WHERE ticket_id = %d ORDER BY created_at ASC", $id ) ); - - $all_users = get_users( array( 'orderby' => 'display_name' ) ); - - $status_opts = array_map( 'trim', explode( ',', get_option('wmt_statuses') ) ); - - $mapping_raw = get_option('wmt_departments', ''); - $dept_opts = array(); - if($mapping_raw) { - $pairs = array_map('trim', explode(',', $mapping_raw)); - foreach($pairs as $pair) { - if(strpos($pair, ':') !== false) { - list($cat, $dept) = explode(':', $pair, 2); - $dept_opts[] = trim($dept); - } - } - $dept_opts = array_unique($dept_opts); - } - - $raw_templates = get_option('wmt_templates', array()); - $tpl_public = ''; - $tpl_internal = ''; - - if( is_array($raw_templates) ) { - foreach($raw_templates as $tpl) { - if(isset($tpl['type']) && isset($tpl['content'])) { - $name = isset($tpl['name']) && !empty($tpl['name']) ? esc_html($tpl['name']) : substr(esc_html($tpl['content']), 0, 30) . '...'; - $content = esc_attr($tpl['content']); - $opt = ''; - if($tpl['type'] === 'internal') $tpl_internal .= $opt; - else $tpl_public .= $opt; - } - } - } - - $colors = array('#e57373', '#f06292', '#ba68c8', '#9575cd', '#7986cb', '#64b5f6', '#4fc3f7', '#4dd0e1', '#4db6ac', '#81c784', '#aed581', '#ffca28', '#ffa726'); - - ?> -
-

Ticket #id; ?> bearbeiten

-
- « Zurück - Ticket drucken - id ), 'wmt_delete_ticket_' . $ticket->id ); - echo 'Ticket löschen'; - ?> -
- -
-
- sender_type === 'system' ): ?> -
System: message); ?> created_at; ?>
- sender_type === 'admin' ); - $bg = $is_admin ? '#fff' : '#e3f2fd'; - $align = $is_admin ? 'left' : 'right'; - $initial = substr($msg->sender_name, 0, 1); - $char_code = ord(strtolower($initial)); - $bg_color = $is_admin ? '#555' : $colors[$char_code % count($colors)]; - ?> -
-
-
- sender_name ); ?> created_at; ?> - (Support)'; ?> - internal_note) ) : ?> -
Interne Notiz:internal_note ) ); ?>
- - message) ): ?> -
message); ?>
- -

Keine öffentliche Nachricht.

- - file_url ) : ?> - Datei herunterladen - -
-
- -
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - -
Abteilung - -
Status - -

Automatik: Bei Antworten wird das Ticket automatisch auf "In Bearbeitung" gesetzt (außer Sie wählen "Geschlossen").

-
Prioritätpriority ); ?>
Zuweisen an - -

Auto-Detect: Wenn Sie antworten, wird das Ticket automatisch Ihnen zugewiesen (sofern noch niemand zugewiesen). Wählen Sie hier einen Kollegen aus, um das Ticket zu übergeben.

-
Antwort & Notizen - - - - -
- - - - - 'message', 'media_buttons' => false, 'textarea_rows' => 10, 'teeny' => false); - wp_editor( $content, 'wmt_message_editor', $settings ); - ?> - - - - -

- Wird gesendet an guest_email ); ?>. -

-
- -
-
- - -
- get_row( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE id = %d", $id ) ); - if ( ! $ticket ) return; - - $messages = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $this->table_messages WHERE ticket_id = %d ORDER BY created_at ASC", $id ) ); - - echo 'Ticket #' . $id . ''; - echo ''; - - echo '

Ticket #' . $id . ': ' . esc_html( $ticket->title ) . '

'; - - echo '
Kunde: ' . esc_html( $ticket->guest_name ) . ' (' . esc_html( $ticket->guest_email ) . ')
'; - echo '
Kategorie: ' . esc_html( $ticket->category ) . '
'; - echo '
Abteilung: ' . ($ticket->department ? esc_html($ticket->department) : '-') . '
'; - echo '
Status: ' . esc_html( $ticket->status ) . '
'; - echo '
Priorität: ' . esc_html( $ticket->priority ) . '
'; - $assigned = $ticket->assigned_to ? get_userdata($ticket->assigned_to)->display_name : 'Niemand'; - echo '
Zugewiesen an: ' . esc_html($assigned) . '
'; - - echo '

Verlauf

'; - - foreach ( $messages as $msg ) { - if ( $msg->sender_type === 'system' ) { - echo '
System: ' . esc_html($msg->message) . ' (' . $msg->created_at . ')
'; - continue; - } - - echo '
'; - echo '
' . esc_html( $msg->sender_name ) . ' ' . ($msg->sender_type === 'admin' ? '(Support)' : '(Kunde)') . ' - ' . $msg->created_at . '
'; - echo '
' . wp_kses_post($msg->message) . '
'; - if ( $msg->sender_type === 'admin' && !empty($msg->internal_note) ) { - echo '
Interne Notiz: ' . esc_html($msg->internal_note) . '
'; - } - if ( $msg->file_url ) { - echo ''; - } - echo '
'; - } - - echo ''; - exit; - } - - private function handle_upload($file_input_name) { - if (empty($_FILES[$file_input_name]['name'])) return ''; - $allowed_raw = get_option('wmt_allowed_filetypes', 'pdf, doc, docx, jpg, png'); - $allowed_exts = array_map('trim', explode(',', strtolower($allowed_raw))); - $file_ext = strtolower(pathinfo($_FILES[$file_input_name]['name'], PATHINFO_EXTENSION)); - - if (!in_array($file_ext, $allowed_exts)) { - wp_die( "Fehler: Der Dateityp .{$file_ext} ist nicht erlaubt. Erlaubt sind: " . esc_html($allowed_raw) ); - } - require_once(ABSPATH . 'wp-admin/includes/file.php'); - $upload = wp_handle_upload($_FILES[$file_input_name], array('test_form' => false)); - if (isset($upload['error'])) wp_die('Upload Fehler: ' . $upload['error']); - return isset($upload['url']) ? $upload['url'] : ''; - } - - private function send_discord_notification( $title, $description, $url ) { - $webhook = get_option( 'wmt_discord_webhook' ); - if ( ! $webhook ) return; - - $data = array( - "embeds" => array( - array( - "title" => $title, - "description" => $description, - "url" => $url, - "color" => 3447003, - "timestamp" => current_time( 'mysql' ) - ) - ) - ); - - wp_remote_post( $webhook, array( - 'body' => wp_json_encode( $data ), - 'headers' => array( 'Content-Type' => 'application/json' ), - 'timeout' => 10 - ) ); - } - - private function send_telegram_notification( $text ) { - $token = get_option( 'wmt_telegram_token' ); - $chat_id = get_option( 'wmt_telegram_chat_id' ); - if ( ! $token || ! $chat_id ) return; - - $url = "https://api.telegram.org/bot{$token}/sendMessage"; - wp_remote_post( $url, array( - 'body' => array( - 'chat_id' => $chat_id, - 'text' => $text, - 'parse_mode' => 'HTML' - ), - 'timeout' => 10 - ) ); - } - - public function handle_delete_ticket() { - if ( isset( $_GET['action'] ) && $_GET['action'] === 'wmt_delete' && isset( $_GET['id'] ) ) { - $id = intval( $_GET['id'] ); - $nonce = isset( $_GET['_wpnonce'] ) ? $_GET['_wpnonce'] : ''; - if ( ! wp_verify_nonce( $nonce, 'wmt_delete_ticket_' . $id ) || ! current_user_can( 'manage_options' ) ) wp_die( 'Sicherheitsfehler' ); - global $wpdb; - $wpdb->delete( $this->table_messages, array( 'ticket_id' => $id ) ); - $wpdb->delete( $this->table_tickets, array( 'id' => $id ) ); - wp_redirect( admin_url( 'admin.php?page=wmt_tickets&deleted=1' ) ); - exit; - } - } - - public function handle_admin_post() { - if ( ! isset( $_POST['wmt_nonce'] ) || ! wp_verify_nonce( $_POST['wmt_nonce'], 'wmt_admin_action' ) ) wp_die( 'Sicherheitsfehler' ); - - $msg_content = isset( $_POST['message'] ) ? wp_kses_post( $_POST['message'] ) : ''; - $tid = intval( $_POST['ticket_id'] ); - - // STATUS LOGIK: - // Wenn nicht "Geschlossen" gewählt wurde, setzen wir es auf "In Bearbeitung" - $status = sanitize_text_field( $_POST['status'] ); - if ( strtolower( $status ) !== 'geschlossen' ) { - $status = 'In Bearbeitung'; - } - - $dept = sanitize_text_field( $_POST['department'] ); - - // Prüfen Dropdown: User manuell ausgewählt? - $assigned = !empty($_POST['assigned_to']) ? intval( $_POST['assigned_to'] ) : null; - - // Auto-Detection Logik - // Wenn im Dropdown "Niemand" ausgewählt ist (NULL), ABER der User gerade antwortet... - if ( empty($assigned) && (!empty($msg_content) || !empty($_FILES['ticket_file']['name'])) ) { - // ...dann weisen wir es dem aktuellen User zu. - $assigned = get_current_user_id(); - } - - $internal_note = sanitize_textarea_field( $_POST['internal_note'] ); - $file_url = $this->handle_upload('ticket_file'); - - global $wpdb; - $old_ticket = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE id = %d", $tid ) ); - - $wpdb->update( $this->table_tickets, - array( 'status' => $status, 'department' => $dept ?: null, 'assigned_to' => $assigned ), - array( 'id' => $tid ) - ); - - if ( $old_ticket && $old_ticket->department !== $dept ) { - $new_dept_name = $dept ?: 'Keine'; - $wpdb->insert( $this->table_messages, array( - 'ticket_id' => $tid, 'sender_name' => 'System', 'sender_type' => 'system', 'message' => "Abteilung geändert zu: {$new_dept_name}" - )); - } - - // Handover Benachrichtigung: Wenn sich der zugewiesene User ändert - if ( $old_ticket->assigned_to != $assigned && $assigned ) { - $user = get_userdata( $assigned ); - if ( $user ) { - $subject = "Ticket #{$tid} wurde Ihnen zugewiesen"; - $body = "Hallo {$user->display_name},\n\nDas Ticket #{$tid} – „{$old_ticket->title}“ wurde Ihnen zur Bearbeitung zugewiesen.\n\nLink: " . admin_url( "admin.php?page=wmt_tickets&action=edit&id={$tid}" ); - wp_mail( $user->user_email, $subject, $body ); - } - } - - if ( ! empty( $msg_content ) || ! empty( $file_url ) ) { - $current_user = wp_get_current_user(); - $wpdb->insert( $this->table_messages, array( - 'ticket_id' => $tid, 'sender_name' => $current_user->display_name, 'sender_type' => 'admin', - 'message' => $msg_content, 'internal_note' => $internal_note, 'file_url' => $file_url - )); - - $view_link = add_query_arg( array( 'wmt_view' => $tid, 'hash' => $old_ticket->ticket_hash ), home_url() ); - $subject = "Neue Antwort zu Ihrem Ticket #{$tid}"; - $body = "Hallo {$old_ticket->guest_name},\n\nEs gibt eine neue Antwort:\n{$msg_content}\n\nLink zum Ticket:\n{$view_link}"; - if($file_url) $body .= "\n\nAnhang: {$file_url}"; - wp_mail( $old_ticket->guest_email, $subject, $body ); - - } elseif ( ! empty( $internal_note ) ) { - $current_user = wp_get_current_user(); - $wpdb->insert( $this->table_messages, array( - 'ticket_id' => $tid, 'sender_name' => $current_user->display_name, 'sender_type' => 'admin', - 'message' => null, 'internal_note' => $internal_note - )); - } - - wp_redirect( admin_url( 'admin.php?page=wmt_tickets&action=edit&id=' . $tid . '&msg=sent' ) ); - exit; - } - - public function handle_csv_export() { - if ( ! isset( $_GET['wmt_action'] ) || $_GET['wmt_action'] !== 'export' || ! current_user_can( 'manage_options' ) ) return; - - global $wpdb; - $search = isset( $_GET['s'] ) ? sanitize_text_field( $_GET['s'] ) : ''; - $sql = "SELECT * FROM $this->table_tickets"; - if($search) { - $sql .= $wpdb->prepare( " WHERE (title LIKE %s OR guest_name LIKE %s OR guest_email LIKE %s)", '%' . $wpdb->esc_like( $search ) . '%', '%' . $wpdb->esc_like( $search ) . '%', '%' . $wpdb->esc_like( $search ) . '%' ); - } - - $results = $wpdb->get_results( $sql ); - - header( 'Content-Type: text/csv' ); - header( 'Content-Disposition: attachment; filename=tickets-export.csv' ); - - $output = fopen( 'php://output', 'w' ); - fputcsv( $output, array( 'ID', 'Titel', 'Kategorie', 'Abteilung', 'Status', 'Priorität', 'Name', 'E-Mail', 'Zugewiesen', 'Datum' ) ); - - foreach ( $results as $row ) { - $assigned = $row->assigned_to ? get_userdata($row->assigned_to)->display_name : ''; - fputcsv( $output, array( $row->id, $row->title, $row->category, $row->department, $row->status, $row->priority, $row->guest_name, $row->guest_email, $assigned, $row->created_at ) ); - } - fclose( $output ); - exit; - } - - public function render_creation_form() { - ob_start(); - if(isset($_GET['upload_error'])) { - echo '
' . esc_html(urldecode($_GET['upload_error'])) . '
'; - } - if( isset( $_GET['wmt_success'] ) ) { - echo '
Ticket erfolgreich erstellt! Bitte prüfen Sie Ihre E-Mails.
'; - } - ?> -
-

Neues Support Ticket erstellen

-
- - - - - - - - - - - - - - - - -
-
- Eine E-Mail mit Links wurde gesendet.'; - } - ?> -
-

Meine Tickets finden

-
- - -
-
-

Kein Ticket ausgewählt.

'; - - global $wpdb; - $ticket = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE id = %d AND ticket_hash = %s", $tid, $hash ) ); - if ( ! $ticket ) return '

Zugriff verweigert.

'; - - $messages = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $this->table_messages WHERE ticket_id = %d ORDER BY created_at ASC", $tid ) ); - $is_closed = ( strtolower($ticket->status) === 'geschlossen' ); - - ob_start(); - ?> -
-
-

Ticket #id; ?>: title ); ?>

-

Status: status ); ?>

-
-
- sender_type === 'system' ): ?> -
message); ?>
- sender_type === 'admin' ); - if(empty($msg->message) && empty($msg->file_url)) continue; - ?> -
- sender_name ); ?> created_at; ?> -

message ) ); ?>

- file_url ) : ?> - Datei ansehen - -
- -
- -
- - - - - - - -
- -
Ticket geschlossen.
- -
- handle_upload('guest_file'); - - $dept = null; - $mapping_raw = get_option('wmt_departments', ''); - if($mapping_raw) { - foreach(array_map('trim', explode(',', $mapping_raw)) as $pair) { - if(strpos($pair, ':') !== false) { - list($map_cat, $map_dept) = explode(':', $pair, 2); - if(trim($map_cat) === $cat) { $dept = trim($map_dept); break; } - } - } - } - - global $wpdb; - $wpdb->insert( $this->table_tickets, array( - 'title' => $title, 'category' => $cat, 'priority' => $prio, 'department' => $dept, - 'guest_name' => $name, 'guest_email' => $email, 'ticket_hash' => $hash - )); - $tid = $wpdb->insert_id; - $wpdb->insert( $this->table_messages, array( - 'ticket_id' => $tid, 'sender_name' => $name, 'sender_type' => 'guest', 'message' => $msg, 'file_url' => $file_url - )); - - $admin_link = admin_url( "admin.php?page=wmt_tickets&action=edit&id={$tid}" ); - $guest_link = add_query_arg( array( 'wmt_view' => $tid, 'hash' => $hash ), home_url() ); - - $admin_email = get_option( 'wmt_admin_email', get_option('admin_email') ); - wp_mail( $admin_email, "Neues Ticket #{$tid}: {$title}", "Von: {$name} ({$email})\nLink: {$admin_link}" ); - - $this->send_discord_notification( "Neues Ticket #{$tid}", "{$title}\nVon: {$name} ({$email})\nPriorität: {$prio}", $admin_link ); - $this->send_telegram_notification( "Neues Ticket #{$tid}\nBetreff: {$title}\nVon: {$name} ({$email})\nPriorität: {$prio}\nLink: {$admin_link}" ); - - $notify_user_ids = get_option( 'wmt_new_ticket_notify_users', array() ); // FIX: Corrected array() - if ( is_array( $notify_user_ids ) && ! empty( $notify_user_ids ) ) { - foreach ( $notify_user_ids as $user_id ) { - $user = get_userdata( $user_id ); - if ( $user ) { - $subject = "Neues Ticket #{$tid}: {$title}"; - $body = "Hallo {$user->display_name},\n\nein neues Ticket wurde erstellt.\n\nBetreff: {$title}\nVon: {$name} ({$email})\nPriorität: {$prio}\n\nLink zum Ticket: {$admin_link}"; - wp_mail( $user->user_email, $subject, $body ); - } - } - } - - wp_mail( $email, "Ihr Ticket #{$tid} wurde erstellt", "Hallo {$name},\n\nVielen Dank für Ihr Ticket.\nLink: {$guest_link}" ); - - wp_redirect( add_query_arg( 'wmt_success', '1', wp_get_referer() ) ); - exit; - } - - public function handle_guest_reply() { - if ( ! isset( $_POST['wmt_reply'] ) || ! wp_verify_nonce( $_POST['wmt_nonce'], 'wmt_guest_reply' ) ) return; - - $tid = intval( $_POST['ticket_id'] ); - $msg = sanitize_textarea_field( $_POST['message'] ); - $file_url = $this->handle_upload('guest_file'); - - global $wpdb; - $ticket = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE id = %d", $tid ) ); - if(!$ticket) return; - - $wpdb->insert( $this->table_messages, array( - 'ticket_id' => $tid, 'sender_name' => $ticket->guest_name, 'sender_type' => 'guest', - 'message' => $msg, 'file_url' => $file_url - )); - - $admin_link = admin_url( "admin.php?page=wmt_tickets&action=edit&id={$tid}" ); - $admin_email = get_option( 'wmt_admin_email', get_option('admin_email') ); - wp_mail( $admin_email, "Neue Antwort zu Ticket #{$tid}", "Kunde hat geantwortet.\nLink: {$admin_link}" ); - - $this->send_discord_notification( "Neue Antwort in Ticket #{$tid}", "Kunde {$ticket->guest_name} hat geantwortet.", $admin_link ); - $this->send_telegram_notification( "Neue Antwort in Ticket #{$tid}\nKunde: {$ticket->guest_name}\nLink: {$admin_link}" ); - - wp_redirect( add_query_arg( array( 'wmt_view' => $tid, 'hash' => $ticket->ticket_hash ), wp_get_referer() ) ); - exit; - } - - public function handle_guest_lookup() { - if ( ! isset( $_POST['wmt_lookup'] ) ) return; - $email = sanitize_email( $_POST['lookup_email'] ); - if( ! is_email($email) ) return; - global $wpdb; - $tickets = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE guest_email = %s", $email ) ); - if( $tickets ) { - $body = "Hier sind Ihre Tickets:\n\n"; - foreach( $tickets as $t ) { - $link = add_query_arg( array( 'wmt_view' => $t->id, 'hash' => $t->ticket_hash ), home_url() ); - $body .= "#{$t->id}: {$t->title}\n{$link}\n\n"; - } - wp_mail( $email, "Ihre Tickets", $body ); - } - wp_redirect( add_query_arg( 'wmt_lookup_sent', '1', wp_get_referer() ) ); - exit; - } -} -new WP_Multi_Ticket_Pro(); +table_tickets = $wpdb->prefix . 'wmt_tickets'; + $this->table_messages = $wpdb->prefix . 'wmt_messages'; + + register_activation_hook( __FILE__, array( $this, 'create_tables' ) ); + add_action( 'plugins_loaded', array( $this, 'check_db_update' ) ); + + add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); + add_action( 'admin_init', array( $this, 'register_settings' ) ); + + add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widget' ) ); + + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'load_analytics_scripts' ) ); + + add_shortcode( 'wmt_form', array( $this, 'render_creation_form' ) ); + add_shortcode( 'wmt_view', array( $this, 'render_ticket_view' ) ); + add_shortcode( 'wmt_lookup', array( $this, 'render_lookup_form' ) ); + + add_action( 'init', array( $this, 'handle_guest_creation' ) ); + add_action( 'init', array( $this, 'handle_guest_reply' ) ); + add_action( 'init', array( $this, 'handle_guest_lookup' ) ); + + add_action( 'admin_post_wmt_admin_reply', array( $this, 'handle_admin_post' ) ); + add_action( 'admin_init', array( $this, 'handle_delete_ticket' ) ); + add_action( 'admin_init', array( $this, 'handle_csv_export' ) ); + } + + // *** HIER: korrigierter Hook-Name für die Analytics-Unterseite *** + public function load_analytics_scripts( $hook ) { + // Für Submenu: parent slug wmt_tickets -> Hook: wmt-tickets_page_wmt_analytics + if ( 'wmt-tickets_page_wmt_analytics' !== $hook ) return; + + $local_js = plugin_dir_path( __FILE__ ) . 'chart.js'; + $js_url = plugins_url( 'chart.js', __FILE__ ); + + if ( file_exists( $local_js ) ) { + wp_enqueue_script( 'chartjs', $js_url, array(), '4.4.0', true ); + } + } + + public function enqueue_styles() { + ?> + + get_charset_collate(); + + $sql_tickets = "CREATE TABLE $this->table_tickets ( + id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + title varchar(255) NOT NULL, + category varchar(100) DEFAULT 'Allgemein', + department varchar(100) DEFAULT NULL, + status varchar(50) DEFAULT 'Offen', + priority varchar(50) DEFAULT 'Mittel', + guest_name varchar(100) NOT NULL, + guest_email varchar(100) NOT NULL, + ticket_hash varchar(64) NOT NULL, + assigned_to bigint(20) UNSIGNED DEFAULT NULL, + created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY assigned_to (assigned_to) + ) $charset_collate;"; + + $sql_messages = "CREATE TABLE $this->table_messages ( + id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + ticket_id bigint(20) UNSIGNED NOT NULL, + sender_name varchar(100) NOT NULL, + sender_type varchar(20) NOT NULL, + message longtext DEFAULT NULL, + internal_note text DEFAULT NULL, + file_url varchar(255) DEFAULT NULL, + created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + PRIMARY KEY (id) + ) $charset_collate;"; + + require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); + dbDelta( $sql_tickets ); + dbDelta( $sql_messages ); + } + + public function check_db_update() { + global $wpdb; + $cols = $wpdb->get_col( "SHOW COLUMNS FROM $this->table_tickets" ); + if( !in_array('ticket_hash', $cols) ) $wpdb->query( "ALTER TABLE $this->table_tickets ADD COLUMN ticket_hash varchar(64) NOT NULL AFTER guest_email" ); + if( !in_array('guest_name', $cols) ) { $wpdb->query( "ALTER TABLE $this->table_tickets ADD COLUMN guest_name varchar(100) NOT NULL" ); $wpdb->query( "ALTER TABLE $this->table_tickets ADD COLUMN guest_email varchar(100) NOT NULL" ); } + if( !in_array('department', $cols) ) $wpdb->query( "ALTER TABLE $this->table_tickets ADD COLUMN department varchar(100) DEFAULT NULL AFTER category" ); + + $mcols = $wpdb->get_col( "SHOW COLUMNS FROM $this->table_messages" ); + if( !in_array('sender_type', $mcols) ) { $wpdb->query( "ALTER TABLE $this->table_messages ADD COLUMN sender_name varchar(100) NOT NULL" ); $wpdb->query( "ALTER TABLE $this->table_messages ADD COLUMN sender_type varchar(20) NOT NULL" ); } + if( !in_array('internal_note', $mcols) ) $wpdb->query( "ALTER TABLE $this->table_messages ADD COLUMN internal_note text DEFAULT NULL AFTER message" ); + if( !in_array('file_url', $mcols) ) $wpdb->query( "ALTER TABLE $this->table_messages ADD COLUMN file_url varchar(255) DEFAULT NULL AFTER internal_note" ); + + $wpdb->query( "ALTER TABLE $this->table_messages MODIFY COLUMN message longtext DEFAULT NULL" ); + } + + public function add_admin_menu() { + add_menu_page( 'Tickets', 'Tickets Pro', 'manage_options', 'wmt_tickets', array( $this, 'render_admin_page' ), 'dashicons-tickets-alt', 30 ); + add_submenu_page( 'wmt_tickets', 'Übersicht', 'Übersicht', 'manage_options', 'wmt_tickets', array( $this, 'render_admin_page' ) ); + add_submenu_page( 'wmt_tickets', 'Analytics', 'Analytics', 'manage_options', 'wmt_analytics', array( $this, 'render_analytics_page' ) ); + add_submenu_page( 'wmt_tickets', 'Einstellungen', 'Einstellungen', 'manage_options', 'wmt_settings', array( $this, 'render_settings_page' ) ); + add_submenu_page( 'wmt_tickets', 'Benachrichtigungen', 'Benachrichtigungen', 'manage_options', 'wmt_notifications', array( $this, 'render_notifications_page' ) ); + } + + public function register_settings() { + register_setting( 'wmt_settings_group', 'wmt_categories' ); + register_setting( 'wmt_settings_group', 'wmt_departments' ); + register_setting( 'wmt_settings_group', 'wmt_priorities' ); + register_setting( 'wmt_settings_group', 'wmt_statuses' ); + register_setting( 'wmt_settings_group', 'wmt_admin_email' ); + register_setting( 'wmt_settings_group', 'wmt_templates' ); + register_setting( 'wmt_settings_group', 'wmt_allowed_filetypes' ); + + register_setting( 'wmt_notifications_group', 'wmt_discord_webhook' ); + register_setting( 'wmt_notifications_group', 'wmt_telegram_token' ); + register_setting( 'wmt_notifications_group', 'wmt_telegram_chat_id' ); + register_setting( 'wmt_notifications_group', 'wmt_new_ticket_notify_users' ); + } + + public function render_analytics_page() { + global $wpdb; + + $total_tickets = $wpdb->get_var("SELECT COUNT(*) FROM $this->table_tickets"); + + if ( $total_tickets == 0 ) { + echo '
'; + echo '
'; + echo '

Noch keine Daten

'; + echo '

Es wurden noch keine Tickets erstellt.

'; + echo 'Zu den Tickets'; + echo '
'; + return; + } + + $open_tickets = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $this->table_tickets WHERE status NOT LIKE %s", '%Geschlossen%')); + $closed_tickets = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $this->table_tickets WHERE status LIKE %s", '%Geschlossen%')); + $new_tickets_30d = $wpdb->get_var("SELECT COUNT(*) FROM $this->table_tickets WHERE created_at >= DATE_SUB(DATE_ADD(NOW(), INTERVAL 2 YEAR), INTERVAL 30 DAY)"); + + $status_data = $wpdb->get_results("SELECT COALESCE(status, 'Unbekannt') as status, COUNT(*) as count FROM $this->table_tickets GROUP BY status"); + + $cat_data = $wpdb->get_results("SELECT COALESCE(category, 'Keine Kategorie') as category, COUNT(*) as count FROM $this->table_tickets GROUP BY category ORDER BY count DESC LIMIT 8"); + + $agent_data = $wpdb->get_results(" + SELECT u.display_name, COUNT(t.id) as count + FROM {$wpdb->users} u + LEFT JOIN $this->table_tickets t ON u.ID = t.assigned_to + GROUP BY u.ID + HAVING count > 0 + ORDER BY count DESC + "); + + $timeline_data = $wpdb->get_results(" + SELECT DATE(created_at) as date, COUNT(*) as count + FROM $this->table_tickets + GROUP BY DATE(created_at) + ORDER BY date ASC + "); + + ?> +
+
+

Analytics Dashboard

+ Live Daten +
+ +
+
+

Gesamt Tickets

+

+
+
+

Aktiv / Offen

+

+

Benötigt Aufmerksamkeit

+
+
+

Geschlossen

+

+
+
+

Letzte 30 Tage

+

+

Neue Eingänge

+
+
+ +
+ +
+

Status Verteilung

+
+
+ + +
+

Top Kategorien

+
+
+ + +
+

Workload pro Agent

+
+
+ + +
+

Tickets pro Tag

+
+
+
+ +
+ + + + + + +
+

Einstellungen

+
+ + + + + + + + + + + + +
Kategorien
Kategorie ➔ Abteilung Zuordnung
Prioritäten
Status
Admin E-Mail
Erlaubte Dateiendungen

Kommagetrennt (z.B. pdf, doc, png).

Textbausteine +
+ $tpl ): ?> +
+ × +
+ + +
+ +
+ +
+ +
+ +
+
+ + + 'display_name' ) ); + $selected_users = get_option( 'wmt_new_ticket_notify_users', array() ); // FIX: Corrected array() + if ( ! is_array( $selected_users ) ) $selected_users = array(); + ?> +
+

Benachrichtigungen

+
+ + + + + + + + + + + + + + + + + + +
Discord Webhook URL + +

Wird bei neuem Ticket und neuer Gast-Antwort gesendet.

+
Telegram Bot Token + +
Telegram Chat ID + +

Für Gruppen/Kanäle mit -100 beginnen.

+
Zusätzliche Benachrichtigungen
bei neuem Ticket
+ +

Diese User erhalten zusätzlich eine E-Mail bei jedem neuen Ticket (Strg/Cmd klicken für Mehrfachauswahl).

+
+ +
+
+ get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $this->table_tickets WHERE status NOT LIKE %s", '%Geschlossen%' ) ); + $link = admin_url( 'admin.php?page=wmt_tickets' ); + + echo '
'; + if ( $count > 0 ) { + echo '
' . intval( $count ) . '
'; + echo '
Offene Tickets
'; + } else { + echo '
0
'; + echo '
Keine offenen Tickets
'; + } + echo 'Alle Tickets ansehen'; + echo '
'; + } + + public function render_admin_page() { + if ( isset( $_GET['wmt_print'] ) && $_GET['wmt_print'] == '1' && isset( $_GET['id'] ) ) { + $this->render_print_view( intval( $_GET['id'] ) ); + return; + } + + if ( isset( $_GET['action'] ) && $_GET['action'] === 'edit' && isset( $_GET['id'] ) ) { + $this->render_admin_detail( intval( $_GET['id'] ) ); + return; + } + + if( isset( $_GET['deleted'] ) && $_GET['deleted'] == '1' ) { + echo '

Ticket erfolgreich gelöscht.

'; + } + + $search = isset( $_GET['s'] ) ? sanitize_text_field( $_GET['s'] ) : ''; + $status_filter = isset( $_GET['status_filter'] ) ? sanitize_text_field( $_GET['status_filter'] ) : ''; + + global $wpdb; + $sql = "SELECT * FROM $this->table_tickets WHERE 1=1"; + + if ( $search ) { + $sql .= $wpdb->prepare( " AND (title LIKE %s OR guest_name LIKE %s OR guest_email LIKE %s)", '%' . $wpdb->esc_like( $search ) . '%', '%' . $wpdb->esc_like( $search ) . '%', '%' . $wpdb->esc_like( $search ) . '%' ); + } + + if ( $status_filter ) { + $sql .= $wpdb->prepare( " AND status = %s", $status_filter ); + } + + $sql .= " ORDER BY updated_at DESC"; + $tickets = $wpdb->get_results( $sql ); + + echo '

Ticket Übersicht

'; + + echo '
'; + echo '
'; + echo ''; + echo ''; + echo ''; + echo ''; + echo '
'; + + $export_url = admin_url( 'admin.php?page=wmt_tickets&wmt_action=export' ); + if($search) $export_url = add_query_arg( 's', $search, $export_url ); + echo 'CSV Export'; + echo '
'; + + echo ''; + echo ''; + echo ''; + foreach ( $tickets as $t ) { + $prio_style = strtolower( $t->priority ) === 'hoch' ? 'color: #d63638; font-weight: bold; font-size: 1.1em;' : ''; + $assigned_user = $t->assigned_to ? get_userdata( $t->assigned_to ) : null; + $assigned_name = $assigned_user ? $assigned_user->display_name : '-'; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
IDBetreffKat.Abt.PrioGast InfoStatusZugewiesenAktionen
#' . $t->id . '' . esc_html( $t->title ) . '' . esc_html( $t->category ) . '' . ( $t->department ? esc_html($t->department) : '-' ) . '' . esc_html( $t->priority ) . '' . esc_html( $t->guest_name ) . '
' . esc_html( $t->guest_email ) . '
' . esc_html( $t->status ) . '' . esc_html( $assigned_name ) . ''; + echo 'Bearbeiten '; + $delete_url = wp_nonce_url( admin_url( 'admin.php?page=wmt_tickets&action=wmt_delete&id=' . $t->id ), 'wmt_delete_ticket_' . $t->id ); + echo 'Löschen'; + echo '
'; + } + + private function render_admin_detail( $id ) { + global $wpdb; + $ticket = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE id = %d", $id ) ); + if ( ! $ticket ) return; + + if ( isset( $_GET['msg'] ) && $_GET['msg'] == 'sent' ) { + echo '

Aktualisierung erfolgreich!

'; + } + + $messages = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $this->table_messages WHERE ticket_id = %d ORDER BY created_at ASC", $id ) ); + + $all_users = get_users( array( 'orderby' => 'display_name' ) ); + + $status_opts = array_map( 'trim', explode( ',', get_option('wmt_statuses') ) ); + + $mapping_raw = get_option('wmt_departments', ''); + $dept_opts = array(); + if($mapping_raw) { + $pairs = array_map('trim', explode(',', $mapping_raw)); + foreach($pairs as $pair) { + if(strpos($pair, ':') !== false) { + list($cat, $dept) = explode(':', $pair, 2); + $dept_opts[] = trim($dept); + } + } + $dept_opts = array_unique($dept_opts); + } + + $raw_templates = get_option('wmt_templates', array()); + $tpl_public = ''; + $tpl_internal = ''; + + if( is_array($raw_templates) ) { + foreach($raw_templates as $tpl) { + if(isset($tpl['type']) && isset($tpl['content'])) { + $name = isset($tpl['name']) && !empty($tpl['name']) ? esc_html($tpl['name']) : substr(esc_html($tpl['content']), 0, 30) . '...'; + $content = esc_attr($tpl['content']); + $opt = ''; + if($tpl['type'] === 'internal') $tpl_internal .= $opt; + else $tpl_public .= $opt; + } + } + } + + $colors = array('#e57373', '#f06292', '#ba68c8', '#9575cd', '#7986cb', '#64b5f6', '#4fc3f7', '#4dd0e1', '#4db6ac', '#81c784', '#aed581', '#ffca28', '#ffa726'); + + ?> +
+

Ticket #id; ?> bearbeiten

+
+ « Zurück + Ticket drucken + id ), 'wmt_delete_ticket_' . $ticket->id ); + echo 'Ticket löschen'; + ?> +
+ +
+
+ sender_type === 'system' ): ?> +
System: message); ?> created_at; ?>
+ sender_type === 'admin' ); + $bg = $is_admin ? '#fff' : '#e3f2fd'; + $align = $is_admin ? 'left' : 'right'; + $initial = substr($msg->sender_name, 0, 1); + $char_code = ord(strtolower($initial)); + $bg_color = $is_admin ? '#555' : $colors[$char_code % count($colors)]; + ?> +
+
+
+ sender_name ); ?> created_at; ?> + (Support)'; ?> + internal_note) ) : ?> +
Interne Notiz:internal_note ) ); ?>
+ + message) ): ?> +
message); ?>
+ +

Keine öffentliche Nachricht.

+ + file_url ) : ?> + Datei herunterladen + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Abteilung + +
Status + +

Automatik: Bei Antworten wird das Ticket automatisch auf "In Bearbeitung" gesetzt (außer Sie wählen "Geschlossen").

+
Prioritätpriority ); ?>
Zuweisen an + +

Auto-Detect: Wenn Sie antworten, wird das Ticket automatisch Ihnen zugewiesen (sofern noch niemand zugewiesen). Wählen Sie hier einen Kollegen aus, um das Ticket zu übergeben.

+
Antwort & Notizen + + + + +
+ + + + + 'message', 'media_buttons' => false, 'textarea_rows' => 10, 'teeny' => false); + wp_editor( $content, 'wmt_message_editor', $settings ); + ?> + + + + +

+ Wird gesendet an guest_email ); ?>. +

+
+ +
+
+ + +
+ get_row( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE id = %d", $id ) ); + if ( ! $ticket ) return; + + $messages = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $this->table_messages WHERE ticket_id = %d ORDER BY created_at ASC", $id ) ); + + echo 'Ticket #' . $id . ''; + echo ''; + + echo '

Ticket #' . $id . ': ' . esc_html( $ticket->title ) . '

'; + + echo '
Kunde: ' . esc_html( $ticket->guest_name ) . ' (' . esc_html( $ticket->guest_email ) . ')
'; + echo '
Kategorie: ' . esc_html( $ticket->category ) . '
'; + echo '
Abteilung: ' . ($ticket->department ? esc_html($ticket->department) : '-') . '
'; + echo '
Status: ' . esc_html( $ticket->status ) . '
'; + echo '
Priorität: ' . esc_html( $ticket->priority ) . '
'; + $assigned = $ticket->assigned_to ? get_userdata($ticket->assigned_to)->display_name : 'Niemand'; + echo '
Zugewiesen an: ' . esc_html($assigned) . '
'; + + echo '

Verlauf

'; + + foreach ( $messages as $msg ) { + if ( $msg->sender_type === 'system' ) { + echo '
System: ' . esc_html($msg->message) . ' (' . $msg->created_at . ')
'; + continue; + } + + echo '
'; + echo '
' . esc_html( $msg->sender_name ) . ' ' . ($msg->sender_type === 'admin' ? '(Support)' : '(Kunde)') . ' - ' . $msg->created_at . '
'; + echo '
' . wp_kses_post($msg->message) . '
'; + if ( $msg->sender_type === 'admin' && !empty($msg->internal_note) ) { + echo '
Interne Notiz: ' . esc_html($msg->internal_note) . '
'; + } + if ( $msg->file_url ) { + echo ''; + } + echo '
'; + } + + echo ''; + exit; + } + + private function handle_upload($file_input_name) { + if (empty($_FILES[$file_input_name]['name'])) return ''; + $allowed_raw = get_option('wmt_allowed_filetypes', 'pdf, doc, docx, jpg, png'); + $allowed_exts = array_map('trim', explode(',', strtolower($allowed_raw))); + $file_ext = strtolower(pathinfo($_FILES[$file_input_name]['name'], PATHINFO_EXTENSION)); + + if (!in_array($file_ext, $allowed_exts)) { + wp_die( "Fehler: Der Dateityp .{$file_ext} ist nicht erlaubt. Erlaubt sind: " . esc_html($allowed_raw) ); + } + require_once(ABSPATH . 'wp-admin/includes/file.php'); + $upload = wp_handle_upload($_FILES[$file_input_name], array('test_form' => false)); + if (isset($upload['error'])) wp_die('Upload Fehler: ' . $upload['error']); + return isset($upload['url']) ? $upload['url'] : ''; + } + + private function send_discord_notification( $title, $description, $url ) { + $webhook = get_option( 'wmt_discord_webhook' ); + if ( ! $webhook ) return; + + $data = array( + "embeds" => array( + array( + "title" => $title, + "description" => $description, + "url" => $url, + "color" => 3447003, + "timestamp" => current_time( 'mysql' ) + ) + ) + ); + + wp_remote_post( $webhook, array( + 'body' => wp_json_encode( $data ), + 'headers' => array( 'Content-Type' => 'application/json' ), + 'timeout' => 10 + ) ); + } + + private function send_telegram_notification( $text ) { + $token = get_option( 'wmt_telegram_token' ); + $chat_id = get_option( 'wmt_telegram_chat_id' ); + if ( ! $token || ! $chat_id ) return; + + $url = "https://api.telegram.org/bot{$token}/sendMessage"; + wp_remote_post( $url, array( + 'body' => array( + 'chat_id' => $chat_id, + 'text' => $text, + 'parse_mode' => 'HTML' + ), + 'timeout' => 10 + ) ); + } + + public function handle_delete_ticket() { + if ( isset( $_GET['action'] ) && $_GET['action'] === 'wmt_delete' && isset( $_GET['id'] ) ) { + $id = intval( $_GET['id'] ); + $nonce = isset( $_GET['_wpnonce'] ) ? $_GET['_wpnonce'] : ''; + if ( ! wp_verify_nonce( $nonce, 'wmt_delete_ticket_' . $id ) || ! current_user_can( 'manage_options' ) ) wp_die( 'Sicherheitsfehler' ); + global $wpdb; + $wpdb->delete( $this->table_messages, array( 'ticket_id' => $id ) ); + $wpdb->delete( $this->table_tickets, array( 'id' => $id ) ); + wp_redirect( admin_url( 'admin.php?page=wmt_tickets&deleted=1' ) ); + exit; + } + } + + public function handle_admin_post() { + if ( ! isset( $_POST['wmt_nonce'] ) || ! wp_verify_nonce( $_POST['wmt_nonce'], 'wmt_admin_action' ) ) wp_die( 'Sicherheitsfehler' ); + + $msg_content = isset( $_POST['message'] ) ? wp_kses_post( $_POST['message'] ) : ''; + $tid = intval( $_POST['ticket_id'] ); + + // STATUS LOGIK: + // Wenn nicht "Geschlossen" gewählt wurde, setzen wir es auf "In Bearbeitung" + $status = sanitize_text_field( $_POST['status'] ); + if ( strtolower( $status ) !== 'geschlossen' ) { + $status = 'In Bearbeitung'; + } + + $dept = sanitize_text_field( $_POST['department'] ); + + // Prüfen Dropdown: User manuell ausgewählt? + $assigned = !empty($_POST['assigned_to']) ? intval( $_POST['assigned_to'] ) : null; + + // Auto-Detection Logik + // Wenn im Dropdown "Niemand" ausgewählt ist (NULL), ABER der User gerade antwortet... + if ( empty($assigned) && (!empty($msg_content) || !empty($_FILES['ticket_file']['name'])) ) { + // ...dann weisen wir es dem aktuellen User zu. + $assigned = get_current_user_id(); + } + + $internal_note = sanitize_textarea_field( $_POST['internal_note'] ); + $file_url = $this->handle_upload('ticket_file'); + + global $wpdb; + $old_ticket = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE id = %d", $tid ) ); + + $wpdb->update( $this->table_tickets, + array( 'status' => $status, 'department' => $dept ?: null, 'assigned_to' => $assigned ), + array( 'id' => $tid ) + ); + + if ( $old_ticket && $old_ticket->department !== $dept ) { + $new_dept_name = $dept ?: 'Keine'; + $wpdb->insert( $this->table_messages, array( + 'ticket_id' => $tid, 'sender_name' => 'System', 'sender_type' => 'system', 'message' => "Abteilung geändert zu: {$new_dept_name}" + )); + } + + // Handover Benachrichtigung: Wenn sich der zugewiesene User ändert + if ( $old_ticket->assigned_to != $assigned && $assigned ) { + $user = get_userdata( $assigned ); + if ( $user ) { + $subject = "Ticket #{$tid} wurde Ihnen zugewiesen"; + $body = "Hallo {$user->display_name},\n\nDas Ticket #{$tid} – „{$old_ticket->title}“ wurde Ihnen zur Bearbeitung zugewiesen.\n\nLink: " . admin_url( "admin.php?page=wmt_tickets&action=edit&id={$tid}" ); + wp_mail( $user->user_email, $subject, $body ); + } + } + + if ( ! empty( $msg_content ) || ! empty( $file_url ) ) { + $current_user = wp_get_current_user(); + $wpdb->insert( $this->table_messages, array( + 'ticket_id' => $tid, 'sender_name' => $current_user->display_name, 'sender_type' => 'admin', + 'message' => $msg_content, 'internal_note' => $internal_note, 'file_url' => $file_url + )); + + $view_link = add_query_arg( array( 'wmt_view' => $tid, 'hash' => $old_ticket->ticket_hash ), home_url() ); + $subject = "Neue Antwort zu Ihrem Ticket #{$tid}"; + $body = "Hallo {$old_ticket->guest_name},\n\nEs gibt eine neue Antwort:\n{$msg_content}\n\nLink zum Ticket:\n{$view_link}"; + if($file_url) $body .= "\n\nAnhang: {$file_url}"; + wp_mail( $old_ticket->guest_email, $subject, $body ); + + } elseif ( ! empty( $internal_note ) ) { + $current_user = wp_get_current_user(); + $wpdb->insert( $this->table_messages, array( + 'ticket_id' => $tid, 'sender_name' => $current_user->display_name, 'sender_type' => 'admin', + 'message' => null, 'internal_note' => $internal_note + )); + } + + wp_redirect( admin_url( 'admin.php?page=wmt_tickets&action=edit&id=' . $tid . '&msg=sent' ) ); + exit; + } + + public function handle_csv_export() { + if ( ! isset( $_GET['wmt_action'] ) || $_GET['wmt_action'] !== 'export' || ! current_user_can( 'manage_options' ) ) return; + + global $wpdb; + $search = isset( $_GET['s'] ) ? sanitize_text_field( $_GET['s'] ) : ''; + $sql = "SELECT * FROM $this->table_tickets"; + if($search) { + $sql .= $wpdb->prepare( " WHERE (title LIKE %s OR guest_name LIKE %s OR guest_email LIKE %s)", '%' . $wpdb->esc_like( $search ) . '%', '%' . $wpdb->esc_like( $search ) . '%', '%' . $wpdb->esc_like( $search ) . '%' ); + } + + $results = $wpdb->get_results( $sql ); + + header( 'Content-Type: text/csv' ); + header( 'Content-Disposition: attachment; filename=tickets-export.csv' ); + + $output = fopen( 'php://output', 'w' ); + fputcsv( $output, array( 'ID', 'Titel', 'Kategorie', 'Abteilung', 'Status', 'Priorität', 'Name', 'E-Mail', 'Zugewiesen', 'Datum' ) ); + + foreach ( $results as $row ) { + $assigned = $row->assigned_to ? get_userdata($row->assigned_to)->display_name : ''; + fputcsv( $output, array( $row->id, $row->title, $row->category, $row->department, $row->status, $row->priority, $row->guest_name, $row->guest_email, $assigned, $row->created_at ) ); + } + fclose( $output ); + exit; + } + + public function render_creation_form() { + ob_start(); + if(isset($_GET['upload_error'])) { + echo '
' . esc_html(urldecode($_GET['upload_error'])) . '
'; + } + if( isset( $_GET['wmt_success'] ) ) { + echo '
Ticket erfolgreich erstellt! Bitte prüfen Sie Ihre E-Mails.
'; + } + ?> +
+

Neues Support Ticket erstellen

+
+ + + + + + + + + + + + + + + + +
+
+ Eine E-Mail mit Links wurde gesendet.'; + } + ?> +
+

Meine Tickets finden

+
+ + +
+
+

Kein Ticket ausgewählt.

'; + + global $wpdb; + $ticket = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE id = %d AND ticket_hash = %s", $tid, $hash ) ); + if ( ! $ticket ) return '

Zugriff verweigert.

'; + + $messages = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $this->table_messages WHERE ticket_id = %d ORDER BY created_at ASC", $tid ) ); + $is_closed = ( strtolower($ticket->status) === 'geschlossen' ); + + ob_start(); + ?> +
+
+

Ticket #id; ?>: title ); ?>

+

Status: status ); ?>

+
+
+ sender_type === 'system' ): ?> +
message); ?>
+ sender_type === 'admin' ); + if(empty($msg->message) && empty($msg->file_url)) continue; + ?> +
+ sender_name ); ?> created_at; ?> +

message ) ); ?>

+ file_url ) : ?> + Datei ansehen + +
+ +
+ +
+ + + + + + + +
+ +
Ticket geschlossen.
+ +
+ handle_upload('guest_file'); + + $dept = null; + $mapping_raw = get_option('wmt_departments', ''); + if($mapping_raw) { + foreach(array_map('trim', explode(',', $mapping_raw)) as $pair) { + if(strpos($pair, ':') !== false) { + list($map_cat, $map_dept) = explode(':', $pair, 2); + if(trim($map_cat) === $cat) { $dept = trim($map_dept); break; } + } + } + } + + global $wpdb; + $wpdb->insert( $this->table_tickets, array( + 'title' => $title, 'category' => $cat, 'priority' => $prio, 'department' => $dept, + 'guest_name' => $name, 'guest_email' => $email, 'ticket_hash' => $hash + )); + $tid = $wpdb->insert_id; + $wpdb->insert( $this->table_messages, array( + 'ticket_id' => $tid, 'sender_name' => $name, 'sender_type' => 'guest', 'message' => $msg, 'file_url' => $file_url + )); + + $admin_link = admin_url( "admin.php?page=wmt_tickets&action=edit&id={$tid}" ); + $guest_link = add_query_arg( array( 'wmt_view' => $tid, 'hash' => $hash ), home_url() ); + + $admin_email = get_option( 'wmt_admin_email', get_option('admin_email') ); + wp_mail( $admin_email, "Neues Ticket #{$tid}: {$title}", "Von: {$name} ({$email})\nLink: {$admin_link}" ); + + $this->send_discord_notification( "Neues Ticket #{$tid}", "{$title}\nVon: {$name} ({$email})\nPriorität: {$prio}", $admin_link ); + $this->send_telegram_notification( "Neues Ticket #{$tid}\nBetreff: {$title}\nVon: {$name} ({$email})\nPriorität: {$prio}\nLink: {$admin_link}" ); + + $notify_user_ids = get_option( 'wmt_new_ticket_notify_users', array() ); // FIX: Corrected array() + if ( is_array( $notify_user_ids ) && ! empty( $notify_user_ids ) ) { + foreach ( $notify_user_ids as $user_id ) { + $user = get_userdata( $user_id ); + if ( $user ) { + $subject = "Neues Ticket #{$tid}: {$title}"; + $body = "Hallo {$user->display_name},\n\nein neues Ticket wurde erstellt.\n\nBetreff: {$title}\nVon: {$name} ({$email})\nPriorität: {$prio}\n\nLink zum Ticket: {$admin_link}"; + wp_mail( $user->user_email, $subject, $body ); + } + } + } + + wp_mail( $email, "Ihr Ticket #{$tid} wurde erstellt", "Hallo {$name},\n\nVielen Dank für Ihr Ticket.\nLink: {$guest_link}" ); + + wp_redirect( add_query_arg( 'wmt_success', '1', wp_get_referer() ) ); + exit; + } + + public function handle_guest_reply() { + if ( ! isset( $_POST['wmt_reply'] ) || ! wp_verify_nonce( $_POST['wmt_nonce'], 'wmt_guest_reply' ) ) return; + + $tid = intval( $_POST['ticket_id'] ); + $msg = sanitize_textarea_field( $_POST['message'] ); + $file_url = $this->handle_upload('guest_file'); + + global $wpdb; + $ticket = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE id = %d", $tid ) ); + if(!$ticket) return; + + $wpdb->insert( $this->table_messages, array( + 'ticket_id' => $tid, 'sender_name' => $ticket->guest_name, 'sender_type' => 'guest', + 'message' => $msg, 'file_url' => $file_url + )); + + $admin_link = admin_url( "admin.php?page=wmt_tickets&action=edit&id={$tid}" ); + $admin_email = get_option( 'wmt_admin_email', get_option('admin_email') ); + wp_mail( $admin_email, "Neue Antwort zu Ticket #{$tid}", "Kunde hat geantwortet.\nLink: {$admin_link}" ); + + $this->send_discord_notification( "Neue Antwort in Ticket #{$tid}", "Kunde {$ticket->guest_name} hat geantwortet.", $admin_link ); + $this->send_telegram_notification( "Neue Antwort in Ticket #{$tid}\nKunde: {$ticket->guest_name}\nLink: {$admin_link}" ); + + wp_redirect( add_query_arg( array( 'wmt_view' => $tid, 'hash' => $ticket->ticket_hash ), wp_get_referer() ) ); + exit; + } + + public function handle_guest_lookup() { + if ( ! isset( $_POST['wmt_lookup'] ) ) return; + $email = sanitize_email( $_POST['lookup_email'] ); + if( ! is_email($email) ) return; + global $wpdb; + $tickets = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $this->table_tickets WHERE guest_email = %s", $email ) ); + if( $tickets ) { + $body = "Hier sind Ihre Tickets:\n\n"; + foreach( $tickets as $t ) { + $link = add_query_arg( array( 'wmt_view' => $t->id, 'hash' => $t->ticket_hash ), home_url() ); + $body .= "#{$t->id}: {$t->title}\n{$link}\n\n"; + } + wp_mail( $email, "Ihre Tickets", $body ); + } + wp_redirect( add_query_arg( 'wmt_lookup_sent', '1', wp_get_referer() ) ); + exit; + } +} +new WP_Multi_Ticket_Pro();