= 1 && class_exists( 'ADVK_Winners' ) ) { if ( $flag ) { ADVK_Winners::set_prize_for_winner( $entry_day, $entry, $prize ); } else { ADVK_Winners::clear_prize_for_winner( $entry_day, $entry ); } } } $day = isset( $_POST['advk_day'] ) ? (int) $_POST['advk_day'] : 0; $paged = isset( $_POST['paged'] ) ? max( 1, (int) $_POST['paged'] ) : 1; $url = add_query_arg( array( 'page' => 'advk-entries', 'advk_day' => $day, 'paged' => $paged, 'advk_notice' => $flag ? 'won' : 'unwon', ), admin_url( 'admin.php' ) ); wp_safe_redirect( $url ); exit; } /** * Manually create a participation entry from the admin * (admin-post.php?action=advk_add_entry). */ public function add_entry() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Keine Berechtigung.', 'adventura-interactive-advent-calendar' ) ); } check_admin_referer( 'advk_add_entry' ); $opts = ADVK_Settings::get_options(); $day = isset( $_POST['advk_new_day'] ) ? (int) $_POST['advk_new_day'] : 0; $first = sanitize_text_field( wp_unslash( $_POST['advk_new_first'] ?? '' ) ); $last = sanitize_text_field( wp_unslash( $_POST['advk_new_last'] ?? '' ) ); $email = sanitize_email( wp_unslash( $_POST['advk_new_email'] ?? '' ) ); $consent = ! empty( $_POST['advk_new_consent'] ); $send_mail = ! empty( $_POST['advk_new_sendmail'] ); $amount = isset( $_POST['advk_new_donation'] ) ? (float) str_replace( ',', '.', (string) wp_unslash( $_POST['advk_new_donation'] ) ) : 0; $notice = 'added'; if ( $day < 1 || $day > ADVK_Data::door_count() || '' === $first || ! is_email( $email ) ) { $notice = 'add_error'; } elseif ( ! empty( $opts['prevent_duplicates'] ) && ADVK_Entries::email_exists_for_day( $email, $day ) ) { $notice = 'add_dupe'; } if ( 'added' === $notice ) { $losnr = 'ADV-' . str_pad( (string) $day, 2, '0', STR_PAD_LEFT ) . '-' . wp_rand( 1000, 9999 ); $ref_code = ! empty( $opts['referral_enabled'] ) ? ADVK_Entries::generate_ref_code() : ''; $id = ADVK_Entries::insert( array( 'day' => $day, 'first_name' => $first, 'last_name' => $last, 'email' => $email, 'consent' => $consent, 'losnr' => $losnr, 'answers' => array(), 'social' => array(), 'correct_count' => 0, 'total_questions' => 0, 'ref_code' => $ref_code, 'referred_by' => '', 'ip' => '', 'consent_ref' => __( 'Manuell im Admin angelegt', 'adventura-interactive-advent-calendar' ), ) ); if ( $id ) { if ( $amount > 0 ) { ADVK_Entries::set_donation( $id, $amount ); } if ( $send_mail ) { // Fire the standard hook so the participant confirmation (and any // admin notification) is sent. No newsletter/reminder opt-in here. $extra = array( 'first' => $first, 'last' => $last, 'newsletter' => false, 'reminder' => false, 'ref' => '', ); do_action( 'advk_entry_created', $id, $day, $email, $losnr, $extra ); } } else { $notice = 'add_error'; } } $url = add_query_arg( array( 'page' => 'advk-entries', 'advk_day' => ( 'added' === $notice ) ? $day : 0, 'advk_notice' => $notice, ), admin_url( 'admin.php' ) ); wp_safe_redirect( $url ); exit; } /** * Record a donation amount for an entry (admin-post.php?action=advk_save_donation). */ public function save_donation() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Keine Berechtigung.', 'adventura-interactive-advent-calendar' ) ); } $entry = isset( $_POST['entry'] ) ? (int) $_POST['entry'] : 0; check_admin_referer( 'advk_save_donation_' . $entry ); $amount = isset( $_POST['amount'] ) ? (float) str_replace( ',', '.', (string) wp_unslash( $_POST['amount'] ) ) : 0; if ( $entry ) { ADVK_Entries::set_donation( $entry, $amount ); } $day = isset( $_POST['advk_day'] ) ? (int) $_POST['advk_day'] : 0; $paged = isset( $_POST['paged'] ) ? max( 1, (int) $_POST['paged'] ) : 1; $url = add_query_arg( array( 'page' => 'advk-entries', 'advk_day' => $day, 'paged' => $paged ), admin_url( 'admin.php' ) ); wp_safe_redirect( $url ); exit; } /** * Handle deletion of a single entry (admin-post.php?action=advk_delete_entry). */ public function delete_entry() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Keine Berechtigung.', 'adventura-interactive-advent-calendar' ) ); } $id = isset( $_GET['entry'] ) ? (int) $_GET['entry'] : 0; check_admin_referer( 'advk_delete_entry_' . $id ); $deleted = $id > 0 ? ADVK_Entries::delete( $id ) : false; $redirect = add_query_arg( array( 'page' => 'advk-entries', 'advk_day' => isset( $_GET['advk_day'] ) ? (int) $_GET['advk_day'] : 0, 'paged' => isset( $_GET['paged'] ) ? max( 1, (int) $_GET['paged'] ) : 1, 'advk_notice' => $deleted ? 'deleted' : 'delete_failed', ), admin_url( 'admin.php' ) ); wp_safe_redirect( $redirect ); exit; } public function add_menu() { add_submenu_page( ADVK_Dashboard::SLUG, __( 'Teilnahmen', 'adventura-interactive-advent-calendar' ), __( 'Teilnahmen', 'adventura-interactive-advent-calendar' ), 'manage_options', 'advk-entries', array( $this, 'render_page' ) ); } /** * Map of day => quiz (with correct indices + option text). Cached per request. * * @return array */ private function quiz_map() { static $map = null; if ( null !== $map ) { return $map; } $map = array(); foreach ( ADVK_Data::get_calendar() as $d ) { $map[ (int) $d['day'] ] = isset( $d['quiz'] ) ? (array) $d['quiz'] : array(); } return $map; } /** * Map of day => list of prize titles (for the manual winner prize picker). * Cached per request. * * @return array */ private function prizes_map() { static $map = null; if ( null !== $map ) { return $map; } $map = array(); foreach ( ADVK_Data::get_calendar() as $d ) { $titles = array(); if ( ! empty( $d['prizes'] ) && is_array( $d['prizes'] ) ) { foreach ( $d['prizes'] as $p ) { $t = isset( $p['title'] ) ? trim( (string) $p['title'] ) : ''; if ( '' !== $t ) { $titles[] = $t; } } } $map[ (int) $d['day'] ] = $titles; } return $map; } public function render_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $opts = ADVK_Settings::get_options(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only list filter via bookmarkable GET link; page is capability-guarded above. $day_filter = isset( $_GET['advk_day'] ) ? (int) $_GET['advk_day'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only pagination via GET; page is capability-guarded above. $paged = isset( $_GET['paged'] ) ? max( 1, (int) $_GET['paged'] ) : 1; $per_page = 25; $offset = ( $paged - 1 ) * $per_page; $total = ADVK_Entries::count( $day_filter ); $entries = ADVK_Entries::query( array( 'per_page' => $per_page, 'offset' => $offset, 'day' => $day_filter, ) ); $total_pages = max( 1, (int) ceil( $total / $per_page ) ); $quiz_map = $this->quiz_map(); $prizes_map = $this->prizes_map(); $export_url = wp_nonce_url( admin_url( 'admin-post.php?action=advk_export&advk_day=' . $day_filter ), 'advk_export' ); ?>


>
answers, true ); $answers = is_array( $answers ) ? $answers : array(); $social = json_decode( (string) $e->social, true ); $social = is_array( $social ) ? $social : array(); $quiz = isset( $quiz_map[ (int) $e->day ] ) ? $quiz_map[ (int) $e->day ] : array(); ?>
created_at ) ); ?> day; ?> first_name . ' ' . $e->last_name ) ); ?> email ); ?> correct_count === (int) $e->total_questions && (int) $e->total_questions > 0 ); ?> correct_count; ?>/total_questions; ?> render_answers( $quiz, $answers ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> referred_by ) ? (string) $e->referred_by : ''; $my_code = isset( $e->ref_code ) ? (string) $e->ref_code : ''; if ( '' !== $ref_by ) : $ref_name = ADVK_Entries::name_for_ref_code( $ref_by ); ?>
—
losnr ); ?>
id ); ?> day ] ) ? $prizes_map[ (int) $e->day ] : array(); ?> is_winner === 1 ) : ?> day, (int) $e->id ); ?>

id ); $direct_min = (int) $opts['donation_direct_min']; ?>
id ); ?>
0 ) : ?>
0 && $donation >= $direct_min ) : ?>
'advk_delete_entry', 'entry' => (int) $e->id, 'advk_day' => $day_filter, 'paged' => $paged, ), admin_url( 'admin-post.php' ) ), 'advk_delete_entry_' . (int) $e->id ); ?>
1 ) : ?>
add_query_arg( 'paged', '%#%' ), 'format' => '', 'current' => $paged, 'total' => $total_pages, 'prev_text' => '‹', 'next_text' => '›', ) ) ); ?>
selectedIndex. * @return string */ private function render_answers( $quiz, $answers ) { if ( empty( $quiz ) ) { return '—'; } $rows = array(); foreach ( $quiz as $qi => $q ) { $sel = isset( $answers[ $qi ] ) ? (int) $answers[ $qi ] : null; $options = isset( $q['options'] ) ? (array) $q['options'] : array(); $correct = isset( $q['correct'] ) ? (int) $q['correct'] : -1; if ( null === $sel || ! isset( $options[ $sel ] ) ) { /* translators: %d: question number (1, 2, 3 …). */ $rows[] = '
' . sprintf( esc_html__( 'F%d: keine Antwort', 'adventura-interactive-advent-calendar' ), $qi + 1 ) . '
'; continue; } $is_right = ( $sel === $correct ); $mark = $is_right ? '✓' : '✗'; /* translators: %d: question number (1, 2, 3 …). */ $label = sprintf( esc_html__( 'F%d:', 'adventura-interactive-advent-calendar' ), $qi + 1 ); $rows[] = '
' . $mark . ' ' . $label . ' ' . esc_html( $options[ $sel ] ) . '
'; } return implode( '', $rows ); } /** * Build a single RFC-4180 CSV line (fields quoted, inner quotes doubled). * * @param array $fields Field values. * @return string */ private function csv_row( $fields ) { $escaped = array(); foreach ( $fields as $f ) { $escaped[] = '"' . str_replace( '"', '""', (string) $f ) . '"'; } return implode( ',', $escaped ) . "\r\n"; } public function export_csv() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Keine Berechtigung.', 'adventura-interactive-advent-calendar' ) ); } check_admin_referer( 'advk_export' ); $day_filter = isset( $_GET['advk_day'] ) ? (int) $_GET['advk_day'] : 0; $quiz_map = $this->quiz_map(); $entries = ADVK_Entries::query( array( 'per_page' => 100000, 'offset' => 0, 'day' => $day_filter, ) ); $filename = 'adventura-teilnahmen' . ( $day_filter ? '-tag-' . $day_filter : '' ) . '-' . wp_date( 'Y-m-d' ) . '.csv'; nocache_headers(); header( 'Content-Type: text/csv; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); // Build the CSV in memory (no direct filesystem handles) and stream it. // UTF-8 BOM so Excel reads umlauts correctly. $csv = chr( 0xEF ) . chr( 0xBB ) . chr( 0xBF ); $csv .= $this->csv_row( array( 'Datum', 'Tag', 'Vorname', 'Nachname', 'E-Mail', 'Einwilligung', 'IP', 'Einwilligung Version', 'Spende (€)', 'Geworben von', 'Eigener Ref-Code', 'Einladungen', 'Los-Nr.', 'Richtig', 'Fragen gesamt', 'Gewinner', 'Antworten', 'Social' ) ); foreach ( $entries as $e ) { $answers = json_decode( (string) $e->answers, true ); $answers = is_array( $answers ) ? $answers : array(); $social = json_decode( (string) $e->social, true ); $social = is_array( $social ) ? $social : array(); $quiz = isset( $quiz_map[ (int) $e->day ] ) ? $quiz_map[ (int) $e->day ] : array(); $answer_parts = array(); foreach ( $quiz as $qi => $q ) { $sel = isset( $answers[ $qi ] ) ? (int) $answers[ $qi ] : null; $options = isset( $q['options'] ) ? (array) $q['options'] : array(); $correct = isset( $q['correct'] ) ? (int) $q['correct'] : -1; if ( null === $sel || ! isset( $options[ $sel ] ) ) { $answer_parts[] = 'F' . ( $qi + 1 ) . ': -'; continue; } $answer_parts[] = 'F' . ( $qi + 1 ) . ': ' . $options[ $sel ] . ( $sel === $correct ? ' (richtig)' : ' (falsch)' ); } $csv .= $this->csv_row( array( mysql2date( 'd.m.Y H:i', $e->created_at ), (int) $e->day, $e->first_name, $e->last_name, $e->email, $e->consent ? 'ja' : 'nein', isset( $e->ip ) ? (string) $e->ip : '', isset( $e->consent_ref ) ? (string) $e->consent_ref : '', ( ADVK_Entries::donation_for( (int) $e->id ) > 0 ) ? number_format_i18n( ADVK_Entries::donation_for( (int) $e->id ), 2 ) : '', isset( $e->referred_by ) ? (string) $e->referred_by : '', isset( $e->ref_code ) ? (string) $e->ref_code : '', isset( $e->ref_code ) && '' !== (string) $e->ref_code ? (int) ADVK_Entries::referral_count( $e->ref_code ) : 0, $e->losnr, (int) $e->correct_count, (int) $e->total_questions, ( (int) $e->is_winner === 1 ) ? 'ja' : 'nein', implode( ' | ', $answer_parts ), implode( ', ', $social ), ) ); } echo $csv; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CSV file download, not HTML; values are quote-escaped in csv_row(). exit; } }