Newwebshop/app/Core/AutoUpdateSystem.php

657 lines
19 KiB
PHP

<?php
/**
* Copyright seit 2024 Webshop System
*
* Auto-Update-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 AutoUpdateSystem
{
private static $instance = null;
private $moduleManager;
private $moduleRepository;
private $eventDispatcher;
private $cache;
private $logger;
private $enabled = true;
private $checkInterval = 86400; // 24 Stunden
private $autoInstall = false;
private $notifyEmail = '';
private function __construct()
{
$this->moduleManager = ModuleManager::getInstance();
$this->moduleRepository = ModuleRepository::getInstance();
$this->eventDispatcher = EventDispatcher::getInstance();
$this->cache = Cache::getInstance();
$this->logger = Logger::getInstance();
$this->loadSettings();
}
/**
* Singleton-Instanz abrufen
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Einstellungen laden
*/
private function loadSettings()
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
SELECT setting_key, setting_value
FROM ws_auto_update_settings
WHERE active = 1
');
$stmt->execute();
$settings = $stmt->fetchAllAssociative();
foreach ($settings as $setting) {
switch ($setting['setting_key']) {
case 'enabled':
$this->enabled = (bool)$setting['setting_value'];
break;
case 'check_interval':
$this->checkInterval = (int)$setting['setting_value'];
break;
case 'auto_install':
$this->autoInstall = (bool)$setting['setting_value'];
break;
case 'notify_email':
$this->notifyEmail = $setting['setting_value'];
break;
}
}
} catch (Exception $e) {
$this->logger->error('Auto-Update-Einstellungen laden Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* Update-Check für alle Module
*/
public function checkForUpdates()
{
if (!$this->enabled) {
return [];
}
$this->logger->info('Update-Check gestartet');
$updates = [];
$modules = $this->moduleManager->getAllModules();
foreach ($modules as $moduleName => $module) {
try {
$update = $this->checkModuleUpdate($moduleName, $module);
if ($update) {
$updates[$moduleName] = $update;
}
} catch (\Exception $e) {
$this->logger->error('Update-Check Fehler für Modul', [
'module_name' => $moduleName,
'error' => $e->getMessage()
]);
}
}
// Updates in Datenbank speichern
$this->saveUpdateResults($updates);
// Benachrichtigung senden falls Updates verfügbar
if (!empty($updates) && $this->notifyEmail) {
$this->sendUpdateNotification($updates);
}
// Auto-Installation falls aktiviert
if ($this->autoInstall && !empty($updates)) {
$this->autoInstallUpdates($updates);
}
$this->logger->info('Update-Check abgeschlossen', [
'updates_found' => count($updates)
]);
return $updates;
}
/**
* Update-Check für einzelnes Modul
*/
public function checkModuleUpdate($moduleName, $module)
{
$currentVersion = $module['version'] ?? '1.0.0';
// Repository-Details abrufen
$repositoryDetails = $this->getModuleRepositoryDetails($moduleName);
if (!$repositoryDetails) {
return null;
}
$latestVersion = $repositoryDetails['latest_version'] ?? '1.0.0';
// Version vergleichen
if (version_compare($latestVersion, $currentVersion, '>')) {
return [
'module_name' => $moduleName,
'current_version' => $currentVersion,
'latest_version' => $latestVersion,
'repository' => $repositoryDetails['repository'] ?? 'official',
'changelog' => $repositoryDetails['changelog'] ?? '',
'download_url' => $repositoryDetails['download_url'] ?? '',
'release_date' => $repositoryDetails['release_date'] ?? '',
'priority' => $repositoryDetails['priority'] ?? 'normal'
];
}
return null;
}
/**
* Repository-Details für Modul abrufen
*/
private function getModuleRepositoryDetails($moduleName)
{
$repositories = $this->moduleRepository->getRepositories();
foreach ($repositories as $repositoryId => $repository) {
if (!$repository['enabled']) {
continue;
}
try {
$details = $this->moduleRepository->getModuleDetails($moduleName, $repositoryId);
if ($details) {
$details['repository'] = $repositoryId;
return $details;
}
} catch (\Exception $e) {
$this->logger->error('Repository-Details Fehler', [
'module_name' => $moduleName,
'repository_id' => $repositoryId,
'error' => $e->getMessage()
]);
}
}
return null;
}
/**
* Update installieren
*/
public function installUpdate($moduleName, $version = null)
{
try {
// Event auslösen
$this->eventDispatcher->dispatch('module.update.before', [
'module_name' => $moduleName,
'version' => $version
]);
// Backup erstellen
$backup = $this->createModuleBackup($moduleName);
// Update installieren
$result = $this->moduleRepository->installModuleFromRepository($moduleName, $version);
if ($result) {
// Update-Status in Datenbank speichern
$this->saveUpdateInstallation($moduleName, $version);
// Event auslösen
$this->eventDispatcher->dispatch('module.update.after', [
'module_name' => $moduleName,
'version' => $version,
'backup' => $backup
]);
$this->logger->info('Update installiert', [
'module_name' => $moduleName,
'version' => $version,
'backup' => $backup
]);
return true;
} else {
// Rollback falls Update fehlschlägt
$this->restoreModuleBackup($moduleName, $backup);
throw new \Exception('Update-Installation fehlgeschlagen');
}
} catch (\Exception $e) {
$this->logger->error('Update-Installation Fehler', [
'module_name' => $moduleName,
'version' => $version,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* Modul-Backup erstellen
*/
private function createModuleBackup($moduleName)
{
$modulesDir = __DIR__ . '/../../../modules/';
$backupDir = __DIR__ . '/../../../backups/modules/';
if (!is_dir($backupDir)) {
mkdir($backupDir, 0755, true);
}
$moduleDir = $modulesDir . $moduleName;
$backupPath = $backupDir . $moduleName . '_backup_' . date('Y-m-d_H-i-s') . '.zip';
if (!is_dir($moduleDir)) {
return null;
}
$zip = new \ZipArchive();
if ($zip->open($backupPath, \ZipArchive::CREATE) !== true) {
return null;
}
$this->addDirectoryToZip($zip, $moduleDir, $moduleName);
$zip->close();
return $backupPath;
}
/**
* Verzeichnis zu ZIP hinzufügen
*/
private function addDirectoryToZip($zip, $dir, $basePath)
{
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir),
\RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = $basePath . '/' . substr($filePath, strlen($dir) + 1);
$zip->addFile($filePath, $relativePath);
}
}
}
/**
* Modul-Backup wiederherstellen
*/
private function restoreModuleBackup($moduleName, $backupPath)
{
if (!$backupPath || !file_exists($backupPath)) {
return false;
}
$modulesDir = __DIR__ . '/../../../modules/';
$moduleDir = $modulesDir . $moduleName;
// Aktuelles Modul entfernen
if (is_dir($moduleDir)) {
$this->removeDirectory($moduleDir);
}
// Backup entpacken
$zip = new \ZipArchive();
if ($zip->open($backupPath) !== true) {
return false;
}
$zip->extractTo($modulesDir);
$zip->close();
return true;
}
/**
* Verzeichnis rekursiv löschen
*/
private function removeDirectory($dir)
{
if (!is_dir($dir)) {
return false;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . '/' . $file;
if (is_dir($path)) {
$this->removeDirectory($path);
} else {
unlink($path);
}
}
return rmdir($dir);
}
/**
* Auto-Installation von Updates
*/
private function autoInstallUpdates($updates)
{
foreach ($updates as $moduleName => $update) {
try {
// Nur automatische Updates für normale Priorität
if ($update['priority'] === 'normal') {
$this->installUpdate($moduleName, $update['latest_version']);
$this->logger->info('Auto-Update installiert', [
'module_name' => $moduleName,
'version' => $update['latest_version']
]);
}
} catch (\Exception $e) {
$this->logger->error('Auto-Update Fehler', [
'module_name' => $moduleName,
'error' => $e->getMessage()
]);
}
}
}
/**
* Update-Benachrichtigung senden
*/
private function sendUpdateNotification($updates)
{
if (empty($this->notifyEmail)) {
return;
}
$subject = 'Module-Updates verfügbar - Webshop System';
$message = "Hallo,\n\n";
$message .= "Es sind Updates für folgende Module verfügbar:\n\n";
foreach ($updates as $moduleName => $update) {
$message .= "- {$moduleName}: {$update['current_version']}{$update['latest_version']}\n";
}
$message .= "\nSie können die Updates im Admin-Bereich installieren.\n\n";
$message .= "Mit freundlichen Grüßen\nWebshop System";
$headers = [
'From: noreply@webshop-system.com',
'Content-Type: text/plain; charset=UTF-8'
];
mail($this->notifyEmail, $subject, $message, implode("\r\n", $headers));
$this->logger->info('Update-Benachrichtigung gesendet', [
'email' => $this->notifyEmail,
'updates_count' => count($updates)
]);
}
/**
* Update-Ergebnisse in Datenbank speichern
*/
private function saveUpdateResults($updates)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
// Alte Update-Checks löschen
$stmt = $conn->prepare('DELETE FROM ws_auto_updates WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY)');
$stmt->execute();
// Neue Updates speichern
$stmt = $conn->prepare('
INSERT INTO ws_auto_updates (
module_name, current_version, latest_version, repository,
changelog, download_url, release_date, priority, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
');
foreach ($updates as $moduleName => $update) {
$stmt->execute([
$moduleName,
$update['current_version'],
$update['latest_version'],
$update['repository'],
$update['changelog'],
$update['download_url'],
$update['release_date'],
$update['priority']
]);
}
} catch (Exception $e) {
$this->logger->error('Update-Ergebnisse speichern Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* Update-Installation in Datenbank speichern
*/
private function saveUpdateInstallation($moduleName, $version)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
INSERT INTO ws_update_installations (
module_name, version, installed_at
) VALUES (?, ?, NOW())
');
$stmt->execute([$moduleName, $version]);
} catch (Exception $e) {
$this->logger->error('Update-Installation speichern Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* Verfügbare Updates abrufen
*/
public function getAvailableUpdates()
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
SELECT * FROM ws_auto_updates
WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY priority DESC, created_at DESC
');
$stmt->execute();
return $stmt->fetchAllAssociative();
} catch (Exception $e) {
$this->logger->error('Verfügbare Updates abrufen Fehler', [
'error' => $e->getMessage()
]);
return [];
}
}
/**
* Update-Historie abrufen
*/
public function getUpdateHistory($moduleName = null)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$sql = 'SELECT * FROM ws_update_installations';
$params = [];
if ($moduleName) {
$sql .= ' WHERE module_name = ?';
$params[] = $moduleName;
}
$sql .= ' ORDER BY installed_at DESC';
$stmt = $conn->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAllAssociative();
} catch (Exception $e) {
$this->logger->error('Update-Historie abrufen Fehler', [
'error' => $e->getMessage()
]);
return [];
}
}
/**
* Einstellungen speichern
*/
public function saveSettings($settings)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
foreach ($settings as $key => $value) {
$stmt = $conn->prepare('
INSERT INTO ws_auto_update_settings (
setting_key, setting_value, active, updated_at
) VALUES (?, ?, 1, NOW())
ON DUPLICATE KEY UPDATE
setting_value = ?, updated_at = NOW()
');
$stmt->execute([$key, $value, $value]);
}
// Einstellungen neu laden
$this->loadSettings();
$this->logger->info('Auto-Update-Einstellungen gespeichert', [
'settings' => $settings
]);
return true;
} catch (Exception $e) {
$this->logger->error('Auto-Update-Einstellungen speichern Fehler', [
'error' => $e->getMessage()
]);
return false;
}
}
/**
* Auto-Update-System aktivieren/deaktivieren
*/
public function setEnabled($enabled)
{
$this->enabled = $enabled;
return $this;
}
/**
* Auto-Update-System Status prüfen
*/
public function isEnabled()
{
return $this->enabled;
}
/**
* Check-Intervall setzen
*/
public function setCheckInterval($interval)
{
$this->checkInterval = $interval;
return $this;
}
/**
* Check-Intervall abrufen
*/
public function getCheckInterval()
{
return $this->checkInterval;
}
/**
* Auto-Installation setzen
*/
public function setAutoInstall($autoInstall)
{
$this->autoInstall = $autoInstall;
return $this;
}
/**
* Auto-Installation Status prüfen
*/
public function isAutoInstallEnabled()
{
return $this->autoInstall;
}
/**
* Benachrichtigungs-E-Mail setzen
*/
public function setNotifyEmail($email)
{
$this->notifyEmail = $email;
return $this;
}
/**
* Benachrichtigungs-E-Mail abrufen
*/
public function getNotifyEmail()
{
return $this->notifyEmail;
}
}