Beispielarbeiten · WordPress
WordPress-Plugin: Anfrageformular mit Admin-Liste und CSV-Export
Ein eigenes Plugin mit Shortcode, eigener Tabelle, Einstellungsseite und Export. Sicherheitsprüfungen wie Nonce, Bereinigung und Maskierung wurden auf einer frischen WordPress-Installation getestet.
Musterarbeit (Demo), kein Kundenauftrag. Gebaut als Probeaufgabe mit erfundenen Beispieldaten; die Prüfzahlen unten stammen aus dem automatischen Prüflauf und sind nicht geschönt.
Was gebaut wurde
- Formular per Shortcode, Einträge in einer eigenen Tabelle
- Einstellungsseite und Liste im Adminbereich
- CSV-Export nur für Administratoren, Formeln in Zellen entschärft
Wie geprüft wurde
Automatischer Prüflauf vom 18.10.2026, aus dem Protokoll (Testdaten erfunden, Zahlen gemessen):
- Eigenes Plugin GL Anfragen (Shortcode, Tabelle, Einstellungsseite, Admin-Liste, CSV) auf WordPress 7.1.3 als ZIP installiert
- 14/14 Pruefungen gruen (Nonce, Validierung, Bereinigung/Maskierung, CSV nur fuer Admin, Formel-Injektion entschaerft)
Alle Prüffälle (14 von 14 grün)
- ✓ php lint
- ✓ plugin aktiv
- ✓ tabelle angelegt
- ✓ einstellung gespeichert
- ✓ formular mit nonce
- ✓ gueltig weiter und danke
- ✓ zweite gespeichert
- ✓ ungueltige mail abgewiesen
- ✓ falsche nonce 403
- ✓ liste maskiert
- ✓ csv admin
- ✓ csv formel entschaerft
- ✓ csv anonym kein csv
- ✓ protokoll sauber
Grenzen
Was diese Probe nicht abdeckt, steht hier, damit niemand mehr erwartet:
- lokale Kopie mit SQLite statt MySQL und php -S statt Apache/nginx; dbDelta-SQL ist MySQL-Syntax und laeuft beim Kunden auf MySQL/MariaDB, hier durch den SQLite-Uebersetzer
- ein Formular-/Tabellen-/CSV-Plugin geprueft; Gutenberg-Bloecke (React/JS-Build) und Page-Builder-Module (Elementor, Divi) sind nicht Teil der Probe
- kein Spam-Schutz ueber Nonce hinaus (kein Captcha/Honeypot-Dienst eingebaut); Mailversand nicht geprueft (lokal gibt es keinen Mailserver)
- keine Einreichung ins wordpress.org-Verzeichnis (Review, SVN) - Lieferung als ZIP an den Kunden
Der Plugin-Code
<?php
/**
* Plugin Name: GL Anfragen
* Description: Anfrageformular per Shortcode [gl_anfrage], speichert in eine eigene Tabelle, Liste und CSV-Export im Admin.
* Version: 1.0.0
* Requires at least: 6.0
* Requires PHP: 7.4
* Author: GL Tools
* License: GPL-2.0-or-later
* Text Domain: gl-anfragen
*/
if (!defined('ABSPATH')) { exit; }
const GL_ANFRAGEN_DB_VERSION = '1';
function gl_anfragen_tabelle() { global $wpdb; return $wpdb->prefix . 'gl_anfragen'; }
register_activation_hook(__FILE__, 'gl_anfragen_aktivieren');
function gl_anfragen_aktivieren() {
global $wpdb;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$t = gl_anfragen_tabelle();
$cs = $wpdb->get_charset_collate();
dbDelta("CREATE TABLE $t (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
erstellt datetime NOT NULL,
name varchar(120) NOT NULL,
email varchar(190) NOT NULL,
nachricht text NOT NULL,
PRIMARY KEY (id)
) $cs;");
add_option('gl_anfragen_danke', 'Danke, wir melden uns.');
update_option('gl_anfragen_db_version', GL_ANFRAGEN_DB_VERSION);
}
register_uninstall_hook(__FILE__, 'gl_anfragen_deinstallieren');
function gl_anfragen_deinstallieren() {
global $wpdb;
$wpdb->query('DROP TABLE IF EXISTS ' . gl_anfragen_tabelle());
delete_option('gl_anfragen_danke');
delete_option('gl_anfragen_db_version');
}
/* ---------- Frontend: Shortcode ---------- */
add_shortcode('gl_anfrage', 'gl_anfragen_shortcode');
function gl_anfragen_shortcode() {
$status = isset($_GET['gl']) ? sanitize_key(wp_unslash($_GET['gl'])) : '';
$out = '';
if ($status === 'danke') {
$out .= '<p class="gl-anfrage-danke">' . esc_html(get_option('gl_anfragen_danke')) . '</p>';
} elseif ($status === 'fehler') {
$out .= '<p class="gl-anfrage-fehler" role="alert">Bitte Name, gueltige E-Mail und Nachricht angeben.</p>';
}
$ziel = esc_url(admin_url('admin-post.php'));
$zurueck = esc_url(get_permalink());
$out .= '<form class="gl-anfrage" method="post" action="' . $ziel . '">'
. '<input type="hidden" name="action" value="gl_anfrage_senden">'
. '<input type="hidden" name="zurueck" value="' . $zurueck . '">'
. wp_nonce_field('gl_anfrage_senden', 'gl_nonce', true, false)
. '<p><label for="gl-name">Name</label><input id="gl-name" name="gl_name" required maxlength="120"></p>'
. '<p><label for="gl-email">E-Mail</label><input id="gl-email" name="gl_email" type="email" required maxlength="190"></p>'
. '<p><label for="gl-nachricht">Nachricht</label><textarea id="gl-nachricht" name="gl_nachricht" required maxlength="5000"></textarea></p>'
. '<p><button type="submit">Anfrage senden</button></p></form>';
return $out;
}
add_action('admin_post_nopriv_gl_anfrage_senden', 'gl_anfragen_senden');
add_action('admin_post_gl_anfrage_senden', 'gl_anfragen_senden');
function gl_anfragen_senden() {
if (!isset($_POST['gl_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['gl_nonce'])), 'gl_anfrage_senden')) {
wp_die('Sicherheitspruefung fehlgeschlagen.', 'Abgewiesen', array('response' => 403));
}
$zurueck = isset($_POST['zurueck']) ? esc_url_raw(wp_unslash($_POST['zurueck'])) : home_url('/');
$zurueck = wp_validate_redirect($zurueck, home_url('/'));
$name = isset($_POST['gl_name']) ? sanitize_text_field(wp_unslash($_POST['gl_name'])) : '';
$email = isset($_POST['gl_email']) ? sanitize_email(wp_unslash($_POST['gl_email'])) : '';
$nachricht = isset($_POST['gl_nachricht']) ? sanitize_textarea_field(wp_unslash($_POST['gl_nachricht'])) : '';
if ($name === '' || !is_email($email) || $nachricht === '' || mb_strlen($nachricht) > 5000) {
wp_safe_redirect(add_query_arg('gl', 'fehler', $zurueck)); exit;
}
global $wpdb;
$wpdb->insert(gl_anfragen_tabelle(), array(
'erstellt' => current_time('mysql'), 'name' => mb_substr($name, 0, 120),
'email' => $email, 'nachricht' => $nachricht,
), array('%s', '%s', '%s', '%s'));
wp_safe_redirect(add_query_arg('gl', 'danke', $zurueck)); exit;
}
/* ---------- Admin: Einstellungen, Liste, CSV ---------- */
add_action('admin_init', function () {
register_setting('gl_anfragen', 'gl_anfragen_danke', array(
'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => 'Danke, wir melden uns.'));
add_settings_section('gl_anfragen_haupt', 'Formular', '__return_false', 'gl-anfragen');
add_settings_field('gl_anfragen_danke', 'Danke-Text', function () {
printf('<input type="text" class="regular-text" name="gl_anfragen_danke" value="%s">',
esc_attr(get_option('gl_anfragen_danke')));
}, 'gl-anfragen', 'gl_anfragen_haupt');
});
add_action('admin_menu', function () {
add_options_page('GL Anfragen', 'GL Anfragen', 'manage_options', 'gl-anfragen', 'gl_anfragen_einstellungen');
add_menu_page('Anfragen', 'Anfragen', 'manage_options', 'gl-anfragen-liste', 'gl_anfragen_liste', 'dashicons-email');
});
function gl_anfragen_einstellungen() {
if (!current_user_can('manage_options')) { return; }
echo '<div class="wrap"><h1>GL Anfragen</h1><form method="post" action="options.php">';
settings_fields('gl_anfragen');
do_settings_sections('gl-anfragen');
submit_button();
echo '</form></div>';
}
function gl_anfragen_liste() {
if (!current_user_can('manage_options')) { return; }
global $wpdb;
$zeilen = $wpdb->get_results('SELECT id, erstellt, name, email, nachricht FROM ' . gl_anfragen_tabelle() . ' ORDER BY id DESC LIMIT 200');
$csv = wp_nonce_url(admin_url('admin-post.php?action=gl_anfragen_csv'), 'gl_anfragen_csv');
echo '<div class="wrap"><h1>Anfragen</h1><p><a class="button" href="' . esc_url($csv) . '">CSV exportieren</a></p>';
echo '<table class="widefat striped gl-anfragen"><thead><tr><th>ID</th><th>Datum</th><th>Name</th><th>E-Mail</th><th>Nachricht</th></tr></thead><tbody>';
foreach ($zeilen as $z) {
printf('<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>', (int) $z->id, esc_html($z->erstellt),
esc_html($z->name), esc_html($z->email), esc_html($z->nachricht));
}
echo '</tbody></table></div>';
}
function gl_anfragen_csv_wert($v) {
$v = (string) $v;
return ($v !== '' && strpos('=+-@', $v[0]) !== false) ? "'" . $v : $v; // Formel-Injektion in Excel verhindern
}
add_action('admin_post_gl_anfragen_csv', function () {
if (!current_user_can('manage_options')) { wp_die('Keine Berechtigung.', 'Abgewiesen', array('response' => 403)); }
check_admin_referer('gl_anfragen_csv');
global $wpdb;
$zeilen = $wpdb->get_results('SELECT id, erstellt, name, email, nachricht FROM ' . gl_anfragen_tabelle() . ' ORDER BY id', ARRAY_A);
nocache_headers();
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=anfragen-' . gmdate('Y-m-d') . '.csv');
$f = fopen('php://output', 'w');
fwrite($f, "\xEF\xBB\xBF");
fputcsv($f, array('id', 'erstellt', 'name', 'email', 'nachricht'), ';', '"', '\\');
foreach ($zeilen as $z) { fputcsv($f, array_map('gl_anfragen_csv_wert', $z), ';', '"', '\\'); }
fclose($f);
exit;
});
So etwas für deinen Betrieb?
Beschreib kurz, was du brauchst. Du bekommst einen Festpreis, bevor ich anfange; ohne dein Ja passiert nichts.