Initial commit

This commit is contained in:
s4luorth
2026-02-07 13:04:04 +01:00
commit 5e0fceab15
82 changed files with 30348 additions and 0 deletions

View File

@@ -0,0 +1,351 @@
<?php
/**
* API Proxy für Skrift Konfigurator
* Leitet Anfragen an das Backend weiter, ohne den API-Token im Frontend zu exponieren
*/
if (!defined('ABSPATH')) { exit; }
final class Skrift_Konfigurator_API_Proxy {
public function __construct() {
add_action('rest_api_init', [$this, 'register_rest_routes']);
}
/**
* REST API Routen registrieren
*/
public function register_rest_routes() {
// Health Check
register_rest_route('skrift/v1', '/proxy/health', [
'methods' => 'GET',
'callback' => [$this, 'proxy_health_check'],
'permission_callback' => ['Skrift_Konfigurator_Admin_Settings', 'rest_api_key_permission'],
]);
// Preview Batch generieren
register_rest_route('skrift/v1', '/proxy/preview/batch', [
'methods' => 'POST',
'callback' => [$this, 'proxy_preview_batch'],
'permission_callback' => ['Skrift_Konfigurator_Admin_Settings', 'rest_api_key_permission'],
]);
// Einzelne Preview abrufen
register_rest_route('skrift/v1', '/proxy/preview/(?P<sessionId>[a-zA-Z0-9_-]+)/(?P<index>\d+)', [
'methods' => 'GET',
'callback' => [$this, 'proxy_preview_get'],
'permission_callback' => ['Skrift_Konfigurator_Admin_Settings', 'rest_api_key_permission'],
]);
// Order generieren
register_rest_route('skrift/v1', '/proxy/order/generate', [
'methods' => 'POST',
'callback' => [$this, 'proxy_order_generate'],
'permission_callback' => ['Skrift_Konfigurator_Admin_Settings', 'rest_api_key_permission'],
]);
// Order finalisieren
register_rest_route('skrift/v1', '/proxy/order/finalize', [
'methods' => 'POST',
'callback' => [$this, 'proxy_order_finalize'],
'permission_callback' => ['Skrift_Konfigurator_Admin_Settings', 'rest_api_key_permission'],
]);
// Motiv hochladen (ans Docker-Backend weiterleiten)
register_rest_route('skrift/v1', '/proxy/motif/upload', [
'methods' => 'POST',
'callback' => [$this, 'proxy_motif_upload'],
'permission_callback' => ['Skrift_Konfigurator_Admin_Settings', 'rest_api_key_permission'],
]);
}
/**
* Holt Backend-Konfiguration
*/
private function get_backend_config(): array {
$settings = Skrift_Konfigurator_Admin_Settings::get_settings();
return [
'api_url' => $settings['backend_connection']['api_url'] ?? '',
'api_token' => $settings['backend_connection']['api_token'] ?? '',
];
}
/**
* Führt einen HTTP-Request ans Backend aus
*/
private function make_backend_request(string $method, string $endpoint, array $body = null): array {
$config = $this->get_backend_config();
if (empty($config['api_url'])) {
return [
'success' => false,
'error' => 'Backend API URL nicht konfiguriert',
'status' => 500,
];
}
$url = rtrim($config['api_url'], '/') . $endpoint;
$args = [
'method' => $method,
'timeout' => 60,
'headers' => [
'Content-Type' => 'application/json',
],
];
// API Token hinzufügen wenn vorhanden
if (!empty($config['api_token'])) {
$args['headers']['X-API-Token'] = $config['api_token'];
}
// Body hinzufügen bei POST/PUT
if ($body !== null && in_array($method, ['POST', 'PUT', 'PATCH'])) {
$args['body'] = wp_json_encode($body);
}
$response = wp_remote_request($url, $args);
if (is_wp_error($response)) {
return [
'success' => false,
'error' => $response->get_error_message(),
'status' => 500,
];
}
$status_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
// Versuche JSON zu parsen
$data = json_decode($response_body, true);
if ($status_code >= 200 && $status_code < 300) {
return [
'success' => true,
'data' => $data ?? $response_body,
'status' => $status_code,
];
}
return [
'success' => false,
'error' => $data['error'] ?? $data['message'] ?? 'Backend-Fehler',
'status' => $status_code,
'data' => $data,
];
}
/**
* Health Check Proxy
*/
public function proxy_health_check($request) {
$result = $this->make_backend_request('GET', '/health');
if (!$result['success']) {
return new WP_Error('backend_error', $result['error'], ['status' => $result['status']]);
}
return $result['data'];
}
/**
* Preview Batch Proxy
*/
public function proxy_preview_batch($request) {
$body = $request->get_json_params();
if (empty($body)) {
return new WP_Error('invalid_request', 'Keine Daten empfangen', ['status' => 400]);
}
$result = $this->make_backend_request('POST', '/api/preview/batch', $body);
if (!$result['success']) {
// Rate Limiting Info weitergeben
if ($result['status'] === 429 && isset($result['data']['retryAfter'])) {
return new WP_REST_Response([
'error' => $result['error'],
'retryAfter' => $result['data']['retryAfter'],
], 429);
}
return new WP_Error('backend_error', $result['error'], ['status' => $result['status']]);
}
return $result['data'];
}
/**
* Einzelne Preview abrufen Proxy
*/
public function proxy_preview_get($request) {
$session_id = $request->get_param('sessionId');
$index = $request->get_param('index');
// Sicherheits-Validierung: Session-ID darf nur alphanumerische Zeichen, Unterstriche und Bindestriche enthalten
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $session_id)) {
return new WP_Error('invalid_session_id', 'Ungültige Session-ID', ['status' => 400]);
}
// Index muss eine positive Ganzzahl sein
$index = absint($index);
$result = $this->make_backend_request('GET', "/api/preview/{$session_id}/{$index}");
if (!$result['success']) {
return new WP_Error('backend_error', $result['error'], ['status' => $result['status']]);
}
// Bei SVG-Daten: Content-Type setzen
if (is_string($result['data']) && strpos($result['data'], '<svg') !== false) {
$response = new WP_REST_Response($result['data']);
$response->header('Content-Type', 'image/svg+xml');
return $response;
}
return $result['data'];
}
/**
* Order generieren Proxy
*/
public function proxy_order_generate($request) {
$body = $request->get_json_params();
if (empty($body)) {
return new WP_Error('invalid_request', 'Keine Daten empfangen', ['status' => 400]);
}
$result = $this->make_backend_request('POST', '/api/order/generate', $body);
if (!$result['success']) {
return new WP_Error('backend_error', $result['error'], ['status' => $result['status']]);
}
return $result['data'];
}
/**
* Order finalisieren Proxy
*/
public function proxy_order_finalize($request) {
$body = $request->get_json_params();
if (empty($body)) {
return new WP_Error('invalid_request', 'Keine Daten empfangen', ['status' => 400]);
}
$result = $this->make_backend_request('POST', '/api/order/finalize', $body);
if (!$result['success']) {
return new WP_Error('backend_error', $result['error'], ['status' => $result['status']]);
}
return $result['data'];
}
/**
* Motiv hochladen Proxy
* Leitet Datei ans Docker-Backend weiter, speichert dort im Auftragsordner
*/
public function proxy_motif_upload($request) {
// Datei aus Request holen
$files = $request->get_file_params();
if (empty($files['motif'])) {
return new WP_Error('no_file', 'Keine Datei empfangen', ['status' => 400]);
}
$file = $files['motif'];
$order_number = $request->get_param('orderNumber');
if (empty($order_number)) {
return new WP_Error('no_order_number', 'Bestellnummer fehlt', ['status' => 400]);
}
// Überprüfe auf Upload-Fehler
if ($file['error'] !== UPLOAD_ERR_OK) {
return new WP_Error('upload_error', 'Upload-Fehler: ' . $file['error'], ['status' => 400]);
}
// Erlaubte Dateitypen prüfen
$allowed_types = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp', 'image/svg+xml', 'application/pdf'];
$file_type = wp_check_filetype($file['name']);
// MIME-Type Prüfung
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$real_type = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
// SVG wird oft als text/xml erkannt
if ($real_type === 'text/xml' || $real_type === 'text/plain') {
$content = file_get_contents($file['tmp_name']);
if (strpos($content, '<svg') !== false) {
$real_type = 'image/svg+xml';
}
}
if (!in_array($real_type, $allowed_types) && !in_array($file['type'], $allowed_types)) {
return new WP_Error('invalid_type', 'Ungültiger Dateityp: ' . $real_type, ['status' => 400]);
}
// Datei ans Docker-Backend senden (multipart/form-data)
$config = $this->get_backend_config();
if (empty($config['api_url'])) {
return new WP_Error('no_backend', 'Backend API URL nicht konfiguriert', ['status' => 500]);
}
$url = rtrim($config['api_url'], '/') . '/api/order/motif';
// Boundary für multipart
$boundary = wp_generate_password(24, false);
// Multipart Body bauen
$body = '';
// orderNumber Feld
$body .= "--{$boundary}\r\n";
$body .= "Content-Disposition: form-data; name=\"orderNumber\"\r\n\r\n";
$body .= $order_number . "\r\n";
// Datei Feld
$body .= "--{$boundary}\r\n";
$body .= "Content-Disposition: form-data; name=\"motif\"; filename=\"" . basename($file['name']) . "\"\r\n";
$body .= "Content-Type: " . ($real_type ?: 'application/octet-stream') . "\r\n\r\n";
$body .= file_get_contents($file['tmp_name']) . "\r\n";
$body .= "--{$boundary}--\r\n";
$args = [
'method' => 'POST',
'timeout' => 60,
'headers' => [
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
],
'body' => $body,
];
// API Token hinzufügen wenn vorhanden
if (!empty($config['api_token'])) {
$args['headers']['X-API-Token'] = $config['api_token'];
}
$response = wp_remote_post($url, $args);
if (is_wp_error($response)) {
return new WP_Error('backend_error', $response->get_error_message(), ['status' => 500]);
}
$status_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
$data = json_decode($response_body, true);
if ($status_code >= 200 && $status_code < 300) {
return $data;
}
return new WP_Error('backend_error', $data['error'] ?? $data['message'] ?? 'Upload fehlgeschlagen', ['status' => $status_code]);
}
}
new Skrift_Konfigurator_API_Proxy();