809 lines
23 KiB
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;
|
|
}
|
|
}
|