Newwebshop/app/API/controllers/WebhookController.php

470 lines
14 KiB
PHP

<?php
/**
* Copyright seit 2024 Webshop System
*
* Webhook Controller für das Webshop-System
*
* @author Webshop System
* @license GPL v3
*/
namespace App\API\Controllers;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
class WebhookController extends ApiController
{
/**
* Webhook registrieren
*/
public function register()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->sendError('Method not allowed', 405);
}
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
$this->sendError('Invalid JSON input', 400);
}
if (empty($input['url']) || empty($input['events'])) {
$this->sendError('URL and events are required', 400);
}
if (!filter_var($input['url'], FILTER_VALIDATE_URL)) {
$this->sendError('Invalid URL', 400);
}
try {
// Webhook erstellen
$stmt = $this->conn->prepare('
INSERT INTO ws_webhook (url, events, secret, active, created_at)
VALUES (?, ?, ?, 1, NOW())
');
$secret = bin2hex(random_bytes(32));
$stmt->execute([
$input['url'],
json_encode($input['events']),
$secret
]);
$webhookId = $this->conn->lastInsertId();
$this->sendResponse([
'success' => true,
'data' => [
'webhook_id' => $webhookId,
'secret' => $secret
],
'message' => 'Webhook registered successfully'
]);
} catch (Exception $e) {
$this->sendError('Failed to register webhook: ' . $e->getMessage(), 500);
}
}
/**
* Webhook-Liste abrufen
*/
public function getWebhooks()
{
try {
$stmt = $this->conn->prepare('
SELECT id, url, events, active, created_at, last_triggered
FROM ws_webhook
ORDER BY created_at DESC
');
$stmt->execute();
$webhooks = $stmt->fetchAllAssociative();
$this->sendResponse([
'success' => true,
'data' => $webhooks
]);
} catch (Exception $e) {
$this->sendError('Failed to fetch webhooks: ' . $e->getMessage(), 500);
}
}
/**
* Webhook aktualisieren
*/
public function updateWebhook($webhookId)
{
if ($_SERVER['REQUEST_METHOD'] !== 'PUT') {
$this->sendError('Method not allowed', 405);
}
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
$this->sendError('Invalid JSON input', 400);
}
try {
$updateFields = [];
$params = [];
if (isset($input['url'])) {
if (!filter_var($input['url'], FILTER_VALIDATE_URL)) {
$this->sendError('Invalid URL', 400);
}
$updateFields[] = 'url = ?';
$params[] = $input['url'];
}
if (isset($input['events'])) {
$updateFields[] = 'events = ?';
$params[] = json_encode($input['events']);
}
if (isset($input['active'])) {
$updateFields[] = 'active = ?';
$params[] = $input['active'] ? 1 : 0;
}
if (empty($updateFields)) {
$this->sendError('No fields to update', 400);
}
$updateFields[] = 'updated_at = NOW()';
$params[] = $webhookId;
$sql = 'UPDATE ws_webhook SET ' . implode(', ', $updateFields) . ' WHERE id = ?';
$stmt = $this->conn->prepare($sql);
$stmt->execute($params);
if ($stmt->rowCount() === 0) {
$this->sendError('Webhook not found', 404);
}
$this->sendResponse([
'success' => true,
'message' => 'Webhook updated successfully'
]);
} catch (Exception $e) {
$this->sendError('Failed to update webhook: ' . $e->getMessage(), 500);
}
}
/**
* Webhook löschen
*/
public function deleteWebhook($webhookId)
{
if ($_SERVER['REQUEST_METHOD'] !== 'DELETE') {
$this->sendError('Method not allowed', 405);
}
try {
$stmt = $this->conn->prepare('DELETE FROM ws_webhook WHERE id = ?');
$stmt->execute([$webhookId]);
if ($stmt->rowCount() === 0) {
$this->sendError('Webhook not found', 404);
}
$this->sendResponse([
'success' => true,
'message' => 'Webhook deleted successfully'
]);
} catch (Exception $e) {
$this->sendError('Failed to delete webhook: ' . $e->getMessage(), 500);
}
}
/**
* Event auslösen
*/
public function triggerEvent($event, $data = [])
{
try {
// Aktive Webhooks für dieses Event abrufen
$stmt = $this->conn->prepare('
SELECT id, url, secret, events
FROM ws_webhook
WHERE active = 1
');
$stmt->execute();
$webhooks = $stmt->fetchAllAssociative();
$triggeredCount = 0;
$failedCount = 0;
foreach ($webhooks as $webhook) {
$events = json_decode($webhook['events'], true) ?: [];
if (in_array($event, $events) || in_array('*', $events)) {
try {
$this->sendWebhook($webhook, $event, $data);
$triggeredCount++;
// Last triggered aktualisieren
$stmt = $this->conn->prepare('
UPDATE ws_webhook
SET last_triggered = NOW()
WHERE id = ?
');
$stmt->execute([$webhook['id']]);
} catch (Exception $e) {
$failedCount++;
$this->logWebhookError($webhook['id'], $event, $e->getMessage());
}
}
}
return [
'triggered' => $triggeredCount,
'failed' => $failedCount
];
} catch (Exception $e) {
throw new Exception('Failed to trigger event: ' . $e->getMessage());
}
}
/**
* Webhook senden
*/
private function sendWebhook($webhook, $event, $data)
{
$payload = [
'event' => $event,
'timestamp' => time(),
'data' => $data
];
$jsonPayload = json_encode($payload);
$signature = hash_hmac('sha256', $jsonPayload, $webhook['secret']);
$headers = [
'Content-Type: application/json',
'Content-Length: ' . strlen($jsonPayload),
'X-Webhook-Signature: ' . $signature,
'User-Agent: Webshop-System/1.0'
];
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => implode("\r\n", $headers),
'content' => $jsonPayload,
'timeout' => 30
]
]);
$response = file_get_contents($webhook['url'], false, $context);
if ($response === false) {
throw new Exception('Failed to send webhook to ' . $webhook['url']);
}
$httpCode = $this->getHttpResponseCode($http_response_header);
if ($httpCode < 200 || $httpCode >= 300) {
throw new Exception('Webhook returned HTTP ' . $httpCode);
}
}
/**
* HTTP-Response-Code extrahieren
*/
private function getHttpResponseCode($headers)
{
if (empty($headers)) {
return 0;
}
$statusLine = $headers[0];
preg_match('/HTTP\/\d\.\d\s+(\d+)/', $statusLine, $matches);
return isset($matches[1]) ? intval($matches[1]) : 0;
}
/**
* Webhook-Fehler loggen
*/
private function logWebhookError($webhookId, $event, $error)
{
try {
$stmt = $this->conn->prepare('
INSERT INTO ws_webhook_log (webhook_id, event, error, created_at)
VALUES (?, ?, ?, NOW())
');
$stmt->execute([$webhookId, $event, $error]);
} catch (Exception $e) {
// Ignore logging errors
}
}
/**
* Webhook-Logs abrufen
*/
public function getWebhookLogs($webhookId = null)
{
try {
if ($webhookId) {
$stmt = $this->conn->prepare('
SELECT * FROM ws_webhook_log
WHERE webhook_id = ?
ORDER BY created_at DESC
LIMIT 100
');
$stmt->execute([$webhookId]);
} else {
$stmt = $this->conn->prepare('
SELECT wl.*, w.url
FROM ws_webhook_log wl
LEFT JOIN ws_webhook w ON wl.webhook_id = w.id
ORDER BY wl.created_at DESC
LIMIT 100
');
$stmt->execute();
}
$logs = $stmt->fetchAllAssociative();
$this->sendResponse([
'success' => true,
'data' => $logs
]);
} catch (Exception $e) {
$this->sendError('Failed to fetch webhook logs: ' . $e->getMessage(), 500);
}
}
/**
* Event-Test
*/
public function testWebhook($webhookId)
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->sendError('Method not allowed', 405);
}
try {
$stmt = $this->conn->prepare('
SELECT * FROM ws_webhook
WHERE id = ? AND active = 1
');
$stmt->execute([$webhookId]);
$webhook = $stmt->fetchAssociative();
if (!$webhook) {
$this->sendError('Webhook not found', 404);
}
$testData = [
'test' => true,
'message' => 'This is a test webhook',
'timestamp' => date('Y-m-d H:i:s')
];
$this->sendWebhook($webhook, 'test', $testData);
$this->sendResponse([
'success' => true,
'message' => 'Test webhook sent successfully'
]);
} catch (Exception $e) {
$this->sendError('Failed to send test webhook: ' . $e->getMessage(), 500);
}
}
/**
* Verfügbare Events abrufen
*/
public function getAvailableEvents()
{
$events = [
'order.created' => 'Bestellung erstellt',
'order.updated' => 'Bestellung aktualisiert',
'order.cancelled' => 'Bestellung storniert',
'order.shipped' => 'Bestellung versendet',
'order.delivered' => 'Bestellung geliefert',
'customer.registered' => 'Kunde registriert',
'customer.updated' => 'Kunde aktualisiert',
'product.created' => 'Produkt erstellt',
'product.updated' => 'Produkt aktualisiert',
'product.deleted' => 'Produkt gelöscht',
'review.created' => 'Bewertung erstellt',
'review.updated' => 'Bewertung aktualisiert',
'newsletter.sent' => 'Newsletter gesendet',
'stock.low' => 'Lagerbestand niedrig',
'stock.out' => 'Lagerbestand aufgebraucht',
'payment.received' => 'Zahlung erhalten',
'payment.failed' => 'Zahlung fehlgeschlagen',
'test' => 'Test-Event'
];
$this->sendResponse([
'success' => true,
'data' => $events
]);
}
/**
* Webhook-Statistiken
*/
public function getWebhookStats()
{
try {
// Webhook-Statistiken
$stmt = $this->conn->prepare('
SELECT
COUNT(*) as total_webhooks,
COUNT(CASE WHEN active = 1 THEN 1 END) as active_webhooks,
COUNT(CASE WHEN active = 0 THEN 1 END) as inactive_webhooks
FROM ws_webhook
');
$stmt->execute();
$webhookStats = $stmt->fetchAssociative();
// Log-Statistiken
$stmt = $this->conn->prepare('
SELECT
COUNT(*) as total_logs,
COUNT(CASE WHEN error IS NOT NULL THEN 1 END) as error_logs,
COUNT(CASE WHEN error IS NULL THEN 1 END) as success_logs
FROM ws_webhook_log
');
$stmt->execute();
$logStats = $stmt->fetchAssociative();
// Event-Statistiken
$stmt = $this->conn->prepare('
SELECT event, COUNT(*) as count
FROM ws_webhook_log
GROUP BY event
ORDER BY count DESC
LIMIT 10
');
$stmt->execute();
$eventStats = $stmt->fetchAllAssociative();
$this->sendResponse([
'success' => true,
'data' => [
'webhooks' => $webhookStats,
'logs' => $logStats,
'events' => $eventStats
]
]);
} catch (Exception $e) {
$this->sendError('Failed to get webhook stats: ' . $e->getMessage(), 500);
}
}
}