Files
KGV-Contact-Form/kgv-contact-form-pro.php
2026-04-13 22:40:28 +02:00

593 lines
22 KiB
PHP
Executable File
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: KGV Kontakt Form
* Description: Kontaktformular mit Nachrichtenbereich, Kategorien, Routing je Kategorie, Datenschutz-Checkbox, gelesen/ungelesen, Mehrfach-E-Mail und Rollenfreigabe für Kontaktanfragen.
* Version: 1.4.2
* Author: Ronny Grobel
* Author URI: https://apex-project.de/
* Plugin URI: https://apex-project.de/
* Update URI: https://git.apex-project.de/Wordpress_Plugins/KGV-Contact-Form
* Gitea Plugin URI: https://git.apex-project.de/Wordpress_Plugins/KGV-Contact-Form
*/
if (!defined('ABSPATH')) exit;
define('KGV_CF_VERSION', '1.4.0');
define('KGV_CF_VIEW_CAP', 'kgv_view_contact_requests');
define('KGV_CF_MANAGE_CAP', 'manage_options');
register_activation_hook(__FILE__, 'kgv_cf_activate');
add_action('plugins_loaded', 'kgv_cf_maybe_upgrade');
add_action('admin_menu', 'kgv_cf_admin_menu');
add_action('admin_init', 'kgv_cf_handle_admin_actions');
add_action('init', 'kgv_cf_handle_form_submit');
add_action('wp_enqueue_scripts', 'kgv_cf_enqueue_assets');
add_shortcode('kgv_contact_form', 'kgv_cf_render_form');
function kgv_cf_messages_table() {
global $wpdb;
return $wpdb->prefix . 'kgv_cf_messages';
}
function kgv_cf_categories_table() {
global $wpdb;
return $wpdb->prefix . 'kgv_cf_categories';
}
function kgv_cf_activate() {
kgv_cf_run_schema_update();
kgv_cf_add_caps();
update_option('kgv_cf_db_version', KGV_CF_VERSION);
}
function kgv_cf_maybe_upgrade() {
$installed = get_option('kgv_cf_db_version', '');
if ($installed !== KGV_CF_VERSION) {
kgv_cf_run_schema_update();
kgv_cf_add_caps();
update_option('kgv_cf_db_version', KGV_CF_VERSION);
}
}
function kgv_cf_add_caps() {
$roles = ['administrator', 'editor', 'author', 'contributor'];
foreach ($roles as $role_name) {
$role = get_role($role_name);
if ($role && !$role->has_cap(KGV_CF_VIEW_CAP)) {
$role->add_cap(KGV_CF_VIEW_CAP);
}
}
}
function kgv_cf_run_schema_update() {
global $wpdb;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$charset_collate = $wpdb->get_charset_collate();
$messages = kgv_cf_messages_table();
$categories = kgv_cf_categories_table();
$sql_messages = "CREATE TABLE $messages (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
created_at DATETIME NOT NULL,
category_id BIGINT UNSIGNED NULL,
category_name VARCHAR(190) NOT NULL DEFAULT '',
recipient_email TEXT NULL,
sender_name VARCHAR(190) NOT NULL DEFAULT '',
sender_email VARCHAR(190) NOT NULL DEFAULT '',
subject VARCHAR(255) NOT NULL DEFAULT '',
message LONGTEXT NOT NULL,
privacy_accepted TINYINT(1) NOT NULL DEFAULT 0,
is_read TINYINT(1) NOT NULL DEFAULT 0,
ip_address VARCHAR(100) NOT NULL DEFAULT '',
user_agent TEXT NULL,
is_sent TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id)
) $charset_collate;";
$sql_categories = "CREATE TABLE $categories (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(190) NOT NULL,
recipient_email TEXT NULL,
sort_order INT NOT NULL DEFAULT 0,
is_active TINYINT(1) NOT NULL DEFAULT 1,
PRIMARY KEY (id)
) $charset_collate;";
dbDelta($sql_messages);
dbDelta($sql_categories);
$count = (int) $wpdb->get_var("SELECT COUNT(*) FROM $categories");
if ($count === 0) {
$wpdb->insert($categories, [
'name' => 'Allgemein',
'recipient_email' => get_option('admin_email'),
'sort_order' => 10,
'is_active' => 1,
], ['%s', '%s', '%d', '%d']);
}
}
function kgv_cf_enqueue_assets() {
wp_enqueue_style(
'kgv-contact-form-style',
plugin_dir_url(__FILE__) . 'assets/kgv-contact-form.css',
[],
KGV_CF_VERSION
);
}
function kgv_cf_get_categories($only_active = false) {
global $wpdb;
$table = kgv_cf_categories_table();
$where = $only_active ? "WHERE is_active = 1" : "";
return $wpdb->get_results("SELECT * FROM $table $where ORDER BY sort_order ASC, name ASC");
}
function kgv_cf_get_category($id) {
global $wpdb;
$table = kgv_cf_categories_table();
return $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $id));
}
function kgv_cf_parse_recipient_emails($raw) {
$parts = array_map('trim', explode(',', (string) $raw));
$parts = array_filter($parts, function($email) {
return is_email($email);
});
if (empty($parts)) {
$parts = [get_option('admin_email')];
}
return array_values(array_unique($parts));
}
function kgv_cf_admin_menu() {
add_menu_page(
'Kontaktformular',
'Kontaktformular',
KGV_CF_VIEW_CAP,
'kgv-contact-form',
'kgv_cf_render_messages_page',
'dashicons-email-alt',
26
);
add_submenu_page(
'kgv-contact-form',
'Nachrichten',
'Nachrichten',
KGV_CF_VIEW_CAP,
'kgv-contact-form',
'kgv_cf_render_messages_page'
);
add_submenu_page(
'kgv-contact-form',
'Kategorien',
'Kategorien',
KGV_CF_MANAGE_CAP,
'kgv-contact-form-categories',
'kgv_cf_render_categories_page'
);
}
function kgv_cf_handle_admin_actions() {
if (!is_admin()) {
return;
}
global $wpdb;
if (isset($_POST['kgv_cf_save_category'])) {
if (!current_user_can(KGV_CF_MANAGE_CAP)) {
wp_die('Keine Berechtigung.');
}
check_admin_referer('kgv_cf_save_category');
$table = kgv_cf_categories_table();
$id = isset($_POST['category_id']) ? absint($_POST['category_id']) : 0;
$name = sanitize_text_field($_POST['name'] ?? '');
$recipient_email = sanitize_textarea_field($_POST['recipient_email'] ?? '');
$sort_order = isset($_POST['sort_order']) ? intval($_POST['sort_order']) : 0;
$is_active = !empty($_POST['is_active']) ? 1 : 0;
$parsed_emails = kgv_cf_parse_recipient_emails($recipient_email);
if ($name && !empty($parsed_emails)) {
$data = [
'name' => $name,
'recipient_email' => implode(', ', $parsed_emails),
'sort_order' => $sort_order,
'is_active' => $is_active,
];
if ($id) {
$wpdb->update($table, $data, ['id' => $id], ['%s', '%s', '%d', '%d'], ['%d']);
} else {
$wpdb->insert($table, $data, ['%s', '%s', '%d', '%d']);
}
wp_safe_redirect(admin_url('admin.php?page=kgv-contact-form-categories&saved=1'));
exit;
}
wp_safe_redirect(admin_url('admin.php?page=kgv-contact-form-categories&error=1'));
exit;
}
if (isset($_GET['kgv_cf_delete_category'])) {
if (!current_user_can(KGV_CF_MANAGE_CAP)) {
wp_die('Keine Berechtigung.');
}
check_admin_referer('kgv_cf_delete_category');
$id = absint($_GET['kgv_cf_delete_category']);
if ($id) {
$wpdb->delete(kgv_cf_categories_table(), ['id' => $id], ['%d']);
}
wp_safe_redirect(admin_url('admin.php?page=kgv-contact-form-categories&deleted=1'));
exit;
}
if (isset($_GET['kgv_cf_delete_message'])) {
if (!current_user_can(KGV_CF_VIEW_CAP)) {
wp_die('Keine Berechtigung.');
}
check_admin_referer('kgv_cf_delete_message');
$id = absint($_GET['kgv_cf_delete_message']);
if ($id) {
$wpdb->delete(kgv_cf_messages_table(), ['id' => $id], ['%d']);
}
wp_safe_redirect(admin_url('admin.php?page=kgv-contact-form&deleted=1'));
exit;
}
if (isset($_GET['kgv_cf_toggle_read'])) {
if (!current_user_can(KGV_CF_VIEW_CAP)) {
wp_die('Keine Berechtigung.');
}
check_admin_referer('kgv_cf_toggle_read');
$id = absint($_GET['kgv_cf_toggle_read']);
$message = $wpdb->get_row($wpdb->prepare(
"SELECT id, is_read FROM " . kgv_cf_messages_table() . " WHERE id = %d",
$id
));
if ($message) {
$new_value = ((int) $message->is_read === 1) ? 0 : 1;
$wpdb->update(
kgv_cf_messages_table(),
['is_read' => $new_value],
['id' => $id],
['%d'],
['%d']
);
}
wp_safe_redirect(admin_url('admin.php?page=kgv-contact-form&updated=1'));
exit;
}
}
function kgv_cf_render_form() {
$categories = kgv_cf_get_categories(true);
ob_start();
if (isset($_GET['kgv_sent']) && $_GET['kgv_sent'] === '1') {
echo '<div class="kgv-cf-alert kgv-cf-alert-success">Nachricht erfolgreich gesendet.</div>';
}
if (isset($_GET['kgv_error']) && $_GET['kgv_error'] === '1') {
echo '<div class="kgv-cf-alert kgv-cf-alert-error">Bitte alle Pflichtfelder korrekt ausfüllen.</div>';
}
?>
<form method="post" class="kgv-contact-form" novalidate>
<div class="kgv-cf-grid">
<p class="kgv-cf-field">
<label for="kgv_name">Name *</label>
<input type="text" id="kgv_name" name="kgv_name" required>
</p>
<p class="kgv-cf-field">
<label for="kgv_email">E-Mail *</label>
<input type="email" id="kgv_email" name="kgv_email" required>
</p>
</div>
<p class="kgv-cf-field">
<label for="kgv_category">Kategorie *</label>
<select id="kgv_category" name="kgv_category" required>
<option value="">Bitte auswählen</option>
<?php foreach ($categories as $category): ?>
<option value="<?php echo esc_attr($category->id); ?>"><?php echo esc_html($category->name); ?></option>
<?php endforeach; ?>
</select>
</p>
<p class="kgv-cf-field">
<label for="kgv_subject">Betreff *</label>
<input type="text" id="kgv_subject" name="kgv_subject" required>
</p>
<p class="kgv-cf-field">
<label for="kgv_message">Nachricht *</label>
<textarea id="kgv_message" name="kgv_message" required rows="7"></textarea>
</p>
<p class="kgv-cf-privacy">
<label>
<input type="checkbox" name="kgv_privacy" value="1" required>
Ich habe die Datenschutzhinweise gelesen und stimme der Verarbeitung meiner Angaben zur Bearbeitung der Anfrage zu. *
</label>
</p>
<input type="text" name="kgv_hp" value="" autocomplete="off" tabindex="-1" class="kgv-cf-hp">
<?php wp_nonce_field('kgv_contact_form_submit', 'kgv_nonce'); ?>
<p class="kgv-cf-submit">
<button type="submit" name="kgv_submit">Senden</button>
</p>
</form>
<?php
return ob_get_clean();
}
function kgv_cf_handle_form_submit() {
if (!isset($_POST['kgv_submit'])) {
return;
}
if (!isset($_POST['kgv_nonce']) || !wp_verify_nonce($_POST['kgv_nonce'], 'kgv_contact_form_submit')) {
return;
}
if (!empty($_POST['kgv_hp'])) {
return;
}
$name = sanitize_text_field($_POST['kgv_name'] ?? '');
$email = sanitize_email($_POST['kgv_email'] ?? '');
$category_id = absint($_POST['kgv_category'] ?? 0);
$subject = sanitize_text_field($_POST['kgv_subject'] ?? '');
$message = sanitize_textarea_field($_POST['kgv_message'] ?? '');
$privacy = !empty($_POST['kgv_privacy']) ? 1 : 0;
$category = $category_id ? kgv_cf_get_category($category_id) : null;
if (!$name || !is_email($email) || !$category || !$subject || !$message || !$privacy) {
kgv_cf_redirect_with_flag('kgv_error', '1');
}
$recipients = kgv_cf_parse_recipient_emails($category->recipient_email);
$body = "Neue Kontaktanfrage\n\n";
$body .= "Kategorie: " . $category->name . "\n";
$body .= "Name: " . $name . "\n";
$body .= "E-Mail: " . $email . "\n";
$body .= "Betreff: " . $subject . "\n";
$body .= "Datenschutz akzeptiert: Ja\n\n";
$body .= "Nachricht:\n" . $message . "\n";
$headers = [
'Content-Type: text/plain; charset=UTF-8',
'Reply-To: ' . $name . ' <' . $email . '>',
];
$sent = wp_mail($recipients, $subject, $body, $headers) ? 1 : 0;
global $wpdb;
$wpdb->insert(kgv_cf_messages_table(), [
'created_at' => current_time('mysql'),
'category_id' => $category->id,
'category_name' => $category->name,
'recipient_email' => implode(', ', $recipients),
'sender_name' => $name,
'sender_email' => $email,
'subject' => $subject,
'message' => $message,
'privacy_accepted' => $privacy,
'is_read' => 0,
'ip_address' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '',
'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_textarea_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '',
'is_sent' => $sent,
], ['%s','%d','%s','%s','%s','%s','%s','%s','%d','%d','%s','%s','%d']);
kgv_cf_redirect_with_flag('kgv_sent', '1');
}
function kgv_cf_redirect_with_flag($key, $value) {
$referer = wp_get_referer();
if (!$referer) {
$referer = home_url('/');
}
wp_safe_redirect(add_query_arg($key, $value, $referer));
exit;
}
function kgv_cf_render_messages_page() {
if (!current_user_can(KGV_CF_VIEW_CAP)) {
wp_die('Keine Berechtigung.');
}
global $wpdb;
$table = kgv_cf_messages_table();
$messages = $wpdb->get_results("SELECT * FROM $table ORDER BY is_read ASC, created_at DESC, id DESC LIMIT 500");
$unread_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE is_read = 0");
echo '<div class="wrap">';
echo '<h1>Kontaktformular Nachrichten</h1>';
if (isset($_GET['deleted'])) {
echo '<div class="notice notice-success is-dismissible"><p>Nachricht gelöscht.</p></div>';
}
if (isset($_GET['updated'])) {
echo '<div class="notice notice-success is-dismissible"><p>Status aktualisiert.</p></div>';
}
echo '<p><strong>Ungelesene Nachrichten:</strong> ' . esc_html($unread_count) . '</p>';
if (empty($messages)) {
echo '<p>Noch keine Nachrichten vorhanden.</p>';
echo '</div>';
return;
}
echo '<table class="widefat striped">';
echo '<thead><tr>';
echo '<th>Status</th>';
echo '<th>Datum</th>';
echo '<th>Kategorie</th>';
echo '<th>Von</th>';
echo '<th>Betreff</th>';
echo '<th>Datenschutz</th>';
echo '<th>Weitergeleitet an</th>';
echo '<th>Mailstatus</th>';
echo '<th>Aktion</th>';
echo '</tr></thead><tbody>';
foreach ($messages as $msg) {
$delete_url = wp_nonce_url(
admin_url('admin.php?page=kgv-contact-form&kgv_cf_delete_message=' . absint($msg->id)),
'kgv_cf_delete_message'
);
$toggle_url = wp_nonce_url(
admin_url('admin.php?page=kgv-contact-form&kgv_cf_toggle_read=' . absint($msg->id)),
'kgv_cf_toggle_read'
);
$status_badge = ((int)$msg->is_read === 1)
? '<span style="display:inline-block;padding:4px 8px;background:#edf7ed;border:1px solid #bfd9bf;border-radius:999px;">Gelesen</span>'
: '<span style="display:inline-block;padding:4px 8px;background:#fff4e5;border:1px solid #e0c08c;border-radius:999px;font-weight:600;">Ungelesen</span>';
echo '<tr' . (((int)$msg->is_read === 0) ? ' style="font-weight:600;"' : '') . '>';
echo '<td>' . $status_badge . '</td>';
echo '<td>' . esc_html(mysql2date('d.m.Y H:i', $msg->created_at)) . '</td>';
echo '<td>' . esc_html($msg->category_name) . '</td>';
echo '<td><strong>' . esc_html($msg->sender_name) . '</strong><br><a href="mailto:' . esc_attr($msg->sender_email) . '">' . esc_html($msg->sender_email) . '</a></td>';
echo '<td><strong>' . esc_html($msg->subject) . '</strong><br>' . nl2br(esc_html(wp_trim_words($msg->message, 24, '…'))) . '</td>';
echo '<td>' . (((int)$msg->privacy_accepted === 1) ? 'Ja' : 'Nein') . '</td>';
echo '<td>' . esc_html($msg->recipient_email) . '</td>';
echo '<td>' . (((int)$msg->is_sent === 1) ? 'Gesendet' : 'Fehler') . '</td>';
echo '<td>';
echo '<a href="' . esc_url($toggle_url) . '">' . (((int)$msg->is_read === 1) ? 'Als ungelesen markieren' : 'Als gelesen markieren') . '</a>';
echo ' | <a href="#" onclick="this.nextElementSibling.style.display=\'block\';return false;">Anzeigen</a>';
echo ' | <a href="' . esc_url($delete_url) . '" onclick="return confirm(\'Nachricht wirklich löschen?\')">Löschen</a>';
echo '<div style="display:none; margin-top:10px; padding:12px; background:#fff; border:1px solid #ddd; min-width:360px; font-weight:400;">';
echo '<p><strong>Nachricht:</strong></p>';
echo '<div>' . nl2br(esc_html($msg->message)) . '</div>';
echo '</div>';
echo '</td>';
echo '</tr>';
}
echo '</tbody></table>';
echo '</div>';
}
function kgv_cf_render_categories_page() {
if (!current_user_can(KGV_CF_MANAGE_CAP)) {
wp_die('Keine Berechtigung.');
}
$edit_id = isset($_GET['edit']) ? absint($_GET['edit']) : 0;
$edit_item = $edit_id ? kgv_cf_get_category($edit_id) : null;
$categories = kgv_cf_get_categories(false);
echo '<div class="wrap">';
echo '<h1>Kontaktformular Kategorien</h1>';
if (isset($_GET['saved'])) {
echo '<div class="notice notice-success is-dismissible"><p>Kategorie gespeichert.</p></div>';
}
if (isset($_GET['deleted'])) {
echo '<div class="notice notice-success is-dismissible"><p>Kategorie gelöscht.</p></div>';
}
if (isset($_GET['error'])) {
echo '<div class="notice notice-error is-dismissible"><p>Bitte einen Namen und mindestens eine gültige E-Mail-Adresse eintragen.</p></div>';
}
echo '<div style="display:grid; grid-template-columns:minmax(320px,420px) 1fr; gap:24px; align-items:start; margin-top:20px;">';
echo '<div style="background:#fff; border:1px solid #ddd; padding:20px;">';
echo '<h2 style="margin-top:0;">' . ($edit_item ? 'Kategorie bearbeiten' : 'Neue Kategorie') . '</h2>';
echo '<form method="post">';
wp_nonce_field('kgv_cf_save_category');
echo '<input type="hidden" name="category_id" value="' . esc_attr($edit_item ? $edit_item->id : 0) . '">';
echo '<p><label for="kgv_cf_name"><strong>Name</strong></label><br>';
echo '<input type="text" id="kgv_cf_name" name="name" value="' . esc_attr($edit_item ? $edit_item->name : '') . '" class="regular-text" required></p>';
echo '<p><label for="kgv_cf_email"><strong>Weiterleitung an E-Mail</strong></label><br>';
echo '<textarea id="kgv_cf_email" name="recipient_email" rows="4" class="large-text" required>' . esc_textarea($edit_item ? $edit_item->recipient_email : '') . '</textarea>';
echo '<br><span style="color:#666;">Mehrere E-Mail-Adressen mit Komma trennen, z. B. info@seite.de, vorstand@seite.de</span></p>';
echo '<p><label for="kgv_cf_sort"><strong>Sortierung</strong></label><br>';
echo '<input type="number" id="kgv_cf_sort" name="sort_order" value="' . esc_attr($edit_item ? $edit_item->sort_order : 0) . '" class="small-text"></p>';
$checked = $edit_item ? ((int)$edit_item->is_active === 1) : true;
echo '<p><label><input type="checkbox" name="is_active" value="1" ' . checked($checked, true, false) . '> Aktiv</label></p>';
submit_button($edit_item ? 'Kategorie aktualisieren' : 'Kategorie speichern', 'primary', 'kgv_cf_save_category');
echo '</form>';
echo '</div>';
echo '<div>';
echo '<table class="widefat striped">';
echo '<thead><tr><th>Name</th><th>E-Mail</th><th>Sortierung</th><th>Status</th><th>Aktion</th></tr></thead><tbody>';
if (empty($categories)) {
echo '<tr><td colspan="5">Keine Kategorien vorhanden.</td></tr>';
} else {
foreach ($categories as $category) {
$edit_url = admin_url('admin.php?page=kgv-contact-form-categories&edit=' . absint($category->id));
$delete_url = wp_nonce_url(
admin_url('admin.php?page=kgv-contact-form-categories&kgv_cf_delete_category=' . absint($category->id)),
'kgv_cf_delete_category'
);
echo '<tr>';
echo '<td>' . esc_html($category->name) . '</td>';
echo '<td>' . esc_html($category->recipient_email) . '</td>';
echo '<td>' . esc_html($category->sort_order) . '</td>';
echo '<td>' . ((int)$category->is_active === 1 ? 'Aktiv' : 'Inaktiv') . '</td>';
echo '<td><a href="' . esc_url($edit_url) . '">Bearbeiten</a> | <a href="' . esc_url($delete_url) . '" onclick="return confirm(\'Kategorie wirklich löschen?\')">Löschen</a></td>';
echo '</tr>';
}
}
echo '</tbody></table>';
echo '</div>';
echo '</div>';
echo '<div style="margin-top:24px; padding:16px; background:#fff; border:1px solid #ddd;">';
echo '<h2 style="margin-top:0;">Zugriff für Mitarbeiter / Autoren</h2>';
echo '<p>Dieses Plugin gibt automatisch folgenden Rollen Leserechte auf die Kontaktanfragen:</p>';
echo '<ul style="list-style:disc; padding-left:18px;">';
echo '<li>Administrator</li>';
echo '<li>Redakteur</li>';
echo '<li>Autor</li>';
echo '<li>Mitarbeiter</li>';
echo '</ul>';
echo '<p><strong>Kategorien verwalten</strong> bleibt absichtlich nur für Administratoren freigeschaltet.</p>';
echo '</div>';
echo '<p style="margin-top:20px;"><strong>Shortcode:</strong> <code>[kgv_contact_form]</code></p>';
echo '</div>';
}