feat: add standalone form page, close all audit gaps, pass v1.0 milestone

Add standalone form page template that bypasses the theme, with admin
setting and auto-creation on plugin activation. Fix reCAPTCHA v3 double
submission, remove jQuery dependency, extend localized JS strings, and
overhaul form CSS/JS. Update milestone audit to PASSED (9/9, 10/10, 5/5).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 12:08:52 +09:00
parent a9b1f2eb40
commit c0021befe2
14 changed files with 2382 additions and 1393 deletions

View File

@@ -75,6 +75,35 @@ class Umzugsliste_Captcha {
return get_option( 'umzugsliste_captcha_secret_key', '' );
}
/**
* Get the captcha provider script URL
*
* @return string Script URL or empty string
*/
public function get_script_url() {
if ( ! $this->is_enabled() ) {
return '';
}
$provider = $this->get_provider();
$site_key = $this->get_site_key();
if ( empty( $site_key ) ) {
return '';
}
switch ( $provider ) {
case 'recaptcha_v2':
return 'https://www.google.com/recaptcha/api.js';
case 'recaptcha_v3':
return 'https://www.google.com/recaptcha/api.js?render=' . $site_key;
case 'hcaptcha':
return 'https://js.hcaptcha.com/1/api.js';
default:
return '';
}
}
/**
* Enqueue captcha provider scripts
*/
@@ -159,10 +188,14 @@ class Umzugsliste_Captcha {
var form = document.getElementById('umzugsliste-form');
if (form) {
form.addEventListener('submit', function(e) {
var tokenField = document.getElementById('g-recaptcha-response');
if (tokenField && tokenField.value) {
return;
}
e.preventDefault();
grecaptcha.execute('<?php echo esc_js( $site_key ); ?>', {action: 'submit'}).then(function(token) {
document.getElementById('g-recaptcha-response').value = token;
form.submit();
tokenField.value = token;
form.requestSubmit();
});
});
}

View File

@@ -27,7 +27,7 @@ class Umzugsliste_Date_Helpers {
$selected = (int) current_time( 'j' );
}
$html = '<div class="small-4 columns"><label>' . esc_html__( 'Day', 'siegel-umzugsliste' ) . '</label><select name="day" class="Stil2">';
$html = '<div class="date-field"><label>' . esc_html__( 'Day', 'siegel-umzugsliste' ) . '</label><select name="day">';
for ( $i = 1; $i <= 31; $i++ ) {
$sel = ( $i === $selected ) ? ' selected' : '';
@@ -50,7 +50,7 @@ class Umzugsliste_Date_Helpers {
$selected = (int) current_time( 'n' );
}
$html = '<div class="small-4 columns"><label>' . esc_html__( 'Month', 'siegel-umzugsliste' ) . '</label><select name="month" class="Stil2">';
$html = '<div class="date-field"><label>' . esc_html__( 'Month', 'siegel-umzugsliste' ) . '</label><select name="month">';
for ( $i = 1; $i <= 12; $i++ ) {
$sel = ( $i === $selected ) ? ' selected' : '';
@@ -73,7 +73,7 @@ class Umzugsliste_Date_Helpers {
$selected = (int) current_time( 'Y' );
}
$html = '<div class="small-4 columns"><label>' . esc_html__( 'Year', 'siegel-umzugsliste' ) . '</label><select name="year" class="Stil2">';
$html = '<div class="date-field"><label>' . esc_html__( 'Year', 'siegel-umzugsliste' ) . '</label><select name="year">';
// Show current year plus 15 years (matching legacy)
$current_year = (int) current_time( 'Y' );

View File

@@ -2,7 +2,7 @@
/**
* Form Renderer
*
* Generates HTML for the umzugsliste form
* Generates HTML for the umzugsliste multi-step wizard form
*
* @package Umzugsliste
*/
@@ -16,27 +16,71 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
class Umzugsliste_Form_Renderer {
/**
* Wizard step definitions
*
* @return array Step number => label
*/
private static function get_steps() {
return array(
1 => __( 'Moving Date & Addresses', 'siegel-umzugsliste' ),
2 => __( 'Living Room', 'siegel-umzugsliste' ),
3 => __( 'Bedroom', 'siegel-umzugsliste' ),
4 => __( 'Study', 'siegel-umzugsliste' ),
5 => __( 'Bathroom & Kitchen', 'siegel-umzugsliste' ),
6 => __( 'Children\'s Room', 'siegel-umzugsliste' ),
7 => __( 'Basement/Storage', 'siegel-umzugsliste' ),
8 => __( 'Additional Work', 'siegel-umzugsliste' ),
9 => __( 'Summary', 'siegel-umzugsliste' ),
);
}
/**
* Render complete form
*
* @return string Complete form HTML
*/
public static function render() {
$steps = self::get_steps();
$form_id = 'umzug_' . uniqid( '', true );
ob_start();
?>
<div class="umzugsliste-wrapper">
<div class="umzugsliste-wizard">
<?php self::render_validation_errors(); ?>
<?php self::render_progress_bar( $steps ); ?>
<div class="running-totals" id="running-totals">
<span class="running-totals-label"><?php echo esc_html__( 'Total', 'siegel-umzugsliste' ); ?>:</span>
<span class="running-totals-qty" id="running-total-qty">0</span> <?php echo esc_html__( 'Items', 'siegel-umzugsliste' ); ?>
<span class="running-totals-sep">&middot;</span>
<span class="running-totals-cbm" id="running-total-cbm">0,00</span> <?php echo esc_html__( 'cbm', 'siegel-umzugsliste' ); ?>
</div>
<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_additional_work_sections();
self::render_sonstiges_field();
self::render_grand_totals();
self::render_submit_section();
// Step 1: Moving date & Addresses
self::render_step_1();
// Step 2: Wohnzimmer
self::render_room_step( 2, 'wohnzimmer' );
// Step 3: Schlafzimmer
self::render_room_step( 3, 'schlafzimmer' );
// Step 4: Arbeitszimmer
self::render_room_step( 4, 'arbeitszimmer' );
// Step 5: Bad & Kueche/Esszimmer (combined)
self::render_step_5();
// Step 6: Kinderzimmer
self::render_room_step( 6, 'kinderzimmer' );
// Step 7: Keller
self::render_room_step( 7, 'keller' );
// Step 8: Additional work
self::render_step_8();
// Step 9: Summary
self::render_step_9( $form_id );
?>
<div class="wizard-nav">
<button type="button" class="wizard-btn wizard-btn-back" id="wizard-back" style="display:none;"><?php echo esc_html__( 'Back', 'siegel-umzugsliste' ); ?></button>
<button type="button" class="wizard-btn wizard-btn-next" id="wizard-next"><?php echo esc_html__( 'Next', 'siegel-umzugsliste' ); ?></button>
<button type="submit" class="wizard-btn wizard-btn-submit" id="wizard-submit" style="display:none;"><?php echo esc_html__( 'Submit Request', 'siegel-umzugsliste' ); ?></button>
</div>
</form>
</div>
<?php
@@ -47,20 +91,16 @@ class Umzugsliste_Form_Renderer {
* Render validation errors if any exist
*/
private static function render_validation_errors() {
// Check for validation errors in transient using form_id from GET parameter
$form_id = isset( $_GET['form_id'] ) ? sanitize_text_field( $_GET['form_id'] ) : '';
if ( empty( $form_id ) ) {
return;
}
$errors = get_transient( 'umzugsliste_errors_' . $form_id );
if ( ! $errors || empty( $errors['messages'] ) ) {
return;
}
// Delete transient after displaying
delete_transient( 'umzugsliste_errors_' . $form_id );
?>
<div class="validation-summary">
@@ -75,102 +115,226 @@ class Umzugsliste_Form_Renderer {
}
/**
* Render form header with logo and company info
* Render progress bar
*
* @param array $steps Step definitions
*/
private static function render_header() {
$plugin_url = plugin_dir_url( dirname( __FILE__ ) );
private static function render_progress_bar( $steps ) {
?>
<div class="row">
<div class="medium-6 columns">
<h1><?php echo esc_html__( 'Moving List', 'siegel-umzugsliste' ); ?></h1>
<div class="progress-bar" id="progress-bar">
<div class="progress-track">
<div class="progress-fill" id="progress-fill"></div>
</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 class="progress-steps">
<?php foreach ( $steps as $num => $label ) : ?>
<div class="progress-dot" data-step="<?php echo esc_attr( $num ); ?>" title="<?php echo esc_attr( $label ); ?>">
<span class="dot-number"><?php echo esc_html( $num ); ?></span>
<span class="dot-label"><?php echo esc_html( $label ); ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
<?php
}
/**
* Render moving date selector
* Step 1: Moving date & Addresses
*/
private static function render_date_selector() {
private static function render_step_1() {
?>
<div class="row">
<div class="large-6 columns">
<fieldset>
<legend><?php echo esc_html__( 'Expected Moving Date', 'siegel-umzugsliste' ); ?></legend>
<div class="wizard-step active" data-step="1">
<h2 class="step-title"><?php echo esc_html__( 'Moving Date & Addresses', 'siegel-umzugsliste' ); ?></h2>
<div class="step-card">
<h3><?php echo esc_html__( 'Expected Moving Date', 'siegel-umzugsliste' ); ?></h3>
<div class="date-selector">
<?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><?php
/* translators: %s: link to privacy policy */
</div>
<p class="privacy-note"><?php
printf(
esc_html__( 'In our %s you can learn how Siegel Umzuege GmbH & Co. KG collects and uses your data.', 'siegel-umzugsliste' ),
'<a href="http://siegel-umzug.de/datenschutz.html">' . esc_html__( 'Privacy Policy', 'siegel-umzugsliste' ) . '</a>'
'<a href="http://siegel-umzug.de/datenschutz.html" target="_blank" rel="noopener">' . esc_html__( 'Privacy Policy', 'siegel-umzugsliste' ) . '</a>'
);
?></p>
</div>
<div class="address-grid">
<div class="step-card">
<h3><?php echo esc_html__( 'Loading Address', 'siegel-umzugsliste' ); ?></h3>
<?php
self::render_address_field( __( 'Name', 'siegel-umzugsliste' ), 'bName', true );
self::render_address_field( __( 'Street', 'siegel-umzugsliste' ), 'bStrasse', true );
self::render_address_field( __( 'ZIP/City', 'siegel-umzugsliste' ), 'bort', true );
self::render_address_field( __( 'Floor', 'siegel-umzugsliste' ), 'info[bGeschoss]' );
self::render_lift_field( 'info[bLift]' );
self::render_address_field( __( 'Phone', 'siegel-umzugsliste' ), 'bTelefon', true );
self::render_address_field( __( 'Fax', 'siegel-umzugsliste' ), 'info[bTelefax]' );
self::render_address_field( __( 'Mobile', 'siegel-umzugsliste' ), 'info[bMobil]' );
self::render_address_field( __( 'Email', 'siegel-umzugsliste' ), 'info[eE-Mail]', true, 'email' );
?>
</div>
<div class="step-card">
<h3><?php echo esc_html__( 'Unloading Address', 'siegel-umzugsliste' ); ?></h3>
<?php
self::render_address_field( __( 'Name', 'siegel-umzugsliste' ), 'eName', true );
self::render_address_field( __( 'Street', 'siegel-umzugsliste' ), 'eStrasse', true );
self::render_address_field( __( 'ZIP/City', 'siegel-umzugsliste' ), 'eort', true );
self::render_address_field( __( 'Floor', 'siegel-umzugsliste' ), 'info[eGeschoss]' );
self::render_lift_field( 'info[eLift]' );
self::render_address_field( __( 'Phone', 'siegel-umzugsliste' ), 'eTelefon' );
self::render_address_field( __( 'Fax', 'siegel-umzugsliste' ), 'info[eTelefax]' );
self::render_address_field( __( 'Mobile', 'siegel-umzugsliste' ), 'info[eMobil]' );
?>
</div>
</div>
<p class="required-note"><?php echo esc_html__( '* Required fields', 'siegel-umzugsliste' ); ?></p>
</div>
<?php
}
/**
* Render a single room step
*
* @param int $step_num Step number
* @param string $room_key Room key
*/
private static function render_room_step( $step_num, $room_key ) {
$rooms = Umzugsliste_Furniture_Data::get_rooms();
$room_label = isset( $rooms[ $room_key ] ) ? $rooms[ $room_key ] : $room_key;
$items = Umzugsliste_Furniture_Data::get_furniture_items( $room_key );
$post_array_name = ucfirst( $room_key );
if ( 'kueche_esszimmer' === $room_key ) {
$post_array_name = 'Kueche_Esszimmer';
}
?>
<div class="wizard-step" data-step="<?php echo esc_attr( $step_num ); ?>">
<h2 class="step-title"><?php echo esc_html( $room_label ); ?></h2>
<div class="step-card">
<div class="furniture-list" data-room="<?php echo esc_attr( $room_key ); ?>">
<?php
foreach ( $items as $item ) {
self::render_furniture_item( $post_array_name, $room_key, $item );
}
?>
<div class="room-totals" data-room="<?php echo esc_attr( $room_key ); ?>">
<span class="room-total-label"><?php echo esc_html__( 'Total', 'siegel-umzugsliste' ) . ' ' . esc_html( $room_label ); ?>:</span>
<span class="room-total-quantity">0</span> <?php echo esc_html__( 'Items', 'siegel-umzugsliste' ); ?>
<span class="room-totals-sep">&middot;</span>
<span class="room-total-cbm">0,00</span> <?php echo esc_html__( 'cbm', 'siegel-umzugsliste' ); ?>
</div>
</div>
</div>
</div>
<?php
}
/**
* Render customer info section (Beladeadresse and Entladeadresse)
* Step 5: Bad + Kueche/Esszimmer combined
*/
private static function render_customer_info() {
private static function render_step_5() {
$rooms = Umzugsliste_Furniture_Data::get_rooms();
?>
<div class="row">
<div class="large-6 columns">
<div class="panel">
<h3><?php echo esc_html__( 'Loading Address', 'siegel-umzugsliste' ); ?></h3>
</div>
<div class="small-12">
<?php self::render_address_field( __( 'Name*', 'siegel-umzugsliste' ), 'bName', true ); ?>
<?php self::render_address_field( __( 'Street*', 'siegel-umzugsliste' ), 'bStrasse', true ); ?>
<?php self::render_address_field( __( 'ZIP/City*', 'siegel-umzugsliste' ), 'bort', true ); ?>
<?php self::render_address_field( __( 'Floor', 'siegel-umzugsliste' ), 'info[bGeschoss]' ); ?>
<?php self::render_lift_field( 'info[bLift]' ); ?>
<?php self::render_address_field( __( 'Phone*', 'siegel-umzugsliste' ), 'bTelefon', true ); ?>
<?php self::render_address_field( __( 'Fax', 'siegel-umzugsliste' ), 'info[bTelefax]' ); ?>
<?php self::render_address_field( __( 'Mobile', 'siegel-umzugsliste' ), 'info[bMobil]' ); ?>
<?php self::render_address_field( __( 'Email*', 'siegel-umzugsliste' ), 'info[eE-Mail]', true ); ?>
</div>
</div>
<div class="wizard-step" data-step="5">
<h2 class="step-title"><?php echo esc_html( $rooms['bad'] ); ?> &amp; <?php echo esc_html( $rooms['kueche_esszimmer'] ); ?></h2>
<div class="large-6 columns">
<div class="panel">
<h3><?php echo esc_html__( 'Unloading Address', 'siegel-umzugsliste' ); ?></h3>
</div>
<div class="small-12">
<?php self::render_address_field( __( 'Name*', 'siegel-umzugsliste' ), 'eName', true ); ?>
<?php self::render_address_field( __( 'Street*', 'siegel-umzugsliste' ), 'eStrasse', true ); ?>
<?php self::render_address_field( __( 'ZIP/City*', 'siegel-umzugsliste' ), 'eort', true ); ?>
<?php self::render_address_field( __( 'Floor', 'siegel-umzugsliste' ), 'info[eGeschoss]' ); ?>
<?php self::render_lift_field( 'info[eLift]' ); ?>
<?php self::render_address_field( __( 'Phone', 'siegel-umzugsliste' ), 'eTelefon' ); ?>
<?php self::render_address_field( __( 'Fax', 'siegel-umzugsliste' ), 'info[eTelefax]' ); ?>
<?php self::render_address_field( __( 'Mobile', 'siegel-umzugsliste' ), 'info[eMobil]' ); ?>
</div>
</div>
<div class="large-12 columns">
<div class="row">
<div class="small-11 columns">
<p><span class="radius secondary label"><?php echo esc_html__( '*Required fields', 'siegel-umzugsliste' ); ?></span></p>
<div class="step-card">
<h3><?php echo esc_html( $rooms['bad'] ); ?></h3>
<div class="furniture-list" data-room="bad">
<?php
$bad_items = Umzugsliste_Furniture_Data::get_furniture_items( 'bad' );
foreach ( $bad_items as $item ) {
self::render_furniture_item( 'Bad', 'bad', $item );
}
?>
<div class="room-totals" data-room="bad">
<span class="room-total-label"><?php echo esc_html__( 'Total', 'siegel-umzugsliste' ) . ' ' . esc_html( $rooms['bad'] ); ?>:</span>
<span class="room-total-quantity">0</span> <?php echo esc_html__( 'Items', 'siegel-umzugsliste' ); ?>
<span class="room-totals-sep">&middot;</span>
<span class="room-total-cbm">0,00</span> <?php echo esc_html__( 'cbm', 'siegel-umzugsliste' ); ?>
</div>
<div class="small-1 columns"></div>
</div>
</div>
<div class="step-card">
<h3><?php echo esc_html( $rooms['kueche_esszimmer'] ); ?></h3>
<div class="furniture-list" data-room="kueche_esszimmer">
<?php
$kueche_items = Umzugsliste_Furniture_Data::get_furniture_items( 'kueche_esszimmer' );
foreach ( $kueche_items as $item ) {
self::render_furniture_item( 'Kueche_Esszimmer', 'kueche_esszimmer', $item );
}
?>
<div class="room-totals" data-room="kueche_esszimmer">
<span class="room-total-label"><?php echo esc_html__( 'Total', 'siegel-umzugsliste' ) . ' ' . esc_html( $rooms['kueche_esszimmer'] ); ?>:</span>
<span class="room-total-quantity">0</span> <?php echo esc_html__( 'Items', 'siegel-umzugsliste' ); ?>
<span class="room-totals-sep">&middot;</span>
<span class="room-total-cbm">0,00</span> <?php echo esc_html__( 'cbm', 'siegel-umzugsliste' ); ?>
</div>
</div>
</div>
</div>
<?php
}
/**
* Step 8: Additional Work + Sonstiges
*/
private static function render_step_8() {
$sections = Umzugsliste_Furniture_Data::get_additional_work();
?>
<div class="wizard-step" data-step="8">
<h2 class="step-title"><?php echo esc_html__( 'Additional Work', 'siegel-umzugsliste' ); ?></h2>
<?php foreach ( $sections as $section_key => $section_data ) : ?>
<div class="step-card">
<h3><?php echo esc_html( $section_data['label'] ); ?></h3>
<div class="additional-work-section" data-section="<?php echo esc_attr( $section_key ); ?>">
<?php
foreach ( $section_data['fields'] as $field ) {
$field_key = self::get_field_key( $field );
$field_name = 'additional_work[' . $section_key . '][' . $field_key . ']';
self::render_additional_field( $field, $field_name, $field_key );
}
?>
</div>
</div>
<?php endforeach; ?>
<div class="step-card">
<h3><?php echo esc_html__( 'Other', 'siegel-umzugsliste' ); ?></h3>
<label for="sonstiges"><?php echo esc_html__( 'Additional notes or requests:', 'siegel-umzugsliste' ); ?></label>
<textarea name="sonstiges" id="sonstiges" rows="5" class="sonstiges-textarea" placeholder="<?php echo esc_attr__( 'Additional notes or requests...', 'siegel-umzugsliste' ); ?>"></textarea>
</div>
</div>
<?php
}
/**
* Step 9: Summary + Captcha + Submit
*
* @param string $form_id Unique form ID
*/
private static function render_step_9( $form_id ) {
$captcha = Umzugsliste_Captcha::get_instance();
?>
<div class="wizard-step" data-step="9">
<h2 class="step-title"><?php echo esc_html__( 'Summary', 'siegel-umzugsliste' ); ?></h2>
<div id="wizard-summary"></div>
<?php
if ( $captcha->is_enabled() ) {
echo '<div class="step-card">';
echo $captcha->render_widget();
echo '</div>';
}
?>
<?php wp_nonce_field( 'umzugsliste_submit', 'umzugsliste_nonce' ); ?>
<input type="hidden" name="umzugsliste_submit" value="1">
<input type="hidden" name="umzugsliste_form_id" value="<?php echo esc_attr( $form_id ); ?>">
</div>
<?php
}
@@ -178,19 +342,17 @@ class Umzugsliste_Form_Renderer {
/**
* Render single address field
*
* @param string $label Field label
* @param string $name Field name
* @param string $label Field label
* @param string $name Field name
* @param bool $required Whether field is required
* @param string $type Input type
*/
private static function render_address_field( $label, $name, $required = false ) {
private static function render_address_field( $label, $name, $required = false, $type = 'text' ) {
$label_display = $required ? $label . '*' : $label;
?>
<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 class="form-group">
<label for="<?php echo esc_attr( $name ); ?>"><?php echo esc_html( $label_display ); ?></label>
<input type="<?php echo esc_attr( $type ); ?>" id="<?php echo esc_attr( $name ); ?>" name="<?php echo esc_attr( $name ); ?>" <?php echo $required ? 'required' : ''; ?>>
</div>
<?php
}
@@ -202,303 +364,101 @@ class Umzugsliste_Form_Renderer {
*/
private static function render_lift_field( $name ) {
?>
<div class="row">
<div class="small-3 columns">
<label class="left"><?php echo esc_html__( 'Elevator', 'siegel-umzugsliste' ); ?></label>
</div>
<div class="small-9 columns">
<input type="radio" name="<?php echo esc_attr( $name ); ?>" value="nein" checked><label><?php echo esc_html__( 'No', 'siegel-umzugsliste' ); ?></label>
<input type="radio" name="<?php echo esc_attr( $name ); ?>" value="ja"><label><?php echo esc_html__( 'Yes', 'siegel-umzugsliste' ); ?></label>
<div class="form-group form-group-radio">
<label><?php echo esc_html__( 'Elevator', 'siegel-umzugsliste' ); ?></label>
<div class="radio-group">
<label class="radio-label"><input type="radio" name="<?php echo esc_attr( $name ); ?>" value="nein" checked> <?php echo esc_html__( 'No', 'siegel-umzugsliste' ); ?></label>
<label class="radio-label"><input type="radio" name="<?php echo esc_attr( $name ); ?>" value="ja"> <?php echo esc_html__( 'Yes', 'siegel-umzugsliste' ); ?></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
* Render single furniture item card
*
* @param string $room_key Room key
* @param string $room_label Room label
* @param string $room_name Post array name
* @param string $room_key Room key
* @param array $item Furniture item data
*/
private static function render_room_section( $room_key, $room_label ) {
$items = Umzugsliste_Furniture_Data::get_furniture_items( $room_key );
private static function render_furniture_item( $room_name, $room_key, $item ) {
$item_name = $item['name'];
$cbm = $item['cbm'];
$has_montage = $item['montage'];
// 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><?php echo esc_html__( 'Quantity', 'siegel-umzugsliste' ); ?></th>
<th><?php echo esc_html__( 'Description', 'siegel-umzugsliste' ); ?></th>
<th><?php echo esc_html__( 'cbm', 'siegel-umzugsliste' ); ?></th>
<th id="thsmall"><?php echo esc_html__( 'Assembly?', 'siegel-umzugsliste' ); ?></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"><?php echo esc_html__( 'Total ', 'siegel-umzugsliste' ) . 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 . ']';
$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>
<div class="furniture-item" data-room="<?php echo esc_attr( $room_key ); ?>" data-cbm="<?php echo esc_attr( $cbm ); ?>">
<input type="text" name="<?php echo esc_attr( $quantity_name ); ?>" class="quantity-input" inputmode="decimal" placeholder="0" maxlength="3">
<span class="item-name"><?php echo esc_html( $item_name ); ?></span>
<span class="item-cbm"><?php echo esc_html( str_replace( '.', ',', (string) $cbm ) ); ?></span>
<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><?php echo esc_html__( 'Yes', 'siegel-umzugsliste' ); ?></label>
<input type="radio" name="<?php echo esc_attr( $montage_name ); ?>" value="nein" checked><label><?php echo esc_html__( 'No', 'siegel-umzugsliste' ); ?></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><?php echo esc_html__( 'Grand Total', 'siegel-umzugsliste' ); ?></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%;"><?php echo esc_html__( 'Grand total all rooms', 'siegel-umzugsliste' ); ?></th>
<th colspan="2" align="right" id="grand-total-cbm" style="width: 40%;">0,00</th>
<th style="width: 10%;">&nbsp;</th>
</tr>
</table>
<?php if ( $has_montage ) : ?>
<div class="montage-toggle">
<label class="radio-label"><input type="radio" name="<?php echo esc_attr( $montage_name ); ?>" value="nein" checked> <?php echo esc_html__( 'No', 'siegel-umzugsliste' ); ?></label>
<label class="radio-label"><input type="radio" name="<?php echo esc_attr( $montage_name ); ?>" value="ja"> <?php echo esc_html__( 'Yes', 'siegel-umzugsliste' ); ?></label>
</div>
</div>
<?php endif; ?>
</div>
<?php
}
/**
* Render submit section
*/
private static function render_submit_section() {
// Generate unique form ID
$form_id = 'umzug_' . uniqid( '', true );
?>
<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">
<input type="hidden" name="umzugsliste_form_id" value="<?php echo esc_attr( $form_id ); ?>">
<button type="submit" class="button"><?php echo esc_html__( 'Submit Request', 'siegel-umzugsliste' ); ?></button>
</div>
</div>
<?php
}
/**
* Render all additional work sections
*/
private static function render_additional_work_sections() {
$sections = Umzugsliste_Furniture_Data::get_additional_work();
foreach ( $sections as $section_key => $section_data ) {
self::render_additional_work_section( $section_key, $section_data );
}
}
/**
* Render single additional work section
* Render additional work field
*
* @param string $section_key Section key
* @param array $section_data Section data with label and fields
* @param array $field Field data
* @param string $field_name Form field name
* @param string $field_key Field key
*/
private static function render_additional_work_section( $section_key, $section_data ) {
?>
<div class="row">
<div class="large-12 columns">
<div class="panel">
<h3><?php echo esc_html( $section_data['label'] ); ?></h3>
private static function render_additional_field( $field, $field_name, $field_key ) {
switch ( $field['type'] ) {
case 'checkbox':
?>
<div class="additional-field additional-field-checkbox">
<label>
<input type="checkbox" name="<?php echo esc_attr( $field_name ); ?>" value="ja">
<?php echo esc_html( $field['name'] ); ?>
</label>
</div>
</div>
</div>
<div class="row">
<div class="large-12 columns">
<div class="additional-work-section" data-section="<?php echo esc_attr( $section_key ); ?>">
<?php
foreach ( $section_data['fields'] as $field ) {
$field_key = self::get_field_key( $field );
$field_name = 'additional_work[' . $section_key . '][' . $field_key . ']';
<?php
break;
switch ( $field['type'] ) {
case 'checkbox':
?>
<div class="row">
<div class="small-9 columns">
<label><?php echo esc_html( $field['name'] ); ?></label>
</div>
<div class="small-3 columns">
<input type="checkbox" name="<?php echo esc_attr( $field_name ); ?>" value="ja">
</div>
</div>
<?php
break;
case 'abbau_aufbau':
?>
<div class="row">
<div class="small-4 columns">
<label><?php echo esc_html( $field['name'] ); ?></label>
</div>
<div class="small-8 columns">
<input type="radio" name="<?php echo esc_attr( $field_name ); ?>" value="Abbau" id="<?php echo esc_attr( $field_key . '_abbau' ); ?>"><label for="<?php echo esc_attr( $field_key . '_abbau' ); ?>"><?php echo esc_html__( 'Disassembly', 'siegel-umzugsliste' ); ?></label>
<input type="radio" name="<?php echo esc_attr( $field_name ); ?>" value="Aufbau" id="<?php echo esc_attr( $field_key . '_aufbau' ); ?>"><label for="<?php echo esc_attr( $field_key . '_aufbau' ); ?>"><?php echo esc_html__( 'Assembly', 'siegel-umzugsliste' ); ?></label>
<input type="radio" name="<?php echo esc_attr( $field_name ); ?>" value="Beides" id="<?php echo esc_attr( $field_key . '_beides' ); ?>"><label for="<?php echo esc_attr( $field_key . '_beides' ); ?>"><?php echo esc_html__( 'Both', 'siegel-umzugsliste' ); ?></label>
</div>
</div>
<?php
break;
case 'checkbox_anzahl':
?>
<div class="row">
<div class="small-1 columns">
<input type="checkbox" name="<?php echo esc_attr( $field_name ); ?>" value="ja">
</div>
<div class="small-8 columns">
<label><?php echo esc_html( $field['name'] ); ?></label>
</div>
<div class="small-3 columns">
<input type="text" name="<?php echo esc_attr( $field_name . '_anzahl' ); ?>" size="4" placeholder="<?php echo esc_attr__( 'Qty.', 'siegel-umzugsliste' ); ?>">
</div>
</div>
<?php
break;
case 'text':
?>
<div class="row">
<div class="small-9 columns">
<label><?php echo esc_html( $field['name'] ); ?></label>
</div>
<div class="small-3 columns">
<input type="text" name="<?php echo esc_attr( $field_name ); ?>" size="6">
</div>
</div>
<?php
break;
}
}
?>
case 'abbau_aufbau':
?>
<div class="additional-field additional-field-abbau">
<span class="additional-field-label"><?php echo esc_html( $field['name'] ); ?></span>
<div class="radio-group">
<label class="radio-label"><input type="radio" name="<?php echo esc_attr( $field_name ); ?>" value="Abbau" id="<?php echo esc_attr( $field_key . '_abbau' ); ?>"> <?php echo esc_html__( 'Disassembly', 'siegel-umzugsliste' ); ?></label>
<label class="radio-label"><input type="radio" name="<?php echo esc_attr( $field_name ); ?>" value="Aufbau" id="<?php echo esc_attr( $field_key . '_aufbau' ); ?>"> <?php echo esc_html__( 'Assembly', 'siegel-umzugsliste' ); ?></label>
<label class="radio-label"><input type="radio" name="<?php echo esc_attr( $field_name ); ?>" value="Beides" id="<?php echo esc_attr( $field_key . '_beides' ); ?>"> <?php echo esc_html__( 'Both', 'siegel-umzugsliste' ); ?></label>
</div>
</div>
</div>
</div>
<?php
}
<?php
break;
/**
* Render Sonstiges free text field
*/
private static function render_sonstiges_field() {
?>
<div class="row">
<div class="large-12 columns">
<div class="panel">
<h3><?php echo esc_html__( 'Other', 'siegel-umzugsliste' ); ?></h3>
case 'checkbox_anzahl':
?>
<div class="additional-field additional-field-qty">
<label>
<input type="checkbox" name="<?php echo esc_attr( $field_name ); ?>" value="ja">
<?php echo esc_html( $field['name'] ); ?>
</label>
<input type="text" name="<?php echo esc_attr( $field_name . '_anzahl' ); ?>" class="qty-small" placeholder="<?php echo esc_attr__( 'Qty.', 'siegel-umzugsliste' ); ?>">
</div>
</div>
</div>
<div class="row">
<div class="large-12 columns">
<label for="sonstiges"><?php echo esc_html__( 'Additional notes or requests:', 'siegel-umzugsliste' ); ?></label>
<textarea name="sonstiges" id="sonstiges" rows="5" class="sonstiges-textarea" placeholder="<?php echo esc_attr__( 'Additional notes or requests...', 'siegel-umzugsliste' ); ?>"></textarea>
</div>
</div>
<?php
<?php
break;
case 'text':
?>
<div class="additional-field additional-field-text">
<label><?php echo esc_html( $field['name'] ); ?></label>
<input type="text" name="<?php echo esc_attr( $field_name ); ?>" class="qty-small">
</div>
<?php
break;
}
}
/**

View File

@@ -83,6 +83,17 @@ class Umzugsliste_Settings {
)
);
// Register form page setting
register_setting(
'umzugsliste_settings',
'umzugsliste_form_page_id',
array(
'type' => 'integer',
'sanitize_callback' => 'absint',
'default' => 0,
)
);
// Register thank you URL setting
register_setting(
'umzugsliste_settings',
@@ -154,6 +165,15 @@ class Umzugsliste_Settings {
'umzugsliste_settings'
);
// Add form page field
add_settings_field(
'umzugsliste_form_page_id',
__( 'Form Page', 'siegel-umzugsliste' ),
array( $this, 'render_form_page_field' ),
'umzugsliste_settings',
'umzugsliste_form_section'
);
// Add thank you URL field
add_settings_field(
'umzugsliste_thankyou_url',
@@ -249,6 +269,22 @@ class Umzugsliste_Settings {
<?php
}
/**
* Render form page dropdown field
*/
public function render_form_page_field() {
$value = get_option( 'umzugsliste_form_page_id', 0 );
wp_dropdown_pages( array(
'name' => 'umzugsliste_form_page_id',
'selected' => $value,
'show_option_none' => __( '-- Select Page --', 'siegel-umzugsliste' ),
'option_none_value' => 0,
) );
?>
<p class="description"><?php echo esc_html__( 'The page that displays the standalone moving list form (bypasses theme template).', 'siegel-umzugsliste' ); ?></p>
<?php
}
/**
* Render thank you URL field
*/

View File

@@ -2,7 +2,8 @@
/**
* Shortcode Handler
*
* Registers and handles the [umzugsliste] shortcode
* Registers and handles the [umzugsliste] shortcode.
* Legacy entry point - the primary entry point is the standalone form page.
*
* @package Umzugsliste
*/
@@ -61,8 +62,8 @@ class Umzugsliste_Shortcode {
* Enqueue CSS and JS assets
*/
public function enqueue_assets() {
$plugin_url = plugin_dir_url( dirname( __FILE__ ) );
$plugin_version = '1.0.0';
$plugin_url = plugin_dir_url( dirname( __FILE__ ) );
$plugin_version = UMZUGSLISTE_VERSION;
// Enqueue form CSS
wp_enqueue_style(
@@ -72,11 +73,11 @@ class Umzugsliste_Shortcode {
$plugin_version
);
// Enqueue form JS (placeholder for Phase 5)
// Enqueue form JS (vanilla JS, no jQuery dependency)
wp_enqueue_script(
'umzugsliste-form',
$plugin_url . 'assets/js/form.js',
array( 'jquery' ),
array(),
$plugin_version,
true
);
@@ -87,6 +88,26 @@ class Umzugsliste_Shortcode {
'invalidEmail' => __( 'Please enter a valid email address', 'siegel-umzugsliste' ),
'selectMovingDate' => __( 'Please select a complete moving date', 'siegel-umzugsliste' ),
'enterFurnitureItem' => __( 'Please enter at least one furniture item', 'siegel-umzugsliste' ),
'stepNext' => __( 'Next', 'siegel-umzugsliste' ),
'stepBack' => __( 'Back', 'siegel-umzugsliste' ),
'stepSubmit' => __( 'Submit Request', 'siegel-umzugsliste' ),
'summaryTitle' => __( 'Summary', 'siegel-umzugsliste' ),
'summaryMovingDate' => __( 'Moving Date', 'siegel-umzugsliste' ),
'summaryLoading' => __( 'Loading Address', 'siegel-umzugsliste' ),
'summaryUnloading' => __( 'Unloading Address', 'siegel-umzugsliste' ),
'summaryGrandTotal' => __( 'Grand Total', 'siegel-umzugsliste' ),
'summaryItems' => __( 'Items', 'siegel-umzugsliste' ),
'summaryCbm' => __( 'cbm', 'siegel-umzugsliste' ),
'summaryMontage' => __( 'Assembly', 'siegel-umzugsliste' ),
'summaryYes' => __( 'Yes', 'siegel-umzugsliste' ),
'summaryNo' => __( 'No', 'siegel-umzugsliste' ),
'summaryAdditional' => __( 'Additional Work', 'siegel-umzugsliste' ),
'summaryOther' => __( 'Other', 'siegel-umzugsliste' ),
'totalLabel' => __( 'Total', 'siegel-umzugsliste' ),
'roomTotalLabel' => __( 'Room Total', 'siegel-umzugsliste' ),
'grandTotalLabel' => __( 'Grand Total', 'siegel-umzugsliste' ),
'quantityLabel' => __( 'Qty', 'siegel-umzugsliste' ),
'cbmLabel' => __( 'cbm', 'siegel-umzugsliste' ),
) );
}
}