643 lines
20 KiB
PHP
643 lines
20 KiB
PHP
<?php
|
|
/**
|
|
* Copyright seit 2024 Webshop System
|
|
*
|
|
* Event-System für PrestaShop-Modul-Kompatibilität
|
|
*
|
|
* @author Webshop System
|
|
* @license GPL v3
|
|
*/
|
|
|
|
namespace App\Core;
|
|
|
|
use Doctrine\DBAL\DriverManager;
|
|
use Doctrine\DBAL\Exception;
|
|
|
|
class EventDispatcher
|
|
{
|
|
private static $instance = null;
|
|
private $listeners = [];
|
|
private $events = [];
|
|
private $statistics = [];
|
|
private $cache = [];
|
|
private $enabled = true;
|
|
|
|
private function __construct()
|
|
{
|
|
$this->initializeDefaultEvents();
|
|
}
|
|
|
|
/**
|
|
* Singleton-Instanz abrufen
|
|
*/
|
|
public static function getInstance()
|
|
{
|
|
if (self::$instance === null) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Standard-Events initialisieren
|
|
*/
|
|
private function initializeDefaultEvents()
|
|
{
|
|
// System-Events
|
|
$this->registerEvent('system.boot', 'System wird gestartet');
|
|
$this->registerEvent('system.shutdown', 'System wird beendet');
|
|
$this->registerEvent('system.error', 'System-Fehler aufgetreten');
|
|
|
|
// User-Events
|
|
$this->registerEvent('user.login', 'Benutzer angemeldet');
|
|
$this->registerEvent('user.logout', 'Benutzer abgemeldet');
|
|
$this->registerEvent('user.register', 'Benutzer registriert');
|
|
$this->registerEvent('user.update', 'Benutzer aktualisiert');
|
|
$this->registerEvent('user.delete', 'Benutzer gelöscht');
|
|
|
|
// Product-Events
|
|
$this->registerEvent('product.create', 'Produkt erstellt');
|
|
$this->registerEvent('product.update', 'Produkt aktualisiert');
|
|
$this->registerEvent('product.delete', 'Produkt gelöscht');
|
|
$this->registerEvent('product.view', 'Produkt angesehen');
|
|
$this->registerEvent('product.add_to_cart', 'Produkt zum Warenkorb hinzugefügt');
|
|
|
|
// Order-Events
|
|
$this->registerEvent('order.create', 'Bestellung erstellt');
|
|
$this->registerEvent('order.update', 'Bestellung aktualisiert');
|
|
$this->registerEvent('order.delete', 'Bestellung gelöscht');
|
|
$this->registerEvent('order.paid', 'Bestellung bezahlt');
|
|
$this->registerEvent('order.shipped', 'Bestellung versendet');
|
|
$this->registerEvent('order.delivered', 'Bestellung geliefert');
|
|
$this->registerEvent('order.cancelled', 'Bestellung storniert');
|
|
|
|
// Cart-Events
|
|
$this->registerEvent('cart.add', 'Artikel zum Warenkorb hinzugefügt');
|
|
$this->registerEvent('cart.remove', 'Artikel aus Warenkorb entfernt');
|
|
$this->registerEvent('cart.update', 'Warenkorb aktualisiert');
|
|
$this->registerEvent('cart.clear', 'Warenkorb geleert');
|
|
|
|
// Payment-Events
|
|
$this->registerEvent('payment.process', 'Zahlung verarbeitet');
|
|
$this->registerEvent('payment.success', 'Zahlung erfolgreich');
|
|
$this->registerEvent('payment.failed', 'Zahlung fehlgeschlagen');
|
|
$this->registerEvent('payment.refund', 'Zahlung erstattet');
|
|
|
|
// Module-Events
|
|
$this->registerEvent('module.install', 'Modul installiert');
|
|
$this->registerEvent('module.uninstall', 'Modul deinstalliert');
|
|
$this->registerEvent('module.enable', 'Modul aktiviert');
|
|
$this->registerEvent('module.disable', 'Modul deaktiviert');
|
|
$this->registerEvent('module.update', 'Modul aktualisiert');
|
|
|
|
// Hook-Events
|
|
$this->registerEvent('hook.register', 'Hook registriert');
|
|
$this->registerEvent('hook.unregister', 'Hook deregistriert');
|
|
$this->registerEvent('hook.execute', 'Hook ausgeführt');
|
|
|
|
// Override-Events
|
|
$this->registerEvent('override.create', 'Override erstellt');
|
|
$this->registerEvent('override.update', 'Override aktualisiert');
|
|
$this->registerEvent('override.delete', 'Override gelöscht');
|
|
$this->registerEvent('override.enable', 'Override aktiviert');
|
|
$this->registerEvent('override.disable', 'Override deaktiviert');
|
|
|
|
// Cache-Events
|
|
$this->registerEvent('cache.clear', 'Cache geleert');
|
|
$this->registerEvent('cache.warm', 'Cache aufgewärmt');
|
|
$this->registerEvent('cache.invalidate', 'Cache invalidiert');
|
|
|
|
// Security-Events
|
|
$this->registerEvent('security.login_attempt', 'Anmeldeversuch');
|
|
$this->registerEvent('security.login_failed', 'Anmeldung fehlgeschlagen');
|
|
$this->registerEvent('security.logout', 'Abmeldung');
|
|
$this->registerEvent('security.permission_denied', 'Zugriff verweigert');
|
|
|
|
// Performance-Events
|
|
$this->registerEvent('performance.slow_query', 'Langsame Datenbankabfrage');
|
|
$this->registerEvent('performance.memory_high', 'Hoher Speicherverbrauch');
|
|
$this->registerEvent('performance.cpu_high', 'Hohe CPU-Last');
|
|
|
|
// Notification-Events
|
|
$this->registerEvent('notification.email_sent', 'E-Mail gesendet');
|
|
$this->registerEvent('notification.sms_sent', 'SMS gesendet');
|
|
$this->registerEvent('notification.push_sent', 'Push-Benachrichtigung gesendet');
|
|
}
|
|
|
|
/**
|
|
* Event registrieren
|
|
*/
|
|
public function registerEvent($eventName, $description = '')
|
|
{
|
|
$this->events[$eventName] = [
|
|
'name' => $eventName,
|
|
'description' => $description,
|
|
'listeners' => [],
|
|
'statistics' => [
|
|
'executions' => 0,
|
|
'total_time' => 0,
|
|
'avg_time' => 0,
|
|
'last_execution' => null
|
|
]
|
|
];
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Event-Listener registrieren
|
|
*/
|
|
public function addListener($eventName, $listener, $priority = 0, $moduleName = null)
|
|
{
|
|
if (!isset($this->events[$eventName])) {
|
|
$this->registerEvent($eventName);
|
|
}
|
|
|
|
$listenerId = $this->generateListenerId($listener, $moduleName);
|
|
|
|
$this->events[$eventName]['listeners'][$listenerId] = [
|
|
'listener' => $listener,
|
|
'priority' => $priority,
|
|
'module_name' => $moduleName,
|
|
'active' => true,
|
|
'executions' => 0,
|
|
'total_time' => 0,
|
|
'avg_time' => 0,
|
|
'last_execution' => null
|
|
];
|
|
|
|
// Nach Priorität sortieren (höhere Priorität zuerst)
|
|
uasort($this->events[$eventName]['listeners'], function($a, $b) {
|
|
return $b['priority'] - $a['priority'];
|
|
});
|
|
|
|
// Event in Datenbank speichern
|
|
$this->saveEventToDatabase($eventName, $listenerId, $listener, $priority, $moduleName);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Event-Listener entfernen
|
|
*/
|
|
public function removeListener($eventName, $listener, $moduleName = null)
|
|
{
|
|
if (!isset($this->events[$eventName])) {
|
|
return $this;
|
|
}
|
|
|
|
$listenerId = $this->generateListenerId($listener, $moduleName);
|
|
|
|
if (isset($this->events[$eventName]['listeners'][$listenerId])) {
|
|
unset($this->events[$eventName]['listeners'][$listenerId]);
|
|
|
|
// Event aus Datenbank entfernen
|
|
$this->removeEventFromDatabase($eventName, $listenerId);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Event ausführen
|
|
*/
|
|
public function dispatch($eventName, $event = null)
|
|
{
|
|
if (!$this->enabled) {
|
|
return $event;
|
|
}
|
|
|
|
if (!isset($this->events[$eventName])) {
|
|
$this->registerEvent($eventName);
|
|
}
|
|
|
|
$startTime = microtime(true);
|
|
$executedListeners = 0;
|
|
|
|
// Event-Objekt erstellen falls nicht vorhanden
|
|
if ($event === null) {
|
|
$event = new Event($eventName);
|
|
}
|
|
|
|
// Alle Listener ausführen
|
|
foreach ($this->events[$eventName]['listeners'] as $listenerId => $listenerData) {
|
|
if (!$listenerData['active']) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
$listenerStartTime = microtime(true);
|
|
|
|
// Listener ausführen
|
|
$result = call_user_func($listenerData['listener'], $event, $eventName, $this);
|
|
|
|
$listenerEndTime = microtime(true);
|
|
$executionTime = ($listenerEndTime - $listenerStartTime) * 1000; // in ms
|
|
|
|
// Statistiken aktualisieren
|
|
$this->updateListenerStatistics($eventName, $listenerId, $executionTime);
|
|
|
|
$executedListeners++;
|
|
|
|
// Event abbrechen falls Listener es verlangt
|
|
if ($event->isPropagationStopped()) {
|
|
break;
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
// Fehler loggen aber weitermachen
|
|
$this->logEventError($eventName, $listenerId, $e);
|
|
}
|
|
}
|
|
|
|
$endTime = microtime(true);
|
|
$totalTime = ($endTime - $startTime) * 1000; // in ms
|
|
|
|
// Event-Statistiken aktualisieren
|
|
$this->updateEventStatistics($eventName, $totalTime, $executedListeners);
|
|
|
|
// Event in Datenbank loggen
|
|
$this->logEventExecution($eventName, $totalTime, $executedListeners);
|
|
|
|
return $event;
|
|
}
|
|
|
|
/**
|
|
* Event-Listener aktivieren/deaktivieren
|
|
*/
|
|
public function setListenerActive($eventName, $listener, $moduleName, $active)
|
|
{
|
|
if (!isset($this->events[$eventName])) {
|
|
return $this;
|
|
}
|
|
|
|
$listenerId = $this->generateListenerId($listener, $moduleName);
|
|
|
|
if (isset($this->events[$eventName]['listeners'][$listenerId])) {
|
|
$this->events[$eventName]['listeners'][$listenerId]['active'] = $active;
|
|
|
|
// Status in Datenbank aktualisieren
|
|
$this->updateListenerStatus($eventName, $listenerId, $active);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Event-System aktivieren/deaktivieren
|
|
*/
|
|
public function setEnabled($enabled)
|
|
{
|
|
$this->enabled = $enabled;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Event-System Status abrufen
|
|
*/
|
|
public function isEnabled()
|
|
{
|
|
return $this->enabled;
|
|
}
|
|
|
|
/**
|
|
* Event-Statistiken abrufen
|
|
*/
|
|
public function getEventStatistics($eventName = null)
|
|
{
|
|
if ($eventName) {
|
|
return isset($this->events[$eventName]) ? $this->events[$eventName]['statistics'] : null;
|
|
}
|
|
|
|
$statistics = [];
|
|
foreach ($this->events as $name => $event) {
|
|
$statistics[$name] = $event['statistics'];
|
|
}
|
|
|
|
return $statistics;
|
|
}
|
|
|
|
/**
|
|
* Listener-Statistiken abrufen
|
|
*/
|
|
public function getListenerStatistics($eventName)
|
|
{
|
|
if (!isset($this->events[$eventName])) {
|
|
return [];
|
|
}
|
|
|
|
$statistics = [];
|
|
foreach ($this->events[$eventName]['listeners'] as $listenerId => $listenerData) {
|
|
$statistics[$listenerId] = [
|
|
'module_name' => $listenerData['module_name'],
|
|
'priority' => $listenerData['priority'],
|
|
'active' => $listenerData['active'],
|
|
'executions' => $listenerData['executions'],
|
|
'total_time' => $listenerData['total_time'],
|
|
'avg_time' => $listenerData['avg_time'],
|
|
'last_execution' => $listenerData['last_execution']
|
|
];
|
|
}
|
|
|
|
return $statistics;
|
|
}
|
|
|
|
/**
|
|
* Alle Events abrufen
|
|
*/
|
|
public function getEvents()
|
|
{
|
|
return $this->events;
|
|
}
|
|
|
|
/**
|
|
* Event-Listener abrufen
|
|
*/
|
|
public function getListeners($eventName)
|
|
{
|
|
if (!isset($this->events[$eventName])) {
|
|
return [];
|
|
}
|
|
|
|
return $this->events[$eventName]['listeners'];
|
|
}
|
|
|
|
/**
|
|
* Event-System zurücksetzen
|
|
*/
|
|
public function reset()
|
|
{
|
|
$this->listeners = [];
|
|
$this->events = [];
|
|
$this->statistics = [];
|
|
$this->cache = [];
|
|
$this->initializeDefaultEvents();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Listener-ID generieren
|
|
*/
|
|
private function generateListenerId($listener, $moduleName)
|
|
{
|
|
if (is_string($listener)) {
|
|
return $moduleName . '_' . $listener;
|
|
} elseif (is_array($listener)) {
|
|
return $moduleName . '_' . get_class($listener[0]) . '_' . $listener[1];
|
|
} else {
|
|
return $moduleName . '_' . spl_object_hash($listener);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Listener-Statistiken aktualisieren
|
|
*/
|
|
private function updateListenerStatistics($eventName, $listenerId, $executionTime)
|
|
{
|
|
if (!isset($this->events[$eventName]['listeners'][$listenerId])) {
|
|
return;
|
|
}
|
|
|
|
$listener = &$this->events[$eventName]['listeners'][$listenerId];
|
|
$listener['executions']++;
|
|
$listener['total_time'] += $executionTime;
|
|
$listener['avg_time'] = $listener['total_time'] / $listener['executions'];
|
|
$listener['last_execution'] = date('Y-m-d H:i:s');
|
|
}
|
|
|
|
/**
|
|
* Event-Statistiken aktualisieren
|
|
*/
|
|
private function updateEventStatistics($eventName, $totalTime, $executedListeners)
|
|
{
|
|
if (!isset($this->events[$eventName])) {
|
|
return;
|
|
}
|
|
|
|
$statistics = &$this->events[$eventName]['statistics'];
|
|
$statistics['executions']++;
|
|
$statistics['total_time'] += $totalTime;
|
|
$statistics['avg_time'] = $statistics['total_time'] / $statistics['executions'];
|
|
$statistics['last_execution'] = date('Y-m-d H:i:s');
|
|
}
|
|
|
|
/**
|
|
* Event-Fehler loggen
|
|
*/
|
|
private function logEventError($eventName, $listenerId, $exception)
|
|
{
|
|
$errorMessage = sprintf(
|
|
'Event-Fehler: %s, Listener: %s, Fehler: %s',
|
|
$eventName,
|
|
$listenerId,
|
|
$exception->getMessage()
|
|
);
|
|
|
|
error_log($errorMessage);
|
|
|
|
// Fehler in Datenbank loggen
|
|
$this->logEventErrorToDatabase($eventName, $listenerId, $exception);
|
|
}
|
|
|
|
/**
|
|
* Event in Datenbank speichern
|
|
*/
|
|
private function saveEventToDatabase($eventName, $listenerId, $listener, $priority, $moduleName)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
INSERT INTO ws_event_listeners (
|
|
event_name, listener_id, listener_data, priority,
|
|
module_name, active, created_at
|
|
) VALUES (?, ?, ?, ?, ?, 1, NOW())
|
|
ON DUPLICATE KEY UPDATE
|
|
listener_data = ?, priority = ?, active = 1, updated_at = NOW()
|
|
');
|
|
|
|
$listenerData = serialize($listener);
|
|
|
|
$stmt->execute([
|
|
$eventName,
|
|
$listenerId,
|
|
$listenerData,
|
|
$priority,
|
|
$moduleName,
|
|
$listenerData,
|
|
$priority
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('Event-Datenbank Fehler: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Event aus Datenbank entfernen
|
|
*/
|
|
private function removeEventFromDatabase($eventName, $listenerId)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
DELETE FROM ws_event_listeners
|
|
WHERE event_name = ? AND listener_id = ?
|
|
');
|
|
$stmt->execute([$eventName, $listenerId]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('Event-Entfernung Fehler: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Listener-Status in Datenbank aktualisieren
|
|
*/
|
|
private function updateListenerStatus($eventName, $listenerId, $active)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
UPDATE ws_event_listeners
|
|
SET active = ?, updated_at = NOW()
|
|
WHERE event_name = ? AND listener_id = ?
|
|
');
|
|
$stmt->execute([$active ? 1 : 0, $eventName, $listenerId]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('Event-Status Fehler: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Event-Ausführung in Datenbank loggen
|
|
*/
|
|
private function logEventExecution($eventName, $executionTime, $executedListeners)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
INSERT INTO ws_event_logs (
|
|
event_name, execution_time, executed_listeners,
|
|
created_at
|
|
) VALUES (?, ?, ?, NOW())
|
|
');
|
|
|
|
$stmt->execute([
|
|
$eventName,
|
|
$executionTime,
|
|
$executedListeners
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('Event-Log Fehler: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Event-Fehler in Datenbank loggen
|
|
*/
|
|
private function logEventErrorToDatabase($eventName, $listenerId, $exception)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
INSERT INTO ws_event_errors (
|
|
event_name, listener_id, error_message,
|
|
error_trace, created_at
|
|
) VALUES (?, ?, ?, ?, NOW())
|
|
');
|
|
|
|
$stmt->execute([
|
|
$eventName,
|
|
$listenerId,
|
|
$exception->getMessage(),
|
|
$exception->getTraceAsString()
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('Event-Error-Log Fehler: ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Event-Klasse für Event-Objekte
|
|
*/
|
|
class Event
|
|
{
|
|
private $name;
|
|
private $data;
|
|
private $propagationStopped = false;
|
|
|
|
public function __construct($name, $data = [])
|
|
{
|
|
$this->name = $name;
|
|
$this->data = $data;
|
|
}
|
|
|
|
public function getName()
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
public function getData()
|
|
{
|
|
return $this->data;
|
|
}
|
|
|
|
public function setData($data)
|
|
{
|
|
$this->data = $data;
|
|
return $this;
|
|
}
|
|
|
|
public function get($key, $default = null)
|
|
{
|
|
return isset($this->data[$key]) ? $this->data[$key] : $default;
|
|
}
|
|
|
|
public function set($key, $value)
|
|
{
|
|
$this->data[$key] = $value;
|
|
return $this;
|
|
}
|
|
|
|
public function has($key)
|
|
{
|
|
return isset($this->data[$key]);
|
|
}
|
|
|
|
public function remove($key)
|
|
{
|
|
unset($this->data[$key]);
|
|
return $this;
|
|
}
|
|
|
|
public function stopPropagation()
|
|
{
|
|
$this->propagationStopped = true;
|
|
return $this;
|
|
}
|
|
|
|
public function isPropagationStopped()
|
|
{
|
|
return $this->propagationStopped;
|
|
}
|
|
}
|