Newwebshop/app/Core/Extension.php

809 lines
23 KiB
PHP

<?php
/**
* Copyright seit 2024 Webshop System
*
* Extension-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 Extension
{
private static $instance = null;
private $extensions = [];
private $eventDispatcher;
private $cache;
private $logger;
private $enabled = true;
private function __construct()
{
$this->eventDispatcher = EventDispatcher::getInstance();
$this->cache = Cache::getInstance();
$this->logger = Logger::getInstance();
$this->loadExtensions();
}
/**
* Singleton-Instanz abrufen
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Extensions laden
*/
private function loadExtensions()
{
$extensionsDir = __DIR__ . '/../../../extensions/';
if (!is_dir($extensionsDir)) {
mkdir($extensionsDir, 0755, true);
return;
}
$extensionDirs = scandir($extensionsDir);
foreach ($extensionDirs as $dir) {
if ($dir !== '.' && $dir !== '..' && is_dir($extensionsDir . $dir)) {
$configFile = $extensionsDir . $dir . '/extension.json';
if (file_exists($configFile)) {
$config = json_decode(file_get_contents($configFile), true);
if ($config) {
$this->extensions[$dir] = array_merge($config, [
'directory' => $dir,
'path' => $extensionsDir . $dir,
'active' => $config['active'] ?? false,
'version' => $config['version'] ?? '1.0.0',
'dependencies' => $config['dependencies'] ?? [],
'hooks' => $config['hooks'] ?? [],
'settings' => $config['settings'] ?? [],
'type' => $config['type'] ?? 'general'
]);
}
}
}
}
}
/**
* Extension registrieren
*/
public function registerExtension($name, $config)
{
$extension = array_merge($config, [
'name' => $name,
'active' => $config['active'] ?? false,
'version' => $config['version'] ?? '1.0.0',
'dependencies' => $config['dependencies'] ?? [],
'hooks' => $config['hooks'] ?? [],
'settings' => $config['settings'] ?? [],
'type' => $config['type'] ?? 'general'
]);
$this->extensions[$name] = $extension;
// Extension in Datenbank speichern
$this->saveExtensionToDatabase($name, $extension);
// Event auslösen
$this->eventDispatcher->dispatch('extension.register', [
'extension_name' => $name,
'extension_config' => $extension
]);
$this->logger->info('Extension registriert', [
'extension_name' => $name,
'version' => $extension['version'],
'type' => $extension['type']
]);
return $this;
}
/**
* Extension aktivieren
*/
public function activateExtension($name)
{
if (!isset($this->extensions[$name])) {
return false;
}
$extension = $this->extensions[$name];
// Dependencies prüfen
if (!$this->checkDependencies($extension['dependencies'])) {
$this->logger->error('Extension-Aktivierung fehlgeschlagen - Dependencies nicht erfüllt', [
'extension_name' => $name,
'dependencies' => $extension['dependencies']
]);
return false;
}
// Extension-Klasse laden
$extensionClass = $this->loadExtensionClass($name);
if (!$extensionClass) {
return false;
}
try {
// Extension initialisieren
$instance = new $extensionClass();
if (method_exists($instance, 'activate')) {
$instance->activate();
}
// Hooks registrieren
$this->registerExtensionHooks($name, $extension['hooks']);
// Extension als aktiv markieren
$this->extensions[$name]['active'] = true;
$this->extensions[$name]['instance'] = $instance;
// Datenbank aktualisieren
$this->updateExtensionStatus($name, true);
// Event auslösen
$this->eventDispatcher->dispatch('extension.activate', [
'extension_name' => $name,
'extension_config' => $extension
]);
$this->logger->info('Extension aktiviert', [
'extension_name' => $name,
'version' => $extension['version'],
'type' => $extension['type']
]);
return true;
} catch (\Exception $e) {
$this->logger->error('Extension-Aktivierung Fehler', [
'extension_name' => $name,
'error' => $e->getMessage()
]);
return false;
}
}
/**
* Extension deaktivieren
*/
public function deactivateExtension($name)
{
if (!isset($this->extensions[$name]) || !$this->extensions[$name]['active']) {
return false;
}
$extension = $this->extensions[$name];
try {
// Extension-Instance deaktivieren
if (isset($extension['instance']) && method_exists($extension['instance'], 'deactivate')) {
$extension['instance']->deactivate();
}
// Hooks deregistrieren
$this->unregisterExtensionHooks($name);
// Extension als inaktiv markieren
$this->extensions[$name]['active'] = false;
unset($this->extensions[$name]['instance']);
// Datenbank aktualisieren
$this->updateExtensionStatus($name, false);
// Event auslösen
$this->eventDispatcher->dispatch('extension.deactivate', [
'extension_name' => $name,
'extension_config' => $extension
]);
$this->logger->info('Extension deaktiviert', [
'extension_name' => $name,
'version' => $extension['version'],
'type' => $extension['type']
]);
return true;
} catch (\Exception $e) {
$this->logger->error('Extension-Deaktivierung Fehler', [
'extension_name' => $name,
'error' => $e->getMessage()
]);
return false;
}
}
/**
* Extension löschen
*/
public function deleteExtension($name)
{
if (!isset($this->extensions[$name])) {
return false;
}
// Extension deaktivieren falls aktiv
if ($this->extensions[$name]['active']) {
$this->deactivateExtension($name);
}
try {
// Extension-Verzeichnis löschen
$extensionPath = $this->extensions[$name]['path'];
if (is_dir($extensionPath)) {
$this->removeDirectory($extensionPath);
}
// Extension aus Array entfernen
unset($this->extensions[$name]);
// Extension aus Datenbank entfernen
$this->deleteExtensionFromDatabase($name);
// Event auslösen
$this->eventDispatcher->dispatch('extension.delete', [
'extension_name' => $name
]);
$this->logger->info('Extension gelöscht', [
'extension_name' => $name
]);
return true;
} catch (\Exception $e) {
$this->logger->error('Extension-Löschung Fehler', [
'extension_name' => $name,
'error' => $e->getMessage()
]);
return false;
}
}
/**
* Extension-Update
*/
public function updateExtension($name, $newVersion)
{
if (!isset($this->extensions[$name])) {
return false;
}
$extension = $this->extensions[$name];
$wasActive = $extension['active'];
try {
// Extension deaktivieren falls aktiv
if ($wasActive) {
$this->deactivateExtension($name);
}
// Update-Logic hier implementieren
// (Download, Backup, Install, etc.)
// Extension-Konfiguration aktualisieren
$this->extensions[$name]['version'] = $newVersion;
$this->extensions[$name]['updated_at'] = date('Y-m-d H:i:s');
// Datenbank aktualisieren
$this->updateExtensionVersion($name, $newVersion);
// Extension wieder aktivieren falls es vorher aktiv war
if ($wasActive) {
$this->activateExtension($name);
}
// Event auslösen
$this->eventDispatcher->dispatch('extension.update', [
'extension_name' => $name,
'old_version' => $extension['version'],
'new_version' => $newVersion
]);
$this->logger->info('Extension aktualisiert', [
'extension_name' => $name,
'old_version' => $extension['version'],
'new_version' => $newVersion
]);
return true;
} catch (\Exception $e) {
$this->logger->error('Extension-Update Fehler', [
'extension_name' => $name,
'error' => $e->getMessage()
]);
return false;
}
}
/**
* Extension-Klasse laden
*/
private function loadExtensionClass($name)
{
$extensionPath = $this->extensions[$name]['path'];
$mainFile = $extensionPath . '/Extension.php';
if (!file_exists($mainFile)) {
return false;
}
require_once $mainFile;
$className = ucfirst($name) . 'Extension';
if (!class_exists($className)) {
return false;
}
return $className;
}
/**
* Dependencies prüfen
*/
private function checkDependencies($dependencies)
{
foreach ($dependencies as $dependency) {
if (is_string($dependency)) {
// Extension-Dependency
if (!isset($this->extensions[$dependency]) || !$this->extensions[$dependency]['active']) {
return false;
}
} elseif (is_array($dependency)) {
// Erweiterte Dependency-Prüfung
$type = $dependency['type'] ?? 'extension';
$name = $dependency['name'] ?? '';
$version = $dependency['version'] ?? '';
switch ($type) {
case 'extension':
if (!isset($this->extensions[$name]) || !$this->extensions[$name]['active']) {
return false;
}
if ($version && version_compare($this->extensions[$name]['version'], $version, '<')) {
return false;
}
break;
case 'plugin':
// Plugin-Dependency prüfen
$pluginManager = Plugin::getInstance();
$plugin = $pluginManager->getPlugin($name);
if (!$plugin || !$plugin['active']) {
return false;
}
if ($version && version_compare($plugin['version'], $version, '<')) {
return false;
}
break;
case 'php':
if (version_compare(PHP_VERSION, $version, '<')) {
return false;
}
break;
case 'extension_php':
if (!extension_loaded($name)) {
return false;
}
break;
}
}
}
return true;
}
/**
* Extension-Hooks registrieren
*/
private function registerExtensionHooks($extensionName, $hooks)
{
foreach ($hooks as $hook) {
$hookName = $hook['name'] ?? '';
$callback = $hook['callback'] ?? '';
$priority = $hook['priority'] ?? 10;
if ($hookName && $callback) {
$this->eventDispatcher->addListener($hookName, function($event) use ($extensionName, $callback) {
return $this->executeExtensionCallback($extensionName, $callback, $event);
}, $priority, $extensionName);
}
}
}
/**
* Extension-Hooks deregistrieren
*/
private function unregisterExtensionHooks($extensionName)
{
// Hooks für diese Extension entfernen
// (Implementierung hängt vom Event-System ab)
}
/**
* Extension-Callback ausführen
*/
private function executeExtensionCallback($extensionName, $callback, $event)
{
if (!isset($this->extensions[$extensionName]['instance'])) {
return $event;
}
$instance = $this->extensions[$extensionName]['instance'];
if (method_exists($instance, $callback)) {
try {
return $instance->$callback($event);
} catch (\Exception $e) {
$this->logger->error('Extension-Callback Fehler', [
'extension_name' => $extensionName,
'callback' => $callback,
'error' => $e->getMessage()
]);
}
}
return $event;
}
/**
* Extension in Datenbank speichern
*/
private function saveExtensionToDatabase($name, $extension)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
INSERT INTO ws_extensions (
extension_name, extension_config, version, type, dependencies,
hooks, settings, active, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE
extension_config = ?, version = ?, type = ?, dependencies = ?,
hooks = ?, settings = ?, updated_at = NOW()
');
$config = json_encode($extension);
$dependencies = json_encode($extension['dependencies']);
$hooks = json_encode($extension['hooks']);
$settings = json_encode($extension['settings']);
$stmt->execute([
$name,
$config,
$extension['version'],
$extension['type'],
$dependencies,
$hooks,
$settings,
$extension['active'] ? 1 : 0,
$config,
$extension['version'],
$extension['type'],
$dependencies,
$hooks,
$settings
]);
} catch (Exception $e) {
$this->logger->error('Extension-Datenbank Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* Extension-Status in Datenbank aktualisieren
*/
private function updateExtensionStatus($name, $active)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
UPDATE ws_extensions
SET active = ?, updated_at = NOW()
WHERE extension_name = ?
');
$stmt->execute([$active ? 1 : 0, $name]);
} catch (Exception $e) {
$this->logger->error('Extension-Status Update Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* Extension-Version in Datenbank aktualisieren
*/
private function updateExtensionVersion($name, $version)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
UPDATE ws_extensions
SET version = ?, updated_at = NOW()
WHERE extension_name = ?
');
$stmt->execute([$version, $name]);
} catch (Exception $e) {
$this->logger->error('Extension-Version Update Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* Extension aus Datenbank löschen
*/
private function deleteExtensionFromDatabase($name)
{
try {
$conn = DriverManager::getConnection([
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
]);
$stmt = $conn->prepare('
DELETE FROM ws_extensions
WHERE extension_name = ?
');
$stmt->execute([$name]);
} catch (Exception $e) {
$this->logger->error('Extension-Datenbank Löschung Fehler', [
'error' => $e->getMessage()
]);
}
}
/**
* 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);
}
/**
* Alle Extensions abrufen
*/
public function getAllExtensions()
{
return $this->extensions;
}
/**
* Aktive Extensions abrufen
*/
public function getActiveExtensions()
{
return array_filter($this->extensions, function($extension) {
return $extension['active'];
});
}
/**
* Extensions nach Typ abrufen
*/
public function getExtensionsByType($type)
{
return array_filter($this->extensions, function($extension) use ($type) {
return $extension['type'] === $type;
});
}
/**
* Extension abrufen
*/
public function getExtension($name)
{
return isset($this->extensions[$name]) ? $this->extensions[$name] : null;
}
/**
* Extension-Statistiken abrufen
*/
public function getExtensionStatistics()
{
$total = count($this->extensions);
$active = count($this->getActiveExtensions());
$inactive = $total - $active;
$types = [];
$versions = [];
foreach ($this->extensions as $extension) {
$type = $extension['type'];
$version = $extension['version'];
$types[$type] = ($types[$type] ?? 0) + 1;
$versions[$version] = ($versions[$version] ?? 0) + 1;
}
return [
'total' => $total,
'active' => $active,
'inactive' => $inactive,
'types' => $types,
'versions' => $versions
];
}
/**
* Extension-System aktivieren/deaktivieren
*/
public function setEnabled($enabled)
{
$this->enabled = $enabled;
return $this;
}
/**
* Extension-System Status prüfen
*/
public function isEnabled()
{
return $this->enabled;
}
}
/**
* Basis-Extension-Klasse
*/
abstract class BaseExtension
{
protected $name;
protected $version;
protected $description;
protected $author;
protected $type;
protected $config;
public function __construct()
{
$this->loadConfig();
}
/**
* Extension aktivieren
*/
abstract public function activate();
/**
* Extension deaktivieren
*/
abstract public function deactivate();
/**
* Extension-Konfiguration laden
*/
protected function loadConfig()
{
$configFile = dirname((new \ReflectionClass($this))->getFileName()) . '/extension.json';
if (file_exists($configFile)) {
$this->config = json_decode(file_get_contents($configFile), true);
$this->name = $this->config['name'] ?? '';
$this->version = $this->config['version'] ?? '1.0.0';
$this->description = $this->config['description'] ?? '';
$this->author = $this->config['author'] ?? '';
$this->type = $this->config['type'] ?? 'general';
}
}
/**
* Extension-Konfiguration abrufen
*/
public function getConfig($key = null)
{
if ($key === null) {
return $this->config;
}
return $this->config[$key] ?? null;
}
/**
* Extension-Konfiguration setzen
*/
public function setConfig($key, $value)
{
$this->config[$key] = $value;
return $this;
}
/**
* Extension-Name abrufen
*/
public function getName()
{
return $this->name;
}
/**
* Extension-Version abrufen
*/
public function getVersion()
{
return $this->version;
}
/**
* Extension-Beschreibung abrufen
*/
public function getDescription()
{
return $this->description;
}
/**
* Extension-Author abrufen
*/
public function getAuthor()
{
return $this->author;
}
/**
* Extension-Typ abrufen
*/
public function getType()
{
return $this->type;
}
}