feat(07-01): add captcha widget and error display

- Render validation errors from transient at form top
- Display error summary with red border
- Integrate captcha widget in submit section
- Position captcha above submit button
- Delete transient after displaying errors

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-16 12:30:35 +09:00
parent 78102c0ab4
commit 64f25041ad

View File

@@ -0,0 +1,370 @@
<?php
/**
* Form Renderer
*
* Generates HTML for the umzugsliste form
*
* @package Umzugsliste
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Form renderer class
*/
class Umzugsliste_Form_Renderer {
/**
* Render complete form
*
* @return string Complete form HTML
*/
public static function render() {
ob_start();
?>
<div class="umzugsliste-wrapper">
<form id="umzugsliste-form" name="umzug" method="post" action="">
<?php
self::render_validation_errors();
self::render_header();
self::render_date_selector();
self::render_customer_info();
self::render_all_rooms();
self::render_grand_totals();
self::render_submit_section();
?>
</form>
</div>
<?php
return ob_get_clean();
}
/**
* Render validation errors if any exist
*/
private static function render_validation_errors() {
// Check for validation errors in transient
$session_id = session_id();
if ( empty( $session_id ) ) {
$session_id = 'default';
}
$errors = get_transient( 'umzugsliste_errors_' . $session_id );
if ( ! $errors || empty( $errors['messages'] ) ) {
return;
}
// Delete transient after displaying
delete_transient( 'umzugsliste_errors_' . $session_id );
?>
<div class="validation-summary">
<h3>Bitte korrigieren Sie folgende Fehler:</h3>
<ul>
<?php foreach ( $errors['messages'] as $message ) : ?>
<li><?php echo esc_html( $message ); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php
}
/**
* Render form header with logo and company info
*/
private static function render_header() {
$plugin_url = plugin_dir_url( dirname( __FILE__ ) );
?>
<div class="row">
<div class="medium-6 columns">
<h1>Umzugsliste</h1>
</div>
<div class="medium-6 columns">
<p><br>Willi-Werner-Straße 6 &middot; 65199 Wiesbaden<br>
E-Mail: <a href="mailto:info@siegel-umzug.de">info@siegel-umzug.de</a><br>
Telefon (06 11) 2 20 20 &middot; Fax (06 11) 2 10 10<br>
Mainz: Telefon (0 61 31) 22 21 41
</p>
</div>
</div>
<?php
}
/**
* Render moving date selector
*/
private static function render_date_selector() {
?>
<div class="row">
<div class="large-6 columns">
<fieldset>
<legend>Voraussichtlicher Umzugstermin</legend>
<?php
echo Umzugsliste_Date_Helpers::render_day_select();
echo Umzugsliste_Date_Helpers::render_month_select();
echo Umzugsliste_Date_Helpers::render_year_select();
?>
</fieldset>
</div>
<div class="large-6 columns">
<p><br>In unserer <a href="http://siegel-umzug.de/datenschutz.html">Datenschutzerklärung</a> erfahren Sie, wie die Siegel Umzüge GmbH & Co. KG Ihre Daten erfasst und verwendet.</p>
</div>
</div>
<?php
}
/**
* Render customer info section (Beladeadresse and Entladeadresse)
*/
private static function render_customer_info() {
?>
<div class="row">
<div class="large-6 columns">
<div class="panel">
<h3>Beladeadresse</h3>
</div>
<div class="small-12">
<?php self::render_address_field( 'Name*', 'bName', true ); ?>
<?php self::render_address_field( 'Straße*', 'bStrasse', true ); ?>
<?php self::render_address_field( 'PLZ/Ort*', 'bort', true ); ?>
<?php self::render_address_field( 'Geschoss', 'info[bGeschoss]' ); ?>
<?php self::render_lift_field( 'info[bLift]' ); ?>
<?php self::render_address_field( 'Telefon*', 'bTelefon', true ); ?>
<?php self::render_address_field( 'Telefax', 'info[bTelefax]' ); ?>
<?php self::render_address_field( 'Mobil', 'info[bMobil]' ); ?>
<?php self::render_address_field( 'E-Mail*', 'info[eE-Mail]', true ); ?>
</div>
</div>
<div class="large-6 columns">
<div class="panel">
<h3>Entladeadresse</h3>
</div>
<div class="small-12">
<?php self::render_address_field( 'Name*', 'eName', true ); ?>
<?php self::render_address_field( 'Straße*', 'eStrasse', true ); ?>
<?php self::render_address_field( 'PLZ/Ort*', 'eort', true ); ?>
<?php self::render_address_field( 'Geschoss', 'info[eGeschoss]' ); ?>
<?php self::render_lift_field( 'info[eLift]' ); ?>
<?php self::render_address_field( 'Telefon', 'eTelefon' ); ?>
<?php self::render_address_field( 'Telefax', 'info[eTelefax]' ); ?>
<?php self::render_address_field( 'Mobil', 'info[eMobil]' ); ?>
</div>
</div>
<div class="large-12 columns">
<div class="row">
<div class="small-11 columns">
<p><span class="radius secondary label">*Pflichtfelder</span></p>
</div>
<div class="small-1 columns"></div>
</div>
</div>
</div>
<?php
}
/**
* Render single address field
*
* @param string $label Field label
* @param string $name Field name
* @param bool $required Whether field is required
*/
private static function render_address_field( $label, $name, $required = false ) {
?>
<div class="row">
<div class="small-3 columns">
<label for="<?php echo esc_attr( $name ); ?>" class="left inline"><?php echo esc_html( $label ); ?></label>
</div>
<div class="small-9 columns">
<input type="text" id="<?php echo esc_attr( $name ); ?>" name="<?php echo esc_attr( $name ); ?>" <?php echo $required ? 'required' : ''; ?>>
</div>
</div>
<?php
}
/**
* Render lift radio field
*
* @param string $name Field name
*/
private static function render_lift_field( $name ) {
?>
<div class="row">
<div class="small-3 columns">
<label class="left">Lift</label>
</div>
<div class="small-9 columns">
<input type="radio" name="<?php echo esc_attr( $name ); ?>" value="nein" checked><label>Nein</label>
<input type="radio" name="<?php echo esc_attr( $name ); ?>" value="ja"><label>Ja</label>
</div>
</div>
<?php
}
/**
* Render all room sections
*/
private static function render_all_rooms() {
$rooms = Umzugsliste_Furniture_Data::get_rooms();
foreach ( $rooms as $room_key => $room_label ) {
self::render_room_section( $room_key, $room_label );
}
}
/**
* Render single room section
*
* @param string $room_key Room key
* @param string $room_label Room label
*/
private static function render_room_section( $room_key, $room_label ) {
$items = Umzugsliste_Furniture_Data::get_furniture_items( $room_key );
// Navigation anchor based on room
$anchor_map = array(
'wohnzimmer' => 'wohn',
'schlafzimmer' => 'schlaf',
'arbeitszimmer' => 'arbeit',
'bad' => 'bad',
'kueche_esszimmer' => 'kueche',
'kinderzimmer' => 'kinder',
'keller' => 'keller',
);
$anchor = isset( $anchor_map[ $room_key ] ) ? $anchor_map[ $room_key ] : $room_key;
// Post array name (capitalize first letter for legacy compatibility)
$post_array_name = ucfirst( $room_key );
// Special case for Küche/Esszimmer
if ( 'kueche_esszimmer' === $room_key ) {
$post_array_name = 'Kueche_Esszimmer';
}
?>
<div class="row">
<div class="large-12 columns">
<div class="panel">
<a name="<?php echo esc_attr( $anchor ); ?>"></a>
<h3 data-magellan-destination="<?php echo esc_attr( $anchor ); ?>"><?php echo esc_html( $room_label ); ?></h3>
</div>
</div>
</div>
<div class="row">
<div class="large-12 columns" style="margin: 10px 0px; overflow-x: auto;">
<table width="100%" data-room="<?php echo esc_attr( $room_key ); ?>">
<thead>
<tr>
<th>Anzahl</th>
<th>Bezeichnung</th>
<th>qbm</th>
<th id="thsmall">Montage?</th>
</tr>
</thead>
<tbody>
<tr>
<td>&nbsp;</td>
<td><strong><?php echo esc_html( $room_label ); ?></strong></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<?php
foreach ( $items as $item ) {
self::render_furniture_row( $post_array_name, $room_key, $item );
}
?>
</tbody>
<tfoot>
<tr class="room-totals">
<th class="room-total-quantity" align="right">0</th>
<th align="left">Summe <?php echo esc_html( $room_label ); ?></th>
<th colspan="2" class="room-total-cbm" align="right">0,00</th>
<th>&nbsp;</th>
</tr>
</tfoot>
</table>
</div>
</div>
<?php
}
/**
* Render single furniture row
*
* @param string $room_name Room post array name
* @param string $room_key Room key
* @param array $item Furniture item data
*/
private static function render_furniture_row( $room_name, $room_key, $item ) {
$item_name = $item['name'];
$cbm = $item['cbm'];
$has_montage = $item['montage'];
// Generate field names matching legacy format
$quantity_name = $room_name . '[v' . $item_name . ']';
$cbm_name = $room_name . '[q' . $item_name . ']';
$montage_name = $room_name . '[m' . $item_name . ']';
?>
<tr class="furniture-row" data-room="<?php echo esc_attr( $room_key ); ?>" data-cbm="<?php echo esc_attr( $cbm ); ?>" data-item="<?php echo esc_attr( $item_name ); ?>">
<td><input type="text" name="<?php echo esc_attr( $quantity_name ); ?>" class="quantity-input" size="2" maxlength="3"></td>
<td><?php echo esc_html( $item_name ); ?></td>
<td><?php echo esc_html( str_replace( '.', ',', (string) $cbm ) ); ?></td>
<input type="hidden" name="<?php echo esc_attr( $cbm_name ); ?>" value="<?php echo esc_attr( $cbm ); ?>">
<td>
<?php if ( $has_montage ) : ?>
<input type="radio" name="<?php echo esc_attr( $montage_name ); ?>" value="ja"><label>Ja</label>
<input type="radio" name="<?php echo esc_attr( $montage_name ); ?>" value="nein" checked><label>Nein</label>
<?php endif; ?>
</td>
</tr>
<?php
}
/**
* Render grand totals section
*/
private static function render_grand_totals() {
?>
<div class="row">
<div class="large-12 columns">
<div class="panel" id="grand-total-section">
<h3>Gesamtsumme</h3>
<table width="100%">
<tr class="grand-totals">
<th align="right" id="grand-total-quantity" style="width: 10%;">0</th>
<th align="left" style="width: 40%;">Gesamtsumme aller Zimmer</th>
<th colspan="2" align="right" id="grand-total-cbm" style="width: 40%;">0,00</th>
<th style="width: 10%;">&nbsp;</th>
</tr>
</table>
</div>
</div>
</div>
<?php
}
/**
* Render submit section
*/
private static function render_submit_section() {
?>
<div class="row">
<div class="large-12 columns">
<?php
// Render captcha widget if enabled
$captcha = Umzugsliste_Captcha::get_instance();
if ( $captcha->is_enabled() ) {
echo $captcha->render_widget();
echo '<div style="margin-bottom: 1rem;"></div>';
}
?>
<?php wp_nonce_field( 'umzugsliste_submit', 'umzugsliste_nonce' ); ?>
<input type="hidden" name="umzugsliste_submit" value="1">
<button type="submit" class="button">Anfrage absenden</button>
</div>
</div>
<?php
}
}