625 lines
18 KiB
PHP
625 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Copyright seit 2024 Webshop System
|
|
*
|
|
* Module-Repository für PrestaShop-Modul-Kompatibilität
|
|
*
|
|
* @author Webshop System
|
|
* @license GPL v3
|
|
*/
|
|
|
|
namespace App\Core;
|
|
|
|
use Doctrine\DBAL\DriverManager;
|
|
use Doctrine\DBAL\Exception;
|
|
|
|
class ModuleRepository
|
|
{
|
|
private static $instance = null;
|
|
private $moduleManager;
|
|
private $eventDispatcher;
|
|
private $cache;
|
|
private $logger;
|
|
private $enabled = true;
|
|
private $repositories = [];
|
|
private $downloadPath;
|
|
private $tempPath;
|
|
|
|
private function __construct()
|
|
{
|
|
$this->moduleManager = ModuleManager::getInstance();
|
|
$this->eventDispatcher = EventDispatcher::getInstance();
|
|
$this->cache = Cache::getInstance();
|
|
$this->logger = Logger::getInstance();
|
|
|
|
$this->downloadPath = __DIR__ . '/../../../downloads/modules/';
|
|
$this->tempPath = __DIR__ . '/../../../temp/modules/';
|
|
|
|
// Verzeichnisse erstellen
|
|
if (!is_dir($this->downloadPath)) {
|
|
mkdir($this->downloadPath, 0755, true);
|
|
}
|
|
if (!is_dir($this->tempPath)) {
|
|
mkdir($this->tempPath, 0755, true);
|
|
}
|
|
|
|
$this->loadRepositories();
|
|
}
|
|
|
|
/**
|
|
* Singleton-Instanz abrufen
|
|
*/
|
|
public static function getInstance()
|
|
{
|
|
if (self::$instance === null) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Repositories laden
|
|
*/
|
|
private function loadRepositories()
|
|
{
|
|
$this->repositories = [
|
|
'official' => [
|
|
'name' => 'Offizielles Repository',
|
|
'url' => 'https://repository.webshop-system.com/official',
|
|
'type' => 'official',
|
|
'enabled' => true
|
|
],
|
|
'community' => [
|
|
'name' => 'Community Repository',
|
|
'url' => 'https://repository.webshop-system.com/community',
|
|
'type' => 'community',
|
|
'enabled' => true
|
|
],
|
|
'custom' => [
|
|
'name' => 'Custom Repository',
|
|
'url' => getenv('CUSTOM_REPOSITORY_URL') ?: '',
|
|
'type' => 'custom',
|
|
'enabled' => !empty(getenv('CUSTOM_REPOSITORY_URL'))
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Module-Liste aus Repository abrufen
|
|
*/
|
|
public function getModulesFromRepository($repositoryId = 'official', $filters = [])
|
|
{
|
|
$cacheKey = 'repository_modules_' . $repositoryId . '_' . md5(serialize($filters));
|
|
|
|
// Cache prüfen
|
|
$cached = $this->cache->get($cacheKey);
|
|
if ($cached !== null) {
|
|
return $cached;
|
|
}
|
|
|
|
if (!isset($this->repositories[$repositoryId])) {
|
|
return [];
|
|
}
|
|
|
|
$repository = $this->repositories[$repositoryId];
|
|
|
|
try {
|
|
$modules = $this->fetchModulesFromRepository($repository, $filters);
|
|
|
|
// Cache setzen (1 Stunde)
|
|
$this->cache->set($cacheKey, $modules, 3600);
|
|
|
|
return $modules;
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Repository-Fehler', [
|
|
'repository_id' => $repositoryId,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Module aus Repository abrufen
|
|
*/
|
|
private function fetchModulesFromRepository($repository, $filters)
|
|
{
|
|
$url = $repository['url'] . '/api/modules';
|
|
|
|
// Filter als Query-Parameter hinzufügen
|
|
if (!empty($filters)) {
|
|
$url .= '?' . http_build_query($filters);
|
|
}
|
|
|
|
$context = stream_context_create([
|
|
'http' => [
|
|
'method' => 'GET',
|
|
'header' => [
|
|
'User-Agent: Webshop-System/1.0',
|
|
'Accept: application/json'
|
|
],
|
|
'timeout' => 30
|
|
]
|
|
]);
|
|
|
|
$response = file_get_contents($url, false, $context);
|
|
|
|
if ($response === false) {
|
|
throw new \Exception('Repository nicht erreichbar');
|
|
}
|
|
|
|
$data = json_decode($response, true);
|
|
|
|
if (!$data || !isset($data['modules'])) {
|
|
throw new \Exception('Ungültige Repository-Antwort');
|
|
}
|
|
|
|
return $data['modules'];
|
|
}
|
|
|
|
/**
|
|
* Modul-Details aus Repository abrufen
|
|
*/
|
|
public function getModuleDetails($moduleName, $repositoryId = 'official')
|
|
{
|
|
$cacheKey = 'repository_module_details_' . $repositoryId . '_' . $moduleName;
|
|
|
|
// Cache prüfen
|
|
$cached = $this->cache->get($cacheKey);
|
|
if ($cached !== null) {
|
|
return $cached;
|
|
}
|
|
|
|
if (!isset($this->repositories[$repositoryId])) {
|
|
return null;
|
|
}
|
|
|
|
$repository = $this->repositories[$repositoryId];
|
|
|
|
try {
|
|
$url = $repository['url'] . '/api/modules/' . urlencode($moduleName);
|
|
|
|
$context = stream_context_create([
|
|
'http' => [
|
|
'method' => 'GET',
|
|
'header' => [
|
|
'User-Agent: Webshop-System/1.0',
|
|
'Accept: application/json'
|
|
],
|
|
'timeout' => 30
|
|
]
|
|
]);
|
|
|
|
$response = file_get_contents($url, false, $context);
|
|
|
|
if ($response === false) {
|
|
throw new \Exception('Repository nicht erreichbar');
|
|
}
|
|
|
|
$data = json_decode($response, true);
|
|
|
|
if (!$data || !isset($data['module'])) {
|
|
throw new \Exception('Modul nicht gefunden');
|
|
}
|
|
|
|
// Cache setzen (1 Stunde)
|
|
$this->cache->set($cacheKey, $data['module'], 3600);
|
|
|
|
return $data['module'];
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Repository-Modul-Details Fehler', [
|
|
'repository_id' => $repositoryId,
|
|
'module_name' => $moduleName,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modul aus Repository herunterladen
|
|
*/
|
|
public function downloadModule($moduleName, $version = null, $repositoryId = 'official')
|
|
{
|
|
if (!isset($this->repositories[$repositoryId])) {
|
|
throw new \Exception('Repository nicht gefunden');
|
|
}
|
|
|
|
$repository = $this->repositories[$repositoryId];
|
|
|
|
try {
|
|
// Modul-Details abrufen
|
|
$moduleDetails = $this->getModuleDetails($moduleName, $repositoryId);
|
|
|
|
if (!$moduleDetails) {
|
|
throw new \Exception('Modul nicht gefunden');
|
|
}
|
|
|
|
// Version bestimmen
|
|
if (!$version) {
|
|
$version = $moduleDetails['latest_version'];
|
|
}
|
|
|
|
// Download-URL erstellen
|
|
$downloadUrl = $repository['url'] . '/download/' . urlencode($moduleName) . '/' . urlencode($version);
|
|
|
|
// Datei herunterladen
|
|
$tempFile = $this->tempPath . $moduleName . '_' . $version . '.zip';
|
|
|
|
$context = stream_context_create([
|
|
'http' => [
|
|
'method' => 'GET',
|
|
'header' => [
|
|
'User-Agent: Webshop-System/1.0'
|
|
],
|
|
'timeout' => 300 // 5 Minuten für Download
|
|
]
|
|
]);
|
|
|
|
$downloadResult = file_put_contents($tempFile, file_get_contents($downloadUrl, false, $context));
|
|
|
|
if ($downloadResult === false) {
|
|
throw new \Exception('Download fehlgeschlagen');
|
|
}
|
|
|
|
// Datei validieren
|
|
if (!$this->validateModuleFile($tempFile)) {
|
|
unlink($tempFile);
|
|
throw new \Exception('Ungültige Modul-Datei');
|
|
}
|
|
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('module.download', [
|
|
'module_name' => $moduleName,
|
|
'version' => $version,
|
|
'repository_id' => $repositoryId,
|
|
'file_path' => $tempFile
|
|
]);
|
|
|
|
$this->logger->info('Modul heruntergeladen', [
|
|
'module_name' => $moduleName,
|
|
'version' => $version,
|
|
'repository_id' => $repositoryId,
|
|
'file_size' => filesize($tempFile)
|
|
]);
|
|
|
|
return $tempFile;
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Modul-Download Fehler', [
|
|
'module_name' => $moduleName,
|
|
'version' => $version,
|
|
'repository_id' => $repositoryId,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modul-Datei validieren
|
|
*/
|
|
private function validateModuleFile($filePath)
|
|
{
|
|
if (!file_exists($filePath)) {
|
|
return false;
|
|
}
|
|
|
|
$zip = new \ZipArchive();
|
|
|
|
if ($zip->open($filePath) !== true) {
|
|
return false;
|
|
}
|
|
|
|
// Mindestanforderungen prüfen
|
|
$requiredFiles = ['module.json', 'Module.php'];
|
|
$hasRequiredFiles = true;
|
|
|
|
foreach ($requiredFiles as $requiredFile) {
|
|
if ($zip->locateName($requiredFile) === false) {
|
|
$hasRequiredFiles = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$zip->close();
|
|
|
|
return $hasRequiredFiles;
|
|
}
|
|
|
|
/**
|
|
* Modul aus Repository installieren
|
|
*/
|
|
public function installModuleFromRepository($moduleName, $version = null, $repositoryId = 'official')
|
|
{
|
|
try {
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('module.install.repository.before', [
|
|
'module_name' => $moduleName,
|
|
'version' => $version,
|
|
'repository_id' => $repositoryId
|
|
]);
|
|
|
|
// Modul herunterladen
|
|
$tempFile = $this->downloadModule($moduleName, $version, $repositoryId);
|
|
|
|
// Modul installieren
|
|
$result = $this->installModuleFromFile($tempFile);
|
|
|
|
// Temporäre Datei löschen
|
|
if (file_exists($tempFile)) {
|
|
unlink($tempFile);
|
|
}
|
|
|
|
if ($result) {
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('module.install.repository.after', [
|
|
'module_name' => $moduleName,
|
|
'version' => $version,
|
|
'repository_id' => $repositoryId,
|
|
'result' => $result
|
|
]);
|
|
|
|
$this->logger->info('Modul aus Repository installiert', [
|
|
'module_name' => $moduleName,
|
|
'version' => $version,
|
|
'repository_id' => $repositoryId
|
|
]);
|
|
|
|
return $result;
|
|
} else {
|
|
throw new \Exception('Modul-Installation fehlgeschlagen');
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Repository-Modul-Installation Fehler', [
|
|
'module_name' => $moduleName,
|
|
'version' => $version,
|
|
'repository_id' => $repositoryId,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modul aus Datei installieren
|
|
*/
|
|
private function installModuleFromFile($filePath)
|
|
{
|
|
$modulesDir = __DIR__ . '/../../../modules/';
|
|
|
|
if (!is_dir($modulesDir)) {
|
|
mkdir($modulesDir, 0755, true);
|
|
}
|
|
|
|
$zip = new \ZipArchive();
|
|
|
|
if ($zip->open($filePath) !== true) {
|
|
return false;
|
|
}
|
|
|
|
// Modul-Name aus ZIP extrahieren
|
|
$moduleName = null;
|
|
$configContent = $zip->getFromName('module.json');
|
|
|
|
if ($configContent) {
|
|
$config = json_decode($configContent, true);
|
|
$moduleName = $config['name'] ?? null;
|
|
}
|
|
|
|
if (!$moduleName) {
|
|
$zip->close();
|
|
return false;
|
|
}
|
|
|
|
// Modul-Verzeichnis erstellen
|
|
$moduleDir = $modulesDir . $moduleName;
|
|
|
|
if (is_dir($moduleDir)) {
|
|
// Bestehendes Modul sichern
|
|
$backupDir = $moduleDir . '_backup_' . date('Y-m-d_H-i-s');
|
|
rename($moduleDir, $backupDir);
|
|
}
|
|
|
|
// ZIP entpacken
|
|
$zip->extractTo($moduleDir);
|
|
$zip->close();
|
|
|
|
// Modul registrieren
|
|
if (file_exists($moduleDir . '/module.json')) {
|
|
$config = json_decode(file_get_contents($moduleDir . '/module.json'), true);
|
|
return $this->moduleManager->registerModule($moduleName, $config);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Repository-Liste abrufen
|
|
*/
|
|
public function getRepositories()
|
|
{
|
|
return $this->repositories;
|
|
}
|
|
|
|
/**
|
|
* Repository hinzufügen
|
|
*/
|
|
public function addRepository($id, $config)
|
|
{
|
|
$this->repositories[$id] = array_merge($config, [
|
|
'id' => $id,
|
|
'enabled' => $config['enabled'] ?? true
|
|
]);
|
|
|
|
// Cache invalidieren
|
|
$this->cache->delete('repository_modules_' . $id . '_*');
|
|
|
|
$this->logger->info('Repository hinzugefügt', [
|
|
'repository_id' => $id,
|
|
'config' => $config
|
|
]);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Repository entfernen
|
|
*/
|
|
public function removeRepository($id)
|
|
{
|
|
if (isset($this->repositories[$id])) {
|
|
unset($this->repositories[$id]);
|
|
|
|
// Cache invalidieren
|
|
$this->cache->delete('repository_modules_' . $id . '_*');
|
|
|
|
$this->logger->info('Repository entfernt', [
|
|
'repository_id' => $id
|
|
]);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Repository aktivieren/deaktivieren
|
|
*/
|
|
public function setRepositoryEnabled($id, $enabled)
|
|
{
|
|
if (isset($this->repositories[$id])) {
|
|
$this->repositories[$id]['enabled'] = $enabled;
|
|
|
|
$this->logger->info('Repository-Status geändert', [
|
|
'repository_id' => $id,
|
|
'enabled' => $enabled
|
|
]);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Repository-Status prüfen
|
|
*/
|
|
public function checkRepositoryStatus($repositoryId)
|
|
{
|
|
if (!isset($this->repositories[$repositoryId])) {
|
|
return false;
|
|
}
|
|
|
|
$repository = $this->repositories[$repositoryId];
|
|
|
|
try {
|
|
$url = $repository['url'] . '/api/status';
|
|
|
|
$context = stream_context_create([
|
|
'http' => [
|
|
'method' => 'GET',
|
|
'header' => [
|
|
'User-Agent: Webshop-System/1.0',
|
|
'Accept: application/json'
|
|
],
|
|
'timeout' => 10
|
|
]
|
|
]);
|
|
|
|
$response = file_get_contents($url, false, $context);
|
|
|
|
if ($response === false) {
|
|
return false;
|
|
}
|
|
|
|
$data = json_decode($response, true);
|
|
|
|
return isset($data['status']) && $data['status'] === 'ok';
|
|
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Repository-Statistiken abrufen
|
|
*/
|
|
public function getRepositoryStatistics()
|
|
{
|
|
$statistics = [];
|
|
|
|
foreach ($this->repositories as $id => $repository) {
|
|
$statistics[$id] = [
|
|
'name' => $repository['name'],
|
|
'type' => $repository['type'],
|
|
'enabled' => $repository['enabled'],
|
|
'status' => $this->checkRepositoryStatus($id),
|
|
'modules_count' => 0
|
|
];
|
|
|
|
if ($repository['enabled']) {
|
|
try {
|
|
$modules = $this->getModulesFromRepository($id);
|
|
$statistics[$id]['modules_count'] = count($modules);
|
|
} catch (\Exception $e) {
|
|
$statistics[$id]['error'] = $e->getMessage();
|
|
}
|
|
}
|
|
}
|
|
|
|
return $statistics;
|
|
}
|
|
|
|
/**
|
|
* Cache invalidieren
|
|
*/
|
|
public function invalidateCache($repositoryId = null)
|
|
{
|
|
if ($repositoryId) {
|
|
$this->cache->delete('repository_modules_' . $repositoryId . '_*');
|
|
$this->cache->delete('repository_module_details_' . $repositoryId . '_*');
|
|
} else {
|
|
$this->cache->delete('repository_*');
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Repository-System aktivieren/deaktivieren
|
|
*/
|
|
public function setEnabled($enabled)
|
|
{
|
|
$this->enabled = $enabled;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Repository-System Status prüfen
|
|
*/
|
|
public function isEnabled()
|
|
{
|
|
return $this->enabled;
|
|
}
|
|
|
|
/**
|
|
* Download-Pfad abrufen
|
|
*/
|
|
public function getDownloadPath()
|
|
{
|
|
return $this->downloadPath;
|
|
}
|
|
|
|
/**
|
|
* Temp-Pfad abrufen
|
|
*/
|
|
public function getTempPath()
|
|
{
|
|
return $this->tempPath;
|
|
}
|
|
}
|