Newwebshop/app/Core/ModuleAPI.php

733 lines
22 KiB
PHP

<?php
/**
* Copyright seit 2024 Webshop System
*
* Module-API für PrestaShop-Modul-Kompatibilität
*
* @author Webshop System
* @license GPL v3
*/
namespace App\Core;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
class ModuleAPI
{
private static $instance = null;
private $moduleManager;
private $eventDispatcher;
private $cache;
private $logger;
private $enabled = true;
private $rateLimit = 1000; // Requests pro Stunde
private $rateLimitWindow = 3600; // 1 Stunde
private $apiKeys = [];
private function __construct()
{
$this->moduleManager = ModuleManager::getInstance();
$this->eventDispatcher = EventDispatcher::getInstance();
$this->cache = Cache::getInstance();
$this->logger = Logger::getInstance();
$this->loadApiKeys();
}
/**
* Singleton-Instanz abrufen
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* API-Request verarbeiten
*/
public function handleRequest($method, $endpoint, $data = [], $headers = [])
{
if (!$this->enabled) {
return $this->createResponse(503, 'API deaktiviert');
}
// API-Key validieren
$apiKey = $this->extractApiKey($headers);
if (!$this->validateApiKey($apiKey)) {
return $this->createResponse(401, 'Ungültiger API-Key');
}
// Rate-Limiting prüfen
if (!$this->checkRateLimit($apiKey)) {
return $this->createResponse(429, 'Rate-Limit überschritten');
}
// Request loggen
$this->logger->info('API Request', [
'method' => $method,
'endpoint' => $endpoint,
'api_key' => $this->maskApiKey($apiKey),
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
]);
try {
// Endpoint auflösen
$handler = $this->resolveEndpoint($method, $endpoint);
if (!$handler) {
return $this->createResponse(404, 'Endpoint nicht gefunden');
}
// Request verarbeiten
$result = call_user_func($handler, $data, $headers);
// Response loggen
$this->logger->info('API Response', [
'method' => $method,
'endpoint' => $endpoint,
'status' => $result['status'] ?? 200
]);
return $result;
} catch (\Exception $e) {
$this->logger->error('API Error', [
'method' => $method,
'endpoint' => $endpoint,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return $this->createResponse(500, 'Interner Server-Fehler: ' . $e->getMessage());
}
}
/**
* Module-Liste abrufen
*/
public function getModules($filters = [])
{
$cacheKey = 'api_modules_' . md5(serialize($filters));
// Cache prüfen
$cached = $this->cache->get($cacheKey);
if ($cached !== null) {
return $this->createResponse(200, 'Module abgerufen', $cached);
}
$modules = $this->moduleManager->getAllModules();
// Filter anwenden
if (!empty($filters['active'])) {
$modules = array_filter($modules, function($module) {
return $module['active'] ?? false;
});
}
if (!empty($filters['type'])) {
$modules = array_filter($modules, function($module) use ($filters) {
return $module['type'] === $filters['type'];
});
}
if (!empty($filters['search'])) {
$search = strtolower($filters['search']);
$modules = array_filter($modules, function($module) use ($search) {
return strpos(strtolower($module['name']), $search) !== false ||
strpos(strtolower($module['description']), $search) !== false;
});
}
// Pagination
$page = $filters['page'] ?? 1;
$limit = min($filters['limit'] ?? 20, 100);
$offset = ($page - 1) * $limit;
$total = count($modules);
$modules = array_slice($modules, $offset, $limit);
$result = [
'modules' => $modules,
'pagination' => [
'page' => $page,
'limit' => $limit,
'total' => $total,
'pages' => ceil($total / $limit)
]
];
// Cache setzen
$this->cache->set($cacheKey, $result, 300); // 5 Minuten
return $this->createResponse(200, 'Module abgerufen', $result);
}
/**
* Einzelnes Modul abrufen
*/
public function getModule($moduleName)
{
$cacheKey = 'api_module_' . $moduleName;
// Cache prüfen
$cached = $this->cache->get($cacheKey);
if ($cached !== null) {
return $this->createResponse(200, 'Modul abgerufen', $cached);
}
$module = $this->moduleManager->getModule($moduleName);
if (!$module) {
return $this->createResponse(404, 'Modul nicht gefunden');
}
// Cache setzen
$this->cache->set($cacheKey, $module, 600); // 10 Minuten
return $this->createResponse(200, 'Modul abgerufen', $module);
}
/**
* Modul installieren
*/
public function installModule($moduleName, $data = [])
{
// Event auslösen
$this->eventDispatcher->dispatch('module.install.before', [
'module_name' => $moduleName,
'data' => $data
]);
try {
$result = $this->moduleManager->installModule($moduleName, $data);
if ($result) {
// Cache invalidieren
$this->cache->delete('api_modules_*');
$this->cache->delete('api_module_' . $moduleName);
// Event auslösen
$this->eventDispatcher->dispatch('module.install.after', [
'module_name' => $moduleName,
'result' => $result
]);
$this->logger->info('Module installiert via API', [
'module_name' => $moduleName,
'data' => $data
]);
return $this->createResponse(201, 'Modul erfolgreich installiert', $result);
} else {
return $this->createResponse(400, 'Modul-Installation fehlgeschlagen');
}
} catch (\Exception $e) {
$this->logger->error('Module-Installation Fehler', [
'module_name' => $moduleName,
'error' => $e->getMessage()
]);
return $this->createResponse(500, 'Installation-Fehler: ' . $e->getMessage());
}
}
/**
* Modul deinstallieren
*/
public function uninstallModule($moduleName)
{
// Event auslösen
$this->eventDispatcher->dispatch('module.uninstall.before', [
'module_name' => $moduleName
]);
try {
$result = $this->moduleManager->uninstallModule($moduleName);
if ($result) {
// Cache invalidieren
$this->cache->delete('api_modules_*');
$this->cache->delete('api_module_' . $moduleName);
// Event auslösen
$this->eventDispatcher->dispatch('module.uninstall.after', [
'module_name' => $moduleName,
'result' => $result
]);
$this->logger->info('Module deinstalliert via API', [
'module_name' => $moduleName
]);
return $this->createResponse(200, 'Modul erfolgreich deinstalliert');
} else {
return $this->createResponse(400, 'Modul-Deinstallation fehlgeschlagen');
}
} catch (\Exception $e) {
$this->logger->error('Module-Deinstallation Fehler', [
'module_name' => $moduleName,
'error' => $e->getMessage()
]);
return $this->createResponse(500, 'Deinstallation-Fehler: ' . $e->getMessage());
}
}
/**
* Modul aktivieren
*/
public function enableModule($moduleName)
{
try {
$result = $this->moduleManager->enableModule($moduleName);
if ($result) {
// Cache invalidieren
$this->cache->delete('api_modules_*');
$this->cache->delete('api_module_' . $moduleName);
$this->logger->info('Module aktiviert via API', [
'module_name' => $moduleName
]);
return $this->createResponse(200, 'Modul erfolgreich aktiviert');
} else {
return $this->createResponse(400, 'Modul-Aktivierung fehlgeschlagen');
}
} catch (\Exception $e) {
$this->logger->error('Module-Aktivierung Fehler', [
'module_name' => $moduleName,
'error' => $e->getMessage()
]);
return $this->createResponse(500, 'Aktivierungs-Fehler: ' . $e->getMessage());
}
}
/**
* Modul deaktivieren
*/
public function disableModule($moduleName)
{
try {
$result = $this->moduleManager->disableModule($moduleName);
if ($result) {
// Cache invalidieren
$this->cache->delete('api_modules_*');
$this->cache->delete('api_module_' . $moduleName);
$this->logger->info('Module deaktiviert via API', [
'module_name' => $moduleName
]);
return $this->createResponse(200, 'Modul erfolgreich deaktiviert');
} else {
return $this->createResponse(400, 'Modul-Deaktivierung fehlgeschlagen');
}
} catch (\Exception $e) {
$this->logger->error('Module-Deaktivierung Fehler', [
'module_name' => $moduleName,
'error' => $e->getMessage()
]);
return $this->createResponse(500, 'Deaktivierungs-Fehler: ' . $e->getMessage());
}
}
/**
* Modul-Konfiguration abrufen
*/
public function getModuleConfig($moduleName)
{
$cacheKey = 'api_module_config_' . $moduleName;
// Cache prüfen
$cached = $this->cache->get($cacheKey);
if ($cached !== null) {
return $this->createResponse(200, 'Modul-Konfiguration abgerufen', $cached);
}
$config = $this->moduleManager->getModuleConfig($moduleName);
if (!$config) {
return $this->createResponse(404, 'Modul-Konfiguration nicht gefunden');
}
// Cache setzen
$this->cache->set($cacheKey, $config, 300); // 5 Minuten
return $this->createResponse(200, 'Modul-Konfiguration abgerufen', $config);
}
/**
* Modul-Konfiguration aktualisieren
*/
public function updateModuleConfig($moduleName, $config)
{
try {
$result = $this->moduleManager->updateModuleConfig($moduleName, $config);
if ($result) {
// Cache invalidieren
$this->cache->delete('api_module_config_' . $moduleName);
$this->logger->info('Module-Konfiguration aktualisiert via API', [
'module_name' => $moduleName,
'config' => $config
]);
return $this->createResponse(200, 'Modul-Konfiguration erfolgreich aktualisiert');
} else {
return $this->createResponse(400, 'Konfigurations-Update fehlgeschlagen');
}
} catch (\Exception $e) {
$this->logger->error('Module-Konfigurations-Update Fehler', [
'module_name' => $moduleName,
'error' => $e->getMessage()
]);
return $this->createResponse(500, 'Update-Fehler: ' . $e->getMessage());
}
}
/**
* Module-Statistiken abrufen
*/
public function getModuleStatistics()
{
$cacheKey = 'api_module_statistics';
// Cache prüfen
$cached = $this->cache->get($cacheKey);
if ($cached !== null) {
return $this->createResponse(200, 'Module-Statistiken abgerufen', $cached);
}
$statistics = $this->moduleManager->getStatistics();
// Cache setzen
$this->cache->set($cacheKey, $statistics, 600); // 10 Minuten
return $this->createResponse(200, 'Module-Statistiken abgerufen', $statistics);
}
/**
* API-Status abrufen
*/
public function getApiStatus()
{
$status = [
'enabled' => $this->enabled,
'version' => '1.0.0',
'rate_limit' => $this->rateLimit,
'rate_limit_window' => $this->rateLimitWindow,
'total_modules' => count($this->moduleManager->getAllModules()),
'active_modules' => count(array_filter($this->moduleManager->getAllModules(), function($m) {
return $m['active'] ?? false;
})),
'uptime' => time() - strtotime('today'),
'memory_usage' => memory_get_usage(true),
'memory_peak' => memory_get_peak_usage(true)
];
return $this->createResponse(200, 'API-Status abgerufen', $status);
}
/**
* Endpoint auflösen
*/
private function resolveEndpoint($method, $endpoint)
{
$endpoints = [
'GET' => [
'/api/v1/modules' => [$this, 'getModules'],
'/api/v1/modules/{name}' => [$this, 'getModule'],
'/api/v1/modules/{name}/config' => [$this, 'getModuleConfig'],
'/api/v1/statistics' => [$this, 'getModuleStatistics'],
'/api/v1/status' => [$this, 'getApiStatus']
],
'POST' => [
'/api/v1/modules' => [$this, 'installModule'],
'/api/v1/modules/{name}/enable' => [$this, 'enableModule'],
'/api/v1/modules/{name}/disable' => [$this, 'disableModule'],
'/api/v1/modules/{name}/config' => [$this, 'updateModuleConfig']
],
'DELETE' => [
'/api/v1/modules/{name}' => [$this, 'uninstallModule']
]
];
if (!isset($endpoints[$method])) {
return null;
}
foreach ($endpoints[$method] as $pattern => $handler) {
if ($this->matchPattern($pattern, $endpoint)) {
return $handler;
}
}
return null;
}
/**
* Pattern-Matching für Endpoints
*/
private function matchPattern($pattern, $endpoint)
{
$pattern = preg_replace('/\{([^}]+)\}/', '([^/]+)', $pattern);
return preg_match('#^' . $pattern . '$#', $endpoint);
}
/**
* API-Key aus Headers extrahieren
*/
private function extractApiKey($headers)
{
$apiKey = null;
// Verschiedene Header-Namen prüfen
$headerNames = ['X-API-Key', 'Authorization', 'Api-Key'];
foreach ($headerNames as $headerName) {
if (isset($headers[$headerName])) {
$value = $headers[$headerName];
// Bearer Token Format
if (strpos($value, 'Bearer ') === 0) {
$apiKey = substr($value, 7);
} else {
$apiKey = $value;
}
break;
}
}
return $apiKey;
}
/**
* API-Key validieren
*/
private function validateApiKey($apiKey)
{
if (!$apiKey) {
return false;
}
return isset($this->apiKeys[$apiKey]) && $this->apiKeys[$apiKey]['active'];
}
/**
* Rate-Limiting prüfen
*/
private function checkRateLimit($apiKey)
{
$cacheKey = 'api_rate_limit_' . md5($apiKey);
$current = time();
$requests = $this->cache->get($cacheKey, []);
// Alte Requests entfernen
$requests = array_filter($requests, function($timestamp) use ($current) {
return $timestamp > ($current - $this->rateLimitWindow);
});
// Neuen Request hinzufügen
$requests[] = $current;
// Cache aktualisieren
$this->cache->set($cacheKey, $requests, $this->rateLimitWindow);
return count($requests) <= $this->rateLimit;
}
/**
* API-Keys laden
*/
private function loadApiKeys()
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
SELECT api_key, name, permissions, active, created_at
FROM ws_api_keys
WHERE active = 1
');
$stmt->execute();
$keys = $stmt->fetchAllAssociative();
foreach ($keys as $key) {
$this->apiKeys[$key['api_key']] = [
'name' => $key['name'],
'permissions' => json_decode($key['permissions'], true) ?: [],
'active' => (bool)$key['active'],
'created_at' => $key['created_at']
];
}
} catch (Exception $e) {
$this->logger->error('API-Keys laden Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* API-Key maskieren für Logs
*/
private function maskApiKey($apiKey)
{
if (strlen($apiKey) <= 8) {
return str_repeat('*', strlen($apiKey));
}
return substr($apiKey, 0, 4) . str_repeat('*', strlen($apiKey) - 8) . substr($apiKey, -4);
}
/**
* Response erstellen
*/
private function createResponse($status, $message, $data = null)
{
$response = [
'status' => $status,
'message' => $message,
'timestamp' => date('c'),
'request_id' => uniqid('api_', true)
];
if ($data !== null) {
$response['data'] = $data;
}
return $response;
}
/**
* API aktivieren/deaktivieren
*/
public function setEnabled($enabled)
{
$this->enabled = $enabled;
return $this;
}
/**
* API-Status prüfen
*/
public function isEnabled()
{
return $this->enabled;
}
/**
* Rate-Limit setzen
*/
public function setRateLimit($limit, $window = 3600)
{
$this->rateLimit = $limit;
$this->rateLimitWindow = $window;
return $this;
}
/**
* API-Key erstellen
*/
public function createApiKey($name, $permissions = [])
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$apiKey = $this->generateApiKey();
$stmt = $conn->prepare('
INSERT INTO ws_api_keys (
api_key, name, permissions, active, created_at
) VALUES (?, ?, ?, 1, NOW())
');
$stmt->execute([
$apiKey,
$name,
json_encode($permissions)
]);
// API-Keys neu laden
$this->loadApiKeys();
$this->logger->info('API-Key erstellt', [
'name' => $name,
'permissions' => $permissions
]);
return $apiKey;
} catch (Exception $e) {
$this->logger->error('API-Key erstellen Fehler', [
'error' => $e->getMessage()
]);
return false;
}
}
/**
* API-Key löschen
*/
public function deleteApiKey($apiKey)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
DELETE FROM ws_api_keys
WHERE api_key = ?
');
$stmt->execute([$apiKey]);
// API-Keys neu laden
$this->loadApiKeys();
$this->logger->info('API-Key gelöscht', [
'api_key' => $this->maskApiKey($apiKey)
]);
return true;
} catch (Exception $e) {
$this->logger->error('API-Key löschen Fehler', [
'error' => $e->getMessage()
]);
return false;
}
}
/**
* API-Key generieren
*/
private function generateApiKey()
{
return 'ws_' . bin2hex(random_bytes(32));
}
}