725 lines
23 KiB
PHP
725 lines
23 KiB
PHP
<?php
|
|
/**
|
|
* Copyright seit 2024 Webshop System
|
|
*
|
|
* Dependency-Manager für PrestaShop-Modul-Kompatibilität
|
|
*
|
|
* @author Webshop System
|
|
* @license GPL v3
|
|
*/
|
|
|
|
namespace App\Core;
|
|
|
|
use Doctrine\DBAL\DriverManager;
|
|
use Doctrine\DBAL\Exception;
|
|
|
|
class DependencyManager
|
|
{
|
|
private static $instance = null;
|
|
private $moduleManager;
|
|
private $plugin;
|
|
private $extension;
|
|
private $eventDispatcher;
|
|
private $cache;
|
|
private $logger;
|
|
private $enabled = true;
|
|
private $dependencyGraph = [];
|
|
private $conflictResolutions = [];
|
|
|
|
private function __construct()
|
|
{
|
|
$this->moduleManager = ModuleManager::getInstance();
|
|
$this->plugin = Plugin::getInstance();
|
|
$this->extension = Extension::getInstance();
|
|
$this->eventDispatcher = EventDispatcher::getInstance();
|
|
$this->cache = Cache::getInstance();
|
|
$this->logger = Logger::getInstance();
|
|
$this->loadDependencyGraph();
|
|
$this->loadConflictResolutions();
|
|
}
|
|
|
|
/**
|
|
* Singleton-Instanz abrufen
|
|
*/
|
|
public static function getInstance()
|
|
{
|
|
if (self::$instance === null) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Dependency-Graph laden
|
|
*/
|
|
private function loadDependencyGraph()
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
SELECT * FROM ws_dependencies
|
|
WHERE active = 1
|
|
ORDER BY priority DESC
|
|
');
|
|
$stmt->execute();
|
|
|
|
$dependencies = $stmt->fetchAllAssociative();
|
|
|
|
foreach ($dependencies as $dependency) {
|
|
$this->dependencyGraph[$dependency['dependent_name']][] = [
|
|
'type' => $dependency['dependency_type'],
|
|
'name' => $dependency['dependency_name'],
|
|
'version' => $dependency['dependency_version'],
|
|
'required' => (bool)$dependency['required'],
|
|
'priority' => $dependency['priority']
|
|
];
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Dependency-Graph laden Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Konflikt-Lösungen laden
|
|
*/
|
|
private function loadConflictResolutions()
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
SELECT * FROM ws_conflict_resolutions
|
|
WHERE active = 1
|
|
');
|
|
$stmt->execute();
|
|
|
|
$resolutions = $stmt->fetchAllAssociative();
|
|
|
|
foreach ($resolutions as $resolution) {
|
|
$this->conflictResolutions[] = [
|
|
'conflict_type' => $resolution['conflict_type'],
|
|
'item1_name' => $resolution['item1_name'],
|
|
'item2_name' => $resolution['item2_name'],
|
|
'resolution_type' => $resolution['resolution_type'],
|
|
'resolution_action' => $resolution['resolution_action'],
|
|
'priority' => $resolution['priority']
|
|
];
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Konflikt-Lösungen laden Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dependencies für Item prüfen
|
|
*/
|
|
public function checkDependencies($itemName, $itemType = 'module')
|
|
{
|
|
$dependencies = $this->getDependencies($itemName, $itemType);
|
|
$results = [];
|
|
|
|
foreach ($dependencies as $dependency) {
|
|
$result = $this->checkSingleDependency($dependency);
|
|
$results[] = $result;
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Einzelne Dependency prüfen
|
|
*/
|
|
private function checkSingleDependency($dependency)
|
|
{
|
|
$type = $dependency['type'];
|
|
$name = $dependency['name'];
|
|
$version = $dependency['version'];
|
|
$required = $dependency['required'];
|
|
|
|
$result = [
|
|
'type' => $type,
|
|
'name' => $name,
|
|
'version' => $version,
|
|
'required' => $required,
|
|
'satisfied' => false,
|
|
'current_version' => null,
|
|
'error' => null
|
|
];
|
|
|
|
try {
|
|
switch ($type) {
|
|
case 'module':
|
|
$module = $this->moduleManager->getModule($name);
|
|
if ($module && $module['active']) {
|
|
$result['satisfied'] = true;
|
|
$result['current_version'] = $module['version'];
|
|
|
|
if ($version && version_compare($module['version'], $version, '<')) {
|
|
$result['satisfied'] = false;
|
|
$result['error'] = 'Version zu alt';
|
|
}
|
|
} else {
|
|
if ($required) {
|
|
$result['error'] = 'Modul nicht gefunden oder inaktiv';
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'plugin':
|
|
$plugin = $this->plugin->getPlugin($name);
|
|
if ($plugin && $plugin['active']) {
|
|
$result['satisfied'] = true;
|
|
$result['current_version'] = $plugin['version'];
|
|
|
|
if ($version && version_compare($plugin['version'], $version, '<')) {
|
|
$result['satisfied'] = false;
|
|
$result['error'] = 'Version zu alt';
|
|
}
|
|
} else {
|
|
if ($required) {
|
|
$result['error'] = 'Plugin nicht gefunden oder inaktiv';
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'extension':
|
|
$extension = $this->extension->getExtension($name);
|
|
if ($extension && $extension['active']) {
|
|
$result['satisfied'] = true;
|
|
$result['current_version'] = $extension['version'];
|
|
|
|
if ($version && version_compare($extension['version'], $version, '<')) {
|
|
$result['satisfied'] = false;
|
|
$result['error'] = 'Version zu alt';
|
|
}
|
|
} else {
|
|
if ($required) {
|
|
$result['error'] = 'Extension nicht gefunden oder inaktiv';
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'php':
|
|
$currentVersion = PHP_VERSION;
|
|
$result['current_version'] = $currentVersion;
|
|
|
|
if (version_compare($currentVersion, $version, '>=')) {
|
|
$result['satisfied'] = true;
|
|
} else {
|
|
$result['error'] = 'PHP-Version zu alt';
|
|
}
|
|
break;
|
|
|
|
case 'extension_php':
|
|
if (extension_loaded($name)) {
|
|
$result['satisfied'] = true;
|
|
$result['current_version'] = phpversion($name);
|
|
|
|
if ($version && version_compare(phpversion($name), $version, '<')) {
|
|
$result['satisfied'] = false;
|
|
$result['error'] = 'Extension-Version zu alt';
|
|
}
|
|
} else {
|
|
if ($required) {
|
|
$result['error'] = 'PHP-Extension nicht geladen';
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
$result['error'] = 'Prüfung fehlgeschlagen: ' . $e->getMessage();
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Dependencies für Item abrufen
|
|
*/
|
|
public function getDependencies($itemName, $itemType = 'module')
|
|
{
|
|
$cacheKey = 'dependencies_' . $itemType . '_' . $itemName;
|
|
|
|
// Cache prüfen
|
|
$cached = $this->cache->get($cacheKey);
|
|
if ($cached !== null) {
|
|
return $cached;
|
|
}
|
|
|
|
$dependencies = [];
|
|
|
|
// Direkte Dependencies
|
|
if (isset($this->dependencyGraph[$itemName])) {
|
|
$dependencies = array_merge($dependencies, $this->dependencyGraph[$itemName]);
|
|
}
|
|
|
|
// Rekursive Dependencies
|
|
foreach ($dependencies as $dependency) {
|
|
$subDependencies = $this->getDependencies($dependency['name'], $dependency['type']);
|
|
$dependencies = array_merge($dependencies, $subDependencies);
|
|
}
|
|
|
|
// Duplikate entfernen
|
|
$dependencies = $this->removeDuplicateDependencies($dependencies);
|
|
|
|
// Cache setzen
|
|
$this->cache->set($cacheKey, $dependencies, 3600); // 1 Stunde
|
|
|
|
return $dependencies;
|
|
}
|
|
|
|
/**
|
|
* Duplikate aus Dependencies entfernen
|
|
*/
|
|
private function removeDuplicateDependencies($dependencies)
|
|
{
|
|
$unique = [];
|
|
$seen = [];
|
|
|
|
foreach ($dependencies as $dependency) {
|
|
$key = $dependency['type'] . ':' . $dependency['name'];
|
|
|
|
if (!isset($seen[$key])) {
|
|
$seen[$key] = true;
|
|
$unique[] = $dependency;
|
|
}
|
|
}
|
|
|
|
return $unique;
|
|
}
|
|
|
|
/**
|
|
* Dependency hinzufügen
|
|
*/
|
|
public function addDependency($dependentName, $dependentType, $dependencyName, $dependencyType, $dependencyVersion = null, $required = true, $priority = 10)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
INSERT INTO ws_dependencies (
|
|
dependent_name, dependent_type, dependency_name, dependency_type,
|
|
dependency_version, required, priority, active, created_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, 1, NOW())
|
|
ON DUPLICATE KEY UPDATE
|
|
dependency_version = ?, required = ?, priority = ?, updated_at = NOW()
|
|
');
|
|
|
|
$stmt->execute([
|
|
$dependentName,
|
|
$dependentType,
|
|
$dependencyName,
|
|
$dependencyType,
|
|
$dependencyVersion,
|
|
$required ? 1 : 0,
|
|
$priority,
|
|
$dependencyVersion,
|
|
$required ? 1 : 0,
|
|
$priority
|
|
]);
|
|
|
|
// Dependency-Graph neu laden
|
|
$this->loadDependencyGraph();
|
|
|
|
// Cache invalidieren
|
|
$this->cache->delete('dependencies_' . $dependentType . '_' . $dependentName);
|
|
|
|
$this->logger->info('Dependency hinzugefügt', [
|
|
'dependent_name' => $dependentName,
|
|
'dependent_type' => $dependentType,
|
|
'dependency_name' => $dependencyName,
|
|
'dependency_type' => $dependencyType,
|
|
'dependency_version' => $dependencyVersion,
|
|
'required' => $required,
|
|
'priority' => $priority
|
|
]);
|
|
|
|
return true;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Dependency hinzufügen Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dependency entfernen
|
|
*/
|
|
public function removeDependency($dependentName, $dependentType, $dependencyName, $dependencyType)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
DELETE FROM ws_dependencies
|
|
WHERE dependent_name = ? AND dependent_type = ?
|
|
AND dependency_name = ? AND dependency_type = ?
|
|
');
|
|
|
|
$stmt->execute([
|
|
$dependentName,
|
|
$dependentType,
|
|
$dependencyName,
|
|
$dependencyType
|
|
]);
|
|
|
|
// Dependency-Graph neu laden
|
|
$this->loadDependencyGraph();
|
|
|
|
// Cache invalidieren
|
|
$this->cache->delete('dependencies_' . $dependentType . '_' . $dependentName);
|
|
|
|
$this->logger->info('Dependency entfernt', [
|
|
'dependent_name' => $dependentName,
|
|
'dependent_type' => $dependentType,
|
|
'dependency_name' => $dependencyName,
|
|
'dependency_type' => $dependencyType
|
|
]);
|
|
|
|
return true;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Dependency entfernen Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Konflikte prüfen
|
|
*/
|
|
public function checkConflicts($itemName, $itemType = 'module')
|
|
{
|
|
$conflicts = [];
|
|
|
|
// Bekannte Konflikte prüfen
|
|
foreach ($this->conflictResolutions as $resolution) {
|
|
if ($this->matchesConflict($itemName, $itemType, $resolution)) {
|
|
$conflicts[] = $resolution;
|
|
}
|
|
}
|
|
|
|
// Dynamische Konflikt-Prüfung
|
|
$dynamicConflicts = $this->checkDynamicConflicts($itemName, $itemType);
|
|
$conflicts = array_merge($conflicts, $dynamicConflicts);
|
|
|
|
return $conflicts;
|
|
}
|
|
|
|
/**
|
|
* Konflikt-Matching
|
|
*/
|
|
private function matchesConflict($itemName, $itemType, $resolution)
|
|
{
|
|
return ($resolution['item1_name'] === $itemName && $resolution['item1_type'] === $itemType) ||
|
|
($resolution['item2_name'] === $itemName && $resolution['item2_type'] === $itemType);
|
|
}
|
|
|
|
/**
|
|
* Dynamische Konflikt-Prüfung
|
|
*/
|
|
private function checkDynamicConflicts($itemName, $itemType)
|
|
{
|
|
$conflicts = [];
|
|
|
|
// Hook-Konflikte prüfen
|
|
$hookConflicts = $this->checkHookConflicts($itemName, $itemType);
|
|
$conflicts = array_merge($conflicts, $hookConflicts);
|
|
|
|
// Namespace-Konflikte prüfen
|
|
$namespaceConflicts = $this->checkNamespaceConflicts($itemName, $itemType);
|
|
$conflicts = array_merge($conflicts, $namespaceConflicts);
|
|
|
|
// Resource-Konflikte prüfen
|
|
$resourceConflicts = $this->checkResourceConflicts($itemName, $itemType);
|
|
$conflicts = array_merge($conflicts, $resourceConflicts);
|
|
|
|
return $conflicts;
|
|
}
|
|
|
|
/**
|
|
* Hook-Konflikte prüfen
|
|
*/
|
|
private function checkHookConflicts($itemName, $itemType)
|
|
{
|
|
$conflicts = [];
|
|
|
|
// Hook-Registrierungen prüfen
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
SELECT hook_name, COUNT(*) as count
|
|
FROM ws_hook_registry
|
|
WHERE item_name != ? AND active = 1
|
|
GROUP BY hook_name
|
|
HAVING count > 1
|
|
');
|
|
$stmt->execute([$itemName]);
|
|
|
|
$hookConflicts = $stmt->fetchAllAssociative();
|
|
|
|
foreach ($hookConflicts as $conflict) {
|
|
$conflicts[] = [
|
|
'type' => 'hook_conflict',
|
|
'hook_name' => $conflict['hook_name'],
|
|
'description' => 'Mehrere Items registrieren denselben Hook',
|
|
'severity' => 'warning'
|
|
];
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Hook-Konflikt-Prüfung Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
|
|
return $conflicts;
|
|
}
|
|
|
|
/**
|
|
* Namespace-Konflikte prüfen
|
|
*/
|
|
private function checkNamespaceConflicts($itemName, $itemType)
|
|
{
|
|
$conflicts = [];
|
|
|
|
// Namespace-Kollisionen prüfen
|
|
$namespaces = $this->getItemNamespaces($itemName, $itemType);
|
|
|
|
foreach ($namespaces as $namespace) {
|
|
if ($this->namespaceExists($namespace)) {
|
|
$conflicts[] = [
|
|
'type' => 'namespace_conflict',
|
|
'namespace' => $namespace,
|
|
'description' => 'Namespace bereits belegt',
|
|
'severity' => 'error'
|
|
];
|
|
}
|
|
}
|
|
|
|
return $conflicts;
|
|
}
|
|
|
|
/**
|
|
* Resource-Konflikte prüfen
|
|
*/
|
|
private function checkResourceConflicts($itemName, $itemType)
|
|
{
|
|
$conflicts = [];
|
|
|
|
// Asset-Konflikte prüfen
|
|
$assets = $this->getItemAssets($itemName, $itemType);
|
|
|
|
foreach ($assets as $asset) {
|
|
if ($this->assetExists($asset)) {
|
|
$conflicts[] = [
|
|
'type' => 'resource_conflict',
|
|
'resource' => $asset,
|
|
'description' => 'Resource bereits vorhanden',
|
|
'severity' => 'warning'
|
|
];
|
|
}
|
|
}
|
|
|
|
return $conflicts;
|
|
}
|
|
|
|
/**
|
|
* Namespaces für Item abrufen
|
|
*/
|
|
private function getItemNamespaces($itemName, $itemType)
|
|
{
|
|
$namespaces = [];
|
|
|
|
switch ($itemType) {
|
|
case 'module':
|
|
$namespaces[] = 'App\\Modules\\' . ucfirst($itemName);
|
|
break;
|
|
case 'plugin':
|
|
$namespaces[] = 'App\\Plugins\\' . ucfirst($itemName);
|
|
break;
|
|
case 'extension':
|
|
$namespaces[] = 'App\\Extensions\\' . ucfirst($itemName);
|
|
break;
|
|
}
|
|
|
|
return $namespaces;
|
|
}
|
|
|
|
/**
|
|
* Assets für Item abrufen
|
|
*/
|
|
private function getItemAssets($itemName, $itemType)
|
|
{
|
|
$assets = [];
|
|
|
|
$basePath = __DIR__ . '/../../../';
|
|
|
|
switch ($itemType) {
|
|
case 'module':
|
|
$assets[] = $basePath . 'modules/' . $itemName . '/assets/';
|
|
break;
|
|
case 'plugin':
|
|
$assets[] = $basePath . 'plugins/' . $itemName . '/assets/';
|
|
break;
|
|
case 'extension':
|
|
$assets[] = $basePath . 'extensions/' . $itemName . '/assets/';
|
|
break;
|
|
}
|
|
|
|
return $assets;
|
|
}
|
|
|
|
/**
|
|
* Namespace-Existenz prüfen
|
|
*/
|
|
private function namespaceExists($namespace)
|
|
{
|
|
// Vereinfachte Prüfung - in der Praxis würde hier eine vollständige Namespace-Analyse stehen
|
|
return class_exists($namespace . '\\Main');
|
|
}
|
|
|
|
/**
|
|
* Asset-Existenz prüfen
|
|
*/
|
|
private function assetExists($asset)
|
|
{
|
|
return file_exists($asset);
|
|
}
|
|
|
|
/**
|
|
* Konflikt-Lösung hinzufügen
|
|
*/
|
|
public function addConflictResolution($conflictType, $item1Name, $item1Type, $item2Name, $item2Type, $resolutionType, $resolutionAction, $priority = 10)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
INSERT INTO ws_conflict_resolutions (
|
|
conflict_type, item1_name, item1_type, item2_name, item2_type,
|
|
resolution_type, resolution_action, priority, active, created_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, NOW())
|
|
');
|
|
|
|
$stmt->execute([
|
|
$conflictType,
|
|
$item1Name,
|
|
$item1Type,
|
|
$item2Name,
|
|
$item2Type,
|
|
$resolutionType,
|
|
$resolutionAction,
|
|
$priority
|
|
]);
|
|
|
|
// Konflikt-Lösungen neu laden
|
|
$this->loadConflictResolutions();
|
|
|
|
$this->logger->info('Konflikt-Lösung hinzugefügt', [
|
|
'conflict_type' => $conflictType,
|
|
'item1_name' => $item1Name,
|
|
'item1_type' => $item1Type,
|
|
'item2_name' => $item2Name,
|
|
'item2_type' => $item2Type,
|
|
'resolution_type' => $resolutionType,
|
|
'resolution_action' => $resolutionAction,
|
|
'priority' => $priority
|
|
]);
|
|
|
|
return true;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Konflikt-Lösung hinzufügen Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dependency-Resolver
|
|
*/
|
|
public function resolveDependencies($itemName, $itemType = 'module')
|
|
{
|
|
$dependencies = $this->getDependencies($itemName, $itemType);
|
|
$resolved = [];
|
|
$unresolved = [];
|
|
|
|
foreach ($dependencies as $dependency) {
|
|
$result = $this->checkSingleDependency($dependency);
|
|
|
|
if ($result['satisfied']) {
|
|
$resolved[] = $result;
|
|
} else {
|
|
$unresolved[] = $result;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'resolved' => $resolved,
|
|
'unresolved' => $unresolved,
|
|
'all_satisfied' => empty($unresolved)
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Dependency-Manager aktivieren/deaktivieren
|
|
*/
|
|
public function setEnabled($enabled)
|
|
{
|
|
$this->enabled = $enabled;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Dependency-Manager Status prüfen
|
|
*/
|
|
public function isEnabled()
|
|
{
|
|
return $this->enabled;
|
|
}
|
|
|
|
/**
|
|
* Dependency-Graph abrufen
|
|
*/
|
|
public function getDependencyGraph()
|
|
{
|
|
return $this->dependencyGraph;
|
|
}
|
|
|
|
/**
|
|
* Konflikt-Lösungen abrufen
|
|
*/
|
|
public function getConflictResolutions()
|
|
{
|
|
return $this->conflictResolutions;
|
|
}
|
|
}
|