diff --git a/src/Classes/Controllers/IndexController.php b/src/Classes/Controllers/IndexController.php index 15b8f284..04093371 100644 --- a/src/Classes/Controllers/IndexController.php +++ b/src/Classes/Controllers/IndexController.php @@ -18,27 +18,25 @@ use RobinTheHood\ModifiedModuleLoaderClient\App; use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader; use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader; -use RobinTheHood\ModifiedModuleLoaderClient\Loader\RemoteModuleLoader; use RobinTheHood\ModifiedModuleLoaderClient\ModuleFilter; use RobinTheHood\ModifiedModuleLoaderClient\ModuleSorter; use RobinTheHood\ModifiedModuleLoaderClient\Category; use RobinTheHood\ModifiedModuleLoaderClient\SendMail; use RobinTheHood\ModifiedModuleLoaderClient\Config; -use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyException; use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyManager; use RobinTheHood\ModifiedModuleLoaderClient\MmlcVersionInfoLoader; -use RobinTheHood\ModifiedModuleLoaderClient\ModuleInstaller; +use RobinTheHood\ModifiedModuleLoaderClient\ModuleManager\ModuleManager; +use RobinTheHood\ModifiedModuleLoaderClient\ModuleManager\ModuleManagerResult; use RobinTheHood\ModifiedModuleLoaderClient\ModuleStatus; use RobinTheHood\ModifiedModuleLoaderClient\Notification; use RobinTheHood\ModifiedModuleLoaderClient\SelfUpdater; -use RuntimeException; class IndexController extends Controller { private const REQUIRED_PHP_VERSION = '7.4.0'; - /** @var ModuleInstaller */ - private $moduleInstaller; + /** @var ModuleManager */ + private $moduleManager; /** @var ModuleFilter */ private $moduleFilter; @@ -47,7 +45,7 @@ public function __construct(ServerRequestInterface $serverRequest, array $sessio { parent::__construct($serverRequest, $session); - $this->moduleInstaller = ModuleInstaller::createFromConfig(); + $this->moduleManager = ModuleManager::createFromConfig(); $this->moduleFilter = ModuleFilter::createFromConfig(); } @@ -66,20 +64,20 @@ public function invoke() return $this->invokeLazyModuleChangeCount(); case 'lazySystemUpdateCount': return $this->invokeLazySystemUpdateCount(); + case 'loadRemoteModule': + return $this->invokePull(); case 'install': return $this->invokeInstall(); + case 'loadAndInstall': + return $this->invokeInstall(); case 'update': return $this->invokeUpdate(); + case 'revertChanges': + return $this->invokeDiscard(); case 'uninstall': return $this->invokeUninstall(); - case 'loadRemoteModule': - return $this->invokeLoadRemoteModule(); - case 'loadAndInstall': - return $this->invokeLoadAndInstall(); case 'unloadLocalModule': - return $this->invokeUnloadLocalModule(); - case 'revertChanges': - return $this->invokeRevertChanges(); + return $this->invokeDelete(); case 'signIn': return $this->invokeSignIn(); case 'signOut': @@ -294,7 +292,7 @@ public function invokeModuleInfo() 'type' => 'warning', 'text' => 'Einige Abhängigkeiten sind nicht installiert. Das Fehlen von Abhängigkeiten kann zu Fehlern - bei der Ausführung des Moduls führen. Installiere die folgenden fehlenden Abhänigkeiten:
' + bei der Ausführung des Moduls führen. Installiere die folgenden fehlenden Abhängigkeiten:
' . nl2br($string) ]); } @@ -374,7 +372,7 @@ public function invokeLazySystemUpdateCount() } } - public function invokeInstall() + public function invokePull() { if ($accessRedirect = $this->checkAccessRight()) { return $accessRedirect; @@ -384,49 +382,29 @@ public function invokeInstall() $archiveName = $queryParams['archiveName'] ?? ''; $version = $queryParams['version'] ?? ''; - $moduleLoader = LocalModuleLoader::createFromConfig(); - $module = $moduleLoader->loadByArchiveNameAndVersion($archiveName, $version); - - if (!$module) { - $this->addModuleNotFoundNotification($archiveName, $version); - return $this->redirect('/'); - } - - $force = $queryParams['force'] ?? ''; - $force = $force === 'true' ? true : false; - - - try { - if ($force) { - $this->moduleInstaller->install($module, true); - } else { - $this->moduleInstaller->installWithDependencies($module); - } - } catch (DependencyException $e) { - $foreInstallUrl = "?action=install" - . "&archiveName={$module->getArchiveName()}" - . "&version={$module->getVersion()}" - . "&ref=moduleInfo" - . "&force=true"; + $moduleManagerResult = $this->moduleManager->pull($archiveName, $version); + if ($moduleManagerResult->getType() === ModuleManagerResult::TYPE_ERROR) { Notification::pushFlashMessage([ - 'text' => $e->getMessage() - . '

Click here to ' - . 'force install ' . $module->getArchiveName() . ':' . $module->getVersion() - . ' without dependencies.', + 'text' => (string) $moduleManagerResult->getMessage(), 'type' => 'error' ]); - } catch (RuntimeException $e) { + } else { Notification::pushFlashMessage([ - 'text' => $e->getMessage(), - 'type' => 'error' + 'text' => 'Module pulled successfully', + 'type' => 'success' ]); } - return $this->redirectRef($archiveName, $module->getVersion()); + $module = $moduleManagerResult->getModule(); + if ($module) { + return $this->redirectRef($module->getArchiveName(), $module->getVersion()); + } + + return $this->redirectRef($archiveName, $version); } - private function invokeRevertChanges() + public function invokeInstall() { if ($accessRedirect = $this->checkAccessRight()) { return $accessRedirect; @@ -435,33 +413,36 @@ private function invokeRevertChanges() $queryParams = $this->serverRequest->getQueryParams(); $archiveName = $queryParams['archiveName'] ?? ''; $version = $queryParams['version'] ?? ''; + $force = $queryParams['force'] ?? ''; + $force = $force === 'true' ? true : false; - $moduleLoader = LocalModuleLoader::createFromConfig(); - $module = $moduleLoader->loadByArchiveNameAndVersion($archiveName, $version); - - if (!$module) { - $this->addModuleNotFoundNotification($archiveName, $version); - return $this->redirect('/'); + if ($force === false) { + $moduleManagerResult = $this->moduleManager->install($archiveName, $version); + } else { + $moduleManagerResult = $this->moduleManager->installWithoutDependencies($archiveName, $version, true); } - try { - $this->moduleInstaller->revertChanges($module); - } catch (DependencyException $e) { + if ($moduleManagerResult->getType() === ModuleManagerResult::TYPE_ERROR) { Notification::pushFlashMessage([ - 'text' => $e->getMessage(), + 'text' => (string) $moduleManagerResult->getMessage(), 'type' => 'error' ]); - } catch (RuntimeException $e) { + } else { Notification::pushFlashMessage([ - 'text' => $e->getMessage(), - 'type' => 'error' + 'text' => 'Module installed successfully', + 'type' => 'success' ]); } - return $this->redirectRef($archiveName, $module->getVersion()); + $module = $moduleManagerResult->getModule(); + if ($module) { + return $this->redirectRef($module->getArchiveName(), $module->getVersion()); + } + + return $this->redirectRef($archiveName, $version); } - public function invokeUninstall() + public function invokeUpdate() { if ($accessRedirect = $this->checkAccessRight()) { return $accessRedirect; @@ -471,32 +452,29 @@ public function invokeUninstall() $archiveName = $queryParams['archiveName'] ?? ''; $version = $queryParams['version'] ?? ''; - $moduleLoader = LocalModuleLoader::createFromConfig(); - $module = $moduleLoader->loadByArchiveNameAndVersion($archiveName, $version); + $moduleManagerResult = $this->moduleManager->update($archiveName); - if (!$module) { - $this->addModuleNotFoundNotification($archiveName, $version); - return $this->redirect('/'); - } - - try { - $this->moduleInstaller->uninstall($module); - } catch (DependencyException $e) { + if ($moduleManagerResult->getType() === ModuleManagerResult::TYPE_ERROR) { Notification::pushFlashMessage([ - 'text' => $e->getMessage(), + 'text' => (string) $moduleManagerResult->getMessage(), 'type' => 'error' ]); - } catch (RuntimeException $e) { + } else { Notification::pushFlashMessage([ - 'text' => $e->getMessage(), - 'type' => 'error' + 'text' => 'Module updated successfully', + 'type' => 'success' ]); } - return $this->redirectRef($archiveName, $module->getVersion()); + $module = $moduleManagerResult->getModule(); + if ($module) { + return $this->redirectRef($module->getArchiveName(), $module->getVersion()); + } + + return $this->redirectRef($archiveName, $version); } - public function invokeUpdate() + public function invokeDiscard() { if ($accessRedirect = $this->checkAccessRight()) { return $accessRedirect; @@ -505,41 +483,32 @@ public function invokeUpdate() $queryParams = $this->serverRequest->getQueryParams(); $archiveName = $queryParams['archiveName'] ?? ''; $version = $queryParams['version'] ?? ''; + $withTemplate = $queryParams['withTemplate'] ?? ''; + $withTemplate = $withTemplate === 'true' ? true : false; - $moduleLoader = LocalModuleLoader::createFromConfig(); - $module = $moduleLoader->loadByArchiveNameAndVersion($archiveName, $version); - - if (!$module) { - $this->addModuleNotFoundNotification($archiveName, $version); - return $this->redirect('/'); - } - - $newModule = $module; + $moduleManagerResult = $this->moduleManager->discard($archiveName, $withTemplate); - try { - $newModule = $this->moduleInstaller->updateWithDependencies($module); - } catch (DependencyException $e) { + if ($moduleManagerResult->getType() === ModuleManagerResult::TYPE_ERROR) { Notification::pushFlashMessage([ - 'text' => $e->getMessage(), + 'text' => (string) $moduleManagerResult->getMessage(), 'type' => 'error' ]); - } catch (RuntimeException $e) { + } else { Notification::pushFlashMessage([ - 'text' => $e->getMessage(), - 'type' => 'error' + 'text' => 'Module discard successfully', + 'type' => 'success' ]); } - if (!$newModule) { - $newestModule = $module->getNewestVersion(); - $this->addModuleNotFoundNotification($archiveName, $newestModule->getVersion()); - return $this->redirect('/'); + $module = $moduleManagerResult->getModule(); + if ($module) { + return $this->redirectRef($module->getArchiveName(), $module->getVersion()); } - return $this->redirectRef($archiveName, $newModule->getVersion()); + return $this->redirectRef($archiveName, $version); } - public function invokeLoadRemoteModule() + public function invokeUninstall() { if ($accessRedirect = $this->checkAccessRight()) { return $accessRedirect; @@ -549,76 +518,29 @@ public function invokeLoadRemoteModule() $archiveName = $queryParams['archiveName'] ?? ''; $version = $queryParams['version'] ?? ''; - $moduleLoader = RemoteModuleLoader::create(); - $module = $moduleLoader->loadByArchiveNameAndVersion($archiveName, $version); - - if (!$module) { - $this->addModuleNotFoundNotification($archiveName, $version); - return $this->redirect('/'); - } + $moduleManagerResult = $this->moduleManager->uninstall($archiveName); - if (!$this->moduleInstaller->pull($module)) { + if ($moduleManagerResult->getType() === ModuleManagerResult::TYPE_ERROR) { Notification::pushFlashMessage([ - 'text' => "Fehler: Das Module $archiveName - $version konnte nicht geladen werden.", + 'text' => (string) $moduleManagerResult->getMessage(), 'type' => 'error' ]); - } - - return $this->redirectRef($archiveName, $module->getVersion()); - } - - public function invokeLoadAndInstall() - { - if ($accessRedirect = $this->checkAccessRight()) { - return $accessRedirect; - } - - $queryParams = $this->serverRequest->getQueryParams(); - $archiveName = $queryParams['archiveName'] ?? ''; - $version = $queryParams['version'] ?? ''; - - $moduleLoader = RemoteModuleLoader::create(); - $module = $moduleLoader->loadByArchiveNameAndVersion($archiveName, $version); - - if (!$module) { - $this->addModuleNotFoundNotification($archiveName, $version); - return $this->redirect('/'); - } - - if (!$this->moduleInstaller->pull($module)) { + } else { Notification::pushFlashMessage([ - 'text' => "Fehler: Das Module $archiveName - $version konnte nicht geladen werden.", - 'type' => 'error' + 'text' => 'Module uninstalled successfully', + 'type' => 'success' ]); - return $this->redirect('/'); } - $moduleLoader = LocalModuleLoader::createFromConfig(); - $module = $moduleLoader->loadByArchiveNameAndVersion($archiveName, $version); - - if (!$module) { - $this->addModuleNotFoundNotification($archiveName, $version); - return $this->redirect('/'); + $module = $moduleManagerResult->getModule(); + if ($module) { + return $this->redirectRef($module->getArchiveName(), $module->getVersion()); } - try { - $this->moduleInstaller->installWithDependencies($module); - } catch (DependencyException $e) { - Notification::pushFlashMessage([ - 'text' => $e->getMessage(), - 'type' => 'error' - ]); - } catch (RuntimeException $e) { - Notification::pushFlashMessage([ - 'text' => $e->getMessage(), - 'type' => 'error' - ]); - } - - return $this->redirectRef($archiveName, $module->getVersion()); + return $this->redirectRef($archiveName, $version); } - public function invokeUnloadLocalModule() + public function invokeDelete() { if ($accessRedirect = $this->checkAccessRight()) { return $accessRedirect; @@ -628,29 +550,26 @@ public function invokeUnloadLocalModule() $archiveName = $queryParams['archiveName'] ?? ''; $version = $queryParams['version'] ?? ''; - $moduleLoader = LocalModuleLoader::createFromConfig(); - $module = $moduleLoader->loadByArchiveNameAndVersion($archiveName, $version); + $moduleManagerResult = $this->moduleManager->delete($archiveName, $version); - if (!$module) { - $this->addModuleNotFoundNotification($archiveName, $version); - return $this->redirect('/'); - } - - try { - $this->moduleInstaller->delete($module); - } catch (DependencyException $e) { + if ($moduleManagerResult->getType() === ModuleManagerResult::TYPE_ERROR) { Notification::pushFlashMessage([ - 'text' => $e->getMessage(), + 'text' => (string) $moduleManagerResult->getMessage(), 'type' => 'error' ]); - } catch (RuntimeException $e) { + } else { Notification::pushFlashMessage([ - 'text' => $e->getMessage(), - 'type' => 'error' + 'text' => 'Module deleded successfully', + 'type' => 'success' ]); } - return $this->redirect('/'); + $module = $moduleManagerResult->getModule(); + if ($module) { + return $this->redirectRef($module->getArchiveName(), $module->getVersion()); + } + + return $this->redirectRef($archiveName, $version); } public function invokeReportIssue() diff --git a/src/Classes/DependencyManager/CombinationSatisfyer.php b/src/Classes/DependencyManager/CombinationSatisfyer.php index e7fb471f..54a06dc5 100644 --- a/src/Classes/DependencyManager/CombinationSatisfyer.php +++ b/src/Classes/DependencyManager/CombinationSatisfyer.php @@ -42,7 +42,7 @@ public function satisfiesCominationsFromModuleTrees( foreach ($combinations as $testCombination) { $foundCombination = new Combination(); - $result = $this->satisfiesCominationFromModuleTrees( + $result = $this->satisfiesCombinationFromModuleTrees( $moduleTrees, $testCombination, $foundCombination, @@ -85,7 +85,7 @@ public function satisfiesCominationsFromModuleTree( $failLog = new FailLog(); foreach ($combinations as $testCombination) { - $result = $this->satisfiesCominationFromModuleTree( + $result = $this->satisfiesCombinationFromModuleTree( $moduleTree, $testCombination, $foundCombination, @@ -113,7 +113,7 @@ public function satisfiesCominationsFromModuleTree( return $combinationSatisfyerResult; } - public function satisfiesCominationsFromModuleWithIterator( + public function satisfiesCombinationsFromModuleWithIterator( ModuleTree $moduleTree, CombinationIterator $combinationIterator ): CombinationSatisfyerResult { @@ -121,7 +121,7 @@ public function satisfiesCominationsFromModuleWithIterator( $foundCombination = new Combination(); $failLog = new FailLog(); $testCombination = $combinationIterator->current(); - $result = $this->satisfiesCominationFromModuleTree( + $result = $this->satisfiesCombinationFromModuleTree( $moduleTree, $testCombination, $foundCombination, @@ -160,7 +160,7 @@ public function satisfiesCominationsFromModuleWithIterator( * * @return bool */ - public function satisfiesCominationFromModuleTree( + public function satisfiesCombinationFromModuleTree( ModuleTree $moduleTree, Combination $combination, Combination &$foundCombination, @@ -198,7 +198,7 @@ public function satisfiesCominationFromModuleTree( $moduleTree->versionConstraint ); - return $this->satisfiesCominationFromModuleTrees( + return $this->satisfiesCombinationFromModuleTrees( $moduleVersion->require, $combination, $foundCombination, @@ -222,7 +222,7 @@ public function satisfiesCominationFromModuleTree( * * @return bool */ - public function satisfiesCominationFromModuleTrees( + public function satisfiesCombinationFromModuleTrees( array $moduleTrees, Combination $combination, Combination &$foundCombination, @@ -232,7 +232,7 @@ public function satisfiesCominationFromModuleTrees( // Context: Expanded $moduleResult = true; foreach ($moduleTrees as $moduleTree) { - $result = $this->satisfiesCominationFromModuleTree( + $result = $this->satisfiesCombinationFromModuleTree( $moduleTree, $combination, $foundCombination, diff --git a/src/Classes/DependencyManager/CombinationSatisfyerResult.php b/src/Classes/DependencyManager/CombinationSatisfyerResult.php index df6cad59..c7d2985a 100644 --- a/src/Classes/DependencyManager/CombinationSatisfyerResult.php +++ b/src/Classes/DependencyManager/CombinationSatisfyerResult.php @@ -13,6 +13,9 @@ namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager; +/** + * Das CombinationSatisfyerResult Objekt liefert Information zum Ergebnis des CombinationStisfyer. + */ class CombinationSatisfyerResult { public const RESULT_COMBINATION_NOT_FOUND = 0; @@ -21,10 +24,18 @@ class CombinationSatisfyerResult /** @var int */ public $result = -1; - /** @var ?Combination */ + /** + * @var ?Combination $testCombination Beinhaltet eine Kombination an allen Modulen und PHP, MMLC und modified + * Versionen die durch ein SystemSet als Auswahl standen. Welche Kombination verwendet wird hängt vom + * CombinationStisfyer ab. Oft ist es die letzte Kombination die probiert wurde. + */ public $testCombination = null; - /** @var ?Combination */ + /** + * @var ?Combination $foundCombination Enthält nur die Elemente (Module, PHP, MMLC und modified Version) aus + * $testCombination die nötig sind, um die Voraussetung für ein Modul zu erfüllen. Ist das Ergbnis + * RESULT_COMBINATION_NOT_FOUND, fehlen in $foundCombination Elemente. + */ public $foundCombination = null; /** @var ?FailLog */ diff --git a/src/Classes/DependencyManager/DependencyBuilder.php b/src/Classes/DependencyManager/DependencyBuilder.php index bdd2f01a..c408db37 100644 --- a/src/Classes/DependencyManager/DependencyBuilder.php +++ b/src/Classes/DependencyManager/DependencyBuilder.php @@ -15,7 +15,6 @@ use RobinTheHood\ModifiedModuleLoaderClient\Config; use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader; use RobinTheHood\ModifiedModuleLoaderClient\Module; -use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator; use RobinTheHood\ModifiedModuleLoaderClient\Semver\Constraint; class DependencyBuilder @@ -40,60 +39,104 @@ public function __construct(ModuleTreeBuilder $moduleTreeBuilder, ModuleLoader $ $this->moduleLoader = $moduleLoader; } - private function logFile($value, $file) + /** + * Testet, ob alle Bedingungen für ein Modul erfüllt sind. Als Ergbnis gibt die Methode ein + * CombinationSatisfyerResult zurück. Als $constraint kann eine konkrete Version oder ein Constraint wie z. B. + * ^1.0.0 angegeben werden. Die Methode versucht eine Kombination mit den neusten Versionen zu finden. Wenn eine + * Kombination gefunden wurde, befindet sich diese in CombinationSatisfyerResult::foundCombination. Das Modul auf + * das mit $archiveName getestet wurde befindet sich ebenfalls in CombinationSatisfyerResult::foundCombination + * + * @param string $archiveName + * @param string $constraint + * @param SystemSet $systemSet Mit $systemSet kann festgelegt werden, welche Module bereits installiert sind, + * welche PHP, MMLC und modified Version vorhanden ist. + * + * @return CombinationSatisfyerResult + */ + public function satisfies(string $archiveName, string $constraint, SystemSet $systemSet): CombinationSatisfyerResult { - if (!Config::getLogging()) { - return; + $systemSet->remove($archiveName); + $constraint = $this->createConstraint($archiveName, $constraint, $systemSet); + + $moduleTree = $this->moduleTreeBuilder->buildByConstraints($archiveName, $constraint); + $this->logFile($moduleTree, '3-moduleTrees.json'); + + $flatEntryBuilder = new FlatEntryBuilder(); + $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree); + $this->logFile($flatEntries, '3-flatEntries.json'); + + $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet); + $this->logFile($flatEntries, '3-flatEntries-fit.json'); + + $combinationIterator = new CombinationIterator($flatEntries); + $combinationSatisfyer = new CombinationSatisfyer(); + $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCombinationsFromModuleWithIterator( + $moduleTree, + $combinationIterator + ); + + return $combinationSatisfyerResult; + } + + /** + * Gehe alle Module durch, die in $systemSet sind und gleichzeitig das Modul $archiveName benötigen. + * Gibt ein $constraint zurück, sodass die Anforderungenden der Module in $systemSet erhaltenbleiben. + */ + private function createConstraint(string $archiveName, string $constraint, SystemSet $systemSet): string + { + /** @var string[] */ + $requiredConstraints = [$constraint]; + + $archives = $systemSet->getArchives(); + foreach ($archives as $archiveNameB => $version) { + $installedModule = $this->getModuleByArchiveNameAndVersion($archiveNameB, $version); + if (!$installedModule) { + continue; + } + + $requiredConstraint = $this->getRequiredConstraint($installedModule, $archiveName); + if (!$requiredConstraint) { + continue; + } + + $requiredConstraints[] = $requiredConstraint; } - $logsRootPath = App::getLogsRoot(); + $constraint = Constraint::createConstraintFromConstraints($requiredConstraints); - @mkdir($logsRootPath); - @mkdir($logsRootPath . '/debug'); - @mkdir($logsRootPath . '/debug/DependencyMananger/'); - $path = $logsRootPath . '/debug/DependencyMananger/' . $file; - file_put_contents($path, json_encode($value, JSON_PRETTY_PRINT)); + return $constraint; } - public function log($var) + private function getModuleByArchiveNameAndVersion(string $archiveName, string $version): ?Module { - print_r($var); + return $this->moduleLoader->loadByArchiveNameAndVersion($archiveName, $version); } - public function test() + private function getRequiredConstraint(Module $installedModule, string $archiveName): string { - $moduleLoader = ModuleLoader::create(Comparator::CARET_MODE_STRICT); - $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order'); + $required = $installedModule->getRequire(); + return $required[$archiveName] ?? ''; + } - if (!$module) { - die('Can not find base module'); + private function logFile($value, $file) + { + if (!Config::getLogging()) { + return; } - $systemSet = new SystemSet(); - $systemSet->set([ - "modified" => '2.0.4.2', - "php" => '7.4.0', - "mmlc" => '1.19.0', - "composer/autoload" => '1.3.0', - "robinthehood/modified-std-module" => '0.9.0', - "robinthehood/modified-orm" => '1.8.1', - "robinthehood/pdf-bill" => '0.17.0' - ]); - - $this->log('TEST: satisfiesContraints1'); - $combinationSatisfyerResult = $this->satisfiesContraints1($module, $systemSet); - $this->log($combinationSatisfyerResult); - - $this->log('TEST: satisfiesContraints2'); - $combinationSatisfyerResult = $this->satisfiesContraints2('firstweb/multi-order', '^1.0.0', $systemSet); - $this->log($combinationSatisfyerResult); - - // var_dump('TEST: satisfiesContraints3'); - $combinationSatisfyerResult = $this->satisfies('firstweb/multi-order', '^1.0.0', $systemSet); - $this->log($combinationSatisfyerResult); + $logsRootPath = App::getLogsRoot(); + + @mkdir($logsRootPath); + @mkdir($logsRootPath . '/debug'); + @mkdir($logsRootPath . '/debug/DependencyMananger/'); + $path = $logsRootPath . '/debug/DependencyMananger/' . $file; + file_put_contents($path, json_encode($value, JSON_PRETTY_PRINT)); } - public function satisfiesContraints1(Module $module, SystemSet $systemSet): CombinationSatisfyerResult + /** + * Diese Methode wird zurzeit nicht verwednet und wurde deswegen auf private gestellt. + */ + private function satisfiesContraints1(Module $module, SystemSet $systemSet): CombinationSatisfyerResult { $moduleTrees = $this->moduleTreeBuilder->buildListByConstraints($module); $this->logFile($moduleTrees, '1-moduleTrees.json'); @@ -118,8 +161,10 @@ public function satisfiesContraints1(Module $module, SystemSet $systemSet): Comb return $combinationSatisfyerResult; } - - public function satisfiesContraints2( + /** + * Diese Methode wird zurzeit nicht verwednet und wurde deswegen auf private gestellt. + */ + private function satisfiesContraints2( string $archiveName, string $constraint, SystemSet $systemSet @@ -146,68 +191,4 @@ public function satisfiesContraints2( return $combinationSatisfyerResult; } - - public function satisfies(string $archiveName, string $constraint, SystemSet $systemSet): CombinationSatisfyerResult - { - $systemSet->remove($archiveName); - $constraint = $this->createConstraint($archiveName, $constraint, $systemSet); - - $moduleTree = $this->moduleTreeBuilder->buildByConstraints($archiveName, $constraint); - $this->logFile($moduleTree, '3-moduleTrees.json'); - - $flatEntryBuilder = new FlatEntryBuilder(); - $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree); - $this->logFile($flatEntries, '3-flatEntries.json'); - - $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet); - $this->logFile($flatEntries, '3-flatEntries-fit.json'); - - $combinationIterator = new CombinationIterator($flatEntries); - $combinationSatisfyer = new CombinationSatisfyer(); - $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleWithIterator( - $moduleTree, - $combinationIterator - ); - return $combinationSatisfyerResult; - } - - /** - * Gehe alle Module durch, die in $systemSet sind und das gleichzeitig Modul $archiveName benötigen. - * Gibt ein $constraint zurück, sodass die Anforderungenden der Module in $systemSet erhaltenbleiben. - */ - private function createConstraint(string $archiveName, string $constraint, SystemSet $systemSet): string - { - /** @var string[] */ - $requiredConstraints = [$constraint]; - - $archives = $systemSet->getArchives(); - foreach ($archives as $archiveNameB => $version) { - $installedModule = $this->getModuleByArchiveNameAndVersion($archiveNameB, $version); - if (!$installedModule) { - continue; - } - - $requiredConstraint = $this->getRequiredConstraint($installedModule, $archiveName); - if (!$requiredConstraint) { - continue; - } - - $requiredConstraints[] = $requiredConstraint; - } - - $constraint = Constraint::createConstraintFromConstraints($requiredConstraints); - - return $constraint; - } - - private function getModuleByArchiveNameAndVersion(string $archiveName, string $version): ?Module - { - return $this->moduleLoader->loadByArchiveNameAndVersion($archiveName, $version); - } - - private function getRequiredConstraint(Module $installedModule, string $archiveName): string - { - $required = $installedModule->getRequire(); - return $required[$archiveName] ?? ''; - } } diff --git a/src/Classes/DependencyManager/DependencyBuilderOld.php b/src/Classes/DependencyManager/DependencyBuilderOld.php new file mode 100644 index 00000000..41c18ab6 --- /dev/null +++ b/src/Classes/DependencyManager/DependencyBuilderOld.php @@ -0,0 +1,213 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\DependencyManager; + +use RobinTheHood\ModifiedModuleLoaderClient\App; +use RobinTheHood\ModifiedModuleLoaderClient\Config; +use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader; +use RobinTheHood\ModifiedModuleLoaderClient\Module; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Constraint; + +class DependencyBuilderOld +{ + /** @var ModuleTreeBuilder*/ + private $moduleTreeBuilder; + + /** @var ModuleLoader*/ + private $moduleLoader; + + public static function create(int $mode): DependencyBuilder + { + $moduleTreeBuilder = ModuleTreeBuilder::create($mode); + $moduleLoader = ModuleLoader::create($mode); + $dependencyBuilder = new DependencyBuilder($moduleTreeBuilder, $moduleLoader); + return $dependencyBuilder; + } + + public function __construct(ModuleTreeBuilder $moduleTreeBuilder, ModuleLoader $moduleLoader) + { + $this->moduleTreeBuilder = $moduleTreeBuilder; + $this->moduleLoader = $moduleLoader; + } + + private function logFile($value, $file) + { + if (!Config::getLogging()) { + return; + } + + $logsRootPath = App::getLogsRoot(); + + @mkdir($logsRootPath); + @mkdir($logsRootPath . '/debug'); + @mkdir($logsRootPath . '/debug/DependencyMananger/'); + $path = $logsRootPath . '/debug/DependencyMananger/' . $file; + file_put_contents($path, json_encode($value, JSON_PRETTY_PRINT)); + } + + public function log($var) + { + print_r($var); + } + + public function test() + { + $moduleLoader = ModuleLoader::create(Comparator::CARET_MODE_STRICT); + $module = $moduleLoader->loadLatestVersionByArchiveName('firstweb/multi-order'); + + if (!$module) { + die('Can not find base module'); + } + + $systemSet = new SystemSet(); + $systemSet->set([ + "modified" => '2.0.4.2', + "php" => '7.4.0', + "mmlc" => '1.19.0', + "composer/autoload" => '1.3.0', + "robinthehood/modified-std-module" => '0.9.0', + "robinthehood/modified-orm" => '1.8.1', + "robinthehood/pdf-bill" => '0.17.0' + ]); + + $this->log('TEST: satisfiesContraints1'); + $combinationSatisfyerResult = $this->satisfiesContraints1($module, $systemSet); + $this->log($combinationSatisfyerResult); + + $this->log('TEST: satisfiesContraints2'); + $combinationSatisfyerResult = $this->satisfiesContraints2('firstweb/multi-order', '^1.0.0', $systemSet); + $this->log($combinationSatisfyerResult); + + // var_dump('TEST: satisfiesContraints3'); + $combinationSatisfyerResult = $this->satisfies('firstweb/multi-order', '^1.0.0', $systemSet); + $this->log($combinationSatisfyerResult); + } + + public function satisfiesContraints1(Module $module, SystemSet $systemSet): CombinationSatisfyerResult + { + $moduleTrees = $this->moduleTreeBuilder->buildListByConstraints($module); + $this->logFile($moduleTrees, '1-moduleTrees.json'); + + $flatEntryBuilder = new FlatEntryBuilder(); + $flatEntries = $flatEntryBuilder->buildListFromModuleTrees($moduleTrees); + $this->logFile($flatEntries, '1-flatEntries.json'); + + $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet); + $this->logFile($flatEntries, '1-flatEntries-fit.json'); + + $combinationBuilder = new CombinationBuilder(); + $combinations = $combinationBuilder->buildAllFromModuleFlatEntries($flatEntries); + $this->logFile($combinations, '1-combinations.json'); + + $combinationSatisfyer = new CombinationSatisfyer(); + $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTrees( + $moduleTrees, + $combinations + ); + + return $combinationSatisfyerResult; + } + + + public function satisfiesContraints2( + string $archiveName, + string $constraint, + SystemSet $systemSet + ): CombinationSatisfyerResult { + $moduleTree = $this->moduleTreeBuilder->buildByConstraints($archiveName, $constraint); + $this->logFile($moduleTree, '2-moduleTrees.json'); + + $flatEntryBuilder = new FlatEntryBuilder(); + $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree); + $this->logFile($flatEntries, '2-flatEntries.json'); + + $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet); + $this->logFile($flatEntries, '2-flatEntries-fit.json'); + + $combinationBuilder = new CombinationBuilder(); + $combinations = $combinationBuilder->buildAllFromModuleFlatEntries($flatEntries); + $this->logFile($combinations, '2-combinations.json'); + + $combinationSatisfyer = new CombinationSatisfyer(); + $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCominationsFromModuleTree( + $moduleTree, + $combinations + ); + + return $combinationSatisfyerResult; + } + + public function satisfies(string $archiveName, string $constraint, SystemSet $systemSet): CombinationSatisfyerResult + { + $systemSet->remove($archiveName); + $constraint = $this->createConstraint($archiveName, $constraint, $systemSet); + + $moduleTree = $this->moduleTreeBuilder->buildByConstraints($archiveName, $constraint); + $this->logFile($moduleTree, '3-moduleTrees.json'); + + $flatEntryBuilder = new FlatEntryBuilder(); + $flatEntries = $flatEntryBuilder->buildListFromModuleTree($moduleTree); + $this->logFile($flatEntries, '3-flatEntries.json'); + + $flatEntries = $flatEntryBuilder->fitSystemSet($flatEntries, $systemSet); + $this->logFile($flatEntries, '3-flatEntries-fit.json'); + + $combinationIterator = new CombinationIterator($flatEntries); + $combinationSatisfyer = new CombinationSatisfyer(); + $combinationSatisfyerResult = $combinationSatisfyer->satisfiesCombinationsFromModuleWithIterator( + $moduleTree, + $combinationIterator + ); + return $combinationSatisfyerResult; + } + + /** + * Gehe alle Module durch, die in $systemSet sind und das gleichzeitig Modul $archiveName benötigen. + * Gibt ein $constraint zurück, sodass die Anforderungenden der Module in $systemSet erhaltenbleiben. + */ + private function createConstraint(string $archiveName, string $constraint, SystemSet $systemSet): string + { + /** @var string[] */ + $requiredConstraints = [$constraint]; + + $archives = $systemSet->getArchives(); + foreach ($archives as $archiveNameB => $version) { + $installedModule = $this->getModuleByArchiveNameAndVersion($archiveNameB, $version); + if (!$installedModule) { + continue; + } + + $requiredConstraint = $this->getRequiredConstraint($installedModule, $archiveName); + if (!$requiredConstraint) { + continue; + } + + $requiredConstraints[] = $requiredConstraint; + } + + $constraint = Constraint::createConstraintFromConstraints($requiredConstraints); + + return $constraint; + } + + private function getModuleByArchiveNameAndVersion(string $archiveName, string $version): ?Module + { + return $this->moduleLoader->loadByArchiveNameAndVersion($archiveName, $version); + } + + private function getRequiredConstraint(Module $installedModule, string $archiveName): string + { + $required = $installedModule->getRequire(); + return $required[$archiveName] ?? ''; + } +} diff --git a/src/Classes/DependencyManager/DependencyManager.php b/src/Classes/DependencyManager/DependencyManager.php index a6990760..044230a4 100644 --- a/src/Classes/DependencyManager/DependencyManager.php +++ b/src/Classes/DependencyManager/DependencyManager.php @@ -110,6 +110,9 @@ public function getAllModulesFromCombination(Combination $combination): array } /** + * Kontrolliert, ob ein Modul installiert werden kann. Es wird getestet, ob genau die Version installiert werden + * kann. + * * @param Module $module * @param string[] $doNotCheck * @@ -137,35 +140,45 @@ public function canBeInstalled(Module $module, array $doNotCheck = []): Combinat . " The following combination is required: {$combinationSatisfyerResult->failLog}"; StaticLogger::log(LogLevel::WARNING, $message); - throw new DependencyException($message); } - // $modules = $this->getAllModulesFromCombination($combinationSatisfyerResult->foundCombination); - // $this->canBeInstalledTestChanged($module, $modules); - return $combinationSatisfyerResult; } /** - * Testet, ob das Modul in $module installiert werden kann, ob das Modul $module - * selbst oder eine Abhängigkeit in $modules im Status 'changed' ist. + * Kontrolliert, ob ein Modul aktuallisert werden kann, Es wird getestet, ob es eine neuerer Version gibt, die alle + * Bedingungen erfüllt. Wenn ja, steckt eine passende Kombination in CombinationSatisfyerResult::foundCombination. * - * @param Module[] $modules + * @param Module $module + * @param string[] $doNotCheck + * + * @return CombinationSatisfyerResult */ - private function canBeInstalledTestChanged(Module $module, array $modules): void + public function canBeUpdated(Module $module, array $doNotCheck = []): CombinationSatisfyerResult { - $module = $module->getInstalledVersion(); - if ($module && $module->isInstalled() && $module->isChanged()) { - $a = $module->getArchiveName(); - throw new DependencyException("Module $a can not be installed because the Module has changes"); + $systemSet = $this->systemSetFactory->getSystemSet(); + + foreach ($doNotCheck as $name) { + $systemSet->remove($name); } - foreach ($modules as $module) { - if ($module && $module->isInstalled() && $module->isChanged()) { - $a = $module->getArchiveName(); - throw new DependencyException("Required Module $a can not be installed because the Module has changes"); - } + $combinationSatisfyerResult = $this->dependencyBuilder->satisfies( + $module->getArchiveName(), + '>' . $module->getVersion(), + $systemSet + ); + + if ($combinationSatisfyerResult->result === CombinationSatisfyerResult::RESULT_COMBINATION_NOT_FOUND) { + $message = "Can not update module {$module->getArchiveName()} in version {$module->getVersion()} " + . "because there are conflicting version contraints. " + . "Perhaps you have installed a module that requires a different version, " + . "or there is no compatible combination of dependencies. " + . " The following combination is required: {$combinationSatisfyerResult->failLog}"; + + StaticLogger::log(LogLevel::WARNING, $message); } + + return $combinationSatisfyerResult; } /** @@ -202,4 +215,26 @@ public function getMissingDependencies(Module $module): array return $missing; } + + /** + * Testet, ob das Modul in $module installiert werden kann, ob das Modul $module + * selbst oder eine Abhängigkeit in $modules im Status 'changed' ist. + * + * @param Module[] $modules + */ + private function canBeInstalledTestChanged(Module $module, array $modules): void + { + $module = $module->getInstalledVersion(); + if ($module && $module->isInstalled() && $module->isChanged()) { + $a = $module->getArchiveName(); + throw new DependencyException("Module $a can not be installed because the Module has changes"); + } + + foreach ($modules as $module) { + if ($module && $module->isInstalled() && $module->isChanged()) { + $a = $module->getArchiveName(); + throw new DependencyException("Required Module $a can not be installed because the Module has changes"); + } + } + } } diff --git a/src/Classes/Loader/LocalModuleLoader.php b/src/Classes/Loader/LocalModuleLoader.php index 5b748ee7..e5027740 100644 --- a/src/Classes/Loader/LocalModuleLoader.php +++ b/src/Classes/Loader/LocalModuleLoader.php @@ -140,6 +140,19 @@ public function loadAllInstalledVersions(): array return $installedModules; } + /** + * Loads the installed module version by a given archiveName + * + * @return Module|null Returns a array of installed module versions. + */ + public function loadInstalledVersionByArchiveName(string $arhciveName): ?Module + { + $modules = $this->loadAllVersions(); + $installedModules = $this->moduleFilter->filterInstalled($modules); + $installedModules = $this->moduleFilter->filterByArchiveName($installedModules, $arhciveName); + return $installedModules[0] ?? null; + } + public function getVendorDirs() { return FileHelper::scanDir($this->modulesRootPath, FileHelper::DIRS_ONLY); diff --git a/src/Classes/Module.php b/src/Classes/Module.php index 871aad9d..b1b99a2c 100644 --- a/src/Classes/Module.php +++ b/src/Classes/Module.php @@ -23,8 +23,6 @@ use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader; use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader; use RobinTheHood\ModifiedModuleLoaderClient\Helpers\FileHelper; -use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator; -use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser; class Module extends ModuleInfo { diff --git a/src/Classes/ModuleChangeManager.php b/src/Classes/ModuleChangeManager.php index 9bd14f92..1876ae3c 100644 --- a/src/Classes/ModuleChangeManager.php +++ b/src/Classes/ModuleChangeManager.php @@ -30,10 +30,6 @@ class ModuleChangeManager */ public static function getChangedFiles(Module $module): ChangedEntryCollection { - if ($module->getArchiveName() === 'robinthehood/modified-std-module') { - $a = 1; - } - $hashFileLoader = new HashFileLoader(); $hashFileLoader->setDefaultScope(ModuleHasher::SCOPE_SHOP_ROOT); $hashFile = $hashFileLoader->load($module->getHashPath()); @@ -50,12 +46,6 @@ public static function getChangedFiles(Module $module): ChangedEntryCollection $srcMmlcChangedEntiresCollection ]); - // if ($module->getArchiveName() === 'robinthehood/modified-std-module') { - // echo '
';
-        //     print_r($hashFile);
-        //     print_r($changedEntiresCollection);
-        //     die();
-        // }
         return $changedEntiresCollection;
     }
 
diff --git a/src/Classes/ModuleHasher/ModuleHasher.php b/src/Classes/ModuleHasher/ModuleHasher.php
index d67d96df..04dedade 100644
--- a/src/Classes/ModuleHasher/ModuleHasher.php
+++ b/src/Classes/ModuleHasher/ModuleHasher.php
@@ -75,7 +75,8 @@ public function createShopRootHashes(Module $module): HashEntryCollection
     public function createShopVendorMmlcHashes(Module $module): HashEntryCollection
     {
         $files = $module->getSrcMmlcFilePaths();
-        $root = App::getShopRoot() . '/' . ModulePathMapper::moduleSrcMmlcToShopVendorMmlc('/', $module->getArchiveName());
+        $root =
+            App::getShopRoot() . '/' . ModulePathMapper::moduleSrcMmlcToShopVendorMmlc('/', $module->getArchiveName());
         return $this->fileHasher->createHashes($files, $root, self::SCOPE_SHOP_VENDOR_MMLC);
     }
 }
diff --git a/src/Classes/ModuleInstaller.php b/src/Classes/ModuleInstaller.php
deleted file mode 100644
index 5116780a..00000000
--- a/src/Classes/ModuleInstaller.php
+++ /dev/null
@@ -1,465 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace RobinTheHood\ModifiedModuleLoaderClient;
-
-use RobinTheHood\ModifiedModuleLoaderClient\App;
-use RobinTheHood\ModifiedModuleLoaderClient\Archive;
-use RobinTheHood\ModifiedModuleLoaderClient\Config;
-use RobinTheHood\ModifiedModuleLoaderClient\FileInfo;
-use RobinTheHood\ModifiedModuleLoaderClient\Api\V1\ApiRequest;
-use RobinTheHood\ModifiedModuleLoaderClient\Archive\Archive as ArchiveArchive;
-use RobinTheHood\ModifiedModuleLoaderClient\Archive\ArchiveHandler;
-use RobinTheHood\ModifiedModuleLoaderClient\Archive\ArchiveName;
-use RobinTheHood\ModifiedModuleLoaderClient\Archive\ArchivePuller;
-use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\Combination;
-use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyManager;
-use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader;
-use RobinTheHood\ModifiedModuleLoaderClient\Helpers\FileHelper;
-use RobinTheHood\ModifiedModuleLoaderClient\Logger\LogLevel;
-use RobinTheHood\ModifiedModuleLoaderClient\Logger\StaticLogger;
-use RobinTheHood\ModifiedModuleLoaderClient\ModuleHasher\ModuleHashFileCreator;
-use RuntimeException;
-
-class ModuleInstaller
-{
-    /** @var DependencyManager */
-    private $dependencyManager;
-
-    /** @var ModuleFilter */
-    private $moduleFilter;
-
-    /** @var LocalModuleLoader */
-    private $localModuleLoader;
-
-    /** @var ArchivePuller */
-    private $archivePuller;
-
-    /** @var ArchiveHandler */
-    private $archiveHandler;
-
-
-
-    // new ArchiveHandler($this->localModuleLoader, App::getModulesRoot());
-    public static function create(int $mode): ModuleInstaller
-    {
-        $dependencyManager = DependencyManager::create($mode);
-        $moduleFilter = ModuleFilter::create($mode);
-        $localModuleLoader = LocalModuleLoader::create($mode);
-        $archivePuller = ArchivePuller::create();
-        $archiveHandler = ArchiveHandler::create($mode);
-        $moduleInstaller = new ModuleInstaller(
-            $dependencyManager,
-            $moduleFilter,
-            $localModuleLoader,
-            $archivePuller,
-            $archiveHandler
-        );
-        return $moduleInstaller;
-    }
-
-    public static function createFromConfig(): ModuleInstaller
-    {
-        return self::create(Config::getDependenyMode());
-    }
-
-    public function __construct(
-        DependencyManager $dependencyManager,
-        ModuleFilter $moduleFilter,
-        LocalModuleLoader $localModuleLoader,
-        ArchivePuller $archivePuller,
-        ArchiveHandler $archiveHandler
-    ) {
-        $this->dependencyManager = $dependencyManager;
-        $this->moduleFilter = $moduleFilter;
-        $this->localModuleLoader = $localModuleLoader;
-        $this->archivePuller = $archivePuller;
-        $this->archiveHandler = $archiveHandler;
-    }
-
-    public function pull(Module $module): bool
-    {
-        if ($module->isLoaded()) {
-            return true;
-        }
-
-        $apiRequest = new ApiRequest();
-        $result = $apiRequest->getArchive($module->getArchiveName(), $module->getVersion());
-
-        $content = $result['content'] ?? [];
-        if (!$content) {
-            return false;
-        }
-
-        $archiveUrl = $content['archiveUrl'] ?? '';
-
-        if (!$archiveUrl) {
-            return false;
-        }
-
-        try {
-            // New
-            $archive = $this->archivePuller->pull($module->getArchiveName(), $module->getVersion(), $archiveUrl);
-            $this->archiveHandler->extract($archive);
-            return true;
-
-            // Old
-            $archive = Archive::pullArchive($archiveUrl, $module->getArchiveName(), $module->getVersion());
-            $archive->untarArchive();
-            return true;
-        } catch (RuntimeException $e) {
-            //Can not pull Archive
-            return false;
-        }
-    }
-
-    public function delete(Module $module)
-    {
-        $path = $module->getLocalRootPath() . $module->getModulePath();
-
-        $filePaths = FileHelper::scanDirRecursive($path, FileHelper::FILES_ONLY);
-
-        $dirPaths = FileHelper::scanDirRecursive($path, FileHelper::DIRS_ONLY);
-        $dirPaths = array_reverse($dirPaths);
-        $dirPaths[] = $path;
-        $dirPaths[] = dirname($path);
-
-        // Delete Files
-        foreach ($filePaths as $path) {
-            if (file_exists($path)) {
-                unlink($path);
-            }
-        }
-
-        // Delete Folders
-        foreach ($dirPaths as $path) {
-            if (file_exists($path)) {
-                @rmdir($path);
-            }
-        }
-    }
-
-    public function install(Module $module, bool $force = false): void
-    {
-        if (!$force) {
-            $this->dependencyManager->canBeInstalled($module);
-        }
-
-        $this->internalInstall($module);
-        $this->createAutoloadFile();
-    }
-
-    public function installWithDependencies(Module $module): void
-    {
-        $combinationSatisfyerResult = $this->dependencyManager->canBeInstalled($module, ['']);
-
-        if (!$combinationSatisfyerResult->foundCombination) {
-            $message =
-                "Can not install module {$module->getArchiveName()} {$module->getVersion()} with dependencies. "
-                . "No possible combination of versions found";
-            StaticLogger::log(LogLevel::WARNING, $message);
-            // NOTE: Vielleicht neue class ModuleException hinzufügen
-            throw new RuntimeException($message);
-        }
-
-        $this->uninstall($module);
-        $this->internalInstall($module);
-        $this->internalInstallDependencies($module, $combinationSatisfyerResult->foundCombination);
-        $this->createAutoloadFile();
-    }
-
-    private function internalInstall(Module $module): void
-    {
-        // Install Source Files to Shop Root
-        $files = $module->getSrcFilePaths();
-
-        foreach ($files as $file) {
-            $src = $module->getLocalRootPath() . $module->getSrcRootPath() . '/' . $file;
-
-            $files = $module->getTemplateFiles($file);
-            foreach ($files as $file) {
-                $overwrite = false;
-                if (!FileInfo::isTemplateFile($file)) {
-                    $overwrite = true;
-                }
-
-                $file = ModulePathMapper::moduleSrcToShopRoot($file);
-
-                $dest = App::getShopRoot() . $file;
-                $this->installFile($src, $dest, $overwrite);
-            }
-        }
-
-        // Install Source Mmlc Files to shop vendor-mmlc
-        $files = $module->getSrcMmlcFilePaths();
-        foreach ($files as $file) {
-            $src = $module->getLocalRootPath() . $module->getSrcMmlcRootPath() . '/' . $file;
-            $file = ModulePathMapper::moduleSrcMmlcToShopVendorMmlc($file, $module->getArchiveName());
-            $dest = App::getShopRoot() . '/' . $file;
-            $this->installFile($src, $dest, true);
-        }
-
-        $moduleHashFileCreator = new ModuleHashFileCreator();
-        $moduleHashFile = $moduleHashFileCreator->createHashFile($module);
-        $moduleHashFile->writeTo($module->getHashPath());
-    }
-
-    private function internalPullAndInstall(Module $module): void
-    {
-        if (!$module->isLoaded()) {
-            $this->pull($module);
-        }
-
-        $reloaded = $this->reload($module);
-
-        if (!$reloaded->isLoaded()) {
-            $message =
-                "Can not pull and install module {$module->getArchiveName()} {$module->getVersion()}. "
-                . "Module is not loaded.";
-            StaticLogger::log(LogLevel::WARNING, $message);
-            // NOTE: Vielleicht neue class ModuleOperationException hinzufügen
-            throw new RuntimeException($message);
-        }
-
-        if ($reloaded->isInstalled()) {
-            return;
-        }
-
-        $this->uninstall($module);
-        $this->internalInstall($module);
-    }
-
-    private function internalInstallDependencies(Module $parentModule, Combination $combination): void
-    {
-        $modules = $this->dependencyManager->getAllModulesFromCombination($combination);
-
-        foreach ($modules as $module) {
-            if ($parentModule->getArchiveName() === $module->getArchiveName()) {
-                continue;
-            }
-            $this->internalPullAndInstall($module);
-        }
-    }
-
-    public function update(Module $module): ?Module
-    {
-        $installedModule = $module->getInstalledVersion();
-        $newModule = $module->getNewestVersion();
-
-        $combinationSatisfyerResult = $this->dependencyManager->canBeInstalled($module);
-
-        if (!$combinationSatisfyerResult->foundCombination) {
-            $message =
-                "Can not update module {$module->getArchiveName()} {$module->getVersion()}. "
-                . "No possible combination of versions found";
-            StaticLogger::log(LogLevel::WARNING, $message);
-            // NOTE: Vielleicht neue class ModuleException hinzufügen
-            throw new RuntimeException($message);
-        }
-
-        if ($installedModule) {
-            $this->uninstall($installedModule);
-        }
-
-        $this->pull($newModule);
-        $newModule = $this->reload($newModule);
-
-        $this->internalInstall($newModule);
-        $this->createAutoloadFile();
-
-        return $newModule;
-    }
-
-    public function updateWithDependencies(Module $module): ?Module
-    {
-        $installedModule = $module->getInstalledVersion();
-        $newModule = $module->getNewestVersion();
-
-        $combinationSatisfyerResult = $this->dependencyManager->canBeInstalled($module);
-
-        if (!$combinationSatisfyerResult->foundCombination) {
-            $message =
-                "Can not update module {$module->getArchiveName()} {$module->getVersion()} with dependencies. "
-                . "No possible combination of versions found";
-            StaticLogger::log(LogLevel::WARNING, $message);
-            // NOTE: Vielleicht neue class ModuleException hinzufügen
-            throw new RuntimeException($message);
-        }
-
-        if ($installedModule) {
-            $this->uninstall($installedModule);
-        }
-
-        $this->pull($newModule);
-        $newModule = $this->reload($newModule);
-
-        $this->internalInstall($newModule);
-        $this->internalInstallDependencies($newModule, $combinationSatisfyerResult->foundCombination);
-        $this->createAutoloadFile();
-
-        return $newModule;
-    }
-
-    private function reload(Module $module): Module
-    {
-        $this->localModuleLoader->resetCache();
-        $reloadedModule = $this->localModuleLoader->loadByArchiveNameAndVersion(
-            $module->getArchiveName(),
-            $module->getVersion()
-        );
-
-        if (!$reloadedModule) {
-            $message = "Can not reload module {$module->getArchiveName()} {$module->getVersion()}";
-            StaticLogger::log(LogLevel::WARNING, $message);
-            // NOTE: Vielleicht neue class ModuleException hinzufügen
-            throw new RuntimeException($message);
-        }
-
-        return $reloadedModule;
-    }
-
-    public function revertChanges(Module $module): void
-    {
-        if (!$module->isInstalled()) {
-            $message =
-                "Can not revert changes because {$module->getArchiveName()} {$module->getVersion()} is not installed.";
-            StaticLogger::log(LogLevel::WARNING, $message);
-            // NOTE: Vielleicht neue class ModuleException hinzufügen
-            throw new RuntimeException($message);
-        }
-
-        $this->internalInstall($module);
-    }
-
-    private function createAutoloadFile(): void
-    {
-        $this->localModuleLoader->resetCache();
-        $localModules = $this->localModuleLoader->loadAllVersions();
-        $installedLocalModules = $this->moduleFilter->filterInstalled($localModules);
-
-        $namespaceEntrys = [];
-        foreach ($installedLocalModules as $module) {
-            $autoload = $module->getAutoload();
-
-            if (!$autoload) {
-                continue;
-            }
-
-            if (!$autoload['psr-4']) {
-                continue;
-            }
-
-            foreach ($autoload['psr-4'] as $namespace => $path) {
-                $path = str_replace($module->getSourceMmlcDir(), 'vendor-mmlc/' . $module->getArchiveName(), $path);
-                $namespaceEntrys[] =
-                    '$loader->setPsr4(\'' . $namespace . '\\\', DIR_FS_DOCUMENT_ROOT . \'' . $path . '\');';
-            }
-        }
-
-        $namespaceEntrys = array_unique($namespaceEntrys);
-        $namespaceMapping = implode("\n", $namespaceEntrys);
-
-        $template = \file_get_contents(App::getTemplatesRoot() . '/autoload.php.tmpl');
-        $template = \str_replace('{VENDOR_PSR4_NAMESPACE_MAPPINGS}', $namespaceMapping, $template);
-
-        if (!file_exists(App::getShopRoot() . '/vendor-no-composer')) {
-            mkdir(App::getShopRoot() . '/vendor-no-composer');
-        }
-        \file_put_contents(App::getShopRoot() . '/vendor-no-composer/autoload.php', $template);
-
-        if (!file_exists(App::getShopRoot() . '/vendor-mmlc')) {
-            mkdir(App::getShopRoot() . '/vendor-mmlc');
-        }
-        \file_put_contents(App::getShopRoot() . '/vendor-mmlc/autoload.php', $template);
-    }
-
-    public function uninstall(Module $module): bool
-    {
-        $installedModule = $module->getInstalledVersion();
-        if (!$installedModule) {
-            return false;
-        }
-
-        if ($installedModule->isChanged()) {
-            $message =
-                "Can not uninstall module {$installedModule->getArchiveName()} {$installedModule->getVersion()} "
-                . "because the module has changes.";
-            StaticLogger::log(LogLevel::WARNING, $message);
-            // NOTE: Vielleicht neue class ModuleException hinzufügen
-            throw new RuntimeException($message);
-        }
-
-        $this->internalUninstall($installedModule);
-        $this->createAutoloadFile();
-
-        return true;
-    }
-
-    private function internalUninstall(Module $module): void
-    {
-        // Uninstall from shop-root
-        $files = $module->getSrcFilePaths();
-        foreach ($files as $file) {
-            $file = ModulePathMapper::moduleSrcToShopRoot($file);
-            $dest = App::getShopRoot() . $file;
-            $this->uninstallFile($dest);
-        }
-
-        // Uninstall from shop-vendor-mmlc
-        $files = $module->getSrcMmlcFilePaths();
-        foreach ($files as $file) {
-            $file = ModulePathMapper::moduleSrcMmlcToShopVendorMmlc($file, $module->getArchiveName());
-            $dest = App::getShopRoot() . $file;
-            $this->uninstallFile($dest);
-            FileHelper::deletePathIsEmpty($dest);
-        }
-
-        if (file_exists($module->getHashPath())) {
-            unlink($module->getHashPath());
-        }
-    }
-
-    private function installFile(string $src, string $dest, bool $overwrite = false): bool
-    {
-        if (!file_exists($src)) {
-            return false;
-        }
-
-        if ($overwrite == false && (file_exists($dest) || is_link($dest))) {
-            return false;
-        } elseif ($overwrite == true && (file_exists($dest) || is_link($dest))) {
-            unlink($dest);
-        }
-
-        FileHelper::makeDirIfNotExists($dest);
-
-        if (file_exists($dest) || is_link($dest)) {
-            return false;
-        }
-
-        if (Config::getInstallMode() == 'link') {
-            symlink($src, $dest);
-        } else {
-            copy($src, $dest);
-        }
-
-        return true;
-    }
-
-    private function uninstallFile(string $dest): void
-    {
-        if (file_exists($dest)) {
-            unlink($dest);
-        }
-    }
-}
diff --git a/src/Classes/ModuleManager/AutoloadFileCreator.php b/src/Classes/ModuleManager/AutoloadFileCreator.php
new file mode 100644
index 00000000..c0ab1952
--- /dev/null
+++ b/src/Classes/ModuleManager/AutoloadFileCreator.php
@@ -0,0 +1,84 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\ModuleManager;
+
+use RobinTheHood\ModifiedModuleLoaderClient\App;
+use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader;
+
+class AutoloadFileCreator
+{
+    // TODO: In createAutoloadFile() Exceptions werfen im Fehlerfall
+    public function createAutoloadFile(): void
+    {
+        $installedLocalModules = $this->getInstalledModules();
+        $autoloadFileContent = $this->buildAutoloadFile($installedLocalModules);
+        $this->writeAutoloadFile($autoloadFileContent);
+    }
+
+    private function getInstalledModules()
+    {
+        $localModuleLoader = LocalModuleLoader::createFromConfig();
+        $localModuleLoader->resetCache();
+        $installedModules = $localModuleLoader->loadAllInstalledVersions();
+        return $installedModules;
+    }
+
+    private function buildAutoloadFile(array $installedModules): string
+    {
+        $namespaceEntrys = [];
+        foreach ($installedModules as $installedModule) {
+            $autoload = $installedModule->getAutoload();
+
+            if (!$autoload) {
+                continue;
+            }
+
+            if (!$autoload['psr-4']) {
+                continue;
+            }
+
+            foreach ($autoload['psr-4'] as $namespace => $path) {
+                $path = str_replace(
+                    $installedModule->getSourceMmlcDir(),
+                    'vendor-mmlc/' . $installedModule->getArchiveName(),
+                    $path
+                );
+
+                $namespaceEntrys[] =
+                    '$loader->setPsr4(\'' . $namespace . '\\\', DIR_FS_DOCUMENT_ROOT . \'' . $path . '\');';
+            }
+        }
+
+        $namespaceEntrys = array_unique($namespaceEntrys);
+        $namespaceMapping = implode("\n", $namespaceEntrys);
+
+        $template = file_get_contents(App::getTemplatesRoot() . '/autoload.php.tmpl');
+        $autoloadFileContent = str_replace('{VENDOR_PSR4_NAMESPACE_MAPPINGS}', $namespaceMapping, $template);
+
+        return $autoloadFileContent;
+    }
+
+    private function writeAutoloadFile(string $autoloadFileContent): void
+    {
+        if (!file_exists(App::getShopRoot() . '/vendor-no-composer')) {
+            mkdir(App::getShopRoot() . '/vendor-no-composer');
+        }
+        file_put_contents(App::getShopRoot() . '/vendor-no-composer/autoload.php', $autoloadFileContent);
+
+        if (!file_exists(App::getShopRoot() . '/vendor-mmlc')) {
+            mkdir(App::getShopRoot() . '/vendor-mmlc');
+        }
+        file_put_contents(App::getShopRoot() . '/vendor-mmlc/autoload.php', $autoloadFileContent);
+    }
+}
diff --git a/src/Classes/ModuleManager/ModuleFileInstaller.php b/src/Classes/ModuleManager/ModuleFileInstaller.php
new file mode 100644
index 00000000..b19a2ec5
--- /dev/null
+++ b/src/Classes/ModuleManager/ModuleFileInstaller.php
@@ -0,0 +1,187 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\ModuleManager;
+
+use RobinTheHood\ModifiedModuleLoaderClient\App;
+use RobinTheHood\ModifiedModuleLoaderClient\Config;
+use RobinTheHood\ModifiedModuleLoaderClient\FileInfo;
+use RobinTheHood\ModifiedModuleLoaderClient\Helpers\FileHelper;
+use RobinTheHood\ModifiedModuleLoaderClient\Module;
+use RobinTheHood\ModifiedModuleLoaderClient\ModuleHasher\ModuleHashFileCreator;
+use RobinTheHood\ModifiedModuleLoaderClient\ModulePathMapper;
+use RuntimeException;
+
+class ModuleFileInstaller
+{
+    /**
+     * (Re-) Installiert / überschreibt ein Modul (archiveName, Version) ohne dabei auf Abhängigkeiten und den
+     * Modulstatus zu achten. Es wird nur auf Dateiebene kontrolliert, ob alle Dateien geschrieben werden konnten.
+     * Erzeugt die modulehash.json. Die Autoload Datei wird NICHT erzeugt / erneuert.
+     */
+    public function install(Module $module, bool $overwriteTemplateFiles = false): void
+    {
+        $this->installFiles($module, $overwriteTemplateFiles);
+        $this->createHashFile($module);
+    }
+
+    /**
+     * Deinstalliert / entfernt ein Modul (archiveName, Version) ohne dabei auf Abhängigkeiten und den Modulstatus zu
+     * achten. Es wird nur auf Dateiebene kontrolliert, ob alles Datien entfernt werden konnten. Die Autoload Datei
+     * wird NICHT aktualisiert.
+     */
+    public function uninstall(Module $module): void
+    {
+        $this->uninstallFiles($module);
+        $this->removeHashFile($module);
+    }
+
+    /**
+     * (Re-) Installiert / Überschreibt nur die Datei zu einem Modul (archiveName, Version). Es wird nur auf Datei-Ebene
+     * kontrolliert, ob alle Dateien geschrieben werden konnten. Die `modulehash.json` Datei wird NICHT erzeugt /
+     * erneuert.
+     */
+    private function installFiles(Module $module, bool $overwriteTemplateFiles): void
+    {
+        // Install Source Files to Shop Root
+        $files = $module->getSrcFilePaths();
+
+        foreach ($files as $file) {
+            $src = $module->getLocalRootPath() . $module->getSrcRootPath() . '/' . $file;
+
+            $expandedFiles = $module->getTemplateFiles($file);
+            foreach ($expandedFiles as $expandedFile) {
+                $overwrite = false;
+
+                if ($overwriteTemplateFiles === true) {
+                    $overwrite = true;
+                }
+
+                if (!FileInfo::isTemplateFile($expandedFile)) {
+                    $overwrite = true;
+                }
+
+                $expandedFile = ModulePathMapper::moduleSrcToShopRoot($expandedFile);
+
+                $dest = App::getShopRoot() . $expandedFile;
+                $this->installFile($src, $dest, $overwrite);
+            }
+        }
+
+        // Install Source Mmlc Files to shop vendor-mmlc
+        $files = $module->getSrcMmlcFilePaths();
+        foreach ($files as $file) {
+            $src = $module->getLocalRootPath() . $module->getSrcMmlcRootPath() . '/' . $file;
+            $file = ModulePathMapper::moduleSrcMmlcToShopVendorMmlc($file, $module->getArchiveName());
+            $dest = App::getShopRoot() . '/' . $file;
+            $this->installFile($src, $dest, true);
+        }
+    }
+
+    private function installFile(string $src, string $dest, bool $overwrite = false): bool
+    {
+        if (!file_exists($src)) {
+            throw new RuntimeException("Can not install file $src - File not exists.");
+        }
+
+        if ($this->fileOrLinkExists($dest) && $overwrite === false) {
+            // Die Datei existiert bereits und soll NICHT überschrieben werden.
+            return false;
+        } elseif ($this->fileOrLinkExists($dest) && $overwrite === true) {
+            // Die Datei existiert bereits soll überschrieben.
+            // Wir löschen die Datei zuerst, bevor wir sie überschreiben.
+            $this->removeFile($dest);
+        }
+
+        FileHelper::makeDirIfNotExists($dest);
+
+        // TODO: Kontrollieren ob hier eine Exception geworfen werden muss, wenn die Datei existiert.
+        if ($this->fileOrLinkExists($dest)) {
+            return false;
+        }
+
+        $this->copyFile($src, $dest);
+
+        return true;
+    }
+
+    private function copyFile(string $srcPath, string $destPath): void
+    {
+        if (Config::getInstallMode() === 'link') {
+            $result = symlink($srcPath, $destPath);
+        } else {
+            $result = copy($srcPath, $destPath);
+        }
+
+        if (!$result) {
+            throw new RuntimeException("Can not copy file $srcPath to $destPath");
+        }
+    }
+
+    /**
+     * Erzeugt / Überschreibt die `modulehash.json zu einem Modul (archive, Version)` Es wird nur auf Datei-Ebene
+     * kontrolliert, ob alle Dateien geschrieben werden konnten.
+     */
+    private function createHashFile(Module $module): void
+    {
+        $moduleHashFileCreator = new ModuleHashFileCreator();
+        $moduleHashFile = $moduleHashFileCreator->createHashFile($module);
+        $moduleHashFile->writeTo($module->getHashPath());
+    }
+
+    private function uninstallFiles(Module $module): void
+    {
+        // Uninstall from shop-root
+        $files = $module->getSrcFilePaths();
+        foreach ($files as $file) {
+            $file = ModulePathMapper::moduleSrcToShopRoot($file);
+            $dest = App::getShopRoot() . $file;
+            $this->removeIfFileExists($dest);
+        }
+
+        // Uninstall from shop-vendor-mmlc
+        $files = $module->getSrcMmlcFilePaths();
+        foreach ($files as $file) {
+            $file = ModulePathMapper::moduleSrcMmlcToShopVendorMmlc($file, $module->getArchiveName());
+            $dest = App::getShopRoot() . $file;
+            $this->removeIfFileExists($dest);
+            FileHelper::deletePathIsEmpty($dest);
+        }
+    }
+
+    private function fileOrLinkExists(string $path): bool
+    {
+        return file_exists($path) || is_link($path);
+    }
+
+    private function removeIfFileExists(string $path): void
+    {
+        if ($this->fileOrLinkExists($path)) {
+            $this->removeFile($path);
+        }
+    }
+
+    private function removeFile(string $path): void
+    {
+        $result = unlink($path);
+        if (!$result) {
+            throw new RuntimeException("Can not remove file $path");
+        }
+    }
+
+    private function removeHashFile(Module $module): void
+    {
+        $path = $module->getHashPath();
+        $this->removeIfFileExists($path);
+    }
+}
diff --git a/src/Classes/ModuleManager/ModuleInstaller.php b/src/Classes/ModuleManager/ModuleInstaller.php
new file mode 100644
index 00000000..3346d0aa
--- /dev/null
+++ b/src/Classes/ModuleManager/ModuleInstaller.php
@@ -0,0 +1,495 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\ModuleManager;
+
+use RuntimeException;
+use RobinTheHood\ModifiedModuleLoaderClient\Config;
+use RobinTheHood\ModifiedModuleLoaderClient\Module;
+use RobinTheHood\ModifiedModuleLoaderClient\Api\V1\ApiRequest;
+use RobinTheHood\ModifiedModuleLoaderClient\Helpers\FileHelper;
+use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader;
+use RobinTheHood\ModifiedModuleLoaderClient\Archive\ArchivePuller;
+use RobinTheHood\ModifiedModuleLoaderClient\Archive\ArchiveHandler;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\Combination;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyManager;
+
+class ModuleInstaller
+{
+    /** @var DependencyManager */
+    private $dependencyManager;
+
+    /** @var LocalModuleLoader */
+    private $localModuleLoader;
+
+    /** @var ArchivePuller */
+    private $archivePuller;
+
+    /** @var ArchiveHandler */
+    private $archiveHandler;
+
+    public static function create(int $mode): ModuleInstaller
+    {
+        $dependencyManager = DependencyManager::create($mode);
+        $localModuleLoader = LocalModuleLoader::create($mode);
+        $archivePuller = ArchivePuller::create();
+        $archiveHandler = ArchiveHandler::create($mode);
+        $moduleInstaller = new ModuleInstaller(
+            $dependencyManager,
+            $localModuleLoader,
+            $archivePuller,
+            $archiveHandler
+        );
+        return $moduleInstaller;
+    }
+
+    public static function createFromConfig(): ModuleInstaller
+    {
+        return self::create(Config::getDependenyMode());
+    }
+
+    public function __construct(
+        DependencyManager $dependencyManager,
+        LocalModuleLoader $localModuleLoader,
+        ArchivePuller $archivePuller,
+        ArchiveHandler $archiveHandler
+    ) {
+        $this->dependencyManager = $dependencyManager;
+        $this->localModuleLoader = $localModuleLoader;
+        $this->archivePuller = $archivePuller;
+        $this->archiveHandler = $archiveHandler;
+    }
+
+    /**
+     * Downloads and prepares a module for installation.
+     *
+     * This method is responsible for downloading a module's archive, extracting its contents, and preparing it for
+     * installation. It performs several checks to ensure the module is not already loaded and retrieves the archive URL
+     * using the specified Module object. Upon successful execution, the method returns the Module instance representing
+     * the downloaded and prepared module.
+     *
+     * @param Module $module The Module to be pulled and prepared for installation.
+     *
+     * @return Module The Module instance representing the downloaded and prepared module.
+     *
+     * @throws RuntimeException
+     *      If the module is already loaded or if any errors occur during the download, extraction, or preparation
+     *      process, a RuntimeException is thrown with a detailed error message.
+     */
+    public function pull(Module $module): Module
+    {
+        $moduleText = "module {$module->getArchiveName()} {$module->getVersion()}";
+
+        if ($module->isLoaded()) {
+            $this->error("Can not pull $moduleText. Modul is already loaded.");
+        }
+
+        // Retrieve the module's archive URL
+        $archiveUrl = $this->getArchiveUrl($module);
+
+        // Download and extract the module's archive
+        $archive = $this->archivePuller->pull($module->getArchiveName(), $module->getVersion(), $archiveUrl);
+        $this->archiveHandler->extract($archive);
+
+        // Reload and return the Module instance
+        $pulledModule = $this->reload($module);
+        return $pulledModule;
+    }
+
+    /**
+     * Deletes a loaded module's files.
+     *
+     * This method is responsible for deleting the files associated with a loaded module. It checks whether the module
+     * is loaded and, if required, verifies whether it's uninstalled before proceeding with the deletion. If the 'force'
+     * parameter is set to true, the method will skip the installation check and forcefully delete the module. It then
+     * calls the 'deleteModuleFiles' method to remove the module's files from the system.
+     *
+     * @param Module $module The loaded module to be deleted.
+     * @param bool $force Set to true to force the deletion even if the module is installed.
+     *
+     * @throws RuntimeException
+     *      If the module is not loaded or is installed (and 'force' is false), a RuntimeException is thrown with a
+     *      detailed error message. Any errors encountered during the deletion process are also reported via exceptions.
+     */
+    public function delete(Module $module, bool $force = false): void
+    {
+        $moduleText = "module {$module->getArchiveName()} {$module->getVersion()}";
+
+        if (!$module->isLoaded()) {
+            $this->error("Can not delete $moduleText. Module is not loaded.");
+        }
+
+        if (!$force && $module->isInstalled()) {
+            $this->error("Can not delete $moduleText. Module is installed.");
+        }
+
+        $this->deleteModuleFiles($module);
+    }
+
+    /**
+     * Installs a module and its dependencies.
+     *
+     * This method is responsible for installing a module along with its dependencies into the shop system. It checks
+     * whether the module is already installed (unless 'force' is set to true) and ensures that a valid combination of
+     * versions for the module's dependencies can be found. If the installation is successful, it proceeds to install
+     * its dependencies. The method offers the flexibility to force the installation of the module even if it's already
+     * installed and skip the dependency check. Any errors encountered during the installation process are reported via
+     * exceptions.
+     *
+     * @param Module $module The module to be installed.
+     * @param bool $force Set to true to force the installation even if the module is already installed.
+     *
+     * @throws RuntimeException
+     *      If the module is already installed (and 'force' is false) or if no valid combination of versions for the
+     *      dependencies can be found, a RuntimeException is thrown with detailed error messages. Any other errors
+     *      during the installation process are also reported via exceptions.
+     */
+    public function install(Module $module, bool $force = false): void
+    {
+        $moduleText = "module {$module->getArchiveName()} {$module->getVersion()}";
+
+        $installedModule = $module->getInstalledVersion();
+        if (!$force && $installedModule) {
+            $installedModuleText = "module {$installedModule->getArchiveName()} {$installedModule->getVersion()}";
+            $this->error("Can not install $moduleText, because $installedModuleText is already installed.");
+        }
+
+        $combinationSatisfyerResult = $this->dependencyManager->canBeInstalled($module);
+
+        if (!$combinationSatisfyerResult->foundCombination) {
+            $this->error("Can not install $moduleText with dependencies. No possible combination of versions found");
+        }
+
+        $this->installWithoutDependencies($module, true, true);
+        $this->installDependencies($module, $combinationSatisfyerResult->foundCombination);
+    }
+
+    /**
+     * Install a module without installing its dependencies.
+     *
+     * This method is responsible for installing a module into the shop system without installing its dependencies. It
+     * checks whether the module is already installed (unless 'force' is set to true) and optionally performs a
+     * dependency check to ensure that a valid combination of versions for the module can be found (unless
+     * 'skipDependencyCheck' is set to true). Any errors during the installation process are reported via exceptions.
+     *
+     * @param Module $module
+     *      The module to be installed.
+     * @param bool $skipDependencyCheck
+     *      Set to true to skip the dependency check (default is false).
+     * @param bool $force
+     *      Set to true to force the installation even if the module is already installed (default is false).
+     *
+     * @throws RuntimeException
+     *      If the module is already installed (and 'force' is false) or if no valid combination of versions for the
+     *      module's dependencies can be found (and 'skipDependencyCheck' is false), a RuntimeException is thrown with
+     *      detailed error messages. Any other errors during the installation process are also reported via exceptions.
+     */
+    public function installWithoutDependencies(
+        Module $module,
+        bool $skipDependencyCheck = false,
+        bool $force = false
+    ): void {
+        $moduleText = "module {$module->getArchiveName()} {$module->getVersion()}";
+
+        if (!$force && $module->isInstalled()) {
+            $this->error("Can not install $moduleText. Module is already installed.");
+        }
+
+        if (!$skipDependencyCheck) {
+            $combinationSatisfyerResult = $this->dependencyManager->canBeInstalled($module);
+
+            if (!$combinationSatisfyerResult->foundCombination) {
+                $this->error("Can not update $moduleText. No possible combination of versions found");
+            }
+        }
+
+        $moduleFileInstaller = new ModuleFileInstaller();
+        $moduleFileInstaller->install($module);
+        $this->reload($module);
+    }
+
+    /**
+     * Update a module to its newest version while potentially updating its dependencies.
+     *
+     * This method is responsible for updating a module to its newest version. It verifies whether the module is
+     * installed (unless 'force' is set to true) and ensures that a valid combination of versions for the module's
+     * dependencies can be found. The newest version is pulled and installed, and its dependencies are also installed as
+     * necessary. Any errors during the update process are reported via exceptions, and the loaded new module is
+     * returned.
+     *
+     * @param Module $module The module to be updated.
+     * @param bool $force Set to true to force the update even if the module is not installed (default is false).
+     *
+     * @throws RuntimeException
+     *      If the module is not installed (and 'force' is false) or if no valid combination of versions for the
+     *      module's dependencies can be found, a RuntimeException is thrown with detailed error messages. Any other
+     *      errors during the update process are also reported via exceptions.
+     */
+    public function update(Module $installedModule, Module $newModule, bool $force = false): void
+    {
+        $moduleText = "module {$installedModule->getArchiveName()} {$installedModule->getVersion()}";
+
+        if (!$force && !$installedModule->isInstalled()) {
+            $this->error("Can not update $moduleText. Module is not installed.");
+        }
+
+        $moduleText = "module {$installedModule->getArchiveName()} {$installedModule->getVersion()}";
+        $newModuleText = "module {$newModule->getArchiveName()} {$newModule->getVersion()}";
+
+        if ($installedModule->getVersion() === $newModule->getVersion()) {
+            $this->error("Can not update $moduleText to $newModuleText.");
+        }
+
+        $combinationSatisfyerResult = $this->dependencyManager->canBeInstalled($newModule);
+        $foundCombination = $combinationSatisfyerResult->foundCombination;
+
+        if (!$foundCombination) {
+            $this->error(
+                "Can not update $moduleText to $newModuleText."
+                . " No possible combination of versions found"
+            );
+        }
+
+        if ($newModule->isLoaded()) {
+            $loadedNewModule = $newModule;
+        } else {
+            $loadedNewModule = $this->pull($newModule);
+        }
+
+        $this->uninstall($installedModule);
+        // Modul installieren
+        $this->installWithoutDependencies($loadedNewModule);
+        // Modul Abhängigkeiten installieren
+        $this->installDependencies($loadedNewModule, $foundCombination);
+    }
+
+    /**
+     * //TODO: Nicht zur Neusten sondern zu höchst möglichsten Version aktualisieren.
+     */
+    public function updateWithoutMissingDependencies(
+        Module $instaledModule,
+        Module $newModule,
+        bool $skipDependencyCheck = false,
+        bool $force = false
+    ): Module {
+        $moduleText = "module {$instaledModule->getArchiveName()} {$instaledModule->getVersion()}";
+
+        if (!$force && !$instaledModule->isInstalled()) {
+            $this->error("Can not update $moduleText. Module is not installed.");
+        }
+
+        $moduleText = "module {$instaledModule->getArchiveName()} {$instaledModule->getVersion()}";
+        $newModuleText = "module {$newModule->getArchiveName()} {$newModule->getVersion()}";
+
+        if (!$skipDependencyCheck) {
+            $combinationSatisfyerResult = $this->dependencyManager->canBeInstalled($newModule);
+
+            if (!$combinationSatisfyerResult->foundCombination) {
+                $this->error(
+                    "Can not update $moduleText to $newModuleText."
+                    . " No possible combination of versions found"
+                );
+            }
+        }
+
+        if ($newModule->isLoaded()) {
+            $loadedNewModule = $newModule;
+        } else {
+            $loadedNewModule = $this->pull($newModule);
+        }
+
+        $this->uninstall($instaledModule);
+        $this->installWithoutDependencies($loadedNewModule);
+
+        return $loadedNewModule;
+    }
+
+    public function discard(Module $module, bool $withTemplate, bool $force): void
+    {
+        $moduleText = "module {$module->getArchiveName()} {$module->getVersion()}";
+
+        if (!$force && !$module->isInstalled()) {
+            $this->error("Can not revert changes because $moduleText is not installed.");
+        }
+
+        $moduleFileInstaller = new ModuleFileInstaller();
+        $moduleFileInstaller->install($module, $withTemplate);
+    }
+
+    public function uninstall(Module $module, bool $force = false): void
+    {
+        $moduleText = "module {$module->getArchiveName()}";
+
+        if (!$module->isInstalled()) {
+            $this->error("Can not uninstall $moduleText because module is not installed.");
+        }
+
+        $moduleText = "module {$module->getArchiveName()} {$module->getVersion()}";
+
+
+        if ($module->isChanged() && $force === false) {
+            $this->error("Can not uninstall $moduleText because the module has changes.");
+        }
+
+        $moduleFileInstaller = new ModuleFileInstaller();
+        $moduleFileInstaller->uninstall($module);
+
+        $this->reload($module);
+    }
+
+    /**
+     * Installs the dependencies specified in the given Combination for a parent Module.
+     *
+     * This method is used internally to install the dependencies specified in a provided Combination for a parent
+     * Module. It retrieves the required modules from the Combination and iterates through them, checking for
+     * compatibility and installing each one. The parent Module is excluded from the installation process.
+     *
+     * @param Module $parentModule The parent Module for which dependencies need to be installed.
+     * @param Combination $combination The Combination specifying the dependencies to be installed.
+     *
+     * @throws \RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyException
+     *      If any of the dependencies cannot be installed due to conflicting versions or other issues,
+     *      a DependencyException may be thrown with details.
+     */
+    private function installDependencies(Module $parentModule, Combination $combination): void
+    {
+        $modules = $this->dependencyManager->getAllModulesFromCombination($combination);
+
+        foreach ($modules as $module) {
+            if ($parentModule->getArchiveName() === $module->getArchiveName()) {
+                continue;
+            }
+            $this->pullAndInstallWithoutDependencies($module);
+        }
+    }
+
+    /**
+     * Loads and installs a module.
+     *
+     * This method is responsible for loading and installing a module specified by the provided Module object. It checks
+     * whether the module is already installed, and if not, it attempts to load and install it. The method also handles
+     * potential errors during the loading and installation process.
+     *
+     * @param Module $module The Module to be loaded and installed.
+     *
+     * @throws RuntimeException
+     *      If the module cannot be loaded or installed successfully, a RuntimeException is thrown with a detailed
+     *      error message.
+     */
+    private function pullAndInstallWithoutDependencies(Module $module): void
+    {
+        if ($module->isInstalled()) {
+            return;
+        }
+
+        if ($module->isLoaded()) {
+            $pulledModule = $module;
+        } else {
+            $pulledModule = $this->pull($module);
+        }
+
+        $this->installWithoutDependencies($pulledModule);
+    }
+
+    /**
+     * Retrieves the archive URL for a given module.
+     *
+     * This method is responsible for obtaining the archive URL for a specific module by making an API request. It
+     * constructs the URL to download the module's archive, handles potential errors in the API response, and returns
+     * the archive URL.
+     *
+     * @param Module $module The Module for which the archive URL should be retrieved.
+     *
+     * @return string The URL to download the module's archive.
+     *
+     * @throws \Exception
+     *      If the API response is empty or lacks the necessary information to construct the archive URL, an Exception
+     *      is thrown with a detailed error message.
+     */
+    private function getArchiveUrl(Module $module): string
+    {
+        $moduleText = "module {$module->getArchiveName()} {$module->getVersion()}";
+
+        $apiRequest = new ApiRequest();
+        $result = $apiRequest->getArchive($module->getArchiveName(), $module->getVersion());
+
+        $content = $result['content'] ?? [];
+        if (!$content) {
+            $this->error("Can not pull $moduleText. ApiRespond is empty.");
+        }
+
+        $archiveUrl = $content['archiveUrl'] ?? '';
+        if (!$archiveUrl) {
+            $this->error("Can not pull $moduleText. archiveUrl is empty.");
+        }
+
+        return $archiveUrl;
+    }
+
+    /**
+     * Löscht alle Module Dateien aus dem Verzeichnis Modules. Es wird nicht kontrolliert ob das Modul geladen oder
+     * installiert ist.
+     */
+    private function deleteModuleFiles(Module $module): void
+    {
+        $path = $module->getLocalRootPath() . $module->getModulePath();
+
+        $filePaths = FileHelper::scanDirRecursive($path, FileHelper::FILES_ONLY);
+
+        $dirPaths = FileHelper::scanDirRecursive($path, FileHelper::DIRS_ONLY);
+        $dirPaths = array_reverse($dirPaths);
+        $dirPaths[] = $path;
+        $dirPaths[] = dirname($path);
+
+        // Delete Files
+        foreach ($filePaths as $path) {
+            if (file_exists($path)) {
+                unlink($path);
+            }
+        }
+
+        // Delete Folders
+        foreach ($dirPaths as $path) {
+            if (file_exists($path)) {
+                @rmdir($path);
+            }
+        }
+    }
+
+    private function reload(Module $module): Module
+    {
+        $moduleText = "module {$module->getArchiveName()} {$module->getVersion()}";
+
+        $this->localModuleLoader->resetCache();
+        $reloadedModule = $this->localModuleLoader->loadByArchiveNameAndVersion(
+            $module->getArchiveName(),
+            $module->getVersion()
+        );
+
+        if (!$reloadedModule) {
+            $this->error("Can not reload $moduleText.");
+        }
+
+        return $reloadedModule;
+    }
+
+    /**
+     * // NOTE: Vielleicht neue class ModuleInstallerException hinzufügen
+     * @return never
+     */
+    private function error(string $message): void
+    {
+        // StaticLogger::log(LogLevel::WARNING, $message);
+        throw new RuntimeException($message);
+    }
+}
diff --git a/src/Classes/ModuleManager/ModuleManager.php b/src/Classes/ModuleManager/ModuleManager.php
new file mode 100644
index 00000000..f6557ced
--- /dev/null
+++ b/src/Classes/ModuleManager/ModuleManager.php
@@ -0,0 +1,519 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\ModuleManager;
+
+use RobinTheHood\ModifiedModuleLoaderClient\Config;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\CombinationSatisfyerResult;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\DependencyBuilder;
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\SystemSetFactory;
+use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader;
+use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader;
+
+class ModuleManager
+{
+    /** @var ModuleInstaller */
+    private $moduleInstaller;
+
+    /** @var ModuleLoader */
+    private $moduleLoader;
+
+    /** @var LocalModuleLoader */
+    private $localModuleLoader;
+
+    /** @var DependencyBuilder */
+    private $dependencyBuilder;
+
+    /** @var SystemSetFactory */
+    private $systemSetFactory;
+
+    /** @var ModuleManagerLoggerInterface */
+    private $logger;
+
+    public static function create(int $mode): ModuleManager
+    {
+        $moduleInstaller = ModuleInstaller::create($mode);
+        $moduleLoader = ModuleLoader::create($mode);
+        $localModuleLoader = LocalModuleLoader::create($mode);
+        $dependencyBuilder = DependencyBuilder::create($mode);
+        $systemSetFactory = SystemSetFactory::create($mode);
+
+        $moduleInstaller = new ModuleManager(
+            $moduleInstaller,
+            $moduleLoader,
+            $localModuleLoader,
+            $dependencyBuilder,
+            $systemSetFactory
+        );
+
+        return $moduleInstaller;
+    }
+
+    public static function createFromConfig(): ModuleManager
+    {
+        return self::create(Config::getDependenyMode());
+    }
+
+    public function __construct(
+        ModuleInstaller $moduleInstaller,
+        ModuleLoader $moduleLoader,
+        LocalModuleLoader $localModuleLoader,
+        DependencyBuilder $dependencyBuilder,
+        SystemSetFactory $systemSetFactory
+    ) {
+        $this->moduleInstaller = $moduleInstaller;
+        $this->moduleLoader = $moduleLoader;
+        $this->localModuleLoader = $localModuleLoader;
+        $this->dependencyBuilder = $dependencyBuilder;
+        $this->systemSetFactory = $systemSetFactory;
+
+        $this->logger = new ModuleManagerNullLogger();
+    }
+
+    public function setLogger(ModuleManagerLoggerInterface $logger): void
+    {
+        $this->logger = $logger;
+    }
+
+    private function error(ModuleManagerMessage $message): ModuleManagerResult
+    {
+        $this->logger->error($message);
+        return ModuleManagerResult::error($message);
+    }
+
+    private function info(ModuleManagerMessage $message): void
+    {
+        $this->logger->info($message);
+    }
+
+    /**
+     * Lädt ein Modul vom Server herunter.
+     */
+    public function pull(string $archiveName, string $versionConstraint): ModuleManagerResult
+    {
+        if ($versionConstraint) {
+            $module = $this->moduleLoader->loadLatestByArchiveNameAndConstraint($archiveName, $versionConstraint);
+        } else {
+            $module = $this->moduleLoader->loadLatestVersionByArchiveName($archiveName);
+        }
+
+        if (!$module) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::PULL_ERROR_MODULE_NOT_FOUND)
+                ->setArchiveName($archiveName)
+                ->setVersionConstraint($versionConstraint)
+            );
+        }
+
+        if ($module->isLoaded()) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::PULL_ERROR_MODULE_ALLREADY_LOADED)
+                ->setArchiveName($archiveName)
+                ->setVersionConstraint($versionConstraint)
+            );
+        }
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::PULL_INFO_START)
+            ->setModule($module)
+        );
+
+        $module = $this->moduleInstaller->pull($module);
+        return ModuleManagerResult::success()
+            ->setModule($module);
+    }
+
+    /**
+     * Löscht ein Modul das bereits heruntergeladen wurde.
+     */
+    public function delete(string $archiveName, string $version): ModuleManagerResult
+    {
+        $module = $this->localModuleLoader->loadByArchiveNameAndVersion($archiveName, $version);
+
+        if (!$module) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::DELETE_ERROR_MODULE_NOT_FOUND)
+                ->setArchiveName($archiveName)
+                ->setVersion($version)
+            );
+        }
+
+        if ($module->isInstalled()) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::DELETE_ERROR_MODULE_IS_INSTALED)
+                ->setModule($module)
+            );
+        }
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::DELETE_INFO_START)
+            ->setModule($module)
+        );
+
+        $this->moduleInstaller->delete($module, false);
+        return ModuleManagerResult::success();
+    }
+
+    /**
+     * Lädt und installiert ein Modul in das Shop System UND lädt und installiert alle Abhängigkeiten bzw.
+     * abhängige Module nach.
+     */
+    public function install(string $archiveName, $versionConstraint): ModuleManagerResult
+    {
+        $moduleLoader = ModuleLoader::createFromConfig();
+        $module = $moduleLoader->loadLatestByArchiveNameAndConstraint($archiveName, $versionConstraint);
+
+        if (!$module) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_ERROR_MODULE_NOT_FOUND)
+                ->setArchiveName($archiveName)
+                ->setVersionConstraint($versionConstraint)
+            );
+        }
+
+        $systemSet = $this->systemSetFactory->getSystemSet();
+
+        $combinationSatisfyerResult = $this->dependencyBuilder->satisfies($archiveName, $versionConstraint, $systemSet);
+        if (
+            $combinationSatisfyerResult->result === CombinationSatisfyerResult::RESULT_COMBINATION_NOT_FOUND
+            || !$combinationSatisfyerResult->foundCombination
+        ) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_ERROR_MODULE_MISSING_REQUIREMENTS)
+                ->setArchiveName($archiveName)
+                ->setVersionConstraint($versionConstraint)
+                ->setCombinationSatisfyerResult($combinationSatisfyerResult)
+            );
+        }
+
+        $version = $combinationSatisfyerResult->foundCombination->getVersion($archiveName);
+
+        $module = $this->moduleLoader->loadByArchiveNameAndVersion($archiveName, $version);
+
+        if (!$module) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_ERROR_MODULE_NOT_FOUND)
+                ->setArchiveName($archiveName)
+                ->setVersionConstraint($versionConstraint)
+            );
+        }
+
+        if ($module->isInstalled()) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_ERROR_MODULE_ALLREADY_INSTALED)
+                ->setModule($module)
+            );
+        }
+
+        if (!$module->isLoaded()) {
+            $this->info(
+                ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_INFO_PULL_MODULE_START)
+                ->setModule($module)
+            );
+            $module = $this->moduleInstaller->pull($module);
+        }
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_INFO_START)
+            ->setModule($module)
+        );
+        $this->moduleInstaller->install($module);
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_INFO_UPDATE_AUTOLOAD_START)
+        );
+        $autoloadFileCreator = new AutoloadFileCreator();
+        $autoloadFileCreator->createAutoloadFile();
+
+        return ModuleManagerResult::success()
+            ->setModule($module);
+    }
+
+    /**
+     * Installiert ein Modul in das Shop System ABER lädt und installiert KEINE Abhängigkeiten / abhängige Module nach.
+     *
+     * @param bool $skipDependencyCheck skip dependency check.
+     */
+    public function installWithoutDependencies(
+        string $archiveName,
+        string $versionConstraint,
+        bool $skipDependencyCheck = false
+    ): ModuleManagerResult {
+        $moduleLoader = ModuleLoader::createFromConfig();
+        $module = $moduleLoader->loadLatestByArchiveNameAndConstraint($archiveName, $versionConstraint);
+
+        if (!$module) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_ERROR_MODULE_NOT_FOUND)
+                ->setArchiveName($archiveName)
+                ->setVersionConstraint($versionConstraint)
+            );
+        }
+
+        if ($skipDependencyCheck === false) {
+            $systemSet = $this->systemSetFactory->getSystemSet();
+
+            $combinationSatisfyerResult
+                = $this->dependencyBuilder->satisfies($archiveName, $versionConstraint, $systemSet);
+            if (
+                $combinationSatisfyerResult->result === CombinationSatisfyerResult::RESULT_COMBINATION_NOT_FOUND
+                || !$combinationSatisfyerResult->foundCombination
+            ) {
+                return $this->error(
+                    ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_ERROR_MODULE_MISSING_REQUIREMENTS)
+                    ->setArchiveName($archiveName)
+                    ->setVersionConstraint($versionConstraint)
+                    ->setCombinationSatisfyerResult($combinationSatisfyerResult)
+                );
+            }
+
+            $version = $combinationSatisfyerResult->foundCombination->getVersion($archiveName);
+
+            $module = $this->moduleLoader->loadByArchiveNameAndVersion($archiveName, $version);
+
+            if (!$module) {
+                return $this->error(
+                    ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_ERROR_MODULE_NOT_FOUND)
+                    ->setArchiveName($archiveName)
+                    ->setVersionConstraint($versionConstraint)
+                );
+            }
+        }
+
+        if ($module->isInstalled()) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_ERROR_MODULE_ALLREADY_INSTALED)
+                ->setModule($module)
+            );
+        }
+
+        if (!$module->isLoaded()) {
+            $this->info(
+                ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_INFO_PULL_MODULE_START)
+                ->setModule($module)
+            );
+            $module = $this->moduleInstaller->pull($module);
+        }
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_INFO_START)
+            ->setModule($module)
+        );
+        $this->moduleInstaller->installWithoutDependencies($module, true, true);
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::INSTALL_INFO_UPDATE_AUTOLOAD_START)
+        );
+        $autoloadFileCreator = new AutoloadFileCreator();
+        $autoloadFileCreator->createAutoloadFile();
+
+        return ModuleManagerResult::success()
+            ->setModule($module);
+    }
+
+    /**
+     * Aktuallisiert das Modul auf die neuste Version. Dabei werden keine Abhänggigkeiten
+     * aktualisiert. Kommen durch das Update jedoch neue Abhängigkeiten hinzu, werden diese installt. Können nicht alle
+     * Abhängigkeiten erfüllt werten, wird nicht aktualisiert und eine Exception geworfen.
+     */
+    public function update(string $archiveName): ModuleManagerResult
+    {
+        $moduleLoader = LocalModuleLoader::createFromConfig();
+        $module = $moduleLoader->loadInstalledVersionByArchiveName($archiveName);
+
+        if (!$module) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::UPDATE_ERROR_MODULE_NOT_FOUND)
+                ->setArchiveName($archiveName)
+            );
+        }
+
+        if (!$module->isInstalled()) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::UPDATE_ERROR_MODULE_NOT_INSTALLED)
+                ->setModule($module)
+            );
+        }
+
+        if ($module->isChanged()) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::UPDATE_ERROR_MODULE_IS_CHANGED)
+                ->setModule($module)
+            );
+        }
+
+        $systemSet = $this->systemSetFactory->getSystemSet();
+        $versionConstraint = '>' . $module->getVersion();
+        $combinationSatisfyerResult = $this->dependencyBuilder->satisfies($archiveName, $versionConstraint, $systemSet);
+        if (
+            $combinationSatisfyerResult->result === CombinationSatisfyerResult::RESULT_COMBINATION_NOT_FOUND
+            || !$combinationSatisfyerResult->foundCombination
+        ) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::UPDATE_ERROR_MODULE_MISSING_REQUIREMENTS)
+                ->setArchiveName($archiveName)
+                ->setVersionConstraint($versionConstraint)
+                ->setCombinationSatisfyerResult($combinationSatisfyerResult)
+            );
+        }
+
+        $version = $combinationSatisfyerResult->foundCombination->getVersion($archiveName);
+        $newModule = $this->moduleLoader->loadByArchiveNameAndVersion($archiveName, $version);
+
+        if (!$newModule) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::UPDATE_ERROR_MODULE_NOT_FOUND)
+                ->setArchiveName($archiveName)
+                ->setVersion($version)
+            );
+        }
+
+        if (!$newModule->isLoaded()) {
+            $this->info(
+                ModuleManagerMessage::create(ModuleManagerMessage::UDPATE_INFO_PULL_MODULE_START)
+                ->setModule($newModule)
+            );
+            $newModule = $this->moduleInstaller->pull($newModule);
+        }
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::UPDATE_INFO_START)
+            ->setModule($module)
+        );
+        $this->moduleInstaller->update($module, $newModule, false);
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::UPDATE_INFO_TO)
+            ->setModule($newModule)
+        );
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::UPDATE_INFO_UPDATE_AUTOLOAD_START)
+        );
+
+        $autoloadFileCreator = new AutoloadFileCreator();
+        $autoloadFileCreator->createAutoloadFile();
+
+        return ModuleManagerResult::success()
+            ->setModule($newModule);
+    }
+
+    /**
+     * Aktualiseirt NUR das Modul auf die neuste Version. Es werden keine fehlenden Abhänggigkeiten
+     * installiert. Es werden keine Abhänggigkeiten aktualisiert. Können nicht alle Abhängigkeiten erfüllt werten,
+     * wird nicht aktualisiert und eine Exception geworfen.
+     */
+    // public function updateWithoutMissingDependencies(string $archvieName, bool $skipDependencyCheck = false): Module
+    // {
+    //     $loadedNewModul = $this->moduleInstaller->updateWithoutMissingDependencies(
+    //         $module,
+    //         $skipDependencyCheck,
+    //         false
+    //     );
+
+    //     $autoloadFileCreator = new AutoloadFileCreator();
+    //     $autoloadFileCreator->createAutoloadFile();
+
+    //     return $loadedNewModul;
+    // }
+
+    /**
+     * Entfernt alle Änderungen die an den Modul-Dateien im Shop gemacht wurden. Änderungen an Template Dateien werden
+     * nicht rückgängig gemacht.
+     */
+    public function discard(string $archiveName, bool $withTemplate = false): ModuleManagerResult
+    {
+        $moduleLoader = LocalModuleLoader::createFromConfig();
+        $module = $moduleLoader->loadInstalledVersionByArchiveName($archiveName);
+
+        if (!$module) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::DISCARD_ERROR_MODULE_NOT_FOUND)
+                ->setArchiveName($archiveName)
+            );
+        }
+
+        if (!$module->isChanged()) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::DISCARD_ERROR_MODULE_NOT_CHANGED)
+                ->setModule($module)
+            );
+        }
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::DISCARD_INFO_START)
+            ->setModule($module)
+        );
+
+        $this->moduleInstaller->discard($module, $withTemplate, false);
+        return ModuleManagerResult::success()
+            ->setModule($module);
+    }
+
+    /**
+     * Deinstalliert nur das Modul, wenn es installiert und nicht mehr als abhänigkeit von einem anderen Modul benötigt
+     * wird. Es werden keine Abhängigkeiten deinstalliert.
+     *
+     * Mit der force Option, kann der Abhängigkeits check übersprungen werden und das Modul wird trozdem deinstalliert.
+     * Das kann aber zur folge haben, dass andere Module nicht mehr funktionieren.
+     */
+    public function uninstall(string $archiveName, bool $force = false): ModuleManagerResult
+    {
+        $moduleLoader = LocalModuleLoader::createFromConfig();
+        $module = $moduleLoader->loadInstalledVersionByArchiveName($archiveName);
+
+        if (!$module) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::UNINSTALL_ERROR_MODULE_NOT_FOUND)
+                ->setArchiveName($archiveName)
+            );
+        }
+
+        if (!$module->isInstalled()) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::UNINSTALL_ERROR_MODULE_NOT_INSTALLED)
+                ->setModule($module)
+            );
+        }
+
+        if ($module->isChanged() && $force === false) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::UNINSTALL_ERROR_MODULE_IS_CHANGED)
+                ->setModule($module)
+            );
+        }
+
+        if ($module->getUsedBy() && $force === false) {
+            return $this->error(
+                ModuleManagerMessage::create(ModuleManagerMessage::UNINSTALL_ERROR_MODULE_IS_USED_BY)
+                ->setModule($module)
+            );
+        }
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::UNINSTALL_INFO_START)
+            ->setModule($module)
+        );
+
+        $this->moduleInstaller->uninstall($module, $force);
+
+        $this->info(
+            ModuleManagerMessage::create(ModuleManagerMessage::UNINSTALL_INFO_UPDATE_AUTOLOAD_START)
+        );
+        $autoloadFileCreator = new AutoloadFileCreator();
+        $autoloadFileCreator->createAutoloadFile();
+
+        return ModuleManagerResult::success()
+            ->setModule($module);
+    }
+}
diff --git a/src/Classes/ModuleManager/ModuleManagerLog.php b/src/Classes/ModuleManager/ModuleManagerLog.php
new file mode 100644
index 00000000..af5124cb
--- /dev/null
+++ b/src/Classes/ModuleManager/ModuleManagerLog.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\ModuleManager;
+
+use RobinTheHood\ModifiedModuleLoaderClient\Module;
+
+class ModuleManagerLog
+{
+    /** @var callable */
+    private $writeFunction;
+
+    /** @var callable */
+    private $errorFunction;
+
+    public function setWriteFunction(callable $writeFunction)
+    {
+        $this->writeFunction = $writeFunction;
+    }
+
+    public function setErrorFunction(callable $errorFunction)
+    {
+        $this->errorFunction = $errorFunction;
+    }
+
+    public function write(string $message, $data1 = null, $data2 = null): void
+    {
+        $function = $this->writeFunction;
+        $function($message, $data1, $data2);
+    }
+
+    public function error(int $errorNo, string $message, $data1 = null, $data2 = null): void
+    {
+        $function = $this->errorFunction;
+        $function($errorNo, $message, $data1, $data2);
+    }
+}
diff --git a/src/Classes/ModuleManager/ModuleManagerLoggerInterface.php b/src/Classes/ModuleManager/ModuleManagerLoggerInterface.php
new file mode 100644
index 00000000..e34bc356
--- /dev/null
+++ b/src/Classes/ModuleManager/ModuleManagerLoggerInterface.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\ModuleManager;
+
+interface ModuleManagerLoggerInterface
+{
+    public function debug(ModuleManagerMessage $message): void;
+    public function info(ModuleManagerMessage $message): void;
+    public function notice(ModuleManagerMessage $message): void;
+    public function warning(ModuleManagerMessage $message): void;
+    public function error(ModuleManagerMessage $message): void;
+}
diff --git a/src/Classes/ModuleManager/ModuleManagerMessage.php b/src/Classes/ModuleManager/ModuleManagerMessage.php
new file mode 100644
index 00000000..fd718a21
--- /dev/null
+++ b/src/Classes/ModuleManager/ModuleManagerMessage.php
@@ -0,0 +1,232 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\ModuleManager;
+
+use RobinTheHood\ModifiedModuleLoaderClient\DependencyManager\CombinationSatisfyerResult;
+use RobinTheHood\ModifiedModuleLoaderClient\Module;
+
+class ModuleManagerMessage
+{
+    public const PULL_INFO_START = 100;
+    public const PULL_ERROR_MODULE_NOT_FOUND = 151;
+    public const PULL_ERROR_MODULE_ALLREADY_LOADED = 152;
+
+    public const DELETE_INFO_START = 200;
+    public const DELETE_ERROR_MODULE_NOT_FOUND = 251;
+    public const DELETE_ERROR_MODULE_IS_INSTALED = 252;
+
+    public const INSTALL_INFO_START = 300;
+    public const INSTALL_INFO_PULL_MODULE_START = 301;
+    public const INSTALL_INFO_UPDATE_AUTOLOAD_START = 302;
+    public const INSTALL_ERROR_MODULE_NOT_FOUND = 351;
+    public const INSTALL_ERROR_MODULE_MISSING_REQUIREMENTS = 352;
+    public const INSTALL_ERROR_MODULE_ALLREADY_INSTALED = 353;
+
+    public const UPDATE_INFO_START = 400;
+    public const UDPATE_INFO_PULL_MODULE_START = 401;
+    public const UPDATE_INFO_UPDATE_AUTOLOAD_START = 402;
+    public const UPDATE_INFO_TO = 403;
+    public const UPDATE_ERROR_MODULE_NOT_FOUND = 451;
+    public const UPDATE_ERROR_MODULE_NOT_INSTALLED = 452;
+    public const UPDATE_ERROR_MODULE_MISSING_REQUIREMENTS = 453;
+    public const UPDATE_ERROR_MODULE_IS_CHANGED = 454;
+
+    public const DISCARD_INFO_START = 500;
+    public const DISCARD_ERROR_MODULE_NOT_FOUND = 551;
+    public const DISCARD_ERROR_MODULE_NOT_CHANGED = 552;
+
+    public const UNINSTALL_INFO_START = 600;
+    public const UNINSTALL_INFO_UPDATE_AUTOLOAD_START = 601;
+    public const UNINSTALL_ERROR_MODULE_NOT_FOUND = 651;
+    public const UNINSTALL_ERROR_MODULE_NOT_INSTALLED = 652;
+    public const UNINSTALL_ERROR_MODULE_IS_CHANGED = 653;
+    public const UNINSTALL_ERROR_MODULE_IS_USED_BY = 654;
+
+    /** @var int */
+    private $code = 0;
+
+    /** @var string */
+    private $message = '';
+
+    /** @var string */
+    private $archiveName = '';
+
+    /** @var string */
+    private $version = '';
+
+    /** @var string */
+    private $versionContraint = '';
+
+    /** @var Module */
+    private $module;
+
+    /** @var CombinationSatisfyerResult */
+    private $combinationSatisfyerResult;
+
+    public static function create(int $code): ModuleManagerMessage
+    {
+        return new ModuleManagerMessage($code);
+    }
+
+    public function __construct(int $code)
+    {
+        $this->code = $code;
+    }
+
+    public function setMessage(string $message): ModuleManagerMessage
+    {
+        $this->message = $message;
+        return $this;
+    }
+
+    public function setArchiveName(string $archiveName): ModuleManagerMessage
+    {
+        $this->archiveName = $archiveName;
+        return $this;
+    }
+
+    public function setVersion(string $version): ModuleManagerMessage
+    {
+        $this->version = $version;
+        return $this;
+    }
+
+    public function setVersionConstraint(string $versionContraint): ModuleManagerMessage
+    {
+        $this->versionContraint = $versionContraint;
+        return $this;
+    }
+
+    public function setModule(Module $module): ModuleManagerMessage
+    {
+        $this->module = $module;
+        return $this;
+    }
+
+    public function setCombinationSatisfyerResult(
+        CombinationSatisfyerResult $combinationSatisfyerResult
+    ): ModuleManagerMessage {
+        $this->combinationSatisfyerResult = $combinationSatisfyerResult;
+        return $this;
+    }
+
+    public function getCode(): int
+    {
+        return $this->code;
+    }
+
+    private function getModulName(): string
+    {
+        if ($this->module) {
+            $name = "{$this->module->getArchiveName()} version {$this->module->getVersion()}";
+        } elseif ($this->archiveName && $this->version) {
+            $name = "{$this->archiveName} version {$this->version}";
+        } elseif ($this->archiveName && $this->versionContraint) {
+            $name = "{$this->archiveName} version {$this->versionContraint}";
+        } elseif ($this->archiveName) {
+            $name = "{$this->archiveName}";
+        } else {
+            $name = "unknown";
+        }
+
+        return "module $name";
+    }
+
+    private function getUsedBy(): string
+    {
+        $subModulesArchiveNames = [];
+        foreach ($this->module->getUsedBy() as $subModule) {
+            $subModulesArchiveNames[] .= $subModule->getArchiveName();
+        }
+        $usedBy = implode("\n", $subModulesArchiveNames);
+        return $usedBy;
+    }
+
+    public function __toString()
+    {
+        if ($this->code === self::PULL_INFO_START) {
+            return sprintf("Pulling %s ...", $this->getModulName());
+        } elseif ($this->code === self::PULL_ERROR_MODULE_NOT_FOUND) {
+            return sprintf("Can not pull %s, because module not found.", $this->getModulName());
+        } elseif ($this->code === self::PULL_ERROR_MODULE_ALLREADY_LOADED) {
+            return sprintf("Can not pull %s, because module is already loaded.", $this->getModulName());
+        } elseif ($this->code === self::DELETE_INFO_START) {
+            return sprintf("Deleting %s ...", $this->getModulName());
+        } elseif ($this->code === self::DELETE_ERROR_MODULE_NOT_FOUND) {
+            return sprintf("Can not delete %s, because module not found.", $this->getModulName());
+        } elseif ($this->code === self::DELETE_ERROR_MODULE_IS_INSTALED) {
+            return sprintf("Can not delete %s, because it is installed.", $this->getModulName());
+        } elseif ($this->code === self::INSTALL_INFO_START) {
+            return sprintf("Installing %s ...", $this->getModulName());
+        } elseif ($this->code === self::INSTALL_INFO_PULL_MODULE_START) {
+            return sprintf("Pulling %s ...", $this->getModulName());
+        } elseif ($this->code === self::INSTALL_INFO_UPDATE_AUTOLOAD_START) {
+            return sprintf("Updating autotoload file ...");
+        } elseif ($this->code === self::INSTALL_ERROR_MODULE_NOT_FOUND) {
+            return sprintf("Can not install %s, because module not found.", $this->getModulName());
+        } elseif ($this->code === self::INSTALL_ERROR_MODULE_MISSING_REQUIREMENTS) {
+            return sprintf(
+                "Can not install %s, because not all requirements are met.\n%s",
+                $this->getModulName(),
+                '' . $this->combinationSatisfyerResult->failLog
+            );
+        } elseif ($this->code === self::INSTALL_ERROR_MODULE_ALLREADY_INSTALED) {
+            return sprintf("Can not install %s, because it is already installed.", $this->getModulName());
+        } elseif ($this->code === self::UPDATE_INFO_START) {
+            return sprintf("Updating %s ...", $this->getModulName());
+        } elseif ($this->code === self::UDPATE_INFO_PULL_MODULE_START) {
+            return sprintf("Pulling %s ...", $this->getModulName());
+        } elseif ($this->code === self::UPDATE_INFO_TO) {
+            return sprintf("Updated to %s.", $this->getModulName());
+        } elseif ($this->code === self::UPDATE_INFO_UPDATE_AUTOLOAD_START) {
+            return sprintf("Updating autotoload file ...", $this->getModulName());
+        } elseif ($this->code === self::UPDATE_ERROR_MODULE_NOT_FOUND) {
+            return sprintf("Can not update %s, because module not found.", $this->getModulName());
+        } elseif ($this->code === self::UPDATE_ERROR_MODULE_NOT_INSTALLED) {
+            return sprintf("Can not update %s, because module is not installed.", $this->getModulName());
+        } elseif ($this->code === self::UPDATE_ERROR_MODULE_MISSING_REQUIREMENTS) {
+            return sprintf(
+                "Can not update %s, because not all requirements are met.\n%s",
+                $this->getModulName(),
+                '' . $this->combinationSatisfyerResult->failLog
+            );
+        } elseif ($this->code === self::UPDATE_ERROR_MODULE_IS_CHANGED) {
+            return sprintf("Can not update %s, because module has changes.", $this->getModulName());
+        } elseif ($this->code === self::DISCARD_INFO_START) {
+            return sprintf("Discarding %s ...", $this->getModulName());
+        } elseif ($this->code === self::DISCARD_ERROR_MODULE_NOT_FOUND) {
+            return sprintf("Can not discard %s, because module not found.", $this->getModulName());
+        } elseif ($this->code === self::DISCARD_ERROR_MODULE_NOT_CHANGED) {
+            return sprintf("Can not discard %s, because module has no changes.", $this->getModulName());
+        } elseif ($this->code === self::UNINSTALL_INFO_START) {
+            return sprintf("Uninstalling %s ...", $this->getModulName());
+        } elseif ($this->code === self::UNINSTALL_INFO_UPDATE_AUTOLOAD_START) {
+            return sprintf("Updating autotoload file ...", $this->getModulName());
+        } elseif ($this->code === self::UNINSTALL_ERROR_MODULE_NOT_FOUND) {
+            return sprintf("Can not uninstall %s, because module not found.", $this->getModulName());
+        } elseif ($this->code === self::UNINSTALL_ERROR_MODULE_NOT_INSTALLED) {
+            return sprintf("Can not uninstall %s, because module is not installed.", $this->getModulName());
+        } elseif ($this->code === self::UNINSTALL_ERROR_MODULE_IS_CHANGED) {
+            return sprintf("Can not uninstall %s, because module has changes.", $this->getModulName());
+        } elseif ($this->code === self::UNINSTALL_ERROR_MODULE_IS_USED_BY) {
+            return sprintf(
+                "Can not uninstall %s, because module is used by other modules.\n%s",
+                $this->getModulName(),
+                $this->getUsedBy()
+            );
+        }
+
+        return "Unknown message";
+    }
+}
diff --git a/src/Classes/ModuleManager/ModuleManagerNullLogger.php b/src/Classes/ModuleManager/ModuleManagerNullLogger.php
new file mode 100644
index 00000000..9177f2bf
--- /dev/null
+++ b/src/Classes/ModuleManager/ModuleManagerNullLogger.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\ModuleManager;
+
+class ModuleManagerNullLogger implements ModuleManagerLoggerInterface
+{
+    public function debug(ModuleManagerMessage $message): void
+    {
+    }
+
+    public function info(ModuleManagerMessage $message): void
+    {
+    }
+
+    public function notice(ModuleManagerMessage $message): void
+    {
+    }
+
+    public function warning(ModuleManagerMessage $message): void
+    {
+    }
+
+    public function error(ModuleManagerMessage $message): void
+    {
+    }
+}
diff --git a/src/Classes/ModuleManager/ModuleManagerResult.php b/src/Classes/ModuleManager/ModuleManagerResult.php
new file mode 100644
index 00000000..2d563b0c
--- /dev/null
+++ b/src/Classes/ModuleManager/ModuleManagerResult.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace RobinTheHood\ModifiedModuleLoaderClient\ModuleManager;
+
+use RobinTheHood\ModifiedModuleLoaderClient\Module;
+
+class ModuleManagerResult
+{
+    public const TYPE_ERROR = 1;
+    public const TYPE_SUCCESS = 2;
+
+    /** @var int */
+    private $type;
+
+    /** @var ModuleManagerMessage */
+    private $message;
+
+    /** @var Module */
+    private $module;
+
+    public function __construct(int $type)
+    {
+        $this->type = $type;
+    }
+
+    public static function success(): ModuleManagerResult
+    {
+        $moduleManagerResult = new ModuleManagerResult(self::TYPE_SUCCESS);
+        return $moduleManagerResult;
+    }
+
+    public static function error(ModuleManagerMessage $message): ModuleManagerResult
+    {
+        $moduleManagerResult = new ModuleManagerResult(self::TYPE_ERROR);
+        $moduleManagerResult->setMessage($message);
+        return $moduleManagerResult;
+    }
+
+    public function setModule(Module $module): ModuleManagerResult
+    {
+        $this->module = $module;
+        return $this;
+    }
+
+    public function setMessage(ModuleManagerMessage $message): ModuleManagerResult
+    {
+        $this->message = $message;
+        return $this;
+    }
+
+    public function getType(): int
+    {
+        return $this->type;
+    }
+
+    public function getModule(): ?Module
+    {
+        return $this->module;
+    }
+
+    public function getMessage(): ?ModuleManagerMessage
+    {
+        return $this->message;
+    }
+}
diff --git a/src/Classes/ViewModels/ModuleViewModel.php b/src/Classes/ViewModels/ModuleViewModel.php
index fae5a649..6762e508 100644
--- a/src/Classes/ViewModels/ModuleViewModel.php
+++ b/src/Classes/ViewModels/ModuleViewModel.php
@@ -16,7 +16,6 @@
 use RobinTheHood\ModifiedModuleLoaderClient\App;
 use RobinTheHood\ModifiedModuleLoaderClient\Module;
 use RobinTheHood\ModifiedModuleLoaderClient\ModuleStatus;
-use RobinTheHood\ModifiedModuleLoaderClient\Notification;
 use RobinTheHood\ModifiedModuleLoaderClient\Semver\ParseErrorException;
 use RobinTheHood\ModifiedModuleLoaderClient\ShopInfo;
 
@@ -39,11 +38,21 @@ public function getInstallUrl(string $ref = ''): string
         return $this->getUrl('install', $ref);
     }
 
+    public function getForceInstallUrl(string $ref = ''): string
+    {
+        return $this->getUrl('install', $ref, null, ['force' => 'true']);
+    }
+
     public function getRevertChangesUrl(string $ref = ''): string
     {
         return $this->getUrl('revertChanges', $ref);
     }
 
+    public function getRevertChangesWithTemplateUrl(string $ref = ''): string
+    {
+        return $this->getUrl('revertChanges', $ref, null, ['withTemplate' => 'true']);
+    }
+
     public function getLoadAndInstallUrl(string $ref = ''): string
     {
         return $this->getUrl('loadAndInstall', $ref);
@@ -194,17 +203,31 @@ public function isChanged(): bool
         return $this->module->isChanged();
     }
 
-    private function getUrl(string $action, string $ref, ?Module $module = null): string
+    /**
+     * @param string $action
+     * @param string $ref
+     * @param ?Module $module
+     * @param string[] $queryParams
+     *
+     * @return string
+     */
+    private function getUrl(string $action, string $ref, ?Module $module = null, array $queryParams = []): string
     {
         if (!$module) {
             $module = $this->module;
         }
 
+        $queryString = http_build_query($queryParams);
+        if ($queryString) {
+            $queryString = '&' . $queryString;
+        }
+
         return
             '?action=' . $action .
             '&archiveName=' . $module->getArchiveName() .
             '&version=' . $module->getVersion() .
-            '&ref=' . $ref;
+            '&ref=' . $ref
+            . $queryString;
     }
 
     /**
@@ -217,7 +240,8 @@ public function getCompatibleStrings(): array
         if (!$this->module->isCompatibleWithModified()) {
             $version = ShopInfo::getModifiedVersion();
             $array[] = [
-                'text' => "Dieses Modul wurde noch nicht mit deiner Version von modified getestet. Du hast modifed Version $version installiert.",
+                'text' => "Dieses Modul wurde nicht mit deiner Version von modified getestet. Du hast modifed Version
+                    $version installiert.",
                 'type' => 'warning'
             ];
         }
@@ -226,7 +250,8 @@ public function getCompatibleStrings(): array
             if (!$this->module->isCompatibleWithPhp()) {
                 $version = phpversion();
                 $array[] = [
-                    'text' => "Dieses Modul wurde noch nicht mit deiner PHP Version getestet. Du verwendest die PHP Version $version.",
+                    'text' => "Dieses Modul wurde noch nicht mit deiner PHP Version getestet. Du verwendest die PHP
+                        Version $version.",
                     'type' => 'warning'
                 ];
             }
@@ -241,7 +266,8 @@ public function getCompatibleStrings(): array
             if (!$this->module->isCompatibleWithMmlc()) {
                 $version = App::getMmlcVersion();
                 $array[] = [
-                    'text' => "Dieses Modul wurde noch nicht mit deiner MMLC Version getestet. Du verwendest die MMLC Version $version.",
+                    'text' => "Dieses Modul wurde noch nicht mit deiner MMLC Version getestet. Du verwendest die MMLC
+                        Version $version.",
                     'type' => 'warning'
                 ];
             }
diff --git a/src/Templates/ModuleInfo.tmpl.php b/src/Templates/ModuleInfo.tmpl.php
index 13152c49..2eea5f9c 100644
--- a/src/Templates/ModuleInfo.tmpl.php
+++ b/src/Templates/ModuleInfo.tmpl.php
@@ -97,8 +97,7 @@
                 
 
                 isRepairable()) { ?>
-                    
+                    
                         
                             
                             Änderungen verwerfen
@@ -112,6 +111,16 @@
                             
                         
                     
+
+                    
+                        
+                            
+                            Änderungen verwerfen inkl. Templates
+                        
+                            
+                            Änderungen übernehmen inkl. Templates (Link-Mode)
+                        
+                    
                 
 
                 isCompatibleLoadableAndInstallable()) { ?>
@@ -127,7 +136,7 @@
                     Installieren
 
                 isIncompatibleInstallable()) { ?>
-                    Installieren (inkompatible Version)
+                    Installieren (inkompatible Version)
 
                 hasInstalledVersion()) { ?>
                     Zur installierten Version
diff --git a/src/Templates/ModuleListingModule.tmpl.php b/src/Templates/ModuleListingModule.tmpl.php
index a68c377e..d3910bdf 100644
--- a/src/Templates/ModuleListingModule.tmpl.php
+++ b/src/Templates/ModuleListingModule.tmpl.php
@@ -1,5 +1,9 @@
 isInstalled() ? 'installiert' : $moduleView->getPriceFormated();
diff --git a/src/Templates/Navi.tmpl.php b/src/Templates/Navi.tmpl.php
index 393e43a3..d7e0ab25 100644
--- a/src/Templates/Navi.tmpl.php
+++ b/src/Templates/Navi.tmpl.php
@@ -1,6 +1,13 @@
-
+
+/**
+ * @phpcs:disable Generic.Files.LineLength.TooLong
+ */
+
+defined('LOADED_FROM_INDEX') && LOADED_FROM_INDEX ?? die('Access denied.');
+
+use RobinTheHood\ModifiedModuleLoaderClient\ShopInfo;
+?>