Files
wp-multi-ticket/wp-multi-ticket.php

1890 lines
100 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/*
* Plugin Name: WP Multi Ticket Pro
* Plugin URI: https://git.viper.ipv64.net/M_Viper/wp-multi-ticket
* Description: Leistungsstarkes Ticket- und Support-System für WordPress mit Gast-Tickets, Admin-Antworten, CSV-Export, Dashboard-Widgets und integriertem Update-Management über Gitea.
* Version: 1.3
* Author: M_Viper
* Author URI: https://m-viper.de
* Requires at least: 6.7.2
* Tested up to: 6.7.2
* Requires PHP: 7.4
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: wp-multi-ticket
* Domain Path: /languages
* Tags: ticket, support, helpdesk, customer-support, guest-tickets, wp-multi
* Support: https://t.me/M_Viper04
*/
if ( ! defined( 'ABSPATH' ) ) exit;
class WP_Multi_Ticket_Pro {
private $table_tickets;
private $table_messages;
private $update_url = 'https://git.viper.ipv64.net/M_Viper/wp-multi-ticket/releases';
public function __construct() {
global $wpdb;
$this->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' ) );
// Hook für Update-Benachrichtigung
add_action( 'admin_notices', array( $this, 'check_plugin_updates' ) );
// E-Mail-Konfiguration
add_action( 'phpmailer_init', array( $this, 'configure_phpmailer' ) );
add_filter( 'wp_mail_from', array( $this, 'custom_wp_mail_from' ) );
add_filter( 'wp_mail_from_name', array( $this, 'custom_wp_mail_from_name' ) );
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' ) );
}
/**
* Hilfsfunktion: Letzte Fehlermeldung aus dem Log holen
*/
private function get_last_error_log_entry() {
$log_file = WP_CONTENT_DIR . '/debug.log';
if ( ! file_exists( $log_file ) || ! is_readable( $log_file ) ) return null;
// Letzte 5KB der Datei lesen
$lines = array_reverse( file( $log_file ) );
foreach ( $lines as $line ) {
// Nach SMTP Debug Zeilen suchen
if ( strpos( $line, 'SMTP Debug' ) !== false ) {
return esc_html( trim( $line ) );
}
// Nach PHP Fatal Warnings suchen
if ( strpos( $line, 'Fatal error' ) !== false ) {
return esc_html( trim( $line ) );
}
}
return null;
}
/**
* FIX: Verbesserte Konfiguration für PHPMailer
*/
public function configure_phpmailer( $phpmailer ) {
$smtp_enabled = get_option( 'wmt_smtp_enabled' );
if ( $smtp_enabled == '1' || $smtp_enabled === true ) {
$encryption = get_option( 'wmt_smtp_encryption', '' );
// Prüfen, ob OpenSSL geladen ist
if ( ( $encryption === 'tls' || $encryption === 'ssl' ) && ! extension_loaded('openssl') ) {
error_log('WMT SMTP Error: OpenSSL extension missing.');
// Setzen einer Error-Info, die manuell abgerufen werden kann
global $wmt_last_smtp_error;
$wmt_last_smtp_error = "FATAL: PHP OpenSSL Extension fehlt. Bitte kontaktieren Sie Ihren Webhoster.";
return;
}
$phpmailer->isSMTP();
$phpmailer->Host = get_option( 'wmt_smtp_host', 'localhost' );
$phpmailer->Port = get_option( 'wmt_smtp_port', 25 );
// SSL/TLS korrekt setzen
if ( $encryption === 'tls' ) {
$phpmailer->SMTPSecure = 'tls';
} elseif ( $encryption === 'ssl' ) {
$phpmailer->SMTPSecure = 'ssl';
} else {
$phpmailer->SMTPSecure = '';
}
$smtp_auth = get_option( 'wmt_smtp_auth' );
$phpmailer->SMTPAuth = ( $smtp_auth == '1' || $smtp_auth === true );
if ( $phpmailer->SMTPAuth ) {
$phpmailer->Username = get_option( 'wmt_smtp_username', '' );
$phpmailer->Password = get_option( 'wmt_smtp_password', '' );
// WICHTIG: Envelope Sender setzen
$phpmailer->Sender = $phpmailer->Username;
}
if ( get_option( 'wmt_smtp_debug', false ) ) {
$phpmailer->SMTPDebug = 3; // Client + Server
$phpmailer->Debugoutput = function($str, $level) {
error_log("WMT SMTP: $str");
};
}
}
$phpmailer->CharSet = 'UTF-8';
}
/**
* Setzt die Absender-E-Mail
*/
public function custom_wp_mail_from( $original_email_address ) {
$custom_email = get_option( 'wmt_from_email', '' );
return $custom_email ? $custom_email : $original_email_address;
}
/**
* Setzt den Absender-Namen
*/
public function custom_wp_mail_from_name( $original_email_from ) {
$custom_name = get_option( 'wmt_from_name', '' );
return $custom_name ? $custom_name : $original_email_from;
}
/**
* Verbesserte E-Mail-Versand-Funktion mit Logging
*/
private function send_mail( $to, $subject, $message, $attachments = array() ) {
$html_message = '
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; background-color: #f4f4f4; margin: 0; padding: 20px; }
.container { max-width: 600px; margin: 0 auto; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.header { background: #0073aa; color: white; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; margin: -20px -20px 20px -20px; }
.header h2 { margin: 0; color: white; }
.content { padding: 0 10px; }
.footer { text-align: center; padding: 20px; font-size: 12px; color: #666; border-top: 1px solid #eee; margin-top: 20px; }
.button { background: #0073aa; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block; margin: 10px 0; font-weight: bold; }
.button:hover { background: #005177; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>' . get_bloginfo('name') . ' Support</h2>
</div>
<div class="content">
' . wpautop( $message ) . '
</div>
<div class="footer">
<p>Diese E-Mail wurde automatisch vom Support-System generiert.</p>
<p><a href="' . home_url() . '">' . get_bloginfo('name') . '</a></p>
</div>
</div>
</body>
</html>';
$headers = array(
'Content-Type: text/html; charset=UTF-8',
);
if ( get_option( 'wmt_bcc_admin', false ) ) {
$admin_email = get_option( 'wmt_admin_email', get_option('admin_email') );
$headers[] = 'Bcc: ' . $admin_email;
}
$result = wp_mail( $to, $subject, $html_message, $headers, $attachments );
// Logging für Debugging
if ( get_option( 'wmt_mail_logging', true ) ) {
$error_msg = 'Mail delivery failed';
// Wenn es fehlschlägt, versuchen wir den letzten Fehler aus dem Log zu holen für mehr Details
if ( ! $result ) {
$log_entry = $this->get_last_error_log_entry();
if ( $log_entry ) {
$error_msg .= " (Server Log: " . $log_entry . ")";
}
}
$logs = get_option( 'wmt_mail_logs', array() );
array_unshift( $logs, array(
'timestamp' => current_time( 'mysql' ),
'to' => $to,
'subject' => $subject,
'success' => $result ? 'YES' : 'NO',
'error' => $result ? '' : $error_msg
));
// Nur letzte 50 behalten
$logs = array_slice( $logs, 0, 50 );
update_option( 'wmt_mail_logs', $logs );
}
if ( ! $result ) {
error_log( 'WMT Mail Error: Failed to send email to ' . $to . ' - Subject: ' . $subject );
}
return $result;
}
/**
* Prüft auf Updates von der Git-URL und zeigt eine Admin-Notice an
*/
public function check_plugin_updates() {
if ( ! current_user_can( 'update_plugins' ) ) {
return;
}
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin_data = get_plugin_data( __FILE__ );
$current_version = $plugin_data['Version'];
$cache_key = 'wmt_ticket_update_check';
$cached = get_transient( $cache_key );
if ( false === $cached ) {
$cached = array(
'version' => $current_version,
'url' => $this->update_url,
);
// Gitea API Releases
$api_url = 'https://git.viper.ipv64.net/api/v1/repos/M_Viper/wp-multi-ticket/releases';
$response = wp_remote_get( $api_url, array(
'timeout' => 10,
'headers' => array(
'Accept' => 'application/json',
'User-Agent' => 'WP-Multi-Ticket-Updater',
),
) );
if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
$releases = json_decode( wp_remote_retrieve_body( $response ), true );
if ( is_array( $releases ) && ! empty( $releases ) ) {
// Neuestes Release (Gitea liefert neueste zuerst)
$latest = $releases[0];
// Version (genau wie im Toolkit)
if ( ! empty( $latest['tag_name'] ) ) {
$cached['version'] = ltrim( $latest['tag_name'], 'vV' );
} elseif ( ! empty( $latest['name'] ) ) {
$cached['version'] = ltrim( $latest['name'], 'vV' );
}
// Exaktes ZIP-Asset suchen
if ( ! empty( $latest['assets'] ) && is_array( $latest['assets'] ) ) {
foreach ( $latest['assets'] as $asset ) {
if (
! empty( $asset['name'] ) &&
! empty( $asset['browser_download_url'] ) &&
strtolower( $asset['name'] ) === 'wp-multi-ticket.zip'
) {
$cached['url'] = $asset['browser_download_url'];
break;
}
}
}
}
}
// Cache setzen
set_transient( $cache_key, $cached, 12 * HOUR_IN_SECONDS );
}
// Update-Hinweis anzeigen
if ( version_compare( $cached['version'], $current_version, '>' ) ) {
echo '<div class="notice notice-warning is-dismissible">';
echo '<p><strong>WP Multi Ticket Pro Update verfügbar</strong></p>';
echo '<p>Neue Version: <strong>' . esc_html( $cached['version'] ) . '</strong><br>';
echo 'Installiert: <strong>' . esc_html( $current_version ) . '</strong></p>';
echo '<p>';
echo '<a href="' . esc_url( $this->update_url ) . '" target="_blank" class="button">Release ansehen</a> ';
echo '<a href="' . esc_url( $cached['url'] ) . '" target="_blank" class="button button-primary">Direkter Download (ZIP)</a>';
echo '</p>';
echo '</div>';
}
}
public function load_analytics_scripts( $hook ) {
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() {
$custom_font_color = get_option( 'wmt_font_color', '#6b7280' );
?>
<style>
.wmt-box { max-width: 800px; margin: 20px auto; background: #fff; padding: 25px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif; color: <?php echo esc_attr($custom_font_color); ?>; }
.wmt-box h1, .wmt-box h2, .wmt-box h3 {
color: <?php echo esc_attr($custom_font_color); ?>;
}
.wmt-btn { background: #0073aa; color: #fff; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 14px; text-decoration: none; display: inline-block; line-height: 1.4; }
.wmt-btn:hover { background: #005177; }
.wmt-btn.danger { background: #dc3545; }
.wmt-btn.danger:hover { background: #a71d2a; }
.wmt-input, .wmt-select, .wmt-textarea { width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
.wmt-label { display: block; font-weight: bold; margin-bottom:5px; }
.wmt-chat { background: #f4f6f8; padding: 20px; border-radius: 8px; margin-bottom: 20px; max-height: 600px; overflow-y: auto; border: 1px solid #eee; }
.wmt-bubble { margin-bottom: 15px; clear: both; padding: 15px; border-radius: 12px; max-width: 80%; position: relative; font-size: 14px; line-height:1.5; box-shadow: 0 1px 2px rgba(0,0,0,0.1); }
.wmt-left { float: left; background: #fff; border-bottom-left-radius: 2px; }
.wmt-right { float: right; background: #e3f2fd; border-bottom-right-radius: 2px; border: 1px solid #bbdefb; }
.wmt-meta { font-size: 11px; color: #888; margin-top: 8px; display: block; }
.wmt-system-msg { text-align: center; margin: 20px 0; font-size: 12px; color: #666; background: #e9ecef; padding: 5px 10px; border-radius: 20px; display: inline-block; width: 100%; box-sizing: border-box; border: 1px solid #ced4da; }
.wmt-internal-note { background: #eee; border-left: 4px solid #6c757d; padding: 10px; margin-bottom: 10px; font-size: 12px; color: #333; font-style: italic; display: block; }
.wmt-internal-note strong { display: block; font-style: normal; color: #495057; margin-bottom: 3px; }
.wmt-alert { padding: 15px; background: #d4edda; color: #155724; border: 1px solid #c3e6cb; margin-bottom: 20px; border-radius: 4px; }
.wmt-error { padding: 15px; background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; margin-bottom: 20px; border-radius: 4px; }
.wmt-warning { padding: 15px; background: #fff3cd; color: #856404; border: 1px solid #ffeeba; margin-bottom: 20px; border-radius: 4px; }
.wmt-avatar { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; font-weight: bold; font-size: 14px; margin-right: 10px; flex-shrink: 0; text-transform: uppercase; }
.wmt-msg-row { display: flex; align-items: flex-start; margin-bottom: 15px; }
.wmt-export-btn, .wmt-print-btn { float: right; margin-top: 5px; background: #28a745; }
.wmt-print-btn { background: #6c757d; }
.wmt-template-box { border: 1px solid #ccc; padding: 15px; background: #f9f9f9; margin-bottom: 10px; border-radius: 4px; position: relative; }
.wmt-tpl-row { display: flex; gap: 10px; margin-bottom: 5px; }
.wmt-tpl-row select, .wmt-tpl-row input { margin-bottom: 0; width: auto; }
.wmt-tpl-remove { position: absolute; top: 10px; right: 10px; color: #a00; text-decoration: none; font-weight: bold; cursor: pointer; }
.wmt-tpl-remove:hover { color: #d00; }
/* Analytics Styles */
.wmt-analytics-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.wmt-analytics-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
.wmt-stat-card { background: #fff; border: 1px solid #e5e7eb; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); transition: transform 0.2s; }
.wmt-stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 6px rgba(0,0,0,0.05); }
.wmt-stat-card h3 { margin: 0 0 5px 0; font-size: 13px; color: #6b7280; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
.wmt-stat-card .number { font-size: 28px; font-weight: 700; color: #111827; margin: 0; line-height: 1.2; }
.wmt-stat-card .trend { font-size: 12px; margin-top: 5px; color: #6b7280; }
.wmt-stat-card.blue { border-top: 4px solid #3b82f6; }
.wmt-stat-card.red { border-top: 4px solid #ef4444; }
.wmt-stat-card.green { border-top: 4px solid #10b981; }
.wmt-stat-card.orange { border-top: 4px solid #f59e0b; }
.wmt-charts-wrapper { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
.wmt-chart-container { background: #fff; border: 1px solid #e5e7eb; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
.wmt-chart-container h3 { margin-top: 0; color: #374151; font-size: 16px; border-bottom: 1px solid #f3f4f6; padding-bottom: 10px; }
.wmt-full-chart { grid-column: span 2; }
@media (max-width: 1200px) {
.wmt-analytics-grid { grid-template-columns: repeat(2, 1fr); }
.wmt-charts-wrapper { grid-template-columns: 1fr; }
.wmt-full-chart { grid-column: span 1; }
}
@media (max-width: 600px) {
.wmt-analytics-grid { grid-template-columns: 1fr; }
}
/* CSS Fallback Styles */
.wmt-css-chart-row { display: flex; align-items: center; margin-bottom: 8px; }
.wmt-css-label { width: 120px; font-size: 13px; font-weight: 600; text-align: right; margin-right: 10px; }
.wmt-css-bar-bg { flex-grow: 1; background: #f3f4f6; height: 24px; border-radius: 4px; overflow: hidden; position: relative; }
.wmt-css-bar-fill { height: 100%; background: #3b82f6; display: flex; align-items: center; padding-left: 10px; color: #fff; font-size: 11px; white-space: nowrap; transition: width 0.5s ease; }
.wmt-css-bar-val { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); font-size: 11px; color: #6c757d; font-weight: bold; }
/* Mail Log Styles */
.wmt-mail-log-table { width: 100%; border-collapse: collapse; margin-top: 20px; }
.wmt-mail-log-table th, .wmt-mail-log-table td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
.wmt-mail-log-table th { background: #f5f5f5; font-weight: bold; }
.wmt-mail-success { color: #155724; font-weight: bold; }
.wmt-mail-failed { color: #721c24; font-weight: bold; }
</style>
<?php
}
public function create_tables() {
global $wpdb;
$charset_collate = $wpdb->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' ) );
add_submenu_page( 'wmt_tickets', 'E-Mail Einstellungen', 'E-Mail Setup', 'manage_options', 'wmt_mail_settings', array( $this, 'render_mail_settings_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_settings_group', 'wmt_font_color' );
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' );
// SMTP Settings
register_setting( 'wmt_mail_settings_group', 'wmt_smtp_enabled' );
register_setting( 'wmt_mail_settings_group', 'wmt_smtp_host' );
register_setting( 'wmt_mail_settings_group', 'wmt_smtp_port' );
register_setting( 'wmt_mail_settings_group', 'wmt_smtp_auth' );
register_setting( 'wmt_mail_settings_group', 'wmt_smtp_username' );
register_setting( 'wmt_mail_settings_group', 'wmt_smtp_password' );
register_setting( 'wmt_mail_settings_group', 'wmt_smtp_encryption' );
register_setting( 'wmt_mail_settings_group', 'wmt_from_email' );
register_setting( 'wmt_mail_settings_group', 'wmt_from_name' );
register_setting( 'wmt_mail_settings_group', 'wmt_bcc_admin' );
register_setting( 'wmt_mail_settings_group', 'wmt_mail_logging' );
register_setting( 'wmt_mail_settings_group', 'wmt_smtp_debug' );
}
public function render_mail_settings_page() {
// Test-Mail senden
if ( isset( $_POST['wmt_send_test_mail'] ) ) {
// Prüfen des Nonce
$nonce_value = isset( $_POST['wmt_test_nonce'] ) ? $_POST['wmt_test_nonce'] : '';
if ( ! wp_verify_nonce( $nonce_value, 'wmt_test_mail' ) ) {
echo '<div class="wmt-error">Sicherheitsfehler beim Senden der Test-E-Mail.</div>';
} else {
$test_email = sanitize_email( $_POST['test_email'] );
// Debug-Modus temporär für Test aktivieren
$orig_debug = get_option('wmt_smtp_debug', false);
if(!$orig_debug) update_option('wmt_smtp_debug', '1');
$result = $this->send_mail(
$test_email,
'Test E-Mail von WP Multi Ticket Pro',
'Wenn Sie diese E-Mail erhalten, funktioniert der E-Mail-Versand korrekt!'
);
// Debug-Modus zurücksetzen
update_option('wmt_smtp_debug', $orig_debug);
if ( $result ) {
echo '<div class="wmt-alert">✓ Test-E-Mail erfolgreich an ' . esc_html($test_email) . ' gesendet!</div>';
} else {
echo '<div class="wmt-error">✗ Test-E-Mail konnte nicht gesendet werden. Prüfen Sie die Tabelle unten für Details.</div>';
}
}
}
// Logs löschen
if ( isset( $_POST['wmt_clear_logs'] ) ) {
// Manuelle Nonce-Prüfung da settings_fields hier nicht verwendet wird
if ( check_admin_referer( 'wmt_clear_logs' ) ) {
delete_option( 'wmt_mail_logs' );
echo '<div class="wmt-alert">Mail-Logs gelöscht.</div>';
}
}
$logs = get_option( 'wmt_mail_logs', array() );
?>
<div class="wrap">
<h1>E-Mail Einstellungen</h1>
<div class="wmt-warning">
<strong>⚠️ Wichtig:</strong> Wenn SMTP nicht funktioniert, prüfen Sie unten die Logs für den genauen Fehler.
</div>
<form method="post" action="options.php">
<?php settings_fields( 'wmt_mail_settings_group' ); ?>
<h2>Grundeinstellungen</h2>
<table class="form-table">
<tr>
<th>Absender E-Mail</th>
<td>
<input type="email" name="wmt_from_email" value="<?php echo esc_attr( get_option('wmt_from_email', get_option('admin_email')) ); ?>" class="regular-text">
<p class="description">Die E-Mail-Adresse, von der Tickets versendet werden.</p>
</td>
</tr>
<tr>
<th>Absender Name</th>
<td>
<input type="text" name="wmt_from_name" value="<?php echo esc_attr( get_option('wmt_from_name', get_bloginfo('name') . ' Support') ); ?>" class="regular-text">
<p class="description">Der Name, der als Absender angezeigt wird.</p>
</td>
</tr>
<tr>
<th>BCC an Admin</th>
<td>
<label>
<input type="checkbox" name="wmt_bcc_admin" value="1" <?php checked( get_option('wmt_bcc_admin'), 1 ); ?>>
Admin erhält BCC-Kopie aller E-Mails
</label>
</td>
</tr>
<tr>
<th>E-Mail Logging</th>
<td>
<label>
<input type="checkbox" name="wmt_mail_logging" value="1" <?php checked( get_option('wmt_mail_logging', 1), 1 ); ?>>
E-Mail-Versand protokollieren (empfohlen für Debugging)
</label>
</td>
</tr>
</table>
<h2>SMTP Einstellungen (empfohlen)</h2>
<table class="form-table">
<tr>
<th>SMTP aktivieren</th>
<td>
<label>
<input type="checkbox" name="wmt_smtp_enabled" value="1" <?php checked( get_option('wmt_smtp_enabled'), 1 ); ?>>
SMTP für E-Mail-Versand verwenden
</label>
<p class="description">Empfohlen für zuverlässigen E-Mail-Versand.</p>
</td>
</tr>
<tr>
<th>SMTP Host</th>
<td>
<input type="text" name="wmt_smtp_host" value="<?php echo esc_attr( get_option('wmt_smtp_host', 'localhost') ); ?>" class="regular-text">
<p class="description">z.B. smtp.gmail.com, smtp.ionos.de, mail.ihr-provider.de</p>
</td>
</tr>
<tr>
<th>SMTP Port</th>
<td>
<input type="number" name="wmt_smtp_port" value="<?php echo esc_attr( get_option('wmt_smtp_port', 587) ); ?>" class="small-text">
<p class="description">Standard: 587 (TLS), 465 (SSL), 25 (keine Verschlüsselung)</p>
</td>
</tr>
<tr>
<th>Verschlüsselung</th>
<td>
<select name="wmt_smtp_encryption">
<option value="">Keine</option>
<option value="tls" <?php selected( get_option('wmt_smtp_encryption'), 'tls' ); ?>>TLS (empfohlen)</option>
<option value="ssl" <?php selected( get_option('wmt_smtp_encryption'), 'ssl' ); ?>>SSL</option>
</select>
</td>
</tr>
<tr>
<th>SMTP Authentifizierung</th>
<td>
<label>
<input type="checkbox" name="wmt_smtp_auth" value="1" <?php checked( get_option('wmt_smtp_auth', 1), 1 ); ?>>
SMTP Authentifizierung verwenden
</label>
</td>
</tr>
<tr>
<th>SMTP Benutzername</th>
<td>
<input type="text" name="wmt_smtp_username" value="<?php echo esc_attr( get_option('wmt_smtp_username') ); ?>" class="regular-text" autocomplete="off">
<p class="description">Meist Ihre E-Mail-Adresse</p>
</td>
</tr>
<tr>
<th>SMTP Passwort</th>
<td>
<input type="password" name="wmt_smtp_password" value="<?php echo esc_attr( get_option('wmt_smtp_password') ); ?>" class="regular-text" autocomplete="new-password">
<p class="description">⚠️ Wird unverschlüsselt in der Datenbank gespeichert!</p>
</td>
</tr>
<tr>
<th>Debug-Modus</th>
<td>
<label>
<input type="checkbox" name="wmt_smtp_debug" value="1" <?php checked( get_option('wmt_smtp_debug'), 1 ); ?>>
SMTP Debug aktivieren (schreibt Logs für jeden Versand)
</label>
<p class="description">Wichtig für den Test! Wird nach dem Test automatisch wieder ausgeschaltet.</p>
</td>
</tr>
</table>
<?php submit_button( 'Einstellungen speichern' ); ?>
</form>
<hr>
<h2>Test-E-Mail senden</h2>
<form method="post">
<input type="hidden" id="wmt_test_nonce" name="wmt_test_nonce" value="<?php echo wp_create_nonce('wmt_test_mail'); ?>" />
<table class="form-table">
<tr>
<th>Test-E-Mail an</th>
<td>
<input type="email" name="test_email" value="<?php echo esc_attr( get_option('wmt_admin_email', get_option('admin_email')) ); ?>" class="regular-text" required>
<button type="submit" name="wmt_send_test_mail" class="button button-primary">Test-Mail senden</button>
</td>
</tr>
</table>
</form>
<?php if ( ! empty( $logs ) ): ?>
<hr>
<h2>E-Mail Logs (letzte 50)</h2>
<form method="post">
<input type="hidden" name="wmt_clear_logs" value="1" />
<?php wp_nonce_field( 'wmt_clear_logs' ); ?>
<button type="submit" name="submit" class="button">Logs löschen</button>
</form>
<table class="wmt-mail-log-table">
<thead>
<tr>
<th>Zeit</th>
<th>Empfänger</th>
<th>Betreff</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ( $logs as $log ): ?>
<tr>
<td><?php echo esc_html( $log['timestamp'] ); ?></td>
<td><?php echo esc_html( $log['to'] ); ?></td>
<td><?php echo esc_html( $log['subject'] ); ?></td>
<td class="<?php echo $log['success'] === 'YES' ? 'wmt-mail-success' : 'wmt-mail-failed'; ?>">
<?php echo $log['success'] === 'YES' ? '✓ Gesendet' : '✗ Fehler'; ?>
<?php if ( ! empty( $log['error'] ) ): ?>
<div style="font-size: 11px; margin-top: 5px; color: #d63638; word-break: break-all; background: #f9f9f9; padding: 5px; border: 1px solid #eee; max-width: 500px;">
<strong>Server-Fehler:</strong><br>
<?php echo esc_html( $log['error'] ); ?>
</div>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<hr>
<h2>Häufige Probleme & Lösungen</h2>
<div style="background: #f9f9f9; padding: 20px; border-left: 4px solid #0073aa; margin-top: 20px;">
<h3>E-Mails kommen nicht an?</h3>
<ol>
<li><strong>Prüfen Sie das Log:</strong> Wenn die Test-Mail fehlschlägt, steht der genaue Fehler oben in der Tabelle unter "Server-Fehler".</li>
<li><strong>Gmail Nutzer:</strong> Verwenden Sie ein <strong>App-Passwort</strong>, nicht Ihr normales Passwort. Port 587 + TLS.</li>
<li><strong>OpenSSL Fehler:</strong> Wenn im Log steht "OpenSSL extension missing", kontaktieren Sie Ihren Hoster.</li>
<li><strong>Firewall:</strong> Manche Hoster blockieren Port 25 und 587.</li>
</ol>
</div>
</div>
<?php
}
public function render_analytics_page() {
global $wpdb;
$total_tickets = $wpdb->get_var("SELECT COUNT(*) FROM $this->table_tickets");
if ( $total_tickets == 0 ) {
echo '<div class="wrap">';
echo '<div style="background: #fff; padding: 50px; text-align: center; border-radius: 8px; border: 1px solid #eee; max-width: 600px; margin: 50px auto;">';
echo '<h2 style="color: #555;">Noch keine Daten</h2>';
echo '<p style="color: #777; font-size: 16px;">Es wurden noch keine Tickets erstellt.</p>';
echo '<a href="' . admin_url('admin.php?page=wmt_tickets') . '" class="wmt-btn">Zu den Tickets</a>';
echo '</div></div>';
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
");
?>
<div class="wrap">
<div class="wmt-analytics-header">
<h1>Analytics Dashboard</h1>
<span style="color: #666; font-size: 14px;">Live Daten</span>
</div>
<div class="wmt-analytics-grid">
<div class="wmt-stat-card blue">
<h3>Gesamt Tickets</h3>
<p class="number"><?php echo intval($total_tickets); ?></p>
</div>
<div class="wmt-stat-card red">
<h3>Aktiv / Offen</h3>
<p class="number"><?php echo intval($open_tickets); ?></p>
<p class="trend">Benötigt Aufmerksamkeit</p>
</div>
<div class="wmt-stat-card green">
<h3>Geschlossen</h3>
<p class="number"><?php echo intval($closed_tickets); ?></p>
</div>
<div class="wmt-stat-card orange">
<h3>Letzte 30 Tage</h3>
<p class="number"><?php echo intval($new_tickets_30d); ?></p>
<p class="trend">Neue Eingänge</p>
</div>
</div>
<div class="wmt-charts-wrapper">
<!-- Status -->
<div class="wmt-chart-container">
<h3>Status Verteilung</h3>
<div style="height: 250px; position: relative;"><canvas id="statusChart"></canvas></div>
</div>
<!-- Kategorien -->
<div class="wmt-chart-container">
<h3>Top Kategorien</h3>
<div style="height: 250px; position: relative;"><canvas id="catChart"></canvas></div>
</div>
<!-- Agent Performance -->
<div class="wmt-chart-container">
<h3>Workload pro Agent</h3>
<div style="height: 250px; position: relative;"><canvas id="agentChart"></canvas></div>
</div>
<!-- Timeline -->
<div class="wmt-chart-container">
<h3>Tickets pro Tag</h3>
<div style="height: 250px; position: relative;"><canvas id="timelineChart"></canvas></div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
const statusLabels = <?php echo json_encode(array_column($status_data, 'status'), JSON_UNESCAPED_UNICODE); ?>;
const statusCounts = <?php echo json_encode(array_column($status_data, 'count'), JSON_UNESCAPED_UNICODE); ?>;
const catLabels = <?php echo json_encode(array_column($cat_data, 'category'), JSON_UNESCAPED_UNICODE); ?>;
const catCounts = <?php echo json_encode(array_column($cat_data, 'count'), JSON_UNESCAPED_UNICODE); ?>;
const agentLabels = <?php echo json_encode(array_column($agent_data, 'display_name'), JSON_UNESCAPED_UNICODE); ?>;
const agentCounts = <?php echo json_encode(array_column($agent_data, 'count'), JSON_UNESCAPED_UNICODE); ?>;
const timeLabels = <?php echo json_encode(array_column($timeline_data, 'date'), JSON_UNESCAPED_UNICODE); ?>;
const timeCounts = <?php echo json_encode(array_column($timeline_data, 'count'), JSON_UNESCAPED_UNICODE); ?>;
const localUrl = "<?php echo plugins_url( 'chart.js', __FILE__ ); ?>";
console.log("WMT DEBUG: Suche nach Chart.js auf: " + localUrl);
if (typeof Chart === 'undefined') {
console.warn("WMT WARN: Chart.js ist nicht geladen. Nutze CSS Fallback.");
const statusContainer = document.getElementById('statusChart').parentElement;
let statusHtml = '<div style="display:grid; grid-template-columns: 1fr 1fr; gap: 10px; padding: 10px; background: #fff3cd; border: 1px solid #ffeeba; border-radius: 4px; color: #856404;">Chart.js konnte nicht geladen werden (Lokale Datei fehlt?). Nutze CSS Fallback:</div>';
statusLabels.forEach((label, i) => {
statusHtml += `<div style="background: #f9fafb; padding: 10px; border-radius: 4px; border: 1px solid #eee;"><strong>${label}:</strong> ${statusCounts[i]}</div>`;
});
statusHtml += '</div>';
statusContainer.innerHTML = statusHtml;
const catContainer = document.getElementById('catChart').parentElement;
let catHtml = '<div style="padding:10px 0;">';
if (catLabels.length > 0) {
let maxCat = Math.max(...catCounts) || 1;
catLabels.forEach((label, i) => {
let pct = (catCounts[i] / maxCat) * 100;
catHtml += `
<div class="wmt-css-chart-row">
<div class="wmt-css-label">${label}</div>
<div class="wmt-css-bar-bg">
<div class="wmt-css-bar-fill" style="width: ${pct}%">
${catCounts[i]}
</div>
<div class="wmt-css-bar-val">${catCounts[i]}</div>
</div>
</div>`;
});
} else {
catHtml = '<div style="padding:50px; text-align:center; color:#999;">Keine Kategorien.</div>';
}
catHtml += '</div><div style="margin-top:20px; font-size:11px; color:#999; text-align:center;">* Lokale Chart.js Datei fehlt oder JS blockiert.</div>';
catContainer.innerHTML = catHtml;
const agentContainer = document.getElementById('agentChart').parentElement;
let agentHtml = '<div style="padding:10px 0;">';
if (agentLabels.length > 0) {
let maxAgent = Math.max(...agentCounts) || 1;
agentLabels.forEach((label, i) => {
let pct = (agentCounts[i] / maxAgent) * 100;
agentHtml += `
<div class="wmt-css-chart-row">
<div class="wmt-css-label">${label}</div>
<div class="wmt-css-bar-bg">
<div class="wmt-css-bar-fill" style="background:#34d399; width: ${pct}%">
${agentCounts[i]}
</div>
<div class="wmt-css-bar-val">${agentCounts[i]}</div>
</div>
</div>`;
});
} else {
agentHtml = '<div style="padding:50px; text-align:center; color:#999;">Keine zugewiesenen Tickets.</div>';
}
agentHtml += '</div>';
agentContainer.innerHTML = agentHtml;
const timeContainer = document.getElementById('timelineChart').parentElement;
let timeHtml = '<div style="padding:10px 0;"><ul style="list-style:none; padding:0;">';
if (timeLabels.length > 0) {
timeLabels.forEach((date, i) => {
timeHtml += `<li style="display:flex; justify-content:space-between; border-bottom:1px solid #eee; padding:8px 0;"><span>${date}</span> <strong>${timeCounts[i]}</strong></li>`;
});
} else {
timeHtml = '<div style="padding:50px; text-align:center; color:#999;">Keine Timeline Daten.</div>';
}
timeHtml += '</ul></div>';
timeContainer.innerHTML = timeHtml;
} else {
console.log("WMT SUCCESS: Chart.js geladen. Erstelle Diagramme.");
const commonOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom' }
}
};
new Chart(document.getElementById('statusChart'), {
type: 'doughnut',
data: {
labels: statusLabels,
datasets: [{
data: statusCounts,
backgroundColor: ['#3b82f6', '#10b981', '#ef4444', '#f59e0b', '#8b5cf6', '#9ca3af'],
borderWidth: 0
}]
},
options: { ...commonOptions, cutout: '70%' }
});
new Chart(document.getElementById('catChart'), {
type: 'bar',
data: {
labels: catLabels,
datasets: [{
label: 'Anzahl',
data: catCounts,
backgroundColor: '#60a5fa',
borderRadius: 4
}]
},
options: { ...commonOptions, scales: { y: { beginAtZero: true, grid: { display: false } }, x: { grid: { display: false } } } }
});
if (agentLabels.length > 0) {
new Chart(document.getElementById('agentChart'), {
type: 'bar',
data: {
labels: agentLabels,
datasets: [{
label: 'Zugewiesene Tickets',
data: agentCounts,
backgroundColor: '#34d399',
borderRadius: 4
}]
},
options: { ...commonOptions, indexAxis: 'y', scales: { x: { beginAtZero: true } } }
});
} else {
document.getElementById('agentChart').parentElement.innerHTML = '<div style="padding:50px; text-align:center; color:#999;">Keine zugewiesenen Tickets.</div>';
}
if (timeLabels.length > 0) {
new Chart(document.getElementById('timelineChart'), {
type: 'line',
data: {
labels: timeLabels,
datasets: [{
label: 'Neue Tickets',
data: timeCounts,
borderColor: '#818cf8',
backgroundColor: 'rgba(129, 140, 248, 0.1)',
fill: true,
tension: 0.4,
pointRadius: 2
}]
},
options: { ...commonOptions, scales: { x: { ticks: { maxTicksLimit: 7 } } } }
});
}
}
});
</script>
<?php
}
public function render_settings_page() {
$templates = get_option( 'wmt_templates', array() );
if ( is_string($templates) ) $templates = array();
?>
<div class="wrap">
<h1>Einstellungen</h1>
<form method="post" action="options.php">
<?php settings_fields( 'wmt_settings_group' ); ?>
<table class="form-table">
<tr><th>Kategorien</th><td><input type="text" name="wmt_categories" value="<?php echo esc_attr( get_option('wmt_categories', 'Allgemein, Technik, Billing') ); ?>" class="regular-text"></td></tr>
<tr><th>Kategorie ➔ Abteilung Zuordnung</th><td><input type="text" name="wmt_departments" value="<?php echo esc_attr( get_option('wmt_departments', 'Technik:IT, Billing:Buchhaltung') ); ?>" class="large-text" style="width:100%;"></td></tr>
<tr><th>Prioritäten</th><td><input type="text" name="wmt_priorities" value="<?php echo esc_attr( get_option('wmt_priorities', 'Hoch, Mittel, Niedrig') ); ?>" class="regular-text"></td></tr>
<tr><th>Status</th><td><input type="text" name="wmt_statuses" value="<?php echo esc_attr( get_option('wmt_statuses', 'Offen, In Bearbeitung, Wartet auf Antwort, Geschlossen') ); ?>" class="regular-text"></td></tr>
<tr><th>Admin E-Mail</th><td><input type="email" name="wmt_admin_email" value="<?php echo esc_attr( get_option('wmt_admin_email', get_option('admin_email') ) ); ?>" class="regular-text"></td></tr>
<tr><th>Erlaubte Dateiendungen</th><td><input type="text" name="wmt_allowed_filetypes" value="<?php echo esc_attr( get_option('wmt_allowed_filetypes', 'pdf, doc, docx, jpg, jpeg, png, txt') ); ?>" class="regular-text"><p class="description">Kommagetrennt (z.B. pdf, doc, png).</p></td></tr>
<!-- Schriftfarbe Einstellung -->
<tr>
<th>Schriftfarbe</th>
<td>
<input type="color" name="wmt_font_color" value="<?php echo esc_attr( get_option('wmt_font_color', '#6b7280') ); ?>" class="regular-text">
<p class="description">Wählen Sie die Standard-Schriftfarbe für das Ticket-System (inkl. Überschriften).</p>
</td>
</tr>
<tr>
<th>Textbausteine</th>
<td>
<div id="wmt-templates-container">
<?php foreach( $templates as $index => $tpl ): ?>
<div class="wmt-template-box">
<a href="#" class="wmt-tpl-remove" onclick="removeTemplate(this); return false;">×</a>
<div class="wmt-tpl-row">
<select name="wmt_templates[<?php echo $index; ?>][type]" style="width: 120px;">
<option value="public" <?php selected($tpl['type'], 'public'); ?>>Öffentlich</option>
<option value="internal" <?php selected($tpl['type'], 'internal'); ?>>Intern</option>
</select>
<input type="text" name="wmt_templates[<?php echo $index; ?>][name]" value="<?php echo esc_attr($tpl['name']); ?>" class="regular-text" placeholder="Name der Vorlage" style="flex-grow:1;">
</div>
<textarea name="wmt_templates[<?php echo $index; ?>][content]" class="large-text" rows="3"><?php echo esc_textarea($tpl['content']); ?></textarea>
</div>
<?php endforeach; ?>
</div>
<button type="button" class="button" onclick="addTemplate()">+ Neue Box hinzufügen</button>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<script type="text/javascript">
function addTemplate() {
var container = document.getElementById('wmt-templates-container');
var count = container.children.length;
var div = document.createElement('div');
div.className = 'wmt-template-box';
div.innerHTML = `
<a href="#" class="wmt-tpl-remove" onclick="removeTemplate(this); return false;">×</a>
<div class="wmt-tpl-row">
<select name="wmt_templates[${count}][type]" style="width: 120px;">
<option value="public">Öffentlich</option>
<option value="internal">Intern</option>
</select>
<input type="text" name="wmt_templates[${count}][name]" class="regular-text" placeholder="Name der Vorlage" style="flex-grow:1;">
</div>
<textarea name="wmt_templates[${count}][content]" class="large-text" rows="3"></textarea>
`;
container.appendChild(div);
}
function removeTemplate(btn) { btn.parentElement.remove(); }
</script>
<?php
}
public function render_notifications_page() {
$all_users = get_users( array( 'orderby' => 'display_name' ) );
$selected_users = get_option( 'wmt_new_ticket_notify_users', array() );
if ( ! is_array( $selected_users ) ) $selected_users = array();
?>
<div class="wrap">
<h1>Benachrichtigungen</h1>
<form method="post" action="options.php">
<?php settings_fields( 'wmt_notifications_group' ); ?>
<table class="form-table">
<tr>
<th>Discord Webhook URL</th>
<td>
<input type="url" name="wmt_discord_webhook" value="<?php echo esc_attr( get_option('wmt_discord_webhook') ); ?>" class="regular-text" placeholder="https://discord.com/api/webhooks/...">
<p class="description">Wird bei neuem Ticket und neuer Gast-Antwort gesendet.</p>
</td>
</tr>
<tr>
<th>Telegram Bot Token</th>
<td>
<input type="text" name="wmt_telegram_token" value="<?php echo esc_attr( get_option('wmt_telegram_token') ); ?>" class="regular-text">
</td>
</tr>
<tr>
<th>Telegram Chat ID</th>
<td>
<input type="text" name="wmt_telegram_chat_id" value="<?php echo esc_attr( get_option('wmt_telegram_chat_id') ); ?>" class="regular-text">
<p class="description">Für Gruppen/Kanäle mit -100 beginnen.</p>
</td>
</tr>
<tr>
<th>Zusätzliche Benachrichtigungen<br>bei neuem Ticket</th>
<td>
<select name="wmt_new_ticket_notify_users[]" multiple size="10" style="width: 400px; height: 200px;">
<?php foreach ( $all_users as $user ): ?>
<option value="<?php echo $user->ID; ?>" <?php echo in_array( $user->ID, $selected_users ) ? 'selected' : ''; ?>>
<?php echo esc_html( $user->display_name ); ?> (<?php echo esc_html( $user->user_email ); ?>)
</option>
<?php endforeach; ?>
</select>
<p class="description">Diese User erhalten <strong>zusätzlich</strong> eine E-Mail bei jedem neuen Ticket (Strg/Cmd klicken für Mehrfachauswahl).</p>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}
public function add_dashboard_widget() {
wp_add_dashboard_widget(
'wmt_dashboard_widget',
'Support Tickets',
array( $this, 'render_dashboard_widget' ),
'high'
);
}
public function render_dashboard_widget() {
global $wpdb;
$count = $wpdb->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 '<div class="wmt-box" style="padding: 15px; margin: 0; border: 1px solid #ccd0d4; background: #fff;">';
if ( $count > 0 ) {
echo '<div style="font-size: 24px; font-weight: bold; color: #d63638; line-height: 1;">' . intval( $count ) . '</div>';
echo '<div style="color: #666;">Offene Tickets</div>';
} else {
echo '<div style="font-size: 24px; font-weight: bold; color: #28a745; line-height: 1;">0</div>';
echo '<div style="color: #666;">Keine offenen Tickets</div>';
}
echo '<a href="' . $link . '" class="wmt-btn" style="display: block; text-align: center; margin-top: 10px;">Alle Tickets ansehen</a>';
echo '</div>';
}
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 '<div class="notice notice-success is-dismissible"><p>Ticket erfolgreich gelöscht.</p></div>';
}
$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 '<div class="wrap"><h1>Ticket Übersicht</h1>';
echo '<div style="margin-bottom: 20px; display:flex; gap:10px; align-items:center; flex-wrap:wrap;">';
echo '<form method="get" style="display:flex; gap:10px; align-items:center;">';
echo '<input type="hidden" name="page" value="wmt_tickets">';
echo '<input type="text" name="s" value="' . esc_attr($search) . '" placeholder="Suche..." class="regular-text">';
echo '<select name="status_filter" class="regular-text">';
echo '<option value="">Alle Status</option>';
foreach( array_map('trim', explode(',', get_option('wmt_statuses'))) as $st ) {
$sel = selected($status_filter, $st, false);
echo '<option value="' . esc_attr($st) . '" ' . $sel . '>' . esc_html($st) . '</option>';
}
echo '</select>';
echo '<input type="submit" value="Filtern" class="button">';
echo '</form>';
$export_url = admin_url( 'admin.php?page=wmt_tickets&wmt_action=export' );
if($search) $export_url = add_query_arg( 's', $search, $export_url );
echo '<a href="' . $export_url . '" class="wmt-btn wmt-export-btn">CSV Export</a>';
echo '</div>';
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr><th>ID</th><th>Betreff</th><th>Kat.</th><th>Abt.</th><th>Prio</th><th>Gast Info</th><th>Status</th><th>Zugewiesen</th><th>Aktionen</th></tr></thead>';
echo '<tbody>';
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 '<tr>';
echo '<td>#' . $t->id . '</td>';
echo '<td>' . esc_html( $t->title ) . '</td>';
echo '<td>' . esc_html( $t->category ) . '</td>';
echo '<td>' . ( $t->department ? esc_html($t->department) : '-' ) . '</td>';
echo '<td style="'.$prio_style.'">' . esc_html( $t->priority ) . '</td>';
echo '<td><strong>' . esc_html( $t->guest_name ) . '</strong><br><small>' . esc_html( $t->guest_email ) . '</small></td>';
echo '<td>' . esc_html( $t->status ) . '</td>';
echo '<td>' . esc_html( $assigned_name ) . '</td>';
echo '<td>';
echo '<a href="' . admin_url( 'admin.php?page=wmt_tickets&action=edit&id=' . $t->id ) . '" class="button">Bearbeiten</a> ';
$delete_url = wp_nonce_url( admin_url( 'admin.php?page=wmt_tickets&action=wmt_delete&id=' . $t->id ), 'wmt_delete_ticket_' . $t->id );
echo '<a href="' . $delete_url . '" class="button wmt-btn danger" onclick="return confirm(\'Wirklich löschen?\')">Löschen</a>';
echo '</td>';
echo '</tr>';
}
echo '</tbody></table></div>';
}
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 '<div class="notice notice-success is-dismissible"><p>Aktualisierung erfolgreich!</p></div>';
}
$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 = '<option value="' . $content . '">' . $name . '</option>';
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');
?>
<div class="wrap">
<h1>Ticket #<?php echo $ticket->id; ?> bearbeiten</h1>
<div style="margin-bottom: 10px;">
<a href="<?php echo admin_url( 'admin.php?page=wmt_tickets' ); ?>" class="button">« Zurück</a>
<a href="#" onclick="wmt_print_ticket(<?php echo $ticket->id; ?>); return false;" class="wmt-btn wmt-print-btn" style="margin-left:10px;">Ticket drucken</a>
<?php
$delete_url = wp_nonce_url( admin_url( 'admin.php?page=wmt_tickets&action=wmt_delete&id=' . $ticket->id ), 'wmt_delete_ticket_' . $ticket->id );
echo '<a href="' . $delete_url . '" class="button wmt-btn danger" style="margin-left:10px;" onclick="return confirm(\'Ticket wirklich löschen?\')">Ticket löschen</a>';
?>
</div>
<div class="wmt-box" style="margin-top:20px; border:1px solid #ccc;">
<div class="wmt-chat">
<?php foreach ( $messages as $msg ) :
if( $msg->sender_type === 'system' ): ?>
<div class="wmt-system-msg"><strong>System:</strong> <?php echo esc_html($msg->message); ?> <small><?php echo $msg->created_at; ?></small></div>
<?php continue; endif;
$is_admin = ( $msg->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)];
?>
<div class="wmt-msg-row" style="flex-direction: <?php echo $is_admin ? 'row' : 'row-reverse'; ?>;">
<div class="wmt-avatar" style="background:<?php echo $bg_color; ?>;"><?php echo esc_html($initial); ?></div>
<div class="wmt-bubble wmt-<?php echo $align; ?>" style="background:<?php echo $bg; ?>;">
<strong><?php echo esc_html( $msg->sender_name ); ?></strong> <small><?php echo $msg->created_at; ?></small>
<?php if( $is_admin ) echo ' <span style="color:#0073aa;">(Support)</span>'; ?>
<?php if( $is_admin && !empty($msg->internal_note) ) : ?>
<div class="wmt-internal-note"><strong>Interne Notiz:</strong><?php echo nl2br( esc_html( $msg->internal_note ) ); ?></div>
<?php endif; ?>
<?php if( !empty($msg->message) ): ?>
<div style="margin-top:5px;"><?php echo wp_kses_post($msg->message); ?></div>
<?php else: ?>
<p style="font-size:11px; color:#999; font-style:italic;">Keine öffentliche Nachricht.</p>
<?php endif; ?>
<?php if ( $msg->file_url ) : ?>
<a href="<?php echo esc_url( $msg->file_url ); ?>" target="_blank" style="display:inline-block; margin-top:10px; text-decoration:none; background:#e9ecef; padding:5px 10px; border-radius:4px; border:1px solid #ccc;">Datei herunterladen</a>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<hr>
<form method="post" action="<?php echo admin_url( 'admin-post.php' ); ?>" enctype="multipart/form-data">
<input type="hidden" name="action" value="wmt_admin_reply">
<?php wp_nonce_field( 'wmt_admin_action', 'wmt_nonce' ); ?>
<input type="hidden" name="ticket_id" value="<?php echo $ticket->id; ?>">
<table class="form-table">
<tr>
<th>Abteilung</th>
<td>
<select name="department" class="regular-text">
<option value="">-- Keine --</option>
<?php foreach( $dept_opts as $d ): ?>
<option value="<?php echo esc_attr($d); ?>" <?php selected($ticket->department, $d); ?>><?php echo esc_html($d); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr>
<th>Status</th>
<td>
<select name="status" class="regular-text">
<?php foreach( $status_opts as $s ): ?>
<option value="<?php echo esc_attr($s); ?>" <?php selected($ticket->status, $s); ?>><?php echo esc_html($s); ?></option>
<?php endforeach; ?>
</select>
<p class="description"><strong>Automatik:</strong> Bei Antworten wird das Ticket automatisch auf "In Bearbeitung" gesetzt (außer Sie wählen "Geschlossen").</p>
</td>
</tr>
<tr>
<th>Priorität</th>
<td><strong style="font-size: 16px; color: #333;"><?php echo esc_html( $ticket->priority ); ?></strong></td>
</tr>
<tr>
<th>Zuweisen an</th>
<td>
<select name="assigned_to" class="regular-text">
<option value="">-- Niemand --</option>
<?php foreach( $all_users as $u ): ?>
<option value="<?php echo $u->ID; ?>" <?php selected($ticket->assigned_to, $u->ID); ?>><?php echo esc_html( $u->display_name ); ?> (<?php echo esc_html( $u->user_email ); ?>)</option>
<?php endforeach; ?>
</select>
<p class="description"><strong>Auto-Detect:</strong> 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.</p>
</td>
</tr>
<tr>
<th>Antwort & Notizen</th>
<td>
<label class="wmt-label" style="color:#666; font-size:12px;">Interne Notiz (Optional)</label>
<select id="wmt_internal_template_select" class="regular-text" style="margin-bottom: 10px;"><option value="">-- Interne Vorlage --</option><?php echo $tpl_internal; ?></select>
<textarea name="internal_note" class="large-text" rows="3" placeholder="Notizen für Kollegen..."></textarea>
<hr style="margin: 20px 0; border:0; border-top:1px dashed #eee;">
<label class="wmt-label">Öffentliche Nachricht (Optional)</label>
<select id="wmt_public_template_select" class="regular-text" style="margin-bottom: 10px;"><option value="">-- Vorlage auswählen --</option><?php echo $tpl_public; ?></select>
<?php
$content = '';
$settings = array('textarea_name' => 'message', 'media_buttons' => false, 'textarea_rows' => 10, 'teeny' => false);
wp_editor( $content, 'wmt_message_editor', $settings );
?>
<label class="wmt-label" style="margin-top:10px;">Datei anhängen (Optional)</label>
<input type="file" name="ticket_file">
<p class="description">
Wird gesendet an <?php echo esc_html( $ticket->guest_email ); ?>.
</p>
</td>
</tr>
</table>
<?php submit_button( 'Speichern & Benachrichtigen' ); ?>
</form>
</div>
<script type="text/javascript">
document.getElementById('wmt_public_template_select').addEventListener('change', function() {
var editor = tinymce.get('wmt_message_editor');
if(this.value && editor) {
editor.setContent(editor.getContent() + (editor.getContent() ? '\n\n' : '') + this.value);
this.value = "";
}
});
document.getElementById('wmt_internal_template_select').addEventListener('change', function() {
var ta = document.querySelector('textarea[name="internal_note"]');
if(this.value && ta) { ta.value += (ta.value ? '\n\n' : '') + this.value; this.value = ""; }
});
function wmt_print_ticket(id) {
var url = '<?php echo admin_url( 'admin.php?page=wmt_tickets' ); ?>&wmt_print=1&id=' + id;
window.open(url, '_blank', 'width=800,height=600,scrollbars=yes');
}
</script>
</div>
<?php
}
private function render_print_view( $id ) {
if ( ! current_user_can( 'manage_options' ) ) return;
global $wpdb;
$ticket = $wpdb->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 '<!DOCTYPE html><html><head><title>Ticket #' . $id . '</title>';
echo '<style type="text/css">
body { font-family: Helvetica, Arial, sans-serif; color: #333; margin: 20px; background: white; }
.header { border-bottom: 2px solid #000; padding-bottom: 20px; margin-bottom: 30px; }
.info-row { display: flex; justify-content: space-between; margin-bottom: 5px; }
.info-label { font-weight: bold; }
.chat-item { margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid #eee; }
.chat-meta { font-size: 12px; color: #666; margin-bottom: 5px; }
.chat-content { line-height: 1.5; font-size: 14px; }
.internal { background: #f9f9f9; padding: 10px; border-left: 3px solid #999; margin-top: 10px; font-size: 12px; font-style: italic; }
.system { text-align: center; background: #eee; padding: 5px; margin: 20px 0; font-size: 11px; color: #666; }
.file-link { font-size: 12px; margin-top: 5px; color: #0066cc; }
</style></head><body>';
echo '<div class="header"><h1>Ticket #' . $id . ': ' . esc_html( $ticket->title ) . '</h1></div>';
echo '<div class="info-row"><span class="info-label">Kunde:</span> ' . esc_html( $ticket->guest_name ) . ' (' . esc_html( $ticket->guest_email ) . ')</div>';
echo '<div class="info-row"><span class="info-label">Kategorie:</span> ' . esc_html( $ticket->category ) . '</div>';
echo '<div class="info-row"><span class="info-label">Abteilung:</span> ' . ($ticket->department ? esc_html($ticket->department) : '-') . '</div>';
echo '<div class="info-row"><span class="info-label">Status:</span> ' . esc_html( $ticket->status ) . '</div>';
echo '<div class="info-row"><span class="info-label">Priorität:</span> ' . esc_html( $ticket->priority ) . '</div>';
$assigned = $ticket->assigned_to ? get_userdata($ticket->assigned_to)->display_name : 'Niemand';
echo '<div class="info-row"><span class="info-label">Zugewiesen an:</span> ' . esc_html($assigned) . '</div>';
echo '<h2 style="margin-top: 40px; border-bottom: 1px solid #ccc; padding-bottom: 10px;">Verlauf</h2>';
foreach ( $messages as $msg ) {
if ( $msg->sender_type === 'system' ) {
echo '<div class="system">System: ' . esc_html($msg->message) . ' (' . $msg->created_at . ')</div>';
continue;
}
echo '<div class="chat-item">';
echo '<div class="chat-meta"><strong>' . esc_html( $msg->sender_name ) . '</strong> ' . ($msg->sender_type === 'admin' ? '(Support)' : '(Kunde)') . ' - ' . $msg->created_at . '</div>';
echo '<div class="chat-content">' . wp_kses_post($msg->message) . '</div>';
if ( $msg->sender_type === 'admin' && !empty($msg->internal_note) ) {
echo '<div class="internal">Interne Notiz: ' . esc_html($msg->internal_note) . '</div>';
}
if ( $msg->file_url ) {
echo '<div class="file-link">Datei: <a href="' . esc_url( $msg->file_url ) . '">' . esc_html(basename($msg->file_url)) . '</a></div>';
}
echo '</div>';
}
echo '<script>window.print();</script></body></html>';
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 <strong>.{$file_ext}</strong> 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:
$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
if ( empty($assigned) && (!empty($msg_content) || !empty($_FILES['ticket_file']['name'])) ) {
$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
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}" );
// HIER: Sendet HTML E-Mail mit Template
$this->send_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}";
// HIER: Sendet HTML E-Mail mit Template
$this->send_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 '<div class="wmt-error">' . esc_html(urldecode($_GET['upload_error'])) . '</div>';
}
if( isset( $_GET['wmt_success'] ) ) {
echo '<div class="wmt-alert">Ticket erfolgreich erstellt! Bitte prüfen Sie Ihre E-Mails.</div>';
}
?>
<div class="wmt-box">
<h2>Neues Support Ticket erstellen</h2>
<form method="post" enctype="multipart/form-data">
<?php wp_nonce_field( 'wmt_guest_create', 'wmt_nonce' ); ?>
<label class="wmt-label">Ihr Name *</label>
<input type="text" name="guest_name" class="wmt-input" required>
<label class="wmt-label">Ihre E-Mail *</label>
<input type="email" name="guest_email" class="wmt-input" required>
<label class="wmt-label">Kategorie</label>
<select name="category" class="wmt-select">
<?php foreach( array_map('trim', explode(',', get_option('wmt_categories'))) as $c ): ?>
<option><?php echo esc_html($c); ?></option>
<?php endforeach; ?>
</select>
<label class="wmt-label">Priorität</label>
<select name="priority" class="wmt-select">
<?php foreach( array_map('trim', explode(',', get_option('wmt_priorities'))) as $p ): ?>
<option><?php echo esc_html($p); ?></option>
<?php endforeach; ?>
</select>
<label class="wmt-label">Betreff *</label>
<input type="text" name="title" class="wmt-input" required>
<label class="wmt-label">Nachricht *</label>
<textarea name="message" class="wmt-textarea" rows="10" required></textarea>
<label class="wmt-label">Datei anhängen (Optional)</label>
<input type="file" name="guest_file" class="wmt-input">
<button type="submit" name="wmt_create" class="wmt-btn">Ticket absenden</button>
</form>
</div>
<?php
return ob_get_clean();
}
public function render_lookup_form() {
ob_start();
if( isset( $_GET['wmt_lookup_sent'] ) ) {
echo '<div class="wmt-alert">Eine E-Mail mit Links wurde gesendet.</div>';
}
?>
<div class="wmt-box">
<h2>Meine Tickets finden</h2>
<form method="post">
<input type="email" name="lookup_email" class="wmt-input" placeholder="ihre@email.de" required>
<button type="submit" name="wmt_lookup" class="wmt-btn">Tickets suchen</button>
</form>
</div>
<?php
return ob_get_clean();
}
public function render_ticket_view() {
$tid = isset( $_GET['wmt_view'] ) ? intval( $_GET['wmt_view'] ) : 0;
$hash = isset( $_GET['hash'] ) ? sanitize_text_field( $_GET['hash'] ) : '';
if ( ! $tid || ! $hash ) return '<div class="wmt-box"><p>Kein Ticket ausgewählt.</p></div>';
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 '<div class="wmt-box" style="background:#ffebee;"><p>Zugriff verweigert.</p></div>';
$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();
?>
<div class="wmt-box">
<div style="border-bottom:1px solid #eee; padding-bottom:10px; margin-bottom:10px;">
<h2>Ticket #<?php echo $ticket->id; ?>: <?php echo esc_html( $ticket->title ); ?></h2>
<p><strong>Status:</strong> <?php echo esc_html( $ticket->status ); ?></p>
</div>
<div class="wmt-chat">
<?php foreach ( $messages as $msg ) :
if( $msg->sender_type === 'system' ): ?>
<div class="wmt-system-msg"><?php echo esc_html($msg->message); ?></div>
<?php continue; endif;
$is_admin = ( $msg->sender_type === 'admin' );
if(empty($msg->message) && empty($msg->file_url)) continue;
?>
<div class="wmt-bubble wmt-<?php echo $is_admin ? 'left' : 'right'; ?>">
<strong><?php echo esc_html( $msg->sender_name ); ?></strong> <small><?php echo $msg->created_at; ?></small>
<p><?php echo nl2br( esc_html( $msg->message ) ); ?></p>
<?php if ( $msg->file_url ) : ?>
<a href="<?php echo esc_url( $msg->file_url ); ?>" target="_blank" style="display:inline-block; margin-top:10px; text-decoration:none; background:#e9ecef; padding:5px 10px; border-radius:4px; border:1px solid #ccc;">Datei ansehen</a>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php if ( ! $is_closed ) : ?>
<form method="post" enctype="multipart/form-data">
<?php wp_nonce_field( 'wmt_guest_reply', 'wmt_nonce' ); ?>
<input type="hidden" name="ticket_id" value="<?php echo $ticket->id; ?>">
<label class="wmt-label">Antwort schreiben</label>
<textarea name="message" class="wmt-textarea" rows="4" required></textarea>
<label class="wmt-label" style="margin-top:10px;">Datei anhängen (Optional)</label>
<input type="file" name="guest_file" class="wmt-input">
<button type="submit" name="wmt_reply" class="wmt-btn">Antwort senden</button>
</form>
<?php else : ?>
<div class="wmt-alert">Ticket geschlossen.</div>
<?php endif; ?>
</div>
<?php
return ob_get_clean();
}
public function handle_guest_creation() {
if ( ! isset( $_POST['wmt_create'] ) || ! wp_verify_nonce( $_POST['wmt_nonce'], 'wmt_guest_create' ) ) return;
$name = sanitize_text_field( $_POST['guest_name'] );
$email = sanitize_email( $_POST['guest_email'] );
$cat = sanitize_text_field( $_POST['category'] );
$prio = sanitize_text_field( $_POST['priority'] );
$title = sanitize_text_field( $_POST['title'] );
$msg = sanitize_textarea_field( $_POST['message'] );
$hash = wp_generate_password( 20, false );
$file_url = $this->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') );
// Admin Benachrichtigung (HTML)
$admin_body = "Ein neues Ticket wurde erstellt.\n\nBetreff: {$title}\nVon: {$name} ({$email})\nPriorität: {$prio}\n\nLink: {$admin_link}";
$this->send_mail( $admin_email, "Neues Ticket #{$tid}: {$title}", $admin_body );
$this->send_discord_notification( "Neues Ticket #{$tid}", "{$title}\nVon: {$name} ({$email})\nPriorität: {$prio}", $admin_link );
$this->send_telegram_notification( "<b>Neues Ticket #{$tid}</b>\n<b>Betreff:</b> {$title}\n<b>Von:</b> {$name} ({$email})\n<b>Priorität:</b> {$prio}\nLink: {$admin_link}" );
$notify_user_ids = get_option( 'wmt_new_ticket_notify_users', 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}";
// HTML Benachrichtigung an zusätzliche User
$this->send_mail( $user->user_email, $subject, $body );
}
}
}
// Gast Bestätigung (HTML)
$guest_body = "Hallo {$name},\n\nVielen Dank für Ihr Ticket #{$tid}.\nWir werden uns so schnell wie möglich bei Ihnen melden.\n\nLink zum Ticket:\n{$guest_link}";
$this->send_mail( $email, "Ihr Ticket #{$tid} wurde erstellt", $guest_body );
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') );
// Admin Benachrichtigung über Gast Antwort (HTML)
$admin_body = "Der Kunde {$ticket->guest_name} hat auf Ticket #{$tid} geantwortet.\n\nLink: {$admin_link}";
$this->send_mail( $admin_email, "Neue Antwort zu Ticket #{$tid}", $admin_body );
$this->send_discord_notification( "Neue Antwort in Ticket #{$tid}", "Kunde {$ticket->guest_name} hat geantwortet.", $admin_link );
$this->send_telegram_notification( "<b>Neue Antwort in Ticket #{$tid}</b>\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";
}
// HTML E-Mail senden
$this->send_mail( $email, "Ihre Tickets", $body );
}
wp_redirect( add_query_arg( 'wmt_lookup_sent', '1', wp_get_referer() ) );
exit;
}
}
new WP_Multi_Ticket_Pro();