764 lines
21 KiB
PHP
764 lines
21 KiB
PHP
<?php
|
|
/**
|
|
* Copyright seit 2024 Webshop System
|
|
*
|
|
* Plugin-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 Plugin
|
|
{
|
|
private static $instance = null;
|
|
private $plugins = [];
|
|
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->loadPlugins();
|
|
}
|
|
|
|
/**
|
|
* Singleton-Instanz abrufen
|
|
*/
|
|
public static function getInstance()
|
|
{
|
|
if (self::$instance === null) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Plugins laden
|
|
*/
|
|
private function loadPlugins()
|
|
{
|
|
$pluginsDir = __DIR__ . '/../../../plugins/';
|
|
|
|
if (!is_dir($pluginsDir)) {
|
|
mkdir($pluginsDir, 0755, true);
|
|
return;
|
|
}
|
|
|
|
$pluginDirs = scandir($pluginsDir);
|
|
|
|
foreach ($pluginDirs as $dir) {
|
|
if ($dir !== '.' && $dir !== '..' && is_dir($pluginsDir . $dir)) {
|
|
$configFile = $pluginsDir . $dir . '/plugin.json';
|
|
|
|
if (file_exists($configFile)) {
|
|
$config = json_decode(file_get_contents($configFile), true);
|
|
|
|
if ($config) {
|
|
$this->plugins[$dir] = array_merge($config, [
|
|
'directory' => $dir,
|
|
'path' => $pluginsDir . $dir,
|
|
'active' => $config['active'] ?? false,
|
|
'version' => $config['version'] ?? '1.0.0',
|
|
'dependencies' => $config['dependencies'] ?? [],
|
|
'hooks' => $config['hooks'] ?? [],
|
|
'settings' => $config['settings'] ?? []
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plugin registrieren
|
|
*/
|
|
public function registerPlugin($name, $config)
|
|
{
|
|
$plugin = array_merge($config, [
|
|
'name' => $name,
|
|
'active' => $config['active'] ?? false,
|
|
'version' => $config['version'] ?? '1.0.0',
|
|
'dependencies' => $config['dependencies'] ?? [],
|
|
'hooks' => $config['hooks'] ?? [],
|
|
'settings' => $config['settings'] ?? []
|
|
]);
|
|
|
|
$this->plugins[$name] = $plugin;
|
|
|
|
// Plugin in Datenbank speichern
|
|
$this->savePluginToDatabase($name, $plugin);
|
|
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('plugin.register', [
|
|
'plugin_name' => $name,
|
|
'plugin_config' => $plugin
|
|
]);
|
|
|
|
$this->logger->info('Plugin registriert', [
|
|
'plugin_name' => $name,
|
|
'version' => $plugin['version']
|
|
]);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Plugin aktivieren
|
|
*/
|
|
public function activatePlugin($name)
|
|
{
|
|
if (!isset($this->plugins[$name])) {
|
|
return false;
|
|
}
|
|
|
|
$plugin = $this->plugins[$name];
|
|
|
|
// Dependencies prüfen
|
|
if (!$this->checkDependencies($plugin['dependencies'])) {
|
|
$this->logger->error('Plugin-Aktivierung fehlgeschlagen - Dependencies nicht erfüllt', [
|
|
'plugin_name' => $name,
|
|
'dependencies' => $plugin['dependencies']
|
|
]);
|
|
return false;
|
|
}
|
|
|
|
// Plugin-Klasse laden
|
|
$pluginClass = $this->loadPluginClass($name);
|
|
if (!$pluginClass) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// Plugin initialisieren
|
|
$instance = new $pluginClass();
|
|
|
|
if (method_exists($instance, 'activate')) {
|
|
$instance->activate();
|
|
}
|
|
|
|
// Hooks registrieren
|
|
$this->registerPluginHooks($name, $plugin['hooks']);
|
|
|
|
// Plugin als aktiv markieren
|
|
$this->plugins[$name]['active'] = true;
|
|
$this->plugins[$name]['instance'] = $instance;
|
|
|
|
// Datenbank aktualisieren
|
|
$this->updatePluginStatus($name, true);
|
|
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('plugin.activate', [
|
|
'plugin_name' => $name,
|
|
'plugin_config' => $plugin
|
|
]);
|
|
|
|
$this->logger->info('Plugin aktiviert', [
|
|
'plugin_name' => $name,
|
|
'version' => $plugin['version']
|
|
]);
|
|
|
|
return true;
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Plugin-Aktivierung Fehler', [
|
|
'plugin_name' => $name,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plugin deaktivieren
|
|
*/
|
|
public function deactivatePlugin($name)
|
|
{
|
|
if (!isset($this->plugins[$name]) || !$this->plugins[$name]['active']) {
|
|
return false;
|
|
}
|
|
|
|
$plugin = $this->plugins[$name];
|
|
|
|
try {
|
|
// Plugin-Instance deaktivieren
|
|
if (isset($plugin['instance']) && method_exists($plugin['instance'], 'deactivate')) {
|
|
$plugin['instance']->deactivate();
|
|
}
|
|
|
|
// Hooks deregistrieren
|
|
$this->unregisterPluginHooks($name);
|
|
|
|
// Plugin als inaktiv markieren
|
|
$this->plugins[$name]['active'] = false;
|
|
unset($this->plugins[$name]['instance']);
|
|
|
|
// Datenbank aktualisieren
|
|
$this->updatePluginStatus($name, false);
|
|
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('plugin.deactivate', [
|
|
'plugin_name' => $name,
|
|
'plugin_config' => $plugin
|
|
]);
|
|
|
|
$this->logger->info('Plugin deaktiviert', [
|
|
'plugin_name' => $name,
|
|
'version' => $plugin['version']
|
|
]);
|
|
|
|
return true;
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Plugin-Deaktivierung Fehler', [
|
|
'plugin_name' => $name,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plugin löschen
|
|
*/
|
|
public function deletePlugin($name)
|
|
{
|
|
if (!isset($this->plugins[$name])) {
|
|
return false;
|
|
}
|
|
|
|
// Plugin deaktivieren falls aktiv
|
|
if ($this->plugins[$name]['active']) {
|
|
$this->deactivatePlugin($name);
|
|
}
|
|
|
|
try {
|
|
// Plugin-Verzeichnis löschen
|
|
$pluginPath = $this->plugins[$name]['path'];
|
|
if (is_dir($pluginPath)) {
|
|
$this->removeDirectory($pluginPath);
|
|
}
|
|
|
|
// Plugin aus Array entfernen
|
|
unset($this->plugins[$name]);
|
|
|
|
// Plugin aus Datenbank entfernen
|
|
$this->deletePluginFromDatabase($name);
|
|
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('plugin.delete', [
|
|
'plugin_name' => $name
|
|
]);
|
|
|
|
$this->logger->info('Plugin gelöscht', [
|
|
'plugin_name' => $name
|
|
]);
|
|
|
|
return true;
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Plugin-Löschung Fehler', [
|
|
'plugin_name' => $name,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plugin-Update
|
|
*/
|
|
public function updatePlugin($name, $newVersion)
|
|
{
|
|
if (!isset($this->plugins[$name])) {
|
|
return false;
|
|
}
|
|
|
|
$plugin = $this->plugins[$name];
|
|
$wasActive = $plugin['active'];
|
|
|
|
try {
|
|
// Plugin deaktivieren falls aktiv
|
|
if ($wasActive) {
|
|
$this->deactivatePlugin($name);
|
|
}
|
|
|
|
// Update-Logic hier implementieren
|
|
// (Download, Backup, Install, etc.)
|
|
|
|
// Plugin-Konfiguration aktualisieren
|
|
$this->plugins[$name]['version'] = $newVersion;
|
|
$this->plugins[$name]['updated_at'] = date('Y-m-d H:i:s');
|
|
|
|
// Datenbank aktualisieren
|
|
$this->updatePluginVersion($name, $newVersion);
|
|
|
|
// Plugin wieder aktivieren falls es vorher aktiv war
|
|
if ($wasActive) {
|
|
$this->activatePlugin($name);
|
|
}
|
|
|
|
// Event auslösen
|
|
$this->eventDispatcher->dispatch('plugin.update', [
|
|
'plugin_name' => $name,
|
|
'old_version' => $plugin['version'],
|
|
'new_version' => $newVersion
|
|
]);
|
|
|
|
$this->logger->info('Plugin aktualisiert', [
|
|
'plugin_name' => $name,
|
|
'old_version' => $plugin['version'],
|
|
'new_version' => $newVersion
|
|
]);
|
|
|
|
return true;
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Plugin-Update Fehler', [
|
|
'plugin_name' => $name,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plugin-Klasse laden
|
|
*/
|
|
private function loadPluginClass($name)
|
|
{
|
|
$pluginPath = $this->plugins[$name]['path'];
|
|
$mainFile = $pluginPath . '/Plugin.php';
|
|
|
|
if (!file_exists($mainFile)) {
|
|
return false;
|
|
}
|
|
|
|
require_once $mainFile;
|
|
|
|
$className = ucfirst($name) . 'Plugin';
|
|
|
|
if (!class_exists($className)) {
|
|
return false;
|
|
}
|
|
|
|
return $className;
|
|
}
|
|
|
|
/**
|
|
* Dependencies prüfen
|
|
*/
|
|
private function checkDependencies($dependencies)
|
|
{
|
|
foreach ($dependencies as $dependency) {
|
|
if (is_string($dependency)) {
|
|
// Plugin-Dependency
|
|
if (!isset($this->plugins[$dependency]) || !$this->plugins[$dependency]['active']) {
|
|
return false;
|
|
}
|
|
} elseif (is_array($dependency)) {
|
|
// Erweiterte Dependency-Prüfung
|
|
$type = $dependency['type'] ?? 'plugin';
|
|
$name = $dependency['name'] ?? '';
|
|
$version = $dependency['version'] ?? '';
|
|
|
|
switch ($type) {
|
|
case 'plugin':
|
|
if (!isset($this->plugins[$name]) || !$this->plugins[$name]['active']) {
|
|
return false;
|
|
}
|
|
if ($version && version_compare($this->plugins[$name]['version'], $version, '<')) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'php':
|
|
if (version_compare(PHP_VERSION, $version, '<')) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'extension':
|
|
if (!extension_loaded($name)) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Plugin-Hooks registrieren
|
|
*/
|
|
private function registerPluginHooks($pluginName, $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 ($pluginName, $callback) {
|
|
return $this->executePluginCallback($pluginName, $callback, $event);
|
|
}, $priority, $pluginName);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plugin-Hooks deregistrieren
|
|
*/
|
|
private function unregisterPluginHooks($pluginName)
|
|
{
|
|
// Hooks für dieses Plugin entfernen
|
|
// (Implementierung hängt vom Event-System ab)
|
|
}
|
|
|
|
/**
|
|
* Plugin-Callback ausführen
|
|
*/
|
|
private function executePluginCallback($pluginName, $callback, $event)
|
|
{
|
|
if (!isset($this->plugins[$pluginName]['instance'])) {
|
|
return $event;
|
|
}
|
|
|
|
$instance = $this->plugins[$pluginName]['instance'];
|
|
|
|
if (method_exists($instance, $callback)) {
|
|
try {
|
|
return $instance->$callback($event);
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Plugin-Callback Fehler', [
|
|
'plugin_name' => $pluginName,
|
|
'callback' => $callback,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $event;
|
|
}
|
|
|
|
/**
|
|
* Plugin in Datenbank speichern
|
|
*/
|
|
private function savePluginToDatabase($name, $plugin)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
INSERT INTO ws_plugins (
|
|
plugin_name, plugin_config, version, dependencies,
|
|
hooks, settings, active, created_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
|
|
ON DUPLICATE KEY UPDATE
|
|
plugin_config = ?, version = ?, dependencies = ?,
|
|
hooks = ?, settings = ?, updated_at = NOW()
|
|
');
|
|
|
|
$config = json_encode($plugin);
|
|
$dependencies = json_encode($plugin['dependencies']);
|
|
$hooks = json_encode($plugin['hooks']);
|
|
$settings = json_encode($plugin['settings']);
|
|
|
|
$stmt->execute([
|
|
$name,
|
|
$config,
|
|
$plugin['version'],
|
|
$dependencies,
|
|
$hooks,
|
|
$settings,
|
|
$plugin['active'] ? 1 : 0,
|
|
$config,
|
|
$plugin['version'],
|
|
$dependencies,
|
|
$hooks,
|
|
$settings
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Plugin-Datenbank Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plugin-Status in Datenbank aktualisieren
|
|
*/
|
|
private function updatePluginStatus($name, $active)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
UPDATE ws_plugins
|
|
SET active = ?, updated_at = NOW()
|
|
WHERE plugin_name = ?
|
|
');
|
|
|
|
$stmt->execute([$active ? 1 : 0, $name]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Plugin-Status Update Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plugin-Version in Datenbank aktualisieren
|
|
*/
|
|
private function updatePluginVersion($name, $version)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
UPDATE ws_plugins
|
|
SET version = ?, updated_at = NOW()
|
|
WHERE plugin_name = ?
|
|
');
|
|
|
|
$stmt->execute([$version, $name]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Plugin-Version Update Fehler', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plugin aus Datenbank löschen
|
|
*/
|
|
private function deletePluginFromDatabase($name)
|
|
{
|
|
try {
|
|
$conn = DriverManager::getConnection([
|
|
'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop'
|
|
]);
|
|
|
|
$stmt = $conn->prepare('
|
|
DELETE FROM ws_plugins
|
|
WHERE plugin_name = ?
|
|
');
|
|
|
|
$stmt->execute([$name]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Plugin-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 Plugins abrufen
|
|
*/
|
|
public function getAllPlugins()
|
|
{
|
|
return $this->plugins;
|
|
}
|
|
|
|
/**
|
|
* Aktive Plugins abrufen
|
|
*/
|
|
public function getActivePlugins()
|
|
{
|
|
return array_filter($this->plugins, function($plugin) {
|
|
return $plugin['active'];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Plugin abrufen
|
|
*/
|
|
public function getPlugin($name)
|
|
{
|
|
return isset($this->plugins[$name]) ? $this->plugins[$name] : null;
|
|
}
|
|
|
|
/**
|
|
* Plugin-Statistiken abrufen
|
|
*/
|
|
public function getPluginStatistics()
|
|
{
|
|
$total = count($this->plugins);
|
|
$active = count($this->getActivePlugins());
|
|
$inactive = $total - $active;
|
|
|
|
$versions = [];
|
|
foreach ($this->plugins as $plugin) {
|
|
$version = $plugin['version'];
|
|
$versions[$version] = ($versions[$version] ?? 0) + 1;
|
|
}
|
|
|
|
return [
|
|
'total' => $total,
|
|
'active' => $active,
|
|
'inactive' => $inactive,
|
|
'versions' => $versions
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Plugin-System aktivieren/deaktivieren
|
|
*/
|
|
public function setEnabled($enabled)
|
|
{
|
|
$this->enabled = $enabled;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Plugin-System Status prüfen
|
|
*/
|
|
public function isEnabled()
|
|
{
|
|
return $this->enabled;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Basis-Plugin-Klasse
|
|
*/
|
|
abstract class BasePlugin
|
|
{
|
|
protected $name;
|
|
protected $version;
|
|
protected $description;
|
|
protected $author;
|
|
protected $config;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->loadConfig();
|
|
}
|
|
|
|
/**
|
|
* Plugin aktivieren
|
|
*/
|
|
abstract public function activate();
|
|
|
|
/**
|
|
* Plugin deaktivieren
|
|
*/
|
|
abstract public function deactivate();
|
|
|
|
/**
|
|
* Plugin-Konfiguration laden
|
|
*/
|
|
protected function loadConfig()
|
|
{
|
|
$configFile = dirname((new \ReflectionClass($this))->getFileName()) . '/plugin.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'] ?? '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plugin-Konfiguration abrufen
|
|
*/
|
|
public function getConfig($key = null)
|
|
{
|
|
if ($key === null) {
|
|
return $this->config;
|
|
}
|
|
|
|
return $this->config[$key] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Plugin-Konfiguration setzen
|
|
*/
|
|
public function setConfig($key, $value)
|
|
{
|
|
$this->config[$key] = $value;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Plugin-Name abrufen
|
|
*/
|
|
public function getName()
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
/**
|
|
* Plugin-Version abrufen
|
|
*/
|
|
public function getVersion()
|
|
{
|
|
return $this->version;
|
|
}
|
|
|
|
/**
|
|
* Plugin-Beschreibung abrufen
|
|
*/
|
|
public function getDescription()
|
|
{
|
|
return $this->description;
|
|
}
|
|
|
|
/**
|
|
* Plugin-Author abrufen
|
|
*/
|
|
public function getAuthor()
|
|
{
|
|
return $this->author;
|
|
}
|
|
}
|