diff --git a/wp-multi-ticket.php b/wp-multi-ticket.php new file mode 100644 index 0000000..d02e234 --- /dev/null +++ b/wp-multi-ticket.php @@ -0,0 +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();