Upload folder via GUI - inc

This commit is contained in:
Git Manager GUI
2026-04-13 18:52:46 +02:00
parent 9c47501712
commit 09ac38e9fa
31 changed files with 2058 additions and 0 deletions

348
inc/classes/class-admin.php Normal file
View File

@@ -0,0 +1,348 @@
<?php
if (!defined('ABSPATH')) exit;
class WMF_Admin {
private static $instance=null;
public static function instance() {
if(is_null(self::$instance)){self::$instance=new self();self::$instance->hook();}
return self::$instance;
}
public function hook() {
add_action('admin_menu', array($this,'register_menu'));
add_action('admin_enqueue_scripts', array($this,'enqueue_assets'));
add_action('admin_init', array($this,'handle_actions'));
add_action('admin_notices', array($this,'admin_notices'));
}
public function register_menu() {
add_menu_page('WP Multi Formular','Formulare','manage_options','wp-multi-formular',
array($this,'page_router'),'dashicons-feedback',25);
add_submenu_page('wp-multi-formular','Alle Formulare','Alle Formulare','manage_options','wp-multi-formular',array($this,'page_router'));
add_submenu_page('wp-multi-formular','Neues Formular','+ Neues Formular','manage_options','wmf-new-form',array($this,'page_builder'));
// Neue Einreichungen zaehlen fuer Badge
$new_count = 0;
$forms = get_posts(array('post_type'=>'wmf-form','post_status'=>'publish','numberposts'=>-1,'fields'=>'ids'));
foreach($forms as $fid) $new_count += wmf_count_submissions($fid,'neu');
$badge = $new_count > 0 ? ' <span class="awaiting-mod update-plugins count-'.intval($new_count).'"><span class="pending-count">'.intval($new_count).'</span></span>' : '';
add_submenu_page('wp-multi-formular','Einreichungen','Einreichungen'.$badge,'manage_options','wmf-submissions',array($this,'page_submissions'));
}
public function page_router() {
// Einzelne Einreichung anzeigen
if(!empty($_GET['view_submission']) && !empty($_GET['form_id'])) {
$sid = intval($_GET['view_submission']);
$form_id = intval($_GET['form_id']);
require WMF_TPL.'admin/page-submission-detail.php';
return;
}
// Einreichung löschen
if(!empty($_GET['wmf_action'])&&$_GET['wmf_action']==='delete_submission'&&!empty($_GET['submission_id'])) {
if(wp_verify_nonce($_GET['_wpnonce']??'','wmf_delete_submission')) {
WMF_Submission::delete(intval($_GET['submission_id']));
$this->safe_redirect(add_query_arg('wmf_notice','deleted',admin_url('admin.php?page=wmf-submissions')));
}
}
// CSV Export
if(!empty($_GET['wmf_action'])&&$_GET['wmf_action']==='export_csv'&&!empty($_GET['form_id'])) {
WMF_Submissions_List::maybe_export(intval($_GET['form_id']));
}
// Einreichungen anzeigen
if(!empty($_GET['view_submissions'])) {
$form_id=intval($_GET['view_submissions']);
require WMF_TPL.'admin/page-submissions.php'; return;
}
// Builder (Bearbeiten)
if(!empty($_GET['edit'])) {
require WMF_TPL.'admin/page-builder.php'; return;
}
require WMF_TPL.'admin/page-forms-list.php';
}
public function page_builder() {
require WMF_TPL.'admin/page-builder.php';
}
public function page_submissions() {
// Einzelne Einreichung anzeigen
if(!empty($_GET['view_submission']) && !empty($_GET['form_id'])) {
$sid = intval($_GET['view_submission']);
$form_id = intval($_GET['form_id']);
$form = get_post($form_id);
?>
<div class="wrap wmf-admin-wrap">
<h1 class="wmf-page-title">
Einreichung #<?php echo intval($sid); ?>
<span style="font-size:14px;font-weight:normal;color:#646970;margin-left:8px;">
aus: <?php echo esc_html($form ? $form->post_title : ''); ?>
</span>
</h1>
<?php WMF_Submissions_List::render_single($sid, $form_id); ?>
</div>
<?php
return;
}
// Einreichungen eines Formulars anzeigen
if(!empty($_GET['form_id'])) {
$form_id = intval($_GET['form_id']);
$form = get_post($form_id);
WMF_Submissions_List::maybe_export($form_id);
?>
<div class="wrap wmf-admin-wrap">
<h1 class="wmf-page-title">
Einreichungen:
<span style="color:#646970;"><?php echo esc_html($form ? $form->post_title : ''); ?></span>
<a href="<?php echo esc_url(admin_url('admin.php?page=wmf-submissions')); ?>"
class="page-title-action">&larr; Alle Formulare</a>
</h1>
<?php WMF_Submissions_List::render($form_id); ?>
</div>
<?php
return;
}
// Übersicht aller Formulare mit Einreichungszahlen
?>
<div class="wrap wmf-admin-wrap">
<h1>Einreichungen</h1>
<?php
$forms = get_posts(array(
'post_type' => 'wmf-form',
'post_status' => 'publish',
'numberposts' => -1,
'orderby' => 'date',
'order' => 'DESC',
));
if(empty($forms)): ?>
<div class="wmf-empty-state">
<span class="dashicons dashicons-email-alt wmf-empty-icon"></span>
<h2>Noch keine Formulare vorhanden</h2>
<p>Erstellen Sie zuerst ein Formular und binden Sie es auf einer Seite ein.</p>
<a href="<?php echo esc_url(admin_url('admin.php?page=wmf-new-form')); ?>"
class="button button-primary button-hero">Formular erstellen</a>
</div>
<?php else: ?>
<table class="wp-list-table widefat fixed striped wmf-submissions-overview">
<thead>
<tr>
<th>Formular</th>
<th style="width:100px;text-align:center;">Gesamt</th>
<th style="width:80px;text-align:center;">Neu</th>
<th style="width:100px;text-align:center;">Gelesen</th>
<th style="width:110px;text-align:center;">Archiviert</th>
<th style="width:130px;text-align:center;">Letzte Einreichung</th>
<th style="width:120px;">Aktionen</th>
</tr>
</thead>
<tbody>
<?php foreach($forms as $form):
$total = wmf_count_submissions($form->ID);
$count_new = wmf_count_submissions($form->ID, 'neu');
$count_read = wmf_count_submissions($form->ID, 'gelesen');
$count_arch = wmf_count_submissions($form->ID, 'archiviert');
global $wpdb;
$last = $wpdb->get_var($wpdb->prepare(
"SELECT created_at FROM {$wpdb->prefix}wmf_submissions WHERE form_id=%d ORDER BY created_at DESC LIMIT 1",
$form->ID
));
$view_url = add_query_arg(array('page'=>'wmf-submissions','form_id'=>$form->ID), admin_url('admin.php'));
$export_url = WMF_Submissions_List::export_url($form->ID);
?>
<tr>
<td>
<strong>
<a href="<?php echo esc_url($view_url); ?>">
<?php echo esc_html($form->post_title); ?>
</a>
</strong>
<div style="font-size:11px;color:#646970;margin-top:2px;">
<?php echo esc_html(wmf_get_shortcode($form->ID)); ?>
</div>
</td>
<td style="text-align:center;">
<a href="<?php echo esc_url($view_url); ?>" style="font-size:18px;font-weight:700;color:#2271b1;text-decoration:none;">
<?php echo intval($total); ?>
</a>
</td>
<td style="text-align:center;">
<?php if($count_new > 0): ?>
<a href="<?php echo esc_url(add_query_arg('sub_status','neu',$view_url)); ?>"
style="display:inline-block;background:#2271b1;color:#fff;border-radius:10px;padding:2px 9px;font-size:12px;font-weight:700;text-decoration:none;">
<?php echo intval($count_new); ?>
</a>
<?php else: ?>
<span style="color:#a7aaad;">0</span>
<?php endif; ?>
</td>
<td style="text-align:center;color:#646970;"><?php echo intval($count_read); ?></td>
<td style="text-align:center;color:#a7aaad;"><?php echo intval($count_arch); ?></td>
<td style="text-align:center;font-size:12px;color:#646970;">
<?php echo $last ? esc_html(date_i18n('d.m.Y H:i', strtotime($last))) : '—'; ?>
</td>
<td>
<a href="<?php echo esc_url($view_url); ?>" class="button button-small">Anzeigen</a>
<?php if($total > 0): ?>
<a href="<?php echo esc_url($export_url); ?>" class="button button-small">CSV</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php
}
/**
* Sicherer Redirect: funktioniert auch wenn bereits Output gesendet wurde
* (z.B. durch andere Plugins wie wp-multi-ticket)
*/
private function safe_redirect($url) {
$url = esc_url_raw($url);
if(!headers_sent()) {
wp_redirect($url);
exit;
}
// Fallback: JavaScript-Redirect wenn Header bereits gesendet
echo '<script>window.location.href=' . wp_json_encode($url) . ';</script>';
echo '<noscript><meta http-equiv="refresh" content="0;url=' . esc_attr($url) . '"></noscript>';
exit;
}
public function handle_actions() {
// Globale Einstellungen speichern (via admin-post.php)
add_action('admin_post_wmf_save_global_settings', array($this, 'save_global_settings'));
add_action('admin_post_wmf_update_submission_status', array($this, 'update_submission_status'));
if(empty($_POST['wmf_admin_action'])||!current_user_can('manage_options')) return;
if($_POST['wmf_admin_action']==='save_form') {
check_admin_referer('wmf_save_form'); $this->save_form();
}
if($_POST['wmf_admin_action']==='delete_form'&&!empty($_POST['form_id'])) {
check_admin_referer('wmf_delete_form_'.intval($_POST['form_id']));
wp_delete_post(intval($_POST['form_id']),true);
$this->safe_redirect(add_query_arg('wmf_notice','form_deleted',admin_url('admin.php?page=wp-multi-formular')));
}
}
private function save_form() {
$form_id=intval($_POST['form_id']??0);
$title=sanitize_text_field($_POST['form_title']??'Neues Formular');
$fields_raw=stripslashes($_POST['wmf_form_fields']??'[]');
$fields=json_decode($fields_raw,true);
if(!is_array($fields)) $fields=array();
// Felder bereinigen
$clean_fields=array();
foreach($fields as $f) {
if(empty($f['id'])||empty($f['type'])) continue;
$f['name']=sanitize_key($f['name']??$f['id']);
$f['label']=sanitize_text_field($f['label']??'');
$f['placeholder']=sanitize_text_field($f['placeholder']??'');
$f['description']=sanitize_text_field($f['description']??'');
$f['required']=in_array($f['required']??'0',array('0','1'))?$f['required']:'0';
$f['width']=in_array($f['width']??'full',array('full','half','third'))?$f['width']:'full';
$clean_fields[]=$f;
}
$step_labels=array();
if(!empty($_POST['step_labels'])&&is_array($_POST['step_labels'])) {
$step_labels=array_map('sanitize_text_field',$_POST['step_labels']);
}
$data=array(
'fields' => $clean_fields,
'submit_label' => sanitize_text_field($_POST['submit_label']??'Absenden'),
'success_message' => sanitize_textarea_field($_POST['success_message']??''),
'error_message' => sanitize_textarea_field($_POST['error_message']??''),
'notify_admin' => !empty($_POST['notify_admin'])?'1':'0',
'admin_email' => sanitize_email($_POST['admin_email']??''),
'admin_subject' => sanitize_text_field($_POST['admin_subject']??''),
'admin_reply_to' => !empty($_POST['admin_reply_to'])?'1':'0',
'notify_sender' => !empty($_POST['notify_sender'])?'1':'0',
'sender_subject' => sanitize_text_field($_POST['sender_subject']??''),
'sender_message' => sanitize_textarea_field($_POST['sender_message']??''),
'from_name' => sanitize_text_field($_POST['from_name']??''),
'from_email' => sanitize_email($_POST['from_email']??''),
'save_submissions' => !empty($_POST['save_submissions'])?'1':'0',
'recaptcha_enabled' => !empty($_POST['recaptcha_enabled'])?'1':'0',
'honeypot_enabled' => !empty($_POST['honeypot_enabled'])?'1':'0',
'redirect_url' => esc_url_raw($_POST['redirect_url']??''),
'css_class' => sanitize_html_class($_POST['css_class']??''),
'multi_step' => !empty($_POST['multi_step'])?'1':'0',
'step_labels' => $step_labels,
'show_progress' => !empty($_POST['show_progress'])?'1':'0',
);
if($form_id) {
wp_update_post(array('ID'=>$form_id,'post_title'=>$title,'post_status'=>'publish'));
} else {
$form_id=wp_insert_post(array('post_type'=>'wmf-form','post_title'=>$title,'post_status'=>'publish'));
}
wmf_save_form_meta($form_id,$data);
$this->safe_redirect(add_query_arg(array('page'=>'wp-multi-formular','edit'=>$form_id,'wmf_notice'=>'saved'),admin_url('admin.php')));
}
public function admin_notices() {
$n=$_GET['wmf_notice']??'';
$msgs=array('saved'=>array('success','Formular gespeichert.'),'deleted'=>array('success','Einreichung gelöscht.'),'form_deleted'=>array('success','Formular gelöscht.'),'global_saved'=>array('success','Globale E-Mail-Einstellungen gespeichert.'));
if(isset($msgs[$n])) printf('<div class="notice notice-%s is-dismissible"><p>%s</p></div>',esc_attr($msgs[$n][0]),esc_html($msgs[$n][1]));
}
public function enqueue_assets($hook) {
$page=$_GET['page']??'';
if(!in_array($page,array('wp-multi-formular','wmf-new-form','wmf-integrations'))&&strpos($hook,'wmf')=== false) return;
wp_enqueue_style('wmf-admin',WMF_URL.'assets/css/admin.css',array(),WMF_VERSION);
wp_enqueue_script('wmf-admin',WMF_URL.'assets/js/admin.js',array('jquery','jquery-ui-sortable','jquery-ui-draggable'),WMF_VERSION,true);
wp_enqueue_style('dashicons');
wp_localize_script('wmf-admin','WMF',array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wmf_admin'),
'fields' => $this->fields_for_js(),
'i18n' => array(
'confirm_delete' => 'Feld wirklich löschen?',
'confirm_delete_form' => 'Formular und alle Einreichungen wirklich löschen?',
'no_label' => '(kein Titel)',
'loading' => 'Lade …',
'preview' => 'Vorschau',
'close_preview' => 'Vorschau schließen',
),
));
}
public function update_submission_status() {
if(!wp_verify_nonce($_POST['_wpnonce'] ?? '', 'wmf_update_status') || !current_user_can('manage_options')) wp_die('Fehler.');
$sid = intval($_POST['submission_id'] ?? 0);
$status = sanitize_text_field($_POST['status'] ?? 'neu');
$form_id = intval($_POST['form_id'] ?? 0);
$redirect = esc_url_raw($_POST['redirect_url'] ?? admin_url('admin.php?page=wp-multi-formular'));
if($sid && in_array($status, array('neu','gelesen','archiviert'))) {
WMF_Submission::update_status($sid, $status);
}
$this->safe_redirect($redirect);
}
public function save_global_settings() {
if(!wp_verify_nonce($_POST['wmf_global_nonce'] ?? '', 'wmf_global_settings_save')) {
wp_die('Sicherheitsfehler.');
}
if(!current_user_can('manage_options')) wp_die('Keine Berechtigung.');
$settings = array(
'from_name' => sanitize_text_field($_POST['wmf_from_name'] ?? ''),
'from_email' => sanitize_email( $_POST['wmf_from_email'] ?? ''),
'smtp_enabled' => !empty($_POST['wmf_smtp_enabled']) ? '1' : '0',
'smtp_host' => sanitize_text_field($_POST['wmf_smtp_host'] ?? ''),
'smtp_port' => intval( $_POST['wmf_smtp_port'] ?? 587),
'smtp_enc' => sanitize_text_field($_POST['wmf_smtp_enc'] ?? 'tls'),
'smtp_user' => sanitize_text_field($_POST['wmf_smtp_user'] ?? ''),
'smtp_pass' => $_POST['wmf_smtp_pass'] ?? '', // Passwort nicht strippen
);
update_option('wmf_global_settings', $settings);
$this->safe_redirect(add_query_arg(array('page'=>'wmf-integrations','wmf_notice'=>'global_saved'), admin_url('admin.php')));
}
private function fields_for_js() {
$r=array();
foreach(wmf_get_fields() as $type=>$f) {
$r[]=array('type'=>$type,'label'=>$f->label,'icon'=>$f->icon,'category'=>$f->category,'defaults'=>$f->defaults());
}
return $r;
}
}

View File

@@ -0,0 +1,44 @@
<?php
if (!defined('ABSPATH')) exit;
class WMF_Builder {
private static $instance=null;
public static function instance() {
if(is_null(self::$instance)){self::$instance=new self();self::$instance->hook();}
return self::$instance;
}
public function hook() {
add_action('wp_ajax_wmf_get_field_settings',array($this,'ajax_field_settings'));
add_action('wp_ajax_wmf_preview_form', array($this,'ajax_preview'));
}
public function ajax_field_settings() {
check_ajax_referer('wmf_admin','nonce');
if(!current_user_can('manage_options')) wp_die();
$type=sanitize_text_field($_POST['field_type']??'');
$field=json_decode(stripslashes($_POST['field_data']??'{}'),true);
$obj=wmf_get_field($type);
if(!$obj) wp_send_json_error('Unbekannter Feldtyp: '.$type);
ob_start(); $obj->settings_panel($field); $html=ob_get_clean();
wp_send_json_success(array('html'=>$html));
}
public function ajax_preview() {
check_ajax_referer('wmf_admin','nonce');
if(!current_user_can('manage_options')) wp_die();
$form_id=intval($_POST['form_id']??0);
$fields=json_decode(stripslashes($_POST['fields']??'[]'),true);
$meta=wmf_get_form_meta($form_id);
$meta['fields']=$fields;
ob_start();
echo '<div class="wmf-preview-wrap">';
foreach($fields as $field) {
$obj=wmf_get_field($field['type']??'');
if(!$obj) continue;
echo '<div class="wmf-field-wrap">';
$obj->render($field,'');
echo '</div>';
}
echo '<div class="wmf-submit-wrap"><button type="button" class="wmf-submit-button" disabled>'.esc_html($meta['submit_label']?:'Absenden').'</button></div>';
echo '</div>';
$html=ob_get_clean();
wp_send_json_success(array('html'=>$html));
}
}

View File

@@ -0,0 +1,21 @@
<?php
if (!defined('ABSPATH')) exit;
class WMF_Field_Registry {
private static $instance=null;
private $fields=array();
public static function instance() {
if(is_null(self::$instance)){self::$instance=new self();self::$instance->register_defaults();}
return self::$instance;
}
private function register_defaults() {
foreach(array('WMF_Field_Text','WMF_Field_Email','WMF_Field_Textarea','WMF_Field_Select',
'WMF_Field_Checkbox','WMF_Field_Radio','WMF_Field_Number','WMF_Field_Phone',
'WMF_Field_URL','WMF_Field_Date','WMF_Field_File','WMF_Field_Rating',
'WMF_Field_Range','WMF_Field_GDPR','WMF_Field_Signature',
'WMF_Field_Hidden','WMF_Field_HTML','WMF_Field_Divider') as $cls) {
$f=new $cls(); $this->fields[$f->type]=$f;
}
}
public function get_fields() { return $this->fields; }
public function get_field($type) { return $this->fields[$type]??null; }
}

View File

@@ -0,0 +1,105 @@
<?php
if (!defined('ABSPATH')) exit;
class WMF_Form_Processor {
private static $instance=null;
public static function instance() {
if(is_null(self::$instance)){self::$instance=new self();self::$instance->hook();}
return self::$instance;
}
public function hook() {
add_action('init',array($this,'maybe_process'),20);
}
public function maybe_process() {
if(empty($_POST['wmf_action'])||$_POST['wmf_action']!=='submit') return;
if(empty($_POST['wmf_form_id'])) return;
if(!session_id()) session_start();
$form_id=intval($_POST['wmf_form_id']);
if(!wp_verify_nonce($_POST['wmf_nonce']??'','wmf_submit_'.$form_id)) {
$this->fail($form_id,'Sicherheitsüberprüfung fehlgeschlagen.'); return;
}
$form=get_post($form_id);
if(!$form||$form->post_type!=='wmf-form') return;
$meta = wmf_get_form_meta($form_id);
$fields = $meta['fields']??array();
// Honeypot prüfen
if(!empty($meta['honeypot_enabled'])&&$meta['honeypot_enabled']==='1') {
if(!empty($_POST['wmf_hp_'.md5($form_id)])) { $this->ok($form_id,$meta); return; } // stille Ablehnung
}
// Werte sammeln + validieren
$values=array(); $errors=array();
foreach($fields as $field) {
$type=$field['type']??'';
$obj=wmf_get_field($type);
if(!$obj) continue;
if(in_array($type,array('html','divider'))) continue;
$raw=$_POST['wmf_fields'][$field['id']]??'';
$val=$obj->sanitize($raw,$field);
$valid=$obj->validate($val,$field);
$values[$field['id']]=$val;
if($valid!==true) $errors[$field['id']]=$valid;
}
// Datei-Uploads
$file_values=array();
if(!empty($_FILES['wmf_files'])) {
foreach($fields as $field) {
if(($field['type']??'')!=='file') continue;
$uploaded=WMF_Field_File::handle_upload($field,$form_id);
if(!empty($uploaded)) $file_values[$field['id']]=$uploaded;
// Validierung nochmal mit Upload-Ergebnis
$valid=wmf_get_field('file')->validate('',$field);
if($valid!==true&&!isset($errors[$field['id']])) $errors[$field['id']]=$valid;
}
}
if(!empty($errors)) {
$_SESSION['wmf_values_'.$form_id]=$values;
$_SESSION['wmf_errors_'.$form_id]=$errors;
wp_redirect($this->current_url()); exit;
}
// Datei-URLs in Werte mergen
foreach($file_values as $fid=>$uploads) {
$urls=array_map(fn($u)=>$u['url'],$uploads);
$values[$fid]=implode(', ',$urls);
}
// Speichern
$submission_id=null;
if(!empty($meta['save_submissions'])&&$meta['save_submissions']==='1') {
$submission_id=WMF_Submission::save($form_id,$values);
}
// E-Mails
if(!empty($meta['notify_admin'])&&$meta['notify_admin']==='1') {
WMF_Mailer::notify_admin($form_id,$meta,$fields,$values,$file_values);
}
if(!empty($meta['notify_sender'])&&$meta['notify_sender']==='1') {
WMF_Mailer::notify_sender($form_id,$meta,$fields,$values);
}
do_action('wmf_form_submitted',$form_id,$meta,$fields,$values,$submission_id);
$this->ok($form_id,$meta);
}
private function ok($form_id,$meta) {
if(!empty($meta['redirect_url'])) { wp_redirect(esc_url_raw($meta['redirect_url'])); exit; }
if(!session_id()) session_start();
$_SESSION['wmf_success_'.$form_id]=true;
wp_redirect($this->current_url()); exit;
}
private function fail($form_id,$msg) {
if(!session_id()) session_start();
$_SESSION['wmf_errors_'.$form_id]=array('_global'=>$msg);
wp_redirect($this->current_url()); exit;
}
private function current_url() {
return (is_ssl()?'https':'http').'://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
}
}

View File

@@ -0,0 +1,145 @@
<?php
if (!defined('ABSPATH')) exit;
class WMF_Form_Renderer {
public static function render($form_id,$args=array()) {
$form=get_post($form_id);
if(!$form||$form->post_type!=='wmf-form'||$form->post_status!=='publish')
return '<p class="wmf-error">Formular nicht gefunden.</p>';
if(!session_id()) session_start();
$meta = wmf_get_form_meta($form_id);
$fields = $meta['fields']??array();
$saved_values = $_SESSION['wmf_values_'.$form_id]??array();
$errors = $_SESSION['wmf_errors_'.$form_id]??array();
$success = $_SESSION['wmf_success_'.$form_id]??false;
unset($_SESSION['wmf_values_'.$form_id],$_SESSION['wmf_errors_'.$form_id],$_SESSION['wmf_success_'.$form_id]);
ob_start();
if($success) {
echo '<div class="wmf-notice wmf-notice-success" role="alert">'.esc_html($meta['success_message']).'</div>';
if(empty($args['always_show'])) return ob_get_clean();
}
if(!empty($errors['_global']))
echo '<div class="wmf-notice wmf-notice-error" role="alert">'.esc_html($errors['_global']).'</div>';
$multi_step=!empty($meta['multi_step'])&&$meta['multi_step']==='1';
// Schritte ermitteln
$steps=array();
foreach($fields as $f) { $s=intval($f['step']??0); if(!isset($steps[$s])) $steps[$s]=array(); $steps[$s][]=$f; }
ksort($steps);
$total_steps=count($steps);
if($total_steps<2) $multi_step=false;
$form_cls='wmf-form'.(!empty($meta['css_class'])?' '.esc_attr($meta['css_class']):'');
$field_json=wp_json_encode($fields);
?>
<div class="wmf-form-wrap" id="wmf-wrap-<?php echo intval($form_id); ?>">
<?php if($multi_step&&!empty($meta['show_progress'])&&$meta['show_progress']==='1'): ?>
<div class="wmf-progress-bar" data-steps="<?php echo $total_steps; ?>">
<?php $step_labels=$meta['step_labels']??array();
foreach(array_keys($steps) as $si=>$snum): $lbl=$step_labels[$si]??'Schritt '.($si+1); ?>
<div class="wmf-step-indicator <?php echo $si===0?'active':''; ?>" data-step="<?php echo $si; ?>">
<span class="wmf-step-num"><?php echo $si+1; ?></span>
<span class="wmf-step-lbl"><?php echo esc_html($lbl); ?></span>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<form class="<?php echo $form_cls; ?>"
id="wmf-form-<?php echo intval($form_id); ?>"
method="post"
enctype="multipart/form-data"
novalidate
data-form-id="<?php echo intval($form_id); ?>"
data-multi-step="<?php echo $multi_step?'1':'0'; ?>"
data-total-steps="<?php echo $total_steps; ?>"
data-fields='<?php echo esc_attr($field_json); ?>'>
<?php wp_nonce_field('wmf_submit_'.$form_id,'wmf_nonce'); ?>
<input type="hidden" name="wmf_form_id" value="<?php echo intval($form_id); ?>">
<input type="hidden" name="wmf_action" value="submit">
<?php // Honeypot
if(!empty($meta['honeypot_enabled'])&&$meta['honeypot_enabled']==='1'):
$hp_name='wmf_hp_'.md5($form_id); ?>
<div class="wmf-hp-field" aria-hidden="true" style="position:absolute;left:-9999px;opacity:0;pointer-events:none;">
<label for="<?php echo esc_attr($hp_name); ?>">Bitte leer lassen</label>
<input type="text" id="<?php echo esc_attr($hp_name); ?>" name="<?php echo esc_attr($hp_name); ?>" value="" tabindex="-1" autocomplete="off">
</div>
<?php endif; ?>
<?php if($multi_step): ?>
<?php foreach(array_values($steps) as $si=>$step_fields): ?>
<div class="wmf-step" data-step="<?php echo $si; ?>" <?php echo $si>0?'style="display:none;"':''; ?>>
<div class="wmf-fields">
<?php self::render_fields($step_fields,$errors,$saved_values); ?>
</div>
<div class="wmf-step-nav">
<?php if($si>0): ?><button type="button" class="wmf-prev-step button button-secondary">← Zurück</button><?php endif; ?>
<?php if($si<$total_steps-1): ?>
<button type="button" class="wmf-next-step button button-primary">Weiter →</button>
<?php else: ?>
<?php self::render_submit($meta,$form_id); ?>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="wmf-fields">
<?php self::render_fields($fields,$errors,$saved_values); ?>
</div>
<?php self::render_submit($meta,$form_id); ?>
<?php endif; ?>
</form>
</div>
<?php
return ob_get_clean();
}
private static function render_fields($fields,$errors,$saved_values) {
foreach($fields as $field) {
$obj=wmf_get_field($field['type']??'');
if(!$obj) continue;
$value=$saved_values[$field['id']]??'';
$err=$errors[$field['id']]??'';
$wrap_cls='wmf-field-wrap'.($err?' wmf-has-error':'');
echo '<div class="'.esc_attr($wrap_cls).'">';
$obj->render($field,$value);
if($err) echo '<p class="wmf-field-error">'.esc_html($err).'</p>';
echo '</div>';
}
}
private static function render_submit($meta,$form_id) {
// reCAPTCHA
$integrations=wmf_get_integrations();
if(!empty($meta['recaptcha_enabled'])&&$meta['recaptcha_enabled']==='1') {
$rv3=$integrations->get_service('recaptchav3');
$rv2=$integrations->get_service('recaptcha');
if($rv3&&$rv3->is_connected()) {
$creds=$rv3->get_credentials();
echo '<input type="hidden" name="wmf_recaptcha_token" id="wmf-rctoken-'.intval($form_id).'">';
echo '<script>if(typeof grecaptcha!=="undefined"){grecaptcha.ready(function(){grecaptcha.execute("'.esc_js($creds['site_key']).'",{action:"submit"}).then(function(t){document.getElementById("wmf-rctoken-'.intval($form_id).'").value=t;});});}</script>';
} elseif($rv2&&$rv2->is_connected()) {
$creds=$rv2->get_credentials();
echo '<div class="wmf-recaptcha g-recaptcha" data-sitekey="'.esc_attr($creds['site_key']).'"></div>';
echo '<script async defer src="https://www.google.com/recaptcha/api.js"></script>';
}
}
?>
<div class="wmf-submit-wrap">
<button type="submit" class="wmf-submit-button">
<?php echo esc_html($meta['submit_label']?:'Absenden'); ?>
</button>
<span class="wmf-spinner" aria-hidden="true"></span>
</div>
<?php
}
}

View File

@@ -0,0 +1,102 @@
<?php
if(!defined('ABSPATH')) exit;
class WMF_Mailer {
/**
* Admin-Benachrichtigung
*/
public static function notify_admin($form_id, $meta, $fields, $values, $file_values = array()) {
$global = get_option('wmf_global_settings', array());
$from_name = !empty($meta['from_name']) ? $meta['from_name'] : ($global['from_name'] ?? get_bloginfo('name'));
$from_email = !empty($meta['from_email']) ? $meta['from_email'] : ($global['from_email'] ?? get_option('admin_email'));
$to = !empty($meta['admin_email']) ? $meta['admin_email'] : get_option('admin_email');
$subject = !empty($meta['admin_subject']) ? $meta['admin_subject'] : 'Neue Formulareinreichung';
$headers = array(
'Content-Type: text/html; charset=UTF-8',
'From: ' . $from_name . ' <' . $from_email . '>',
);
// Reply-To auf Absender-E-Mail-Feld setzen
if(!empty($meta['admin_reply_to']) && $meta['admin_reply_to'] === '1') {
foreach($fields as $f) {
if(($f['type'] ?? '') === 'email' && !empty($values[$f['id']]) && is_email($values[$f['id']])) {
$headers[] = 'Reply-To: ' . $values[$f['id']];
break;
}
}
}
wp_mail($to, $subject, self::build_body($fields, $values, $file_values), $headers);
}
/**
* Absender-Bestaetigung
*/
public static function notify_sender($form_id, $meta, $fields, $values) {
$global = get_option('wmf_global_settings', array());
$from_name = !empty($meta['from_name']) ? $meta['from_name'] : ($global['from_name'] ?? get_bloginfo('name'));
$from_email = !empty($meta['from_email']) ? $meta['from_email'] : ($global['from_email'] ?? get_option('admin_email'));
$email = '';
foreach($fields as $f) {
if(($f['type'] ?? '') === 'email' && !empty($values[$f['id']]) && is_email($values[$f['id']])) {
$email = $values[$f['id']];
break;
}
}
if(!$email) return;
$headers = array(
'Content-Type: text/html; charset=UTF-8',
'From: ' . $from_name . ' <' . $from_email . '>',
);
$body = '<html><body style="font-family:sans-serif;color:#1d2327;max-width:600px;margin:0 auto;">'
. wpautop(esc_html($meta['sender_message'] ?? ''))
. '<p style="color:#888;font-size:12px;margin-top:24px;">Gesendet ueber ' . get_bloginfo('name') . '</p>'
. '</body></html>';
wp_mail($email, $meta['sender_subject'] ?? 'Ihre Nachricht wurde empfangen', $body, $headers);
}
/**
* E-Mail-Body aufbauen
*/
private static function build_body($fields, $values, $file_values) {
$rows = '';
foreach($fields as $f) {
if(in_array($f['type'] ?? '', array('html','divider','hidden'))) continue;
$lbl = $f['label'] ?? $f['id'];
$val = $values[$f['id']] ?? '';
if(is_array($val)) $val = implode(', ', $val);
if(($f['type'] ?? '') === 'gdpr')
$val = ($val === '1') ? '&#10003; Zugestimmt' : '&#10007; Nicht zugestimmt';
if(($f['type'] ?? '') === 'signature' && !empty($val))
$val = '<img src="' . esc_attr($val) . '" style="max-width:300px;border:1px solid #ddd;">';
if(isset($file_values[$f['id']])) {
$links = array_map(
fn($u) => '<a href="' . esc_url($u['url']) . '">' . esc_html($u['name']) . '</a>',
$file_values[$f['id']]
);
$val = implode('<br>', $links);
}
$rows .= '<tr>'
. '<th style="text-align:left;padding:8px 14px;background:#f6f7f7;border-bottom:1px solid #eee;white-space:nowrap;font-weight:600;">' . esc_html($lbl) . '</th>'
. '<td style="padding:8px 14px;border-bottom:1px solid #eee;">' . $val . '</td>'
. '</tr>';
}
return '<html><body style="font-family:Arial,sans-serif;color:#1d2327;max-width:600px;margin:0 auto;">'
. '<div style="background:#2271b1;padding:20px 24px;border-radius:4px 4px 0 0;">'
. '<h2 style="color:#fff;margin:0;font-size:18px;">Neue Formulareinreichung</h2>'
. '<p style="color:rgba(255,255,255,.8);margin:4px 0 0;font-size:13px;">' . get_bloginfo('name') . ' &middot; ' . current_time('d.m.Y H:i') . '</p>'
. '</div>'
. '<div style="border:1px solid #dcdcde;border-top:none;border-radius:0 0 4px 4px;padding:0;">'
. '<table style="border-collapse:collapse;width:100%;">' . $rows . '</table>'
. '</div>'
. '<p style="color:#888;font-size:11px;margin-top:16px;text-align:center;">Diese E-Mail wurde automatisch gesendet von WP Multi Formular</p>'
. '</body></html>';
}
}

View File

@@ -0,0 +1,43 @@
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
class WMF_Post_Type {
private static $instance = null;
public static function instance() {
if ( is_null( self::$instance ) ) { self::$instance = new self(); self::$instance->hook(); }
return self::$instance;
}
public function hook() {
add_action( 'init', array( $this, 'register' ) );
add_action( 'admin_init', array( $this, 'redirect_cpt' ) );
}
public static function register() {
register_post_type( 'wmf-form', array(
'labels' => array(
'name' => 'Formulare',
'singular_name' => 'Formular',
'not_found' => 'Keine Formulare gefunden.',
),
'public' => false,
'show_ui' => true,
'show_in_menu' => false,
'supports' => array( 'title' ),
'capability_type' => 'post',
'has_archive' => false,
'rewrite' => false,
) );
}
public function redirect_cpt() {
if ( ! is_admin() ) return;
$pt = $_GET['post_type'] ?? '';
if ( $pt === 'wmf-form' && basename( $_SERVER['PHP_SELF'] ) === 'edit.php' ) {
wp_redirect( admin_url( 'admin.php?page=wp-multi-formular' ) ); exit;
}
if ( isset( $_GET['post'], $_GET['action'] ) && $_GET['action'] === 'edit' ) {
$p = get_post( intval( $_GET['post'] ) );
if ( $p && $p->post_type === 'wmf-form' ) {
wp_redirect( admin_url( 'admin.php?page=wp-multi-formular&edit=' . $p->ID ) ); exit;
}
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
if (!defined('ABSPATH')) exit;
class WMF_Shortcode {
private static $instance=null;
public static function instance() {
if(is_null(self::$instance)){self::$instance=new self();self::$instance->hook();}
return self::$instance;
}
public function hook() {
add_shortcode('wp_multi_formular',array($this,'render'));
add_action('wp_enqueue_scripts',array($this,'enqueue_assets'));
add_action('init',array($this,'start_session'),1);
}
public function start_session() { if(!session_id()&&!headers_sent()) session_start(); }
public function render($atts) {
$atts=shortcode_atts(array('id'=>0),$atts,'wp_multi_formular');
$id=intval($atts['id']);
if(!$id) return '<p class="wmf-error">Bitte geben Sie eine Formular-ID an.</p>';
return WMF_Form_Renderer::render($id);
}
public function enqueue_assets() {
wp_enqueue_style('wp-multi-formular',WMF_URL.'assets/css/frontend.css',array(),WMF_VERSION);
wp_enqueue_script('wp-multi-formular',WMF_URL.'assets/js/frontend.js',array('jquery'),WMF_VERSION,true);
wp_localize_script('wp-multi-formular','WMF_Frontend',array(
'ajax_url'=>admin_url('admin-ajax.php'),
'i18n'=>array(
'required' =>'Dieses Feld ist ein Pflichtfeld.',
'email' =>'Bitte geben Sie eine gültige E-Mail-Adresse ein.',
'url' =>'Bitte geben Sie eine gültige URL ein.',
'step_of' =>'Schritt %1 von %2',
'next' =>'Weiter',
'prev' =>'Zurück',
),
));
}
}

View File

@@ -0,0 +1,54 @@
<?php
if(!defined('ABSPATH')) exit;
class WMF_SMTP {
private static $instance = null;
public static function instance() {
if(is_null(self::$instance)) {
self::$instance = new self();
self::$instance->hook();
}
return self::$instance;
}
public function hook() {
$opts = get_option('wmf_global_settings', array());
if(!empty($opts['smtp_enabled']) && $opts['smtp_enabled'] === '1' && !empty($opts['smtp_host'])) {
add_action('phpmailer_init', array($this, 'configure_smtp'));
}
}
public function configure_smtp($phpmailer) {
$opts = get_option('wmf_global_settings', array());
if(empty($opts['smtp_host'])) return;
$phpmailer->isSMTP();
$phpmailer->Host = $opts['smtp_host'];
$phpmailer->Port = intval($opts['smtp_port'] ?? 587);
$phpmailer->SMTPSecure = $opts['smtp_enc'] ?? 'tls';
if(!empty($opts['smtp_user'])) {
$phpmailer->SMTPAuth = true;
$phpmailer->Username = $opts['smtp_user'];
$phpmailer->Password = $opts['smtp_pass'] ?? '';
} else {
$phpmailer->SMTPAuth = false;
}
// From-Adresse global setzen falls nicht per Mail-Header gesetzt
if(!empty($opts['from_email'])) {
$phpmailer->From = $opts['from_email'];
$phpmailer->FromName = $opts['from_name'] ?? get_bloginfo('name');
}
$phpmailer->SMTPOptions = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
)
);
}
}

View File

@@ -0,0 +1,18 @@
<?php
if (!defined('ABSPATH')) exit;
class WMF_Submission {
public static function save($form_id,$values) {
global $wpdb;
$wpdb->insert($wpdb->prefix.'wmf_submissions',array(
'form_id'=>$form_id,'data'=>wp_json_encode($values),
'ip'=>wmf_get_client_ip(),'user_agent'=>$_SERVER['HTTP_USER_AGENT']??'',
'status'=>'neu','created_at'=>current_time('mysql'),
),array('%d','%s','%s','%s','%s','%s'));
return $wpdb->insert_id;
}
public static function delete($id) { global $wpdb; $wpdb->delete($wpdb->prefix.'wmf_submissions',array('id'=>$id),array('%d')); }
public static function update_status($id,$status) {
global $wpdb;
$wpdb->update($wpdb->prefix.'wmf_submissions',array('status'=>sanitize_text_field($status)),array('id'=>$id),array('%s'),array('%d'));
}
}

View File

@@ -0,0 +1,481 @@
<?php
if(!defined('ABSPATH')) exit;
class WMF_Submissions_List {
/* ================================================================
LISTE ALLER EINREICHUNGEN EINES FORMULARS
================================================================ */
public static function render($form_id) {
$meta = wmf_get_form_meta($form_id);
$fields = self::get_display_fields($meta);
$per_page = 25;
$current_page= max(1, intval($_GET['paged'] ?? 1));
$offset = ($current_page - 1) * $per_page;
$status_filter = sanitize_text_field($_GET['sub_status'] ?? '');
$search = sanitize_text_field($_GET['sub_search'] ?? '');
$submissions = wmf_get_submissions($form_id, array(
'limit' => $per_page,
'offset' => $offset,
'status' => $status_filter,
'search' => $search,
));
$total = wmf_count_submissions($form_id, $status_filter, $search);
$total_pages = ceil($total / $per_page);
$base_url = add_query_arg(array(
'page' => 'wp-multi-formular',
'view_submissions' => $form_id,
), admin_url('admin.php'));
?>
<div class="wmf-submissions-wrap">
<!-- Header -->
<div class="wmf-subs-header">
<h2 class="wmf-subs-title">
Einreichungen
<span class="wmf-subs-count"><?php echo intval($total); ?></span>
</h2>
<div class="wmf-subs-actions">
<?php if($submissions): ?>
<a href="<?php echo esc_url(self::export_url($form_id)); ?>"
class="button">&#11015; CSV exportieren</a>
<?php endif; ?>
</div>
</div>
<!-- Filter-Leiste -->
<div class="wmf-subs-filter-bar">
<form method="get" action="<?php echo esc_url(admin_url('admin.php')); ?>">
<input type="hidden" name="page" value="wp-multi-formular">
<input type="hidden" name="view_submissions" value="<?php echo intval($form_id); ?>">
<!-- Status-Tabs -->
<div class="wmf-status-tabs">
<?php
$statuses = array('' => 'Alle', 'neu' => 'Neu', 'gelesen' => 'Gelesen', 'archiviert' => 'Archiviert');
foreach($statuses as $s_key => $s_label):
$cnt = ($s_key === '') ? wmf_count_submissions($form_id) : wmf_count_submissions($form_id, $s_key);
$active = ($status_filter === $s_key) ? 'current' : '';
?>
<a href="<?php echo esc_url(add_query_arg(array('sub_status'=>$s_key,'paged'=>1), $base_url)); ?>"
class="wmf-status-tab <?php echo $active; ?>">
<?php echo esc_html($s_label); ?>
<span class="count">(<?php echo intval($cnt); ?>)</span>
</a>
<?php endforeach; ?>
</div>
<!-- Suche -->
<div class="wmf-subs-search">
<input type="search" name="sub_search" value="<?php echo esc_attr($search); ?>"
placeholder="Einreichungen durchsuchen …" class="wmf-search-input">
<button type="submit" class="button">Suchen</button>
<?php if($search): ?>
<a href="<?php echo esc_url($base_url); ?>" class="button">✕ Zurücksetzen</a>
<?php endif; ?>
</div>
</form>
</div>
<?php if(empty($submissions)): ?>
<div class="wmf-empty-submissions">
<span class="dashicons dashicons-email-alt" style="font-size:48px;width:48px;height:48px;color:#c3c4c7;margin-bottom:12px;display:block;"></span>
<?php if($search||$status_filter): ?>
<p>Keine Einreichungen gefunden.</p>
<a href="<?php echo esc_url($base_url); ?>" class="button">Filter zurücksetzen</a>
<?php else: ?>
<p>Noch keine Einreichungen vorhanden.</p>
<?php endif; ?>
</div>
<?php else: ?>
<!-- Tabelle -->
<div class="wmf-table-wrap">
<table class="wp-list-table widefat fixed striped wmf-submissions-table">
<thead>
<tr>
<th class="wmf-col-id">#</th>
<th class="wmf-col-date">Datum</th>
<?php foreach(array_slice($fields, 0, 4) as $f): ?>
<th><?php echo esc_html($f['label']); ?></th>
<?php endforeach; ?>
<th class="wmf-col-status">Status</th>
<th class="wmf-col-actions">Aktionen</th>
</tr>
</thead>
<tbody>
<?php foreach($submissions as $row):
$data = json_decode($row->data, true) ?? array();
$view_url = add_query_arg(array(
'page' => 'wp-multi-formular',
'view_submission' => $row->id,
'form_id' => $form_id,
), admin_url('admin.php'));
?>
<tr class="wmf-sub-row <?php echo $row->status === 'neu' ? 'wmf-sub-new' : ''; ?>">
<td class="wmf-col-id">
<a href="<?php echo esc_url($view_url); ?>" class="wmf-sub-id-link">
#<?php echo intval($row->id); ?>
</a>
</td>
<td class="wmf-col-date">
<a href="<?php echo esc_url($view_url); ?>">
<?php echo esc_html(date_i18n('d.m.Y', strtotime($row->created_at))); ?>
<span class="wmf-time"><?php echo esc_html(date_i18n('H:i', strtotime($row->created_at))); ?></span>
</a>
</td>
<?php foreach(array_slice($fields, 0, 4) as $f):
$v = $data[$f['id']] ?? '';
if(is_array($v)) $v = implode(', ', $v);
if(($f['type']??'') === 'signature') $v = '(Unterschrift)';
$v_short = mb_strlen($v) > 60 ? mb_substr($v, 0, 60).'…' : $v;
?>
<td>
<a href="<?php echo esc_url($view_url); ?>" title="<?php echo esc_attr($v); ?>">
<?php echo esc_html($v_short); ?>
</a>
</td>
<?php endforeach; ?>
<td class="wmf-col-status">
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" class="wmf-status-form">
<?php wp_nonce_field('wmf_update_status'); ?>
<input type="hidden" name="action" value="wmf_update_submission_status">
<input type="hidden" name="submission_id" value="<?php echo intval($row->id); ?>">
<input type="hidden" name="form_id" value="<?php echo intval($form_id); ?>">
<input type="hidden" name="redirect_url" value="<?php echo esc_attr(add_query_arg(array('page'=>'wp-multi-formular','view_submissions'=>$form_id,'paged'=>$current_page,'sub_status'=>$status_filter), admin_url('admin.php'))); ?>">
<select name="status" class="wmf-status-select wmf-status-<?php echo esc_attr($row->status); ?>" onchange="this.form.submit()">
<option value="neu" <?php selected($row->status,'neu'); ?>>Neu</option>
<option value="gelesen" <?php selected($row->status,'gelesen'); ?>>Gelesen</option>
<option value="archiviert" <?php selected($row->status,'archiviert'); ?>>Archiviert</option>
</select>
</form>
</td>
<td class="wmf-col-actions">
<a href="<?php echo esc_url($view_url); ?>" class="button button-small">
&#128065; Ansehen
</a>
<a href="<?php echo esc_url(wp_nonce_url(
add_query_arg(array('wmf_action'=>'delete_submission','submission_id'=>$row->id,'form_id'=>$form_id,'page'=>'wp-multi-formular'), admin_url('admin.php')),
'wmf_delete_submission'
)); ?>"
class="button button-small wmf-btn-delete"
onclick="return confirm('Einreichung #<?php echo intval($row->id); ?> wirklich löschen?')">
&#128465; Löschen
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Pagination -->
<?php if($total_pages > 1): ?>
<div class="wmf-pagination">
<?php if($current_page > 1): ?>
<a href="<?php echo esc_url(add_query_arg('paged', $current_page-1, $base_url)); ?>" class="button">&laquo; Zurück</a>
<?php endif; ?>
<span class="wmf-page-info">
Seite <?php echo $current_page; ?> von <?php echo $total_pages; ?>
</span>
<?php if($current_page < $total_pages): ?>
<a href="<?php echo esc_url(add_query_arg('paged', $current_page+1, $base_url)); ?>" class="button">Weiter &raquo;</a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<?php
}
/* ================================================================
DETAILANSICHT EINER EINZELNEN EINREICHUNG
================================================================ */
public static function render_single($submission_id, $form_id) {
global $wpdb;
$row = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wmf_submissions WHERE id = %d AND form_id = %d",
$submission_id, $form_id
));
if(!$row) {
echo '<div class="notice notice-error"><p>Einreichung nicht gefunden.</p></div>';
return;
}
// Als gelesen markieren
if($row->status === 'neu') {
WMF_Submission::update_status($submission_id, 'gelesen');
$row->status = 'gelesen';
}
$data = json_decode($row->data, true) ?? array();
$meta = wmf_get_form_meta($form_id);
$fields = self::get_display_fields($meta);
$form = get_post($form_id);
$back_url = add_query_arg(array(
'page' => 'wp-multi-formular',
'view_submissions' => $form_id,
), admin_url('admin.php'));
$prev_url = self::get_adjacent_url($submission_id, $form_id, 'prev');
$next_url = self::get_adjacent_url($submission_id, $form_id, 'next');
?>
<div class="wmf-submission-detail">
<!-- Navigation -->
<div class="wmf-detail-nav">
<a href="<?php echo esc_url($back_url); ?>" class="button">
&larr; Alle Einreichungen
</a>
<div class="wmf-detail-nav-arrows">
<?php if($prev_url): ?>
<a href="<?php echo esc_url($prev_url); ?>" class="button" title="Vorherige Einreichung">&laquo; Vorherige</a>
<?php else: ?>
<button class="button" disabled>&laquo; Vorherige</button>
<?php endif; ?>
<?php if($next_url): ?>
<a href="<?php echo esc_url($next_url); ?>" class="button" title="Nächste Einreichung">Nächste &raquo;</a>
<?php else: ?>
<button class="button" disabled>Nächste &raquo;</button>
<?php endif; ?>
</div>
</div>
<!-- Meta-Karte -->
<div class="wmf-detail-meta-card">
<div class="wmf-detail-meta-item">
<span class="wmf-detail-meta-label">Einreichung</span>
<span class="wmf-detail-meta-value">#<?php echo intval($row->id); ?></span>
</div>
<div class="wmf-detail-meta-item">
<span class="wmf-detail-meta-label">Formular</span>
<span class="wmf-detail-meta-value"><?php echo esc_html($form ? $form->post_title : 'Unbekannt'); ?></span>
</div>
<div class="wmf-detail-meta-item">
<span class="wmf-detail-meta-label">Datum</span>
<span class="wmf-detail-meta-value"><?php echo esc_html(date_i18n('d.m.Y H:i', strtotime($row->created_at))); ?></span>
</div>
<div class="wmf-detail-meta-item">
<span class="wmf-detail-meta-label">IP-Adresse</span>
<span class="wmf-detail-meta-value"><?php echo esc_html($row->ip ?: '—'); ?></span>
</div>
<div class="wmf-detail-meta-item">
<span class="wmf-detail-meta-label">Status</span>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;">
<?php wp_nonce_field('wmf_update_status'); ?>
<input type="hidden" name="action" value="wmf_update_submission_status">
<input type="hidden" name="submission_id" value="<?php echo intval($row->id); ?>">
<input type="hidden" name="form_id" value="<?php echo intval($form_id); ?>">
<input type="hidden" name="redirect_url" value="<?php echo esc_attr(add_query_arg(array('page'=>'wp-multi-formular','view_submission'=>$row->id,'form_id'=>$form_id), admin_url('admin.php'))); ?>">
<select name="status" class="wmf-status-select wmf-status-<?php echo esc_attr($row->status); ?>" onchange="this.form.submit()">
<option value="neu" <?php selected($row->status,'neu'); ?>>Neu</option>
<option value="gelesen" <?php selected($row->status,'gelesen'); ?>>Gelesen</option>
<option value="archiviert" <?php selected($row->status,'archiviert'); ?>>Archiviert</option>
</select>
</form>
</div>
<div class="wmf-detail-meta-item wmf-detail-meta-actions">
<a href="<?php echo esc_url(wp_nonce_url(
add_query_arg(array('wmf_action'=>'delete_submission','submission_id'=>$row->id,'form_id'=>$form_id,'page'=>'wp-multi-formular'), admin_url('admin.php')),
'wmf_delete_submission'
)); ?>"
class="button wmf-btn-delete"
onclick="return confirm('Einreichung #<?php echo intval($row->id); ?> wirklich löschen?')">
&#128465; Löschen
</a>
<button onclick="window.print()" class="button">&#128438; Drucken</button>
</div>
</div>
<!-- Felder-Inhalt -->
<div class="wmf-detail-fields">
<h3 class="wmf-detail-section-title">Eingereichte Daten</h3>
<div class="wmf-detail-fields-grid">
<?php foreach($fields as $field):
$val = $data[$field['id']] ?? '';
$type = $field['type'] ?? 'text';
?>
<div class="wmf-detail-field <?php echo 'wmf-detail-field-' . esc_attr($type); ?>">
<div class="wmf-detail-field-label">
<?php echo esc_html($field['label']); ?>
<span class="wmf-detail-field-type"><?php echo esc_html($type); ?></span>
</div>
<div class="wmf-detail-field-value">
<?php self::render_field_value($val, $field); ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Technische Infos (aufklappbar) -->
<details class="wmf-detail-technical">
<summary>Technische Informationen</summary>
<table class="widefat" style="margin-top:12px;">
<tr><th>User-Agent</th><td><?php echo esc_html($row->user_agent ?: '—'); ?></td></tr>
<tr><th>Erstellt am</th><td><?php echo esc_html($row->created_at); ?></td></tr>
<tr><th>Datenbank-ID</th><td><?php echo intval($row->id); ?></td></tr>
</table>
</details>
</div>
<style>
@media print {
#adminmenumain,#wpadminbar,.wmf-detail-nav,.wmf-detail-meta-actions,
#screen-meta,#wpfooter,.notice { display:none!important; }
.wmf-submission-detail { margin:0; padding:0; }
.wmf-detail-fields { border:none; }
}
</style>
<?php
}
/* ================================================================
FELDINHALTE RENDERN (je nach Typ)
================================================================ */
private static function render_field_value($val, $field) {
$type = $field['type'] ?? 'text';
if($val === '' || $val === null || $val === array()) {
echo '<span class="wmf-empty-value">—</span>';
return;
}
switch($type) {
case 'checkbox':
$vals = is_array($val) ? $val : explode(', ', $val);
echo '<ul class="wmf-val-list">';
foreach($vals as $v) echo '<li>' . esc_html($v) . '</li>';
echo '</ul>';
break;
case 'gdpr':
$icon = $val === '1' ? '✅' : '❌';
echo $icon . ' ' . ($val === '1' ? 'Zugestimmt' : 'Nicht zugestimmt');
break;
case 'signature':
if(strpos($val, 'data:image') === 0) {
echo '<img src="' . esc_attr($val) . '" style="max-width:300px;border:1px solid #dcdcde;border-radius:4px;background:#fff;">';
} else {
echo '<span class="wmf-empty-value">Keine Unterschrift</span>';
}
break;
case 'file':
$urls = is_array($val) ? $val : explode(', ', $val);
echo '<div class="wmf-val-files">';
foreach($urls as $url) {
$url = trim($url);
if(!$url) continue;
$filename = basename(parse_url($url, PHP_URL_PATH));
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$is_image = in_array($ext, array('jpg','jpeg','png','gif','webp'));
if($is_image) {
echo '<div class="wmf-val-file-img"><a href="' . esc_url($url) . '" target="_blank">';
echo '<img src="' . esc_url($url) . '" style="max-width:200px;max-height:150px;border-radius:4px;"></a></div>';
} else {
echo '<a href="' . esc_url($url) . '" target="_blank" class="wmf-val-file-link">📎 ' . esc_html($filename) . '</a>';
}
}
echo '</div>';
break;
case 'rating':
$stars = intval($val);
$max = intval($field['max_stars'] ?? 5);
echo '<div class="wmf-val-stars">';
for($i = 1; $i <= $max; $i++) {
echo '<span style="color:' . ($i <= $stars ? '#f0b429' : '#ddd') . ';font-size:20px;">★</span>';
}
echo ' <span style="color:#646970;font-size:13px;">' . $stars . '/' . $max . '</span></div>';
break;
case 'url':
echo '<a href="' . esc_url($val) . '" target="_blank">' . esc_html($val) . '</a>';
break;
case 'email':
echo '<a href="mailto:' . esc_attr($val) . '">' . esc_html($val) . '</a>';
break;
case 'textarea':
echo '<div class="wmf-val-textarea">' . nl2br(esc_html($val)) . '</div>';
break;
default:
if(is_array($val)) {
echo '<ul class="wmf-val-list">';
foreach($val as $v) echo '<li>' . esc_html($v) . '</li>';
echo '</ul>';
} else {
echo '<span class="wmf-val-text">' . esc_html($val) . '</span>';
}
}
}
/* ================================================================
HILFSFUNKTIONEN
================================================================ */
private static function get_display_fields($meta) {
return array_values(array_filter($meta['fields'] ?? array(), function($f) {
return !in_array($f['type'] ?? '', array('html','divider','hidden'));
}));
}
private static function get_adjacent_url($submission_id, $form_id, $direction) {
global $wpdb;
$op = $direction === 'prev' ? '>' : '<';
$order = $direction === 'prev' ? 'ASC' : 'DESC';
$adj = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM {$wpdb->prefix}wmf_submissions WHERE form_id = %d AND id {$op} %d ORDER BY id {$order} LIMIT 1",
$form_id, $submission_id
));
if(!$adj) return null;
return add_query_arg(array('page'=>'wp-multi-formular','view_submission'=>$adj,'form_id'=>$form_id), admin_url('admin.php'));
}
public static function status_label($s) {
return array('neu'=>'Neu','gelesen'=>'Gelesen','archiviert'=>'Archiviert')[$s] ?? $s;
}
public static function export_url($form_id) {
return wp_nonce_url(add_query_arg(array('wmf_action'=>'export_csv','form_id'=>$form_id,'page'=>'wp-multi-formular'), admin_url('admin.php')), 'wmf_export_csv');
}
public static function maybe_export($form_id) {
if(empty($_GET['wmf_action']) || $_GET['wmf_action'] !== 'export_csv') return;
if(!wp_verify_nonce($_GET['_wpnonce'] ?? '', 'wmf_export_csv')) return;
$meta = wmf_get_form_meta($form_id);
$fields = self::get_display_fields($meta);
$rows = wmf_get_submissions($form_id, array('limit'=>9999));
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="formular-' . $form_id . '-einreichungen.csv"');
$out = fopen('php://output', 'w');
fprintf($out, chr(0xEF).chr(0xBB).chr(0xBF));
$header = array('ID','Datum','Status','IP');
foreach($fields as $f) $header[] = $f['label'];
fputcsv($out, $header, ';');
foreach($rows as $row) {
$data = json_decode($row->data, true) ?? array();
$line = array($row->id, date_i18n('d.m.Y H:i', strtotime($row->created_at)), self::status_label($row->status), $row->ip);
foreach($fields as $f) {
$v = $data[$f['id']] ?? '';
if(is_array($v)) $v = implode(', ', $v);
if(($f['type']??'') === 'signature') $v = '(Unterschrift)';
$line[] = $v;
}
fputcsv($out, $line, ';');
}
fclose($out);
exit;
}
}