feat: add core implementation files for phases 4-6

Add missing implementation files and planning docs:
- Phase 04: Shortcode handler and date helpers for form rendering
- Phase 05: Planning documentation for volume calculations
- Phase 06: Email generator for legacy HTML table format

These complete the form rendering, calculation, and email system.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-16 23:06:23 +09:00
parent 82e856e098
commit 9c8ddc555c
11 changed files with 2307 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
<?php
/**
* Date Helper Functions
*
* Provides date dropdown selectors for the moving date field
*
* @package Umzugsliste
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Date helpers class
*/
class Umzugsliste_Date_Helpers {
/**
* Render day dropdown
*
* @param int $selected Selected day (1-31)
* @return string HTML for day dropdown
*/
public static function render_day_select( $selected = null ) {
if ( null === $selected ) {
$selected = (int) current_time( 'j' );
}
$html = '<div class="small-4 columns"><label>Tag</label><select name="day" class="Stil2">';
for ( $i = 1; $i <= 31; $i++ ) {
$sel = ( $i === $selected ) ? ' selected' : '';
$html .= '<option value="' . $i . '"' . $sel . '>' . $i . '</option>';
}
$html .= '</select></div>';
return $html;
}
/**
* Render month dropdown
*
* @param int $selected Selected month (1-12)
* @return string HTML for month dropdown
*/
public static function render_month_select( $selected = null ) {
if ( null === $selected ) {
$selected = (int) current_time( 'n' );
}
$html = '<div class="small-4 columns"><label>Monat</label><select name="month" class="Stil2">';
for ( $i = 1; $i <= 12; $i++ ) {
$sel = ( $i === $selected ) ? ' selected' : '';
$html .= '<option value="' . $i . '"' . $sel . '>' . $i . '</option>';
}
$html .= '</select></div>';
return $html;
}
/**
* Render year dropdown
*
* @param int $selected Selected year
* @return string HTML for year dropdown
*/
public static function render_year_select( $selected = null ) {
if ( null === $selected ) {
$selected = (int) current_time( 'Y' );
}
$html = '<div class="small-4 columns"><label>Jahr</label><select name="year" class="Stil2">';
// Show current year plus 15 years (matching legacy)
$current_year = (int) current_time( 'Y' );
for ( $i = 0; $i <= 15; $i++ ) {
$year = $current_year + $i;
$sel = ( $year === $selected ) ? ' selected' : '';
$html .= '<option value="' . $year . '"' . $sel . '>' . $year . '</option>';
}
$html .= '</select></div>';
return $html;
}
}

View File

@@ -0,0 +1,313 @@
<?php
/**
* Email Generator
*
* Generates HTML email matching legacy format exactly
*
* @package Umzugsliste
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Email generator class
*/
class Umzugsliste_Email_Generator {
/**
* Generate complete email HTML
*
* @param array $data Form submission data
* @return string Complete HTML email
*/
public static function generate( $data ) {
$content = '';
// Moving date
$content .= self::generate_date_section(
$data['day'] ?? '',
$data['month'] ?? '',
$data['year'] ?? ''
);
// Customer info
$content .= self::generate_customer_info_section( $data );
// All rooms
$content .= self::generate_all_rooms( $data );
// Grand totals
$content .= self::generate_grand_totals( $data );
// Wrap in HTML document
return self::wrap_html( $content );
}
/**
* Generate moving date section
*
* @param string $day Day
* @param string $month Month
* @param string $year Year
* @return string HTML
*/
private static function generate_date_section( $day, $month, $year ) {
return "<div class='row'>
<div class='large-6 columns'>
<fieldset>
<legend>Voraussichtlicher Umzugstermin</legend>
<p>" . esc_html( $day ) . "." . esc_html( $month ) . "." . esc_html( $year ) . "</p>
</fieldset>
</div>
</div>";
}
/**
* Generate customer info section
*
* @param array $data Form data
* @return string HTML
*/
private static function generate_customer_info_section( $data ) {
$info = $data['info'] ?? array();
// Build customer info array matching legacy structure
$info_array = array(
'bName' => $data['bName'] ?? '',
'eName' => $data['eName'] ?? '',
'bStraße' => $data['bStrasse'] ?? '',
'eStraße' => $data['eStrasse'] ?? '',
'bPLZ/Ort' => $data['bort'] ?? '',
'ePLZ/Ort' => $data['eort'] ?? '',
'bGeschoss' => $info['bGeschoss'] ?? '',
'eGeschoss' => $info['eGeschoss'] ?? '',
'bLift' => $info['bLift'] ?? 'nein',
'eLift' => $info['eLift'] ?? 'nein',
'bTelefon' => $data['bTelefon'] ?? '',
'eTelefon' => $data['eTelefon'] ?? '',
'bTelefax' => $info['bTelefax'] ?? '',
'eTelefax' => $info['eTelefax'] ?? '',
'bMobil' => $info['bMobil'] ?? '',
'eMobil' => $info['eMobil'] ?? '',
'eE-Mail' => $info['eE-Mail'] ?? '',
);
$html = "<div class='row'>
<div class='large-12 columns' style='margin: 10px 0px; overflow-x: auto;'>
<table width='100%'>
<thead>
<tr>
<th align='left' bgcolor='#CCCCCC' colspan='2'>Beladeadresse</th>
<th align='left' bgcolor='#CCCCCC' colspan='2'>Entladeadresse</th>
</tr>
</thead>
<tbody><tr>";
// Alternate between Belade and Entlade columns
$zumbruch = 'Nein';
foreach ( $info_array as $key => $value ) {
// Remove prefix (b or e) for label
$label = substr( $key, 1 );
$html .= '<td>' . $label . '</td>';
$html .= '<td>' . esc_html( $value ) . '</td>';
if ( 'Ja' === $zumbruch ) {
$html .= '</tr><tr>';
$zumbruch = 'Nein';
} else {
$zumbruch = 'Ja';
}
}
$html .= '</tr></tbody></table></div></div>';
return $html;
}
/**
* Generate all room sections
*
* @param array $data Form data
* @return string HTML
*/
private static function generate_all_rooms( $data ) {
$html = '';
$rooms = Umzugsliste_Furniture_Data::get_rooms();
foreach ( $rooms as $room_key => $room_label ) {
// Get post array name for this room
$post_array_name = ucfirst( $room_key );
if ( 'kueche_esszimmer' === $room_key ) {
$post_array_name = 'Kueche_Esszimmer';
}
// Get room data from submission
$room_data = $data[ $post_array_name ] ?? array();
// Only include room if it has items with quantities
if ( self::has_items_with_quantities( $room_data ) ) {
$html .= self::generate_room_section( $room_key, $room_label, $room_data );
}
}
return $html;
}
/**
* Check if room has any items with quantities
*
* @param array $room_data Room submission data
* @return bool True if has items
*/
private static function has_items_with_quantities( $room_data ) {
foreach ( $room_data as $key => $value ) {
if ( substr( $key, 0, 1 ) === 'v' && ! empty( $value ) && floatval( $value ) > 0 ) {
return true;
}
}
return false;
}
/**
* Generate single room section
*
* @param string $room_key Room key
* @param string $room_label Room label
* @param array $room_data Room submission data
* @return string HTML
*/
private static function generate_room_section( $room_key, $room_label, $room_data ) {
$html = "<div class='row'>
<div class='large-12 columns' style='margin: 10px 0px; overflow-x: auto;'>
<table width='100%'>
<thead>
<tr>
<th align='left' bgcolor='#CCCCCC' width='54'>Anzahl</th>
<th align='left' bgcolor='#CCCCCC'>Bezeichnung</th>
<th align='right' bgcolor='#CCCCCC'>qbm</th>
<th align='right' bgcolor='#CCCCCC'>Gesamt</th>
<th align='left' bgcolor='#CCCCCC'>Montage?</th>
</tr>
</thead>
<tbody>
<tr>
<td>&nbsp;</td>
<td><strong>" . esc_html( $room_label ) . "</strong></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>";
// Generate rows for each furniture item
$room_total_quantity = 0;
$room_total_cbm = 0;
// Process items in groups of v, q, m
$processed_items = array();
foreach ( $room_data as $key => $value ) {
if ( substr( $key, 0, 1 ) === 'v' ) {
$item_name = substr( $key, 1 );
if ( ! empty( $value ) && floatval( $value ) > 0 ) {
$quantity = floatval( str_replace( ',', '.', trim( $value ) ) );
$cbm = isset( $room_data[ 'q' . $item_name ] ) ? floatval( $room_data[ 'q' . $item_name ] ) : 0;
$montage = isset( $room_data[ 'm' . $item_name ] ) ? $room_data[ 'm' . $item_name ] : 'nein';
$item_total = $quantity * $cbm;
$room_total_quantity += $quantity;
$room_total_cbm += $item_total;
// Format for display
$cbm_display = str_replace( '.', ',', number_format( $cbm, 2, '.', '' ) );
$total_display = str_replace( '.', ',', number_format( $item_total, 2, '.', '' ) );
$html .= '<tr>';
$html .= '<td>' . esc_html( $value ) . '</td>';
$html .= '<td>' . esc_html( $item_name ) . '</td>';
$html .= "<td align='right'>" . esc_html( $cbm_display ) . '</td>';
$html .= "<td align='right'>" . esc_html( $total_display ) . '</td>';
$html .= '<td>&nbsp;' . esc_html( $montage ) . '</td>';
$html .= '</tr>';
$processed_items[] = $item_name;
}
}
}
// Room totals
$room_total_display = str_replace( '.', ',', number_format( $room_total_cbm, 2, '.', '' ) );
$html .= "<tr>
<th bgcolor='CCCCCC' align='right'>" . $room_total_quantity . "</th>
<th bgcolor='CCCCCC' align='left'>Summe " . esc_html( $room_label ) . "</th>
<th bgcolor='CCCCCC' colspan='2' align='right'>" . esc_html( $room_total_display ) . "</th>
<th bgcolor='CCCCCC'>&nbsp;</th>
</tr>";
$html .= '</tbody></table></div></div>';
return $html;
}
/**
* Generate grand totals section
*
* @param array $data Form data
* @return string HTML
*/
private static function generate_grand_totals( $data ) {
$grand_total_quantity = 0;
$grand_total_cbm = 0;
$rooms = Umzugsliste_Furniture_Data::get_rooms();
foreach ( $rooms as $room_key => $room_label ) {
$post_array_name = ucfirst( $room_key );
if ( 'kueche_esszimmer' === $room_key ) {
$post_array_name = 'Kueche_Esszimmer';
}
$room_data = $data[ $post_array_name ] ?? array();
foreach ( $room_data as $key => $value ) {
if ( substr( $key, 0, 1 ) === 'v' && ! empty( $value ) && floatval( $value ) > 0 ) {
$item_name = substr( $key, 1 );
$quantity = floatval( str_replace( ',', '.', trim( $value ) ) );
$cbm = isset( $room_data[ 'q' . $item_name ] ) ? floatval( $room_data[ 'q' . $item_name ] ) : 0;
$grand_total_quantity += $quantity;
$grand_total_cbm += ( $quantity * $cbm );
}
}
}
$grand_total_display = str_replace( '.', ',', number_format( $grand_total_cbm, 2, '.', '' ) );
return "<tr><th>&nbsp;</th></tr>
<tr>
<th bgcolor='CCCCCC' align='right'>" . $grand_total_quantity . "</th>
<th bgcolor='CCCCCC' align='left'>Gesamtsummen</th>
<th bgcolor='CCCCCC' colspan='2' align='right'>" . esc_html( $grand_total_display ) . "</th>
<th bgcolor='CCCCCC'>&nbsp;</th>
</tr></tbody></table></div></div>";
}
/**
* Wrap content in HTML document structure
*
* @param string $content Email content
* @return string Complete HTML document
*/
private static function wrap_html( $content ) {
return "<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'>
<html>
<head>
<title>Siegel Umzüge - Internetanfrage</title>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
</head>
<body>" . $content . "</body>
</html>";
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* Shortcode Handler
*
* Registers and handles the [umzugsliste] shortcode
*
* @package Umzugsliste
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Shortcode class
*/
class Umzugsliste_Shortcode {
/**
* Instance
*
* @var Umzugsliste_Shortcode
*/
private static $instance = null;
/**
* Get instance
*
* @return Umzugsliste_Shortcode
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
add_shortcode( 'umzugsliste', array( $this, 'render_form' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
/**
* Render form shortcode
*
* @param array $atts Shortcode attributes
* @return string Form HTML
*/
public function render_form( $atts ) {
// Ensure assets are enqueued
$this->enqueue_assets();
// Render the form
return Umzugsliste_Form_Renderer::render();
}
/**
* Enqueue CSS and JS assets
*/
public function enqueue_assets() {
$plugin_url = plugin_dir_url( dirname( __FILE__ ) );
$plugin_version = '1.0.0';
// Enqueue form CSS
wp_enqueue_style(
'umzugsliste-form',
$plugin_url . 'assets/css/form.css',
array(),
$plugin_version
);
// Enqueue form JS (placeholder for Phase 5)
wp_enqueue_script(
'umzugsliste-form',
$plugin_url . 'assets/js/form.js',
array( 'jquery' ),
$plugin_version,
true
);
}
}