feat: add test email admin page and comprehensive form fill button

- New Umzugsliste_Test_Email class with test data generator covering all
  fields: addresses, 7 rooms with items, all additional work sections, sonstiges
- Admin page under Moving List > Test Email with inline preview iframe
  and Send Test Email button (manage_options capability)
- Replace Step 1 dev fill button with Fill All that populates every field
  across all 9 steps (furniture, additional work, sonstiges)
- Fix getFieldVal crash when select has no selection (selectedIndex=-1)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 00:40:08 +09:00
parent 60f82f1224
commit f5ca452a85
5 changed files with 410 additions and 13 deletions

View File

@@ -69,6 +69,16 @@ class Umzugsliste_Admin_Menu {
array( $this, 'settings_page' ) // Callback
);
// Add Test Email submenu
add_submenu_page(
'umzugsliste',
'Test Email',
'Test Email',
'manage_options',
'umzugsliste-test-email',
array( Umzugsliste_Test_Email::get_instance(), 'render_admin_page' )
);
// Remove duplicate top-level menu item
remove_submenu_page( 'umzugsliste', 'umzugsliste' );
}

View File

@@ -206,20 +206,99 @@ class Umzugsliste_Form_Renderer {
<p class="required-note"><?php echo esc_html__( '* Required fields', 'siegel-umzugsliste' ); ?></p>
</div>
<?php if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) : ?>
<button type="button" id="dev-autofill" class="dev-autofill-btn">&#9881; Fill</button>
<button type="button" id="dev-autofill" class="dev-autofill-btn">&#9881; Fill All</button>
<script>
document.getElementById('dev-autofill').addEventListener('click', function() {
var fields = {
'bName':'Max Mustermann','bStrasse':'Musterstr. 12',
'bort':'10115 Berlin','bTelefon':'030 12345678',
'eName':'Erika Musterfrau','eStrasse':'Zielweg 5',
'eort':'80331 München','info[eE-Mail]':'test@example.com'
};
for (var n in fields) {
var el = document.querySelector('[name="'+n+'"]');
if (el) { el.value = fields[n]; el.dispatchEvent(new Event('input',{bubbles:true})); }
function setField(name, val) {
var el = document.querySelector('[name="'+name+'"]');
if (el) { el.value = val; el.dispatchEvent(new Event('input',{bubbles:true})); }
}
document.getElementById('wizard-next').click();
function setRadio(name, val) {
var el = document.querySelector('[name="'+name+'"][value="'+val+'"]');
if (el) el.checked = true;
}
/* Step 1: Date — option values are plain numbers (no zero-pad) */
var d = new Date();
setField('umzug_day', String(d.getDate()));
setField('umzug_month', String(d.getMonth()+1));
setField('umzug_year', String(d.getFullYear()));
/* Step 1: Addresses */
var addr = {
'bName':'Max Mustermann','bStrasse':'Musterstraße 42',
'bort':'65197 Wiesbaden','bTelefon':'0611 123456',
'eName':'Erika Musterfrau','eStrasse':'Beispielweg 7',
'eort':'55116 Mainz','eTelefon':'06131 654321',
'info[bGeschoss]':'2. OG','info[eGeschoss]':'EG',
'info[bTelefax]':'0611 123457','info[eTelefax]':'06131 654322',
'info[bMobil]':'0170 1234567','info[eMobil]':'0171 7654321',
'info[eE-Mail]':'test@example.com'
};
for (var n in addr) setField(n, addr[n]);
setRadio('info[bLift]','ja');
/* Steps 2-7: Furniture — ALL items in every room */
document.querySelectorAll('.furniture-list').forEach(function(list) {
var items = list.querySelectorAll('.furniture-item');
for (var i = 0; i < items.length; i++) {
var qty = (i % 3) + 1;
var inp = items[i].querySelector('.quantity-input');
if (inp) {
inp.value = String(qty);
inp.classList.add('has-value');
items[i].classList.add('has-quantity');
inp.dispatchEvent(new Event('input',{bubbles:true}));
}
if (i % 2 === 0) {
var mj = items[i].querySelector('.montage-toggle input[value="ja"]');
if (mj) mj.checked = true;
}
}
});
/* Step 8: Additional work — Montage checkboxes */
document.querySelectorAll('[data-section="montage"] input[type="checkbox"]').forEach(function(cb) { cb.checked = true; });
/* Schrank radios — cycle Abbau/Aufbau/Beides */
var abbauVals = ['Abbau','Aufbau','Beides'], ai = 0;
document.querySelectorAll('[data-section="schrank"] .additional-field-abbau').forEach(function(f) {
var r = f.querySelector('input[value="'+abbauVals[ai%3]+'"]');
if (r) r.checked = true;
ai++;
});
/* Elektriker — check + anzahl */
var ez = 1;
document.querySelectorAll('[data-section="elektriker"] .additional-field-qty').forEach(function(f) {
var cb = f.querySelector('input[type="checkbox"]'); if (cb) cb.checked = true;
var q = f.querySelector('.qty-small'); if (q) q.value = String(ez++);
});
/* Dübelarbeiten — check + anzahl */
var dz = 2;
document.querySelectorAll('[data-section="duebelarbeiten"] .additional-field-qty').forEach(function(f) {
var cb = f.querySelector('input[type="checkbox"]'); if (cb) cb.checked = true;
var q = f.querySelector('.qty-small'); if (q) { q.value = String(dz); dz += 2; }
});
/* Packarbeiten — all checkboxes + text quantities */
document.querySelectorAll('[data-section="packarbeiten"] input[type="checkbox"]').forEach(function(cb) { cb.checked = true; });
var pv = [25, 5], pi = 0;
document.querySelectorAll('[data-section="packarbeiten"] .additional-field-text .qty-small').forEach(function(inp) {
inp.value = String(pv[pi] || 5); pi++;
});
/* Anfahrt — all checkboxes + distances */
document.querySelectorAll('[data-section="anfahrt"] input[type="checkbox"]').forEach(function(cb) { cb.checked = true; });
var av = [15, 25], avi = 0;
document.querySelectorAll('[data-section="anfahrt"] .additional-field-text .qty-small').forEach(function(inp) {
inp.value = String(av[avi] || 25); avi++;
});
/* Sonstiges */
var s = document.querySelector('[name="sonstiges"]');
if (s) s.value = 'Bitte vorsichtig mit dem antiken Schrank im Wohnzimmer.\nDas Klavier muss besonders geschützt werden.';
});
</script>
<?php endif; ?>

View File

@@ -0,0 +1,306 @@
<?php
/**
* Test Email
*
* Admin tool for generating and previewing test emails with all fields populated
*
* @package Umzugsliste
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Test email class
*/
class Umzugsliste_Test_Email {
/**
* Single instance
*
* @var Umzugsliste_Test_Email
*/
private static $instance = null;
/**
* Get instance
*
* @return Umzugsliste_Test_Email
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
add_action( 'admin_init', array( $this, 'maybe_output_preview' ) );
}
/**
* Intercept preview request before admin page header is output
*/
public function maybe_output_preview() {
if ( ! isset( $_GET['page'] ) || 'umzugsliste-test-email' !== $_GET['page'] ) {
return;
}
if ( ! isset( $_GET['action'] ) || 'preview' !== $_GET['action'] ) {
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'Unauthorized' );
}
switch_to_locale( 'de_DE' );
$data = self::generate_test_data();
$html = Umzugsliste_Email_Generator::generate( $data );
restore_previous_locale();
echo $html;
exit;
}
/**
* Generate comprehensive test data array with every field populated
*
* Must be called within de_DE locale context so __() returns German strings
*
* @return array Complete form submission data
*/
public static function generate_test_data() {
$data = array();
// Date - today
$data['umzug_day'] = date( 'd' );
$data['umzug_month'] = date( 'm' );
$data['umzug_year'] = date( 'Y' );
// Address fields - realistic German test values
$data['bName'] = 'Max Mustermann';
$data['eName'] = 'Erika Musterfrau';
$data['bStrasse'] = 'Musterstraße 42';
$data['eStrasse'] = 'Beispielweg 7';
$data['bort'] = '65197 Wiesbaden';
$data['eort'] = '55116 Mainz';
$data['bTelefon'] = '0611 123456';
$data['eTelefon'] = '06131 654321';
// Info array
$data['info'] = array(
'bGeschoss' => '2. OG',
'eGeschoss' => 'EG',
'bLift' => 'ja',
'eLift' => 'nein',
'bTelefax' => '0611 123457',
'eTelefax' => '06131 654322',
'bMobil' => '0170 1234567',
'eMobil' => '0171 7654321',
'eE-Mail' => 'test@example.com',
);
// Room data - pick 2-3 items per room with fixed quantities
$room_picks = array(
'wohnzimmer' => array( array( 0, 2 ), array( 4, 4 ), array( 8, 1 ) ),
'schlafzimmer' => array( array( 2, 1 ), array( 3, 2 ), array( 5, 2 ) ),
'arbeitszimmer' => array( array( 1, 1 ), array( 3, 2 ), array( 8, 1 ) ),
'bad' => array( array( 0, 1 ), array( 1, 1 ) ),
'kueche_esszimmer' => array( array( 4, 1 ), array( 7, 4 ), array( 9, 1 ) ),
'kinderzimmer' => array( array( 2, 1 ), array( 3, 1 ), array( 6, 1 ) ),
'keller' => array( array( 0, 2 ), array( 4, 4 ), array( 8, 1 ) ),
);
$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';
}
$furniture_items = Umzugsliste_Furniture_Data::get_furniture_items( $room_key );
$picks = $room_picks[ $room_key ];
$room_data = array();
foreach ( $picks as $pick ) {
$idx = $pick[0];
$quantity = $pick[1];
if ( isset( $furniture_items[ $idx ] ) ) {
$item = $furniture_items[ $idx ];
$name = $item['name'];
$room_data[ 'v' . $name ] = (string) $quantity;
$room_data[ 'q' . $name ] = (string) $item['cbm'];
$room_data[ 'm' . $name ] = ( $quantity > 2 ) ? 'ja' : 'nein';
}
}
$data[ $post_array_name ] = $room_data;
}
// Additional work
$sections = Umzugsliste_Furniture_Data::get_additional_work();
$additional_work = array();
// Montage - both checkboxes checked
$montage_data = array();
foreach ( $sections['montage']['fields'] as $field ) {
$key = self::get_field_key( $field );
$montage_data[ $key ] = 'ja';
}
$additional_work['montage'] = $montage_data;
// Schrank - mix of Abbau, Aufbau, Beides
$schrank_values = array( 'Abbau', 'Aufbau', 'Beides', 'Abbau', 'Aufbau', 'Beides' );
$schrank_data = array();
$i = 0;
foreach ( $sections['schrank']['fields'] as $field ) {
$key = self::get_field_key( $field );
$schrank_data[ $key ] = $schrank_values[ $i % count( $schrank_values ) ];
$i++;
}
$additional_work['schrank'] = $schrank_data;
// Elektriker - all checked with varied _anzahl values
$elektriker_data = array();
$anzahl = 1;
foreach ( $sections['elektriker']['fields'] as $field ) {
$key = self::get_field_key( $field );
$elektriker_data[ $key ] = 'ja';
$elektriker_data[ $key . '_anzahl' ] = (string) $anzahl;
$anzahl++;
}
$additional_work['elektriker'] = $elektriker_data;
// Duebelarbeiten - all checked with varied _anzahl values
$duebel_data = array();
$anzahl = 2;
foreach ( $sections['duebelarbeiten']['fields'] as $field ) {
$key = self::get_field_key( $field );
$duebel_data[ $key ] = 'ja';
$duebel_data[ $key . '_anzahl' ] = (string) $anzahl;
$anzahl += 2;
}
$additional_work['duebelarbeiten'] = $duebel_data;
// Packarbeiten - all 4 checkboxes checked, text quantities filled
$pack_data = array();
$pack_fields = $sections['packarbeiten']['fields'];
for ( $i = 0; $i < 4 && $i < count( $pack_fields ); $i++ ) {
$key = self::get_field_key( $pack_fields[ $i ] );
$pack_data[ $key ] = 'ja';
}
for ( $i = 4; $i < 6 && $i < count( $pack_fields ); $i++ ) {
$key = self::get_field_key( $pack_fields[ $i ] );
$pack_data[ $key ] = ( $i === 4 ) ? '25' : '5';
}
$additional_work['packarbeiten'] = $pack_data;
// Anfahrt - all checkboxes checked + distance text values
$anfahrt_data = array();
$anfahrt_fields = $sections['anfahrt']['fields'];
foreach ( $anfahrt_fields as $i => $field ) {
$key = self::get_field_key( $field );
if ( 'checkbox' === $field['type'] ) {
$anfahrt_data[ $key ] = 'ja';
} else {
$anfahrt_data[ $key ] = ( $i === 6 ) ? '15' : '25';
}
}
$additional_work['anfahrt'] = $anfahrt_data;
$data['additional_work'] = $additional_work;
// Sonstiges
$data['sonstiges'] = "Bitte vorsichtig mit dem antiken Schrank im Wohnzimmer.\nDas Klavier muss besonders geschützt werden.";
return $data;
}
/**
* Get field key matching email generator logic
*
* @param array $field Field definition
* @return string Field key
*/
private static function get_field_key( $field ) {
if ( ! empty( $field['key'] ) ) {
return sanitize_key( $field['key'] );
}
return sanitize_title( $field['name'] );
}
/**
* Render admin page
*/
public function render_admin_page() {
// Handle send test email
$notice = '';
if ( isset( $_POST['send_test_email'] ) && check_admin_referer( 'umzugsliste_send_test_email' ) ) {
$notice = $this->send_test_email();
}
$preview_url = add_query_arg(
array(
'page' => 'umzugsliste-test-email',
'action' => 'preview',
),
admin_url( 'admin.php' )
);
?>
<div class="wrap">
<h1>Test Email</h1>
<?php if ( $notice ) : ?>
<?php echo $notice; ?>
<?php endif; ?>
<form method="post" style="margin-bottom: 20px;">
<?php wp_nonce_field( 'umzugsliste_send_test_email' ); ?>
<p>
<?php $to = get_option( 'umzugsliste_receiver_email', get_option( 'admin_email' ) ); ?>
<strong>Recipient:</strong> <?php echo esc_html( $to ); ?>
</p>
<p>
<?php submit_button( 'Send Test Email', 'primary', 'send_test_email', false ); ?>
</p>
</form>
<h2>Email Preview</h2>
<iframe
src="<?php echo esc_url( $preview_url ); ?>"
style="width: 100%; height: 800px; border: 1px solid #ccd0d4; background: #fff;"
></iframe>
</div>
<?php
}
/**
* Send test email
*
* @return string Notice HTML
*/
private function send_test_email() {
switch_to_locale( 'de_DE' );
$data = self::generate_test_data();
$html = Umzugsliste_Email_Generator::generate( $data );
restore_previous_locale();
$to = get_option( 'umzugsliste_receiver_email', get_option( 'admin_email' ) );
$subject = 'TEST - Internetanfrage - Anfrage vom ' . date( 'd.m.Y H:i' );
$headers = array( 'Content-Type: text/html; charset=UTF-8' );
$sent = wp_mail( $to, $subject, $html, $headers );
if ( $sent ) {
return '<div class="notice notice-success is-dismissible"><p>Test email sent to <strong>' . esc_html( $to ) . '</strong>.</p></div>';
}
return '<div class="notice notice-error is-dismissible"><p>Failed to send test email. Check your mail configuration.</p></div>';
}
}