657 lines
19 KiB
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;
|
|
}
|
|
}
|