Files
Skrift-Kofnigurator/skrift-configurator/includes/admin-settings.php
2026-02-07 13:04:04 +01:00

944 lines
44 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Admin Settings für Skrift Konfigurator
* Verwaltung von Produkten, Preisen und Beschreibungen
*/
if (!defined('ABSPATH')) { exit; }
class Skrift_Konfigurator_Admin_Settings {
const OPTION_KEY = 'skrift_konfigurator_settings';
public function __construct() {
add_action('admin_menu', [$this, 'add_admin_menu']);
add_action('admin_init', [$this, 'register_settings']);
}
public function add_admin_menu(): void {
add_options_page(
'Skrift Konfigurator Einstellungen',
'Skrift Konfigurator',
'manage_options',
'skrift-konfigurator',
[$this, 'render_settings_page']
);
}
public function register_settings(): void {
register_setting('skrift_konfigurator', self::OPTION_KEY, [
'type' => 'array',
'sanitize_callback' => [$this, 'sanitize_settings'],
]);
}
public function sanitize_settings($input) {
$sanitized = [];
// Produkte sanitieren
if (isset($input['products']) && is_array($input['products'])) {
$sanitized['products'] = [];
foreach ($input['products'] as $key => $product) {
$sanitized['products'][sanitize_key($key)] = [
'label' => sanitize_text_field($product['label'] ?? ''),
'description' => sanitize_textarea_field($product['description'] ?? ''),
'base_price' => floatval($product['base_price'] ?? 0),
];
}
}
// Preise sanitieren
if (isset($input['prices']) && is_array($input['prices'])) {
$sanitized['prices'] = [];
foreach ($input['prices'] as $key => $value) {
$sanitized['prices'][sanitize_key($key)] = floatval($value);
}
}
// Dynamische Preisformeln sanitieren
if (isset($input['dynamic_pricing'])) {
$sanitized['dynamic_pricing'] = [
'business_formula' => sanitize_textarea_field($input['dynamic_pricing']['business_formula'] ?? ''),
'private_formula' => sanitize_textarea_field($input['dynamic_pricing']['private_formula'] ?? ''),
'business_min_quantity' => intval($input['dynamic_pricing']['business_min_quantity'] ?? 0),
'private_min_quantity' => intval($input['dynamic_pricing']['private_min_quantity'] ?? 0),
'business_normal_quantity' => intval($input['dynamic_pricing']['business_normal_quantity'] ?? 0),
'private_normal_quantity' => intval($input['dynamic_pricing']['private_normal_quantity'] ?? 0),
];
}
// Backend-Verbindung sanitieren
if (isset($input['backend_connection'])) {
$sanitized['backend_connection'] = [
'api_url' => esc_url_raw($input['backend_connection']['api_url'] ?? ''),
'api_token' => sanitize_text_field($input['backend_connection']['api_token'] ?? ''),
'webhook_url_business' => esc_url_raw($input['backend_connection']['webhook_url_business'] ?? ''),
'webhook_url_private' => esc_url_raw($input['backend_connection']['webhook_url_private'] ?? ''),
'redirect_url_business' => esc_url_raw($input['backend_connection']['redirect_url_business'] ?? ''),
'redirect_url_private' => esc_url_raw($input['backend_connection']['redirect_url_private'] ?? ''),
];
}
// REST API Key sanitieren
if (isset($input['api_security'])) {
$sanitized['api_security'] = [
'api_key' => sanitize_text_field($input['api_security']['api_key'] ?? ''),
];
}
// PayPal-Verbindung sanitieren
if (isset($input['paypal'])) {
$sanitized['paypal'] = [
'enabled' => !empty($input['paypal']['enabled']),
'mode' => sanitize_text_field($input['paypal']['mode'] ?? 'sandbox'),
'client_id_sandbox' => sanitize_text_field($input['paypal']['client_id_sandbox'] ?? ''),
'client_secret_sandbox' => sanitize_text_field($input['paypal']['client_secret_sandbox'] ?? ''),
'client_id_live' => sanitize_text_field($input['paypal']['client_id_live'] ?? ''),
'client_secret_live' => sanitize_text_field($input['paypal']['client_secret_live'] ?? ''),
];
}
// Schriftmuster und Platzhalter-Hilfe sanitieren
if (isset($input['font_sample'])) {
$sanitized['font_sample'] = [
'url' => esc_url_raw($input['font_sample']['url'] ?? ''),
'placeholder_help_url' => esc_url_raw($input['font_sample']['placeholder_help_url'] ?? ''),
];
}
return $sanitized;
}
public function render_settings_page(): void {
if (!current_user_can('manage_options')) {
return;
}
$settings = $this->get_settings();
?>
<style>
.sk-admin-wrap { max-width: 1200px; }
.sk-admin-section { background: #fff; padding: 20px; margin: 20px 0; border: 1px solid #ccd0d4; box-shadow: 0 1px 1px rgba(0,0,0,.04); }
.sk-admin-section h2 { margin-top: 0; padding-bottom: 10px; border-bottom: 1px solid #eee; }
.sk-product-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px; margin-top: 20px; }
.sk-product-card { background: #f9f9f9; padding: 15px; border: 1px solid #ddd; border-radius: 4px; }
.sk-product-card h3 { margin-top: 0; color: #0073aa; font-size: 16px; }
.sk-field-row { margin-bottom: 15px; }
.sk-field-row label { display: block; font-weight: 600; margin-bottom: 5px; }
.sk-field-row input[type="text"], .sk-field-row textarea { width: 100%; }
.sk-field-row input[type="number"] { width: 120px; }
.sk-price-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 15px; margin-top: 15px; }
.sk-price-item { background: #f9f9f9; padding: 12px; border-left: 3px solid #0073aa; }
.sk-price-item label { display: flex; justify-content: space-between; align-items: center; }
.sk-price-item strong { color: #23282d; }
.sk-multiplier-table { width: 100%; border-collapse: collapse; margin-top: 10px; }
.sk-multiplier-table th, .sk-multiplier-table td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
.sk-multiplier-table th { background: #f0f0f1; font-weight: 600; }
.sk-multiplier-table input[type="number"] { width: 100px; }
</style>
<div class="wrap sk-admin-wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<form method="post" action="options.php">
<?php
settings_fields('skrift_konfigurator');
do_settings_sections('skrift_konfigurator');
?>
<!-- Produkte -->
<div class="sk-admin-section">
<h2>📦 Produkte</h2>
<p>Verwalten Sie Namen, Beschreibungen und Startpreise für alle Produkte.</p>
<div class="sk-product-grid">
<?php $this->render_product_card('businessbriefe', 'Businessbriefe', $settings); ?>
<?php $this->render_product_card('business-postkarten', 'Business Postkarten', $settings); ?>
<?php $this->render_product_card('follow-ups', 'Follow-ups', $settings); ?>
<?php $this->render_product_card('einladungen', 'Einladungen', $settings); ?>
<?php $this->render_product_card('private-briefe', 'Private Briefe', $settings); ?>
</div>
</div>
<!-- Format Aufpreise -->
<div class="sk-admin-section">
<h2>📐 Format Aufpreise</h2>
<p>Aufpreise wenn bei bestimmten Produkten das Format gewechselt wird.</p>
<div class="sk-price-grid">
<div class="sk-price-item">
<label>
<strong>A4 Aufpreis (Follow-ups/Einladungen)</strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][a4_upgrade_surcharge]"
value="<?php echo esc_attr($settings['prices']['a4_upgrade_surcharge'] ?? '0.50'); ?>"> €
</span>
</label>
<small style="display: block; margin-top: 5px; color: #666;">Aufpreis pro Stück wenn bei Postkarten oder Einladungen auf A4 gewechselt wird</small>
</div>
</div>
</div>
<!-- Versand & Umschlag -->
<div class="sk-admin-section">
<h2>🚚 Versand & Umschlag</h2>
<div class="sk-price-grid">
<div class="sk-price-item">
<label>
<strong>Porto Inland / Deutschland (pro Stück)</strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][shipping_domestic]"
value="<?php echo esc_attr($settings['prices']['shipping_domestic'] ?? '0.95'); ?>"> €
</span>
</label>
<small style="display: block; margin-top: 5px; color: #666;">Portokosten für Versand innerhalb Deutschlands (0% MwSt.)</small>
</div>
<div class="sk-price-item">
<label>
<strong>Porto Ausland (pro Stück)</strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][shipping_international]"
value="<?php echo esc_attr($settings['prices']['shipping_international'] ?? '1.25'); ?>"> €
</span>
</label>
<small style="display: block; margin-top: 5px; color: #666;">Portokosten für Auslandsversand (0% MwSt.)</small>
</div>
<div class="sk-price-item">
<label>
<strong>Serviceaufschlag Versand (pro Stück)</strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][shipping_service]"
value="<?php echo esc_attr($settings['prices']['shipping_service'] ?? '0.95'); ?>"> €
</span>
</label>
<small style="display: block; margin-top: 5px; color: #666;">Service-Aufschlag für Direktversand (19% MwSt.)</small>
</div>
<div class="sk-price-item">
<label>
<strong>Bulkversand (einmalig)</strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][shipping_bulk]"
value="<?php echo esc_attr($settings['prices']['shipping_bulk'] ?? '4.95'); ?>"> €
</span>
</label>
</div>
<div class="sk-price-item">
<label>
<strong>Kuvert (Grundpreis pro Stück)</strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][envelope_base]"
value="<?php echo esc_attr($settings['prices']['envelope_base'] ?? '0.50'); ?>"> €
</span>
</label>
<small style="display: block; margin-top: 5px; color: #666;">Grundpreis für Kuvert</small>
</div>
<div class="sk-price-item">
<label>
<strong>Aufschlag Beschriftung (pro Stück)</strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][envelope_labeling]"
value="<?php echo esc_attr($settings['prices']['envelope_labeling'] ?? '0.50'); ?>"> €
</span>
</label>
<small style="display: block; margin-top: 5px; color: #666;">Aufschlag für Beschriftung des Umschlags (Empfängeradresse oder individueller Text)</small>
</div>
</div>
</div>
<!-- Zusatzleistungen -->
<div class="sk-admin-section">
<h2>✨ Zusatzleistungen</h2>
<div class="sk-price-grid">
<div class="sk-price-item">
<label>
<strong>Motiv Upload <span style="color: #0073aa; font-size: 12px;">(einmalig)</span></strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][motif_upload]"
value="<?php echo esc_attr($settings['prices']['motif_upload'] ?? '0.30'); ?>"> €
</span>
</label>
</div>
<div class="sk-price-item">
<label>
<strong>Bedruckte Karten zusenden (pro Stück)</strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][motif_printed]"
value="<?php echo esc_attr($settings['prices']['motif_printed'] ?? '0.00'); ?>"> €
</span>
</label>
</div>
<div class="sk-price-item">
<label>
<strong>Designservice <span style="color: #0073aa; font-size: 12px;">(einmalig)</span></strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][motif_design]"
value="<?php echo esc_attr($settings['prices']['motif_design'] ?? '0.00'); ?>"> €
</span>
</label>
</div>
<div class="sk-price-item">
<label>
<strong>Textservice <span style="color: #0073aa; font-size: 12px;">(einmalig)</span></strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][textservice]"
value="<?php echo esc_attr($settings['prices']['textservice'] ?? '0.00'); ?>"> €
</span>
</label>
</div>
<div class="sk-price-item">
<label>
<strong>API-Anbindung <span style="color: #0073aa; font-size: 12px;">(einmalig)</span></strong>
<span>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][api_connection]"
value="<?php echo esc_attr($settings['prices']['api_connection'] ?? '250.00'); ?>"> €
</span>
</label>
<small style="display: block; margin-top: 5px; color: #666;">Einmalige Einrichtungsgebühr für die API-Anbindung</small>
</div>
</div>
</div>
<!-- Follow-ups Mengenstaffel -->
<div class="sk-admin-section">
<h2>📊 Follow-ups Preis-Multiplikatoren</h2>
<p>Die Gesamtkosten pro Schriftstück werden mit diesen Multiplikatoren je nach Menge multipliziert.</p>
<table class="sk-multiplier-table">
<thead>
<tr>
<th>Menge</th>
<th>Multiplikator</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>5 - 49 Stück</strong></td>
<td>
<input type="number" step="0.1" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][followup_mult_5_49]"
value="<?php echo esc_attr($settings['prices']['followup_mult_5_49'] ?? '2.0'); ?>">
<small style="margin-left: 10px; color: #666;">×</small>
</td>
</tr>
<tr>
<td><strong>50 - 199 Stück</strong></td>
<td>
<input type="number" step="0.1" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][followup_mult_50_199]"
value="<?php echo esc_attr($settings['prices']['followup_mult_50_199'] ?? '1.7'); ?>">
<small style="margin-left: 10px; color: #666;">×</small>
</td>
</tr>
<tr>
<td><strong>200 - 499 Stück</strong></td>
<td>
<input type="number" step="0.1" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][followup_mult_200_499]"
value="<?php echo esc_attr($settings['prices']['followup_mult_200_499'] ?? '1.4'); ?>">
<small style="margin-left: 10px; color: #666;">×</small>
</td>
</tr>
<tr>
<td><strong>500 - 999 Stück</strong></td>
<td>
<input type="number" step="0.1" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][followup_mult_500_999]"
value="<?php echo esc_attr($settings['prices']['followup_mult_500_999'] ?? '1.2'); ?>">
<small style="margin-left: 10px; color: #666;">×</small>
</td>
</tr>
<tr>
<td><strong>1000+ Stück</strong></td>
<td>
<input type="number" step="0.1" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][followup_mult_1000_plus]"
value="<?php echo esc_attr($settings['prices']['followup_mult_1000_plus'] ?? '1.0'); ?>">
<small style="margin-left: 10px; color: #666;">×</small>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Steuern -->
<div class="sk-admin-section">
<h2>📋 Steuern</h2>
<div class="sk-price-grid">
<div class="sk-price-item">
<label>
<strong>Mehrwertsteuersatz (%)</strong>
<span>
<input type="number" step="0.01" min="0" max="100"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[prices][tax_rate]"
value="<?php echo esc_attr($settings['prices']['tax_rate'] ?? '19'); ?>"> %
</span>
</label>
<small style="display: block; margin-top: 5px; color: #666;">Gilt für alle Positionen inkl. Versand</small>
</div>
</div>
</div>
<!-- Dynamische Preisberechnung -->
<div class="sk-admin-section">
<h2>🧮 Dynamische Preisberechnung</h2>
<p>Konfigurieren Sie die dynamische Preisberechnung basierend auf Mengen. Die Formeln unterstützen Platzhalter wie <code>%qty%</code> (aktuelle Menge), <code>%norm_b%</code> (Normalpreis Menge Business), <code>%mind_b%</code> (Mind. Menge Business), <code>%norm_p%</code> (Normalpreis Menge Privat), <code>%mind_p%</code> (Mind. Menge Privat).</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-top: 20px;">
<!-- Business -->
<div style="background: #f9f9f9; padding: 20px; border: 1px solid #ddd; border-radius: 4px;">
<h3 style="margin-top: 0; color: #0073aa;">Business</h3>
<div class="sk-field-row">
<label><strong>Mindestmenge Business</strong></label>
<input type="number" step="1" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[dynamic_pricing][business_min_quantity]"
value="<?php echo esc_attr($settings['dynamic_pricing']['business_min_quantity'] ?? '50'); ?>"
style="width: 150px;">
<small style="display: block; margin-top: 5px; color: #666;">Minimale Menge für Business-Bestellungen (außer Follow-ups)</small>
</div>
<div class="sk-field-row" style="margin-top: 15px;">
<label><strong>Normalpreis Menge Business</strong></label>
<input type="number" step="1" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[dynamic_pricing][business_normal_quantity]"
value="<?php echo esc_attr($settings['dynamic_pricing']['business_normal_quantity'] ?? '200'); ?>"
style="width: 150px;">
<small style="display: block; margin-top: 5px; color: #666;">Ab dieser Menge gilt der Normalpreis (Multiplikator = 1)</small>
</div>
<div class="sk-field-row" style="margin-top: 15px;">
<label><strong>Dynamische Formel Business</strong></label>
<textarea
name="<?php echo esc_attr(self::OPTION_KEY); ?>[dynamic_pricing][business_formula]"
rows="8"
style="width: 100%; font-family: monospace; font-size: 12px;"
placeholder="(%qty% >= %norm_b%) ? 1 : (2 - Math.sqrt(%qty% / %norm_b%))"><?php echo esc_textarea($settings['dynamic_pricing']['business_formula'] ?? ''); ?></textarea>
<small style="display: block; margin-top: 5px; color: #666;">
<strong>JavaScript-Formel zur Berechnung des Preis-Multiplikators.</strong><br>
Platzhalter verwenden: <code>%qty%</code> für aktuelle Menge, <code>%norm_b%</code> für Normalpreis-Menge Business, <code>%mind_b%</code> für Mindestmenge Business.<br>
Beispiel: <code>(%qty% >= %norm_b%) ? 1 : (2 - Math.sqrt(%qty% / %norm_b%))</code><br>
Dies bedeutet: Wenn die Menge >= Normalpreis-Menge ist, dann Multiplikator = 1, sonst dynamische Berechnung.
</small>
</div>
</div>
<!-- Privat -->
<div style="background: #f9f9f9; padding: 20px; border: 1px solid #ddd; border-radius: 4px;">
<h3 style="margin-top: 0; color: #0073aa;">Privat</h3>
<div class="sk-field-row">
<label><strong>Mindestmenge Privat</strong></label>
<input type="number" step="1" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[dynamic_pricing][private_min_quantity]"
value="<?php echo esc_attr($settings['dynamic_pricing']['private_min_quantity'] ?? '10'); ?>"
style="width: 150px;">
<small style="display: block; margin-top: 5px; color: #666;">Minimale Menge für Private Bestellungen</small>
</div>
<div class="sk-field-row" style="margin-top: 15px;">
<label><strong>Normalpreis Menge Privat</strong></label>
<input type="number" step="1" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[dynamic_pricing][private_normal_quantity]"
value="<?php echo esc_attr($settings['dynamic_pricing']['private_normal_quantity'] ?? '50'); ?>"
style="width: 150px;">
<small style="display: block; margin-top: 5px; color: #666;">Ab dieser Menge gilt der Normalpreis (Multiplikator = 1)</small>
</div>
<div class="sk-field-row" style="margin-top: 15px;">
<label><strong>Dynamische Formel Privat</strong></label>
<textarea
name="<?php echo esc_attr(self::OPTION_KEY); ?>[dynamic_pricing][private_formula]"
rows="8"
style="width: 100%; font-family: monospace; font-size: 12px;"
placeholder="(%qty% >= %norm_p%) ? 1 : (2 - Math.sqrt(%qty% / %norm_p%))"><?php echo esc_textarea($settings['dynamic_pricing']['private_formula'] ?? ''); ?></textarea>
<small style="display: block; margin-top: 5px; color: #666;">
<strong>JavaScript-Formel zur Berechnung des Preis-Multiplikators.</strong><br>
Platzhalter verwenden: <code>%qty%</code> für aktuelle Menge, <code>%norm_p%</code> für Normalpreis-Menge Privat, <code>%mind_p%</code> für Mindestmenge Privat.<br>
Beispiel: <code>(%qty% >= %norm_p%) ? 1 : (2 - Math.sqrt(%qty% / %norm_p%))</code><br>
Dies bedeutet: Wenn die Menge >= Normalpreis-Menge ist, dann Multiplikator = 1, sonst dynamische Berechnung.
</small>
</div>
</div>
</div>
<div style="margin-top: 20px; padding: 15px; background: #fff3cd; border-left: 4px solid #ffc107;">
<strong>Verfügbare Platzhalter:</strong>
<ul style="margin: 10px 0 0 20px;">
<li><code>%qty%</code> - Aktuelle Menge (eingegebene Stückzahl)</li>
<li><code>%norm_b%</code> - Normalpreis Menge Business</li>
<li><code>%mind_b%</code> - Mindestmenge Business</li>
<li><code>%norm_p%</code> - Normalpreis Menge Privat</li>
<li><code>%mind_p%</code> - Mindestmenge Privat</li>
</ul>
</div>
</div>
<!-- Backend-Verbindung -->
<div class="sk-admin-section">
<h2>🔌 Backend-Verbindung</h2>
<p>Konfigurieren Sie die Verbindung zum Backend-System für erweiterte Funktionen.</p>
<div class="sk-price-grid">
<div class="sk-price-item">
<label>
<strong>API URL / Domain</strong>
</label>
<input type="url"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[backend_connection][api_url]"
value="<?php echo esc_attr($settings['backend_connection']['api_url'] ?? ''); ?>"
placeholder="https://api.example.com"
style="width: 100%; margin-top: 8px;">
<small style="display: block; margin-top: 5px; color: #666;">Basis-URL des Backend-Systems (z.B. https://api.example.com)</small>
</div>
<div class="sk-price-item">
<label>
<strong>API Token / Authentifizierung</strong>
</label>
<input type="text"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[backend_connection][api_token]"
value="<?php echo esc_attr($settings['backend_connection']['api_token'] ?? ''); ?>"
placeholder="sk_live_..."
style="width: 100%; margin-top: 8px; font-family: monospace;">
<small style="display: block; margin-top: 5px; color: #666;">Authentifizierungs-Token für API-Zugriff</small>
</div>
<div class="sk-price-item">
<label>
<strong>Webhook URL Geschäftskunden (B2B)</strong>
</label>
<input type="url"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[backend_connection][webhook_url_business]"
value="<?php echo esc_attr($settings['backend_connection']['webhook_url_business'] ?? ''); ?>"
placeholder="https://api.example.com/webhooks/order-business"
style="width: 100%; margin-top: 8px; font-family: monospace;">
<small style="display: block; margin-top: 5px; color: #666;">Webhook wird nach Klick auf "Jetzt kostenpflichtig bestellen" für Geschäftskunden aufgerufen</small>
</div>
<div class="sk-price-item">
<label>
<strong>Webhook URL Privatkunden (B2C)</strong>
</label>
<input type="url"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[backend_connection][webhook_url_private]"
value="<?php echo esc_attr($settings['backend_connection']['webhook_url_private'] ?? ''); ?>"
placeholder="https://api.example.com/webhooks/order-private"
style="width: 100%; margin-top: 8px; font-family: monospace;">
<small style="display: block; margin-top: 5px; color: #666;">Webhook wird nach erfolgreicher PayPal-Zahlung für Privatkunden aufgerufen</small>
</div>
<div class="sk-price-item">
<label>
<strong>Redirect URL Geschäftskunden</strong>
</label>
<input type="url"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[backend_connection][redirect_url_business]"
value="<?php echo esc_attr($settings['backend_connection']['redirect_url_business'] ?? ''); ?>"
placeholder="https://example.com/danke-business"
style="width: 100%; margin-top: 8px;">
<small style="display: block; margin-top: 5px; color: #666;">Weiterleitung nach Bestellung für Geschäftskunden (nach Klick auf "Jetzt kostenpflichtig bestellen")</small>
</div>
<div class="sk-price-item">
<label>
<strong>Redirect URL Privatkunden</strong>
</label>
<input type="url"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[backend_connection][redirect_url_private]"
value="<?php echo esc_attr($settings['backend_connection']['redirect_url_private'] ?? ''); ?>"
placeholder="https://example.com/danke-privat"
style="width: 100%; margin-top: 8px;">
<small style="display: block; margin-top: 5px; color: #666;">Weiterleitung nach erfolgreicher PayPal-Zahlung für Privatkunden</small>
</div>
</div>
</div>
<!-- REST API Security -->
<div class="sk-admin-section">
<h2>🔐 REST API Sicherheit</h2>
<p>Konfigurieren Sie einen API-Key für die REST-API-Endpunkte. Dieser Key muss im Header <code>X-Skrift-API-Key</code> mitgesendet werden.</p>
<div class="sk-price-grid">
<div class="sk-price-item" style="grid-column: 1 / -1;">
<label>
<strong>API Key</strong>
</label>
<div style="display: flex; gap: 10px; margin-top: 8px;">
<input type="text" id="sk-api-key-input"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[api_security][api_key]"
value="<?php echo esc_attr($settings['api_security']['api_key'] ?? ''); ?>"
placeholder="sk_api_..."
style="flex: 1; font-family: monospace;">
<button type="button" class="button" onclick="document.getElementById('sk-api-key-input').value = 'sk_api_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);">
Generieren
</button>
</div>
<small style="display: block; margin-top: 5px; color: #666;">Leer lassen um API-Key-Prüfung zu deaktivieren (nicht empfohlen für Produktion)</small>
</div>
</div>
</div>
<!-- Schriftmuster Fallback -->
<div class="sk-admin-section">
<h2>✍️ Schriftmuster (Vorschau-Fallback)</h2>
<p>Wenn die Vorschau-Generierung fehlschlägt oder nicht verfügbar ist, wird ein "Schriftmuster ansehen"-Link angezeigt. Der Link öffnet sich in einem neuen Tab.</p>
<div class="sk-price-grid">
<div class="sk-price-item" style="grid-column: 1 / -1;">
<label>
<strong>Schriftmuster-URL</strong>
</label>
<input type="url"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[font_sample][url]"
value="<?php echo esc_attr($settings['font_sample']['url'] ?? ''); ?>"
placeholder="https://example.com/schriftmuster"
style="width: 100%; margin-top: 8px;">
<small style="display: block; margin-top: 5px; color: #666;">URL zur Schriftmuster-Seite (wird in neuem Tab geöffnet)</small>
</div>
<div class="sk-price-item" style="grid-column: 1 / -1;">
<label>
<strong>Platzhalter-Hilfe URL</strong>
</label>
<input type="url"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[font_sample][placeholder_help_url]"
value="<?php echo esc_attr($settings['font_sample']['placeholder_help_url'] ?? ''); ?>"
placeholder="https://example.com/platzhalter-hilfe"
style="width: 100%; margin-top: 8px;">
<small style="display: block; margin-top: 5px; color: #666;">URL zur Platzhalter-Hilfeseite (wird bei Platzhalter-Infotexten als Link angezeigt)</small>
</div>
</div>
</div>
<!-- PayPal-Verbindung -->
<div class="sk-admin-section">
<h2>💳 PayPal-Verbindung (nur Privatkunden)</h2>
<p>Konfigurieren Sie die PayPal-Zahlungsintegration. PayPal ist nur für Privatkunden aktiviert.</p>
<div class="sk-price-grid">
<div class="sk-price-item" style="grid-column: 1 / -1;">
<label style="display: flex; align-items: center; gap: 10px;">
<input type="checkbox"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[paypal][enabled]"
value="1"
<?php checked($settings['paypal']['enabled'] ?? false); ?>>
<strong>PayPal-Zahlung aktivieren</strong>
</label>
<small style="display: block; margin-top: 5px; color: #666;">Aktiviert PayPal als Zahlungsoption für Privatkunden</small>
</div>
<div class="sk-price-item" style="grid-column: 1 / -1;">
<label>
<strong>Modus</strong>
</label>
<select
name="<?php echo esc_attr(self::OPTION_KEY); ?>[paypal][mode]"
style="width: 200px; margin-top: 8px;">
<option value="sandbox" <?php selected(($settings['paypal']['mode'] ?? 'sandbox'), 'sandbox'); ?>>Sandbox (Test)</option>
<option value="live" <?php selected(($settings['paypal']['mode'] ?? 'sandbox'), 'live'); ?>>Live (Produktion)</option>
</select>
<small style="display: block; margin-top: 5px; color: #666;">Sandbox für Tests, Live für echte Zahlungen</small>
</div>
</div>
<h3 style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd;">Sandbox-Zugangsdaten (Test)</h3>
<div class="sk-price-grid">
<div class="sk-price-item">
<label>
<strong>Client ID (Sandbox)</strong>
</label>
<input type="text"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[paypal][client_id_sandbox]"
value="<?php echo esc_attr($settings['paypal']['client_id_sandbox'] ?? ''); ?>"
placeholder="AZn4..."
style="width: 100%; margin-top: 8px; font-family: monospace;">
</div>
<div class="sk-price-item">
<label>
<strong>Client Secret (Sandbox)</strong>
</label>
<input type="password"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[paypal][client_secret_sandbox]"
value="<?php echo esc_attr($settings['paypal']['client_secret_sandbox'] ?? ''); ?>"
placeholder="EL3..."
style="width: 100%; margin-top: 8px; font-family: monospace;">
</div>
</div>
<h3 style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd;">Live-Zugangsdaten (Produktion)</h3>
<div class="sk-price-grid">
<div class="sk-price-item">
<label>
<strong>Client ID (Live)</strong>
</label>
<input type="text"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[paypal][client_id_live]"
value="<?php echo esc_attr($settings['paypal']['client_id_live'] ?? ''); ?>"
placeholder="AZn4..."
style="width: 100%; margin-top: 8px; font-family: monospace;">
</div>
<div class="sk-price-item">
<label>
<strong>Client Secret (Live)</strong>
</label>
<input type="password"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[paypal][client_secret_live]"
value="<?php echo esc_attr($settings['paypal']['client_secret_live'] ?? ''); ?>"
placeholder="EL3..."
style="width: 100%; margin-top: 8px; font-family: monospace;">
</div>
</div>
<div style="margin-top: 20px; padding: 15px; background: #e7f3ff; border-left: 4px solid #2196f3;">
<strong>Hinweis:</strong> Um PayPal zu aktivieren, benötigen Sie ein PayPal Business-Konto.
Sie erhalten die API-Zugangsdaten im <a href="https://developer.paypal.com/dashboard/applications/" target="_blank">PayPal Developer Dashboard</a>.
</div>
</div>
<?php submit_button('Einstellungen speichern'); ?>
</form>
<!-- URL Parameter Dokumentation -->
<div class="sk-section" style="margin-top: 40px; padding: 20px; background: #f9f9f9; border: 1px solid #ddd; border-radius: 8px;">
<h2 style="margin-top: 0;">URL-Parameter</h2>
<p>Der Konfigurator unterstützt folgende URL-Parameter:</p>
<table class="widefat" style="margin-top: 15px;">
<thead>
<tr>
<th style="width: 200px;">Parameter</th>
<th style="width: 200px;">Werte</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>?businessbriefe</code><br>
<code>?business-postkarten</code><br>
<code>?follow-ups</code><br>
<code>?einladungen</code><br>
<code>?private-briefe</code></td>
<td></td>
<td>Produkt direkt vorauswählen. Der Produktauswahlschritt wird übersprungen.</td>
</tr>
<tr>
<td><code>quantity</code></td>
<td>Zahl (z.B. <code>100</code>)</td>
<td>Menge vorausfüllen.</td>
</tr>
<tr>
<td><code>format</code></td>
<td><code>a4</code>, <code>a6h</code>, <code>a6q</code></td>
<td>Format vorauswählen. <code>a6h</code> = A6 Hochformat, <code>a6q</code> = A6 Querformat.</td>
</tr>
<tr>
<td><code>noPrice</code></td>
<td></td>
<td>Preise im Konfigurator ausblenden.</td>
</tr>
<tr>
<td><code>noLimits</code></td>
<td></td>
<td>Keine Mindestmengen. Erlaubt Bestellungen ab 1 Stück.</td>
</tr>
</tbody>
</table>
<h3 style="margin-top: 25px;">Beispiele</h3>
<ul style="margin-left: 20px;">
<li><code>/konfigurator/?businessbriefe</code> Direkt zu Business Briefe</li>
<li><code>/konfigurator/?einladungen&quantity=25&format=a6h</code> Einladungen mit 25 Stück im A6 Hochformat</li>
<li><code>/konfigurator/?businessbriefe&noPrice</code> Business Briefe ohne Preisanzeige</li>
<li><code>/konfigurator/?private-briefe&noLimits</code> Private Briefe ohne Mindestmenge</li>
<li><code>/konfigurator/?business-postkarten&noLimits&noPrice</code> Postkarten ohne Mindestmenge und ohne Preise</li>
</ul>
</div>
</div>
<?php
}
private function render_product_card(string $key, string $default_label, array $settings): void {
$label = $settings['products'][$key]['label'] ?? $default_label;
$description = $settings['products'][$key]['description'] ?? 'Professionelle handgeschriebene Korrespondenz';
$base_price = $settings['products'][$key]['base_price'] ?? '2.50';
?>
<div class="sk-product-card">
<h3><?php echo esc_html($default_label); ?></h3>
<div class="sk-field-row">
<label>Produktname</label>
<input type="text"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[products][<?php echo esc_attr($key); ?>][label]"
value="<?php echo esc_attr($label); ?>"
placeholder="<?php echo esc_attr($default_label); ?>">
</div>
<div class="sk-field-row">
<label>Beschreibung</label>
<textarea
name="<?php echo esc_attr(self::OPTION_KEY); ?>[products][<?php echo esc_attr($key); ?>][description]"
rows="3"
placeholder="Professionelle handgeschriebene Korrespondenz"><?php echo esc_textarea($description); ?></textarea>
</div>
<div class="sk-field-row">
<label>Startpreis (ab)</label>
<div>
<input type="number" step="0.01" min="0"
name="<?php echo esc_attr(self::OPTION_KEY); ?>[products][<?php echo esc_attr($key); ?>][base_price]"
value="<?php echo esc_attr($base_price); ?>"> €
</div>
</div>
</div>
<?php
}
public static function get_settings(): array {
$defaults = [
'products' => [
'businessbriefe' => [
'label' => 'Businessbriefe',
'description' => 'Professionelle handgeschriebene Korrespondenz',
'base_price' => 2.50,
],
'business-postkarten' => [
'label' => 'Business Postkarten',
'description' => 'Professionelle handgeschriebene Korrespondenz',
'base_price' => 1.80,
],
'follow-ups' => [
'label' => 'Follow-ups',
'description' => 'Professionelle handgeschriebene Korrespondenz',
'base_price' => 2.50,
],
'einladungen' => [
'label' => 'Einladungen',
'description' => 'Professionelle handgeschriebene Korrespondenz',
'base_price' => 1.80,
],
'private-briefe' => [
'label' => 'Private Briefe',
'description' => 'Professionelle handgeschriebene Korrespondenz',
'base_price' => 2.50,
],
],
'prices' => [
// Versand & Umschlag
'shipping_domestic' => 0.95, // Porto Inland
'shipping_international' => 1.25, // Porto Ausland
'shipping_service' => 0.95, // Serviceaufschlag Versand
'shipping_bulk' => 4.95, // Bulkversand einmalig
'envelope_base' => 0.50, // Kuvert Grundpreis
'envelope_labeling' => 0.50, // Aufschlag Beschriftung
// Legacy fields for backwards compatibility
'shipping_direct' => 2.40,
'envelope_recipient_address' => 0.50,
'envelope_custom_text' => 0.30,
// Format Aufpreise
'a4_upgrade_surcharge' => 0.50,
// Zusatzleistungen
'motif_upload' => 0.30,
'motif_printed' => 0.00,
'motif_design' => 0.00,
'textservice' => 0.00,
'api_connection' => 250.00,
// Follow-ups Multiplikatoren
'followup_mult_5_49' => 2.0,
'followup_mult_50_199' => 1.7,
'followup_mult_200_499' => 1.4,
'followup_mult_500_999' => 1.2,
'followup_mult_1000_plus' => 1.0,
// Steuern
'tax_rate' => 19,
'shipping_tax_rate' => 0,
],
'dynamic_pricing' => [
'business_formula' => "(%qty% >= %norm_b%) ? 1 : (2 - Math.sqrt(%qty% / %norm_b%))",
'private_formula' => "(%qty% >= %norm_p%) ? 1 : (2 - Math.sqrt(%qty% / %norm_p%))",
'business_min_quantity' => 50,
'private_min_quantity' => 10,
'business_normal_quantity' => 200,
'private_normal_quantity' => 50,
],
'backend_connection' => [
'api_url' => '',
'api_token' => '',
'webhook_url_business' => '',
'webhook_url_private' => '',
'redirect_url_business' => '',
'redirect_url_private' => '',
],
'paypal' => [
'enabled' => false,
'mode' => 'sandbox',
'client_id_sandbox' => '',
'client_secret_sandbox' => '',
'client_id_live' => '',
'client_secret_live' => '',
],
'api_security' => [
'api_key' => '',
],
'font_sample' => [
'url' => '',
'placeholder_help_url' => '',
],
];
$saved = get_option(self::OPTION_KEY, []);
// Merge nested arrays properly
$merged = $defaults;
foreach (['products', 'prices', 'dynamic_pricing', 'backend_connection', 'paypal', 'api_security', 'font_sample'] as $section) {
if (isset($saved[$section]) && is_array($saved[$section])) {
$merged[$section] = array_merge($defaults[$section], $saved[$section]);
}
}
return $merged;
}
/**
* Prüft ob ein API-Key gültig ist
*/
public static function validate_api_key($provided_key): bool {
$settings = self::get_settings();
$stored_key = $settings['api_security']['api_key'] ?? '';
// Wenn kein Key konfiguriert ist, ist alles erlaubt (für Entwicklung)
if (empty($stored_key)) {
return true;
}
// Key vergleichen (timing-safe)
return hash_equals($stored_key, $provided_key);
}
/**
* Permission Callback für REST API mit API-Key
*/
public static function rest_api_key_permission($request): bool {
$api_key = $request->get_header('X-Skrift-API-Key');
return self::validate_api_key($api_key ?? '');
}
}
new Skrift_Konfigurator_Admin_Settings();