tickets = $tickets; $this->messages = $messages; $this->attachments = $attachments; $this->activity = $activity; } public function init() { add_action( 'admin_menu', array( $this, 'register_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'admin_post_dpsupport_create_ticket', array( $this, 'handle_create_ticket' ) ); add_action( 'admin_post_dpsupport_add_message', array( $this, 'handle_add_message' ) ); add_action( 'admin_post_dpsupport_update_ticket', array( $this, 'handle_update_ticket' ) ); add_action( 'admin_post_dpsupport_archive_ticket', array( $this, 'handle_archive_ticket' ) ); add_action( 'admin_post_dpsupport_restore_ticket', array( $this, 'handle_restore_ticket' ) ); add_action( 'admin_post_dpsupport_delete_ticket', array( $this, 'handle_delete_ticket' ) ); add_action( 'admin_post_dpsupport_save_settings', array( $this, 'handle_save_settings' ) ); add_action( 'admin_post_dpsupport_send_test_email', array( $this, 'handle_send_test_email' ) ); add_action( 'admin_post_dpsupport_create_support_page', array( $this, 'handle_create_support_page' ) ); add_action( 'admin_post_dpsupport_resend_guest_link', array( $this, 'handle_resend_guest_link' ) ); } public function register_menu() { add_menu_page( __( '24laser Support Tickets', '24laser-support-tickets' ), __( 'Support', '24laser-support-tickets' ), 'dpsupport_view_support', 'dpsupport-support', array( $this, 'render_tickets_page' ), 'dashicons-sos', 56 ); add_submenu_page( 'dpsupport-support', __( 'Tickets', '24laser-support-tickets' ), __( 'Tickets', '24laser-support-tickets' ), 'dpsupport_view_support', 'dpsupport-support', array( $this, 'render_tickets_page' ) ); add_submenu_page( 'dpsupport-support', __( 'Neues Ticket', '24laser-support-tickets' ), __( 'Neues Ticket', '24laser-support-tickets' ), 'dpsupport_manage_support', 'dpsupport-support-new', array( $this, 'render_new_ticket_page' ) ); add_submenu_page( 'dpsupport-support', __( 'Statistiken', '24laser-support-tickets' ), __( 'Statistiken', '24laser-support-tickets' ), 'dpsupport_view_support', 'dpsupport-support-statistics', array( $this, 'render_statistics_page' ) ); add_submenu_page( 'dpsupport-support', __( 'Einstellungen', '24laser-support-tickets' ), __( 'Einstellungen', '24laser-support-tickets' ), 'dpsupport_manage_settings', 'dpsupport-support-settings', array( $this, 'render_settings_page' ) ); } public function enqueue_assets( $hook ) { if ( false === strpos( (string) $hook, 'dpsupport-support' ) ) { return; } wp_enqueue_style( 'dpsupport-admin', DPSUPPORT_URL . 'assets/css/admin.css', array(), DPSUPPORT_VERSION ); wp_enqueue_script( 'dpsupport-admin', DPSUPPORT_URL . 'assets/js/admin.js', array(), DPSUPPORT_VERSION, true ); } private function guard( $capability = 'dpsupport_view_support' ) { if ( ! current_user_can( $capability ) ) { wp_die( esc_html__( 'Du hast keine Berechtigung für diesen Bereich.', '24laser-support-tickets' ) ); } } public function render_tickets_page() { $this->guard(); $this->render_notice(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only ticket navigation; capability checks are enforced before rendering. $ticket_id = isset( $_GET['ticket_id'] ) ? absint( $_GET['ticket_id'] ) : 0; if ( $ticket_id ) { $this->render_ticket_detail( $ticket_id ); return; } // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only list filters/pagination; these values never mutate state. $status = isset( $_GET['status'] ) ? sanitize_key( wp_unslash( $_GET['status'] ) ) : ''; $priority = isset( $_GET['priority'] ) ? sanitize_key( wp_unslash( $_GET['priority'] ) ) : ''; $category = isset( $_GET['category'] ) ? sanitize_key( wp_unslash( $_GET['category'] ) ) : ''; $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; $archive = isset( $_GET['archive'] ) && 'archived' === sanitize_key( wp_unslash( $_GET['archive'] ) ) ? 'archived' : 'active'; $paged = max( 1, isset( $_GET['paged'] ) ? absint( $_GET['paged'] ) : 1 ); // phpcs:enable WordPress.Security.NonceVerification.Recommended $per_page = 20; $args = compact( 'status', 'priority', 'category', 'search', 'archive', 'paged', 'per_page' ); $tickets = $this->tickets->query( $args ); $total = $this->tickets->count( $args ); $statuses = $this->tickets->statuses(); $priorities = $this->tickets->priorities(); $categories = $this->tickets->categories(); $sources = $this->activity->creation_sources( wp_list_pluck( $tickets, 'id' ) ); ?>

id ] ?? 'unknown'; ?>
subject ); ?>ticket_number ); ?> · source_label( $source ) ); ?>customer_name ?: '—' ); ?>
customer_email ?: '—' ); ?>
status ] ?? $ticket->status ); ?>priority ] ?? $ticket->priority ); ?>updated_at ) ); ?>
1 ) { echo '
' . wp_kses_post( paginate_links( array( 'base' => add_query_arg( 'paged', '%#%' ), 'format' => '', 'current' => $paged, 'total' => $total_pages, 'type' => 'plain' ) ) ) . '
'; } ?>
guard( 'dpsupport_manage_support' ); $this->render_notice(); $categories = $this->tickets->categories(); $priorities = $this->tickets->priorities(); ?>

tickets->get( $ticket_id ); if ( ! $ticket ) { wp_die( esc_html__( 'Ticket wurde nicht gefunden.', '24laser-support-tickets' ) ); } $messages = $this->messages->get_for_ticket( $ticket_id ); $activity = $this->activity->get_for_ticket( $ticket_id ); $statuses = $this->tickets->statuses(); $priorities = $this->tickets->priorities(); $categories = $this->tickets->categories(); $source = $this->activity->creation_source( $ticket_id ); $attachment_map = array(); foreach ( (array) $this->attachments->get_for_ticket( $ticket_id ) as $attachment ) { $attachment_map[ absint( $attachment->message_id ) ][] = absint( $attachment->attachment_id ); } $guest_url = empty( $ticket->customer_id ) && 'frontend_guest' === $source ? DPSUPPORT_Guest_Ticket_Access::ticket_url( $ticket ) : ''; ?>
←

subject ); ?>

ticket_number ); ?> · source_label( $source ) ); ?>

archived_at ) ) : ?>
customer_name ?: '—' ); ?>customer_email ?: '—' ); ?>
status ] ?? $ticket->status ); ?>
priority ] ?? $ticket->priority ); ?>
category ] ?? $ticket->category ); ?>

is_internal ) ? 'internal' : $message->author_type; ?>
author_name ?: ( 'customer' === $message->author_type ? __( 'Kunde', '24laser-support-tickets' ) : __( 'Support', '24laser-support-tickets' ) ) ); ?>created_at ) ); ?>is_internal ) ) : ?> ·
body ) ); ?>
id ] ) ) : ?>
id ] as $attachment_id ) : $url = wp_get_attachment_url( $attachment_id ); if ( ! $url ) { continue; } ?>
archived_at ) && 'closed' !== $ticket->status ) : ?>

guard(); $stats = ( new DPSUPPORT_Statistics() )->overview( 30 ); ?>

guard( 'dpsupport_manage_settings' ); $this->render_notice(); $categories = $this->tickets->categories(); $category_lines = array(); foreach ( $categories as $key => $label ) { $category_lines[] = $key . ' | ' . $label; } $page_id = absint( get_option( DPSUPPORT_Guest_Ticket_Access::OPTION_PAGE_ID, 0 ) ); $page_url = $page_id ? get_permalink( $page_id ) : ''; ?>

[dpsupport_form]

guard( 'dpsupport_manage_support' ); check_admin_referer( 'dpsupport_create_ticket' ); $subject = sanitize_text_field( wp_unslash( $_POST['subject'] ?? '' ) ); $message = wp_kses_post( wp_unslash( $_POST['message'] ?? '' ) ); if ( '' === trim( $subject ) || '' === trim( wp_strip_all_tags( $message ) ) ) { $this->redirect_new( 'error', __( 'Betreff und Nachricht sind erforderlich.', '24laser-support-tickets' ) ); } $ticket_id = $this->tickets->create( array( 'customer_name' => sanitize_text_field( wp_unslash( $_POST['customer_name'] ?? '' ) ), 'customer_email' => sanitize_email( wp_unslash( $_POST['customer_email'] ?? '' ) ), 'subject' => $subject, 'category' => sanitize_key( wp_unslash( $_POST['category'] ?? '' ) ), 'status' => 'new', 'priority' => sanitize_key( wp_unslash( $_POST['priority'] ?? 'normal' ) ) ) ); if ( is_wp_error( $ticket_id ) ) { $this->redirect_new( 'error', $ticket_id->get_error_message() ); } $message_id = $this->messages->create( $ticket_id, array( 'author_type' => 'staff', 'author_user_id' => get_current_user_id(), 'body' => $message, 'is_internal' => 0 ) ); if ( is_wp_error( $message_id ) ) { $this->tickets->delete_permanently( $ticket_id ); $this->redirect_new( 'error', $message_id->get_error_message() ); } $this->activity->add( $ticket_id, 'ticket_created', __( 'Ticket wurde im Backend erstellt.', '24laser-support-tickets' ) ); $this->handle_upload( $ticket_id, $message_id ); DPSUPPORT_Notifications::notify_customer_ticket_created( $ticket_id, $message_id, false ); do_action( 'dpsupport_ticket_created', $ticket_id, 'backend' ); $this->redirect_ticket( $ticket_id, 'success', __( 'Ticket wurde angelegt.', '24laser-support-tickets' ) ); } public function handle_add_message() { $this->guard( 'dpsupport_reply_support' ); $ticket_id = absint( $_POST['ticket_id'] ?? 0 ); check_admin_referer( 'dpsupport_add_message_' . $ticket_id ); $ticket = $this->tickets->get( $ticket_id ); if ( ! $ticket ) { $this->redirect_list( 'error', __( 'Ticket wurde nicht gefunden.', '24laser-support-tickets' ) ); } $body = wp_kses_post( wp_unslash( $_POST['message'] ?? '' ) ); if ( '' === trim( wp_strip_all_tags( $body ) ) ) { $this->redirect_ticket( $ticket_id, 'error', __( 'Bitte gib eine Nachricht ein.', '24laser-support-tickets' ) ); } $is_internal = ! empty( $_POST['is_internal'] ); $body = DPSUPPORT_Workflow::prepare_staff_message( $body, $ticket, $is_internal, get_current_user_id() ); $message_id = $this->messages->create( $ticket_id, array( 'author_type' => 'staff', 'author_user_id' => get_current_user_id(), 'body' => $body, 'is_internal' => $is_internal ? 1 : 0 ) ); if ( is_wp_error( $message_id ) ) { $this->redirect_ticket( $ticket_id, 'error', $message_id->get_error_message() ); } $this->tickets->touch( $ticket_id ); $this->activity->add( $ticket_id, $is_internal ? 'internal_note' : 'staff_reply', $is_internal ? __( 'Interne Notiz hinzugefügt.', '24laser-support-tickets' ) : __( 'Support hat geantwortet.', '24laser-support-tickets' ) ); $this->handle_upload( $ticket_id, $message_id ); if ( ! $is_internal ) { DPSUPPORT_Notifications::notify_customer_staff_reply( $ticket_id, $message_id ); do_action( 'dpsupport_staff_replied', $ticket_id, $message_id ); } $this->redirect_ticket( $ticket_id, 'success', __( 'Nachricht wurde gespeichert.', '24laser-support-tickets' ) ); } public function handle_update_ticket() { $this->guard( 'dpsupport_manage_support' ); $ticket_id = absint( $_POST['ticket_id'] ?? 0 ); check_admin_referer( 'dpsupport_update_ticket_' . $ticket_id ); $ok = $this->tickets->update( $ticket_id, array( 'customer_name' => sanitize_text_field( wp_unslash( $_POST['customer_name'] ?? '' ) ), 'customer_email' => sanitize_email( wp_unslash( $_POST['customer_email'] ?? '' ) ), 'subject' => sanitize_text_field( wp_unslash( $_POST['subject'] ?? '' ) ), 'status' => sanitize_key( wp_unslash( $_POST['status'] ?? '' ) ), 'priority' => sanitize_key( wp_unslash( $_POST['priority'] ?? '' ) ), 'category' => sanitize_key( wp_unslash( $_POST['category'] ?? '' ) ) ) ); if ( $ok ) { $this->activity->add( $ticket_id, 'ticket_updated', __( 'Ticketdaten wurden aktualisiert.', '24laser-support-tickets' ) ); } $this->redirect_ticket( $ticket_id, $ok ? 'success' : 'error', $ok ? __( 'Ticket wurde gespeichert.', '24laser-support-tickets' ) : __( 'Ticket konnte nicht gespeichert werden.', '24laser-support-tickets' ) ); } public function handle_archive_ticket() { $this->guard( 'dpsupport_manage_support' ); $ticket_id = absint( $_POST['ticket_id'] ?? 0 ); check_admin_referer( 'dpsupport_archive_ticket_' . $ticket_id ); $this->tickets->archive( $ticket_id ); $this->activity->add( $ticket_id, 'archived', __( 'Ticket wurde archiviert.', '24laser-support-tickets' ) ); $this->redirect_list( 'success', __( 'Ticket wurde archiviert.', '24laser-support-tickets' ) ); } public function handle_restore_ticket() { $this->guard( 'dpsupport_manage_support' ); $ticket_id = absint( $_POST['ticket_id'] ?? 0 ); check_admin_referer( 'dpsupport_restore_ticket_' . $ticket_id ); $this->tickets->restore( $ticket_id ); $this->activity->add( $ticket_id, 'restored', __( 'Ticket wurde wiederhergestellt.', '24laser-support-tickets' ) ); $this->redirect_ticket( $ticket_id, 'success', __( 'Ticket wurde wiederhergestellt.', '24laser-support-tickets' ) ); } public function handle_delete_ticket() { $this->guard( 'dpsupport_delete_tickets' ); $ticket_id = absint( $_POST['ticket_id'] ?? 0 ); check_admin_referer( 'dpsupport_delete_ticket_' . $ticket_id ); foreach ( $this->attachments->get_attachment_ids_for_ticket( $ticket_id ) as $attachment_id ) { wp_delete_attachment( $attachment_id, true ); } $this->tickets->delete_permanently( $ticket_id ); $this->redirect_list( 'success', __( 'Ticket wurde dauerhaft gelöscht.', '24laser-support-tickets' ), 'archived' ); } public function handle_save_settings() { $this->guard( 'dpsupport_manage_settings' ); check_admin_referer( 'dpsupport_save_settings' ); update_option( 'dpsupport_notification_email', sanitize_text_field( wp_unslash( $_POST['notification_email'] ?? '' ) ) ); update_option( 'dpsupport_support_from_name', sanitize_text_field( wp_unslash( $_POST['from_name'] ?? '' ) ) ); update_option( 'dpsupport_support_from_email', sanitize_email( wp_unslash( $_POST['from_email'] ?? '' ) ) ); update_option( 'dpsupport_support_reply_to_email', sanitize_email( wp_unslash( $_POST['reply_to_email'] ?? '' ) ) ); update_option( 'dpsupport_notify_staff_new_ticket', ! empty( $_POST['notify_staff_new'] ) ? 'yes' : 'no' ); update_option( 'dpsupport_notify_staff_customer_reply', ! empty( $_POST['notify_staff_reply'] ) ? 'yes' : 'no' ); update_option( 'dpsupport_notify_customer_ticket_created', ! empty( $_POST['notify_customer_created'] ) ? 'yes' : 'no' ); update_option( 'dpsupport_notify_customer_staff_reply', ! empty( $_POST['notify_customer_reply'] ) ? 'yes' : 'no' ); update_option( 'dpsupport_default_status', $this->tickets->sanitize_status( sanitize_key( wp_unslash( $_POST['default_status'] ?? 'new' ) ) ) ); update_option( 'dpsupport_default_priority', $this->tickets->sanitize_priority( sanitize_key( wp_unslash( $_POST['default_priority'] ?? 'normal' ) ) ) ); $categories = array(); $raw_lines = preg_split( '/\r\n|\r|\n/', sanitize_textarea_field( wp_unslash( $_POST['categories'] ?? '' ) ) ); foreach ( (array) $raw_lines as $line ) { $parts = array_map( 'trim', explode( '|', $line, 2 ) ); $key = sanitize_key( $parts[0] ?? '' ); $label = sanitize_text_field( $parts[1] ?? '' ); if ( $key && $label ) { $categories[ $key ] = $label; } } if ( ! $categories ) { $categories = array( 'allgemein' => __( 'Allgemein', '24laser-support-tickets' ), 'sonstiges' => __( 'Sonstiges', '24laser-support-tickets' ) ); } update_option( 'dpsupport_categories', $categories ); $default_category = sanitize_key( wp_unslash( $_POST['default_category'] ?? 'allgemein' ) ); update_option( 'dpsupport_default_category', isset( $categories[ $default_category ] ) ? $default_category : array_key_first( $categories ) ); update_option( 'dpsupport_frontend_form_enabled', ! empty( $_POST['frontend_enabled'] ) ? 'yes' : 'no' ); update_option( 'dpsupport_frontend_form_guests', ! empty( $_POST['frontend_guests'] ) ? 'yes' : 'no' ); update_option( 'dpsupport_frontend_form_title', sanitize_text_field( wp_unslash( $_POST['frontend_title'] ?? '' ) ) ); update_option( 'dpsupport_frontend_form_intro', sanitize_textarea_field( wp_unslash( $_POST['frontend_intro'] ?? '' ) ) ); update_option( 'dpsupport_frontend_form_success', sanitize_text_field( wp_unslash( $_POST['frontend_success'] ?? '' ) ) ); $this->redirect_settings( 'success', __( 'Einstellungen wurden gespeichert.', '24laser-support-tickets' ) ); } public function handle_send_test_email() { $this->guard( 'dpsupport_manage_settings' ); check_admin_referer( 'dpsupport_send_test_email' ); $result = DPSUPPORT_Notifications::send_test_email( sanitize_email( wp_unslash( $_POST['recipient'] ?? '' ) ) ); $this->redirect_settings( ! empty( $result['sent'] ) ? 'success' : 'error', $result['message'] ?? __( 'Test abgeschlossen.', '24laser-support-tickets' ) ); } public function handle_create_support_page() { $this->guard( 'dpsupport_manage_settings' ); check_admin_referer( 'dpsupport_create_support_page' ); $page_id = absint( get_option( DPSUPPORT_Guest_Ticket_Access::OPTION_PAGE_ID, 0 ) ); if ( $page_id && 'trash' !== get_post_status( $page_id ) ) { $this->redirect_settings( 'notice', __( 'Es ist bereits eine Supportseite hinterlegt.', '24laser-support-tickets' ) ); } $page_id = wp_insert_post( array( 'post_title' => __( 'Support', '24laser-support-tickets' ), 'post_content' => '[dpsupport_form]', 'post_status' => 'publish', 'post_type' => 'page' ), true ); if ( is_wp_error( $page_id ) ) { $this->redirect_settings( 'error', $page_id->get_error_message() ); } update_option( DPSUPPORT_Guest_Ticket_Access::OPTION_PAGE_ID, absint( $page_id ) ); DPSUPPORT_Guest_Ticket_Access::remember_current_page( get_permalink( $page_id ) ); $this->redirect_settings( 'success', __( 'Supportseite wurde erstellt.', '24laser-support-tickets' ) ); } public function handle_resend_guest_link() { $this->guard( 'dpsupport_manage_support' ); $ticket_id = absint( $_POST['ticket_id'] ?? 0 ); check_admin_referer( 'dpsupport_resend_guest_link_' . $ticket_id ); $ticket = $this->tickets->get( $ticket_id ); if ( ! $ticket || ! empty( $ticket->customer_id ) || 'frontend_guest' !== $this->activity->creation_source( $ticket_id ) ) { $this->redirect_ticket( $ticket_id, 'error', __( 'Gastzugang ist für dieses Ticket nicht verfügbar.', '24laser-support-tickets' ) ); } $result = DPSUPPORT_Notifications::send_guest_access_link_detailed( $ticket_id ); if ( ! empty( $result['sent'] ) ) { $this->activity->add( $ticket_id, 'guest_access_resent', __( 'Persönlicher Gastzugangslink wurde erneut per E-Mail gesendet.', '24laser-support-tickets' ) ); } $this->redirect_ticket( $ticket_id, ! empty( $result['sent'] ) ? 'success' : 'error', $result['message'] ?? __( 'Versand abgeschlossen.', '24laser-support-tickets' ) ); } private function handle_upload( $ticket_id, $message_id ) { return DPSUPPORT_Upload_Handler::handle( $ticket_id, $message_id, $this->attachments ); } private function source_label( $source ) { $labels = array( 'backend' => __( 'Backend', '24laser-support-tickets' ), 'customer_portal' => __( 'Kundenkonto', '24laser-support-tickets' ), 'frontend_customer' => __( 'Frontend · Kunde', '24laser-support-tickets' ), 'frontend_guest' => __( 'Frontend · Gast', '24laser-support-tickets' ), 'unknown' => __( 'Unbekannt', '24laser-support-tickets' ) ); return $labels[ $source ] ?? $labels['unknown']; } private function render_notice() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only flash-message parameters generated by the plugin after a safe redirect. $type = isset( $_GET['dpsupport_notice_type'] ) ? sanitize_key( wp_unslash( $_GET['dpsupport_notice_type'] ) ) : ''; $text = isset( $_GET['dpsupport_notice'] ) ? sanitize_text_field( wp_unslash( $_GET['dpsupport_notice'] ) ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Recommended if ( ! $text ) { return; } $class = 'error' === $type ? 'notice-error' : ( 'notice' === $type ? 'notice-warning' : 'notice-success' ); echo '

' . esc_html( $text ) . '

'; } private function notice_args( $type, $message ) { return array( 'dpsupport_notice_type' => sanitize_key( $type ), 'dpsupport_notice' => sanitize_text_field( $message ) ); } private function redirect_ticket( $ticket_id, $type, $message ) { wp_safe_redirect( add_query_arg( array_merge( array( 'page' => 'dpsupport-support', 'ticket_id' => absint( $ticket_id ) ), $this->notice_args( $type, $message ) ), admin_url( 'admin.php' ) ) ); exit; } private function redirect_list( $type, $message, $archive = 'active' ) { $args = array_merge( array( 'page' => 'dpsupport-support' ), $this->notice_args( $type, $message ) ); if ( 'archived' === $archive ) { $args['archive'] = 'archived'; } wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) ); exit; } private function redirect_new( $type, $message ) { wp_safe_redirect( add_query_arg( array_merge( array( 'page' => 'dpsupport-support-new' ), $this->notice_args( $type, $message ) ), admin_url( 'admin.php' ) ) ); exit; } private function redirect_settings( $type, $message ) { wp_safe_redirect( add_query_arg( array_merge( array( 'page' => 'dpsupport-support-settings' ), $this->notice_args( $type, $message ) ), admin_url( 'admin.php' ) ) ); exit; } }