diff --git a/src/Classes/IndexController.php b/src/Classes/IndexController.php index eb95ebd2..0721258a 100644 --- a/src/Classes/IndexController.php +++ b/src/Classes/IndexController.php @@ -16,8 +16,6 @@ use RobinTheHood\ModifiedModuleLoaderClient\Loader\ModuleLoader; use RobinTheHood\ModifiedModuleLoaderClient\Loader\LocalModuleLoader; use RobinTheHood\ModifiedModuleLoaderClient\Loader\RemoteModuleLoader; -use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator; -use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser; use RobinTheHood\ModifiedModuleLoaderClient\ModuleFilter; use RobinTheHood\ModifiedModuleLoaderClient\ModuleSorter; use RobinTheHood\ModifiedModuleLoaderClient\Category; @@ -137,36 +135,32 @@ public function invokeSelfUpdate() return $accessRedirect; } - $selfUpdater = new SelfUpdater(); - $installedVersion = $selfUpdater->getInstalledVersion(); + // Nächste mögliche MMLC Version ermittlen $latest = Config::getSelfUpdate() == 'latest'; - $version = $selfUpdater->getNewestVersionInfo($latest); + $installedMmlcVersionString = App::getMmlcVersion(); + $selfUpdater = new SelfUpdater(MmlcVersionInfoLoader::createLoader()); + $mmlcVersionInfo = $selfUpdater->getNextMmlcVersionInfo($installedMmlcVersionString, $latest); + // Update durchführen, wenn ausgewählt und vorhanden $queryParams = $this->serverRequest->getQueryParams(); $installVersion = $queryParams['install'] ?? ''; - - if ($installVersion) { - $selfUpdater->update($installVersion); + if ($mmlcVersionInfo && $mmlcVersionInfo->version === $installVersion) { + $selfUpdater->update($mmlcVersionInfo); return $this->redirect('/?action=selfUpdate'); } - // Postupdate ausführen, falls erforderlich - $executed = $selfUpdater->checkAndDoPostUpdate(); + // Postupdate ausführen. Kann immer aufgerufen werden. Die Methode entscheidet selbst, + // ob etwas getan werden muss oder nicht. + $postUpdateExecuted = $selfUpdater->postUpdate(); - // Wenn der Postupdate durchgeführt werden musste, die Seite noch einmal - // automatisch neu laden - if ($executed) { + // Wenn ein Postupdate durchgeführt wurde, die Seite noch einmal automatisch neu laden. + if ($postUpdateExecuted) { return $this->redirect('/?action=selfUpdate'); } - $checkUpdate = $selfUpdater->checkUpdate(); - - $comparator = new Comparator(new Parser()); - return $this->render('SelfUpdate', [ - 'comparator' => $comparator, - 'version' => $version, - 'installedVersion' => $installedVersion, + 'mmlcVersionInfo' => $mmlcVersionInfo, + 'installedVersionString' => $installedMmlcVersionString, 'serverName' => $_SERVER['SERVER_NAME'] ?? 'unknown Server Name' ]); } @@ -645,8 +639,11 @@ public function calcModuleChangeCount() public function calcSystemUpdateCount() { - $selfUpdater = new SelfUpdater(); - $checkUpdate = $selfUpdater->checkUpdate(); + $latest = Config::getSelfUpdate() == 'latest'; + $installedMmlcVersionString = App::getMmlcVersion(); + + $selfUpdater = new SelfUpdater(MmlcVersionInfoLoader::createLoader()); + $checkUpdate = $selfUpdater->updateAvailable($installedMmlcVersionString, $latest); if ($checkUpdate) { return 1; } diff --git a/src/Classes/MmlcVersionInfo.php b/src/Classes/MmlcVersionInfo.php new file mode 100644 index 00000000..575ae2c0 --- /dev/null +++ b/src/Classes/MmlcVersionInfo.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; + +class MmlcVersionInfo +{ + /** @var string */ + public $version; + + /** @var string */ + public $fileName; +} diff --git a/src/Classes/MmlcVersionInfoLoader.php b/src/Classes/MmlcVersionInfoLoader.php new file mode 100644 index 00000000..0a7ce597 --- /dev/null +++ b/src/Classes/MmlcVersionInfoLoader.php @@ -0,0 +1,170 @@ + + * + * 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\Api\V1\ApiRequest; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Filter; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\ParseErrorException; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Sorter; + +class MmlcVersionInfoLoader +{ + /** @var ApiRequest */ + private $apiRequest; + + /** @var Parser */ + private $parser; + + /** @var Filter */ + private $filter; + + public static function createLoader(): MmlcVersionInfoLoader + { + $parser = new Parser(); + $comparator = new Comparator($parser); + $sorter = new Sorter($comparator); + $filter = new Filter($parser, $comparator, $sorter); + + $mmlcVersionInfoLoader = new MmlcVersionInfoLoader( + new ApiRequest(), + $parser, + $filter + ); + + return $mmlcVersionInfoLoader; + } + + public function __construct(ApiRequest $apiRequest, Parser $parser, Filter $filter) + { + $this->apiRequest = $apiRequest; + $this->parser = $parser; + $this->filter = $filter; + } + + /** + * Fetch all MmlcVersionInfo via api from MMLS + * + * @return MmlcVersionInfo[] all available MmlcVersionInfo + */ + public function getAll(): array + { + $result = $this->apiRequest->getAllVersions(); + + $content = $result['content'] ?? []; + if (!$content) { + return []; + } + + $mmlcVersionInfos = []; + foreach ($content as $mmlcVersionInfoAsArray) { + if (!array_key_exists('version', $mmlcVersionInfoAsArray)) { + continue; + } + + if (!array_key_exists('fileName', $mmlcVersionInfoAsArray)) { + continue; + } + + try { + $this->parser->parse($mmlcVersionInfoAsArray['version']); + } catch (ParseErrorException $e) { + continue; + } + + $mmlcVersionInfo = new MmlcVersionInfo(); + $mmlcVersionInfo->version = $mmlcVersionInfoAsArray['version']; + $mmlcVersionInfo->fileName = $mmlcVersionInfoAsArray['fileName']; + $mmlcVersionInfos[] = $mmlcVersionInfo; + } + return $mmlcVersionInfos; + } + + /** + * Returns the latest MmlcVersionInfo + * + * @param bool $latest + * + * @return ?MmlcVersionInfo Returns the latest MmlcVersionInfo + */ + public function getNewest($latest = false): ?MmlcVersionInfo + { + $mmlcVersionInfos = $this->getAll(); + $versionStrings = $this->getVersionStringsFromMmlcVersionInfos($mmlcVersionInfos); + + if (!$latest) { + $versionStrings = $this->filter->stable($versionStrings); + } + + $versionString = $this->filter->latest($versionStrings); + $mmlcVersionInfo = $this->getMmlcVersionInfoByVersionString($versionString, $mmlcVersionInfos); + + return $mmlcVersionInfo; + } + + /** + * @param string $installtedVersionString + * @param bool $$latest + * + * @return ?MmlcVersionInfo + */ + public function getNextNewest(string $installtedVersionString, bool $latest = false): ?MmlcVersionInfo + { + $versionInfos = $this->getAll(); + $versionStrings = $this->getVersionStringsFromMmlcVersionInfos($versionInfos); + + if (!$latest) { + $versionStrings = $this->filter->stable($versionStrings); + } + + $version = $this->parser->parse($installtedVersionString); + $constrain = '<=' . $version->nextMinor(); + + $versionString = $this->filter->latestByConstraint($constrain, $versionStrings); + $versionInfo = $this->getMmlcVersionInfoByVersionString($versionString, $versionInfos); + + return $versionInfo; + } + + /** + * @param string $versionString + * @param MmlcVersionInfo[] $mmlcVersionInfos + * + * @return ?MmlcVersionInfo + */ + private function getMmlcVersionInfoByVersionString(string $versionString, array $mmlcVersionInfos): ?MmlcVersionInfo + { + foreach ($mmlcVersionInfos as $mmlcVersionInfo) { + if ($mmlcVersionInfo->version === $versionString) { + return $mmlcVersionInfo; + } + } + return null; + } + + /** + * @param MmlcVersionInfo[] $mmlcVersionInfos + * + * @return string[] + */ + private function getVersionStringsFromMmlcVersionInfos(array $mmlcVersionInfos): array + { + $versionStrings = []; + foreach ($mmlcVersionInfos as $mmlcVersionInfo) { + $versionStrings[] = $mmlcVersionInfo->version; + } + return $versionStrings; + } +} diff --git a/src/Classes/SelfUpdater.php b/src/Classes/SelfUpdater.php index ec85bf6d..76dad5be 100644 --- a/src/Classes/SelfUpdater.php +++ b/src/Classes/SelfUpdater.php @@ -17,199 +17,199 @@ use RobinTheHood\ModifiedModuleLoaderClient\Config; use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser; use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator; -use RobinTheHood\ModifiedModuleLoaderClient\Semver\ParseErrorException; use RobinTheHood\ModifiedModuleLoaderClient\Helpers\FileHelper; use RobinTheHood\ModifiedModuleLoaderClient\Api\V1\HttpRequest; -use RobinTheHood\ModifiedModuleLoaderClient\Api\V1\ApiRequest; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Filter; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Sorter; class SelfUpdater { - /** - * @var string - */ + /** @var string */ private $appRoot = ''; - /** - * @var string - */ + /** @var string */ private $remoteUpdateServer; - /** - * @var Comparator - */ + /** @var Comparator */ protected $comparator; - /** - * @var Parser - */ + /** @var Parser */ protected $parser; - private $apiRequest; + /** @var Filter */ + protected $filter; - public function __construct($apiRequest = null) - { - $this->apiRequest = $apiRequest; + /** @var MmlcVersionInfoLoader */ + protected $mmlcVersionInfoLoader; - // appRoot wird in die Variable ausgelagert, da während der Installation, - // wenn Dateien verschoben werden, die Methode App::getRoot() nicht - // mehr richtige Ergebnisse liefert. + /** + * Während der Installtion werden Dateien und damit auch die Pfade von Klassen verschoben. + * Wird eine Klasse geladen, nachdem die Datei verschoben wurde, kann der PHP Classloader die Klasse + * nicht finden und ein Fehler tritt auf. Aus diesem Grund müssen alle benötigten Daten und Klassen in den Speicher + * geladen werden, bevor die Dateien verschoben werden. + */ + public function __construct(MmlcVersionInfoLoader $mmlcVersionInfoLoader) + { $this->appRoot = App::getRoot(); - - $remoteAddress = Config::getRemoteAddress() ?? ''; - - if (empty(Config::getRemoteAddress())) { - throw new \RuntimeException('Unable to connect. RemoteAddress is empty or not set.'); - } - - $this->remoteUpdateServer = str_replace('/api.php', '/Downloads/', $remoteAddress); - + $this->mmlcVersionInfoLoader = $mmlcVersionInfoLoader; + $this->remoteUpdateServer = $this->getRomteUpdateServer(); $this->comparator = new Comparator(new Parser()); $this->parser = new Parser(); + $this->filter = new Filter($this->parser, $this->comparator, new Sorter($this->comparator)); } - - public function checkUpdate(): bool + /** + * Gibt true zurück wenn eine neue MMLC Version verfügbar ist. + */ + public function updateAvailable(string $installedMmlcVersionString, bool $latest): bool { - $latest = Config::getSelfUpdate() == 'latest'; - $newestVersionInfo = $this->getNewestVersionInfo($latest); - $installedVersion = $this->getInstalledVersion(); - - try { - if ($this->comparator->greaterThan($newestVersionInfo['version'], $installedVersion)) { - return true; - } - } catch (ParseErrorException $e) { - // do nothing + if ($this->getNextMmlcVersionInfo($installedMmlcVersionString, $latest)) { + return true; } - return false; } - public function getVersionInfos(): array + /** + * Gibt die nächst möglichste MmlcVersionInfo zurück, falls eine neue MMLC Version verfügbar ist. + */ + public function getNextMmlcVersionInfo(string $installedMmlcVersionString, bool $latest): ?MmlcVersionInfo { - if ($this->apiRequest) { - $apiRequest = $this->apiRequest; - } else { - $apiRequest = new ApiRequest(); + $mmlcVersionInfo = $this->mmlcVersionInfoLoader->getNextNewest($installedMmlcVersionString, $latest); + if (!$mmlcVersionInfo) { + return null; } - $result = $apiRequest->getAllVersions(); - $content = $result['content'] ?? []; - if (!$content) { - return []; + if (!$this->comparator->greaterThan($mmlcVersionInfo->version, $installedMmlcVersionString)) { + return null; } - return $content; + return $mmlcVersionInfo; } - public function getInstalledVersion(): string + public function update(MmlcVersionInfo $mmlcVersionInfo): void { - $json = file_get_contents($this->appRoot . '/config/version.json'); - $version = json_decode($json); - if ($version) { - return $version->version; + if (!$mmlcVersionInfo->fileName) { + return; } - return ''; // Better throw an exception - } - - /** - * @return array Returns the latest version info - */ - public function getNewestVersionInfo($latest = false): array - { - $versionInfos = $this->getVersionInfos(); - - $newestVersionInfo = ['fileName' => '', 'version' => '0.0.0-alpha']; - - foreach ($versionInfos as $versionInfo) { - try { - $version = $this->parser->parse($versionInfo['version']); - if (!$latest && $version->getTag()) { - continue; - } - - if ($this->comparator->greaterThan($versionInfo['version'], $newestVersionInfo['version'])) { - $newestVersionInfo = $versionInfo; - } - } catch (ParseErrorException $e) { - // do nothing - } + if (!$mmlcVersionInfo->version) { + return; } - return $newestVersionInfo; - } + $this->createRestore($mmlcVersionInfo); + $this->download($mmlcVersionInfo); + $this->backup($mmlcVersionInfo); + $this->untar($mmlcVersionInfo); + $this->verifyUntar($mmlcVersionInfo); + $this->install($mmlcVersionInfo); + $this->setupConfig($mmlcVersionInfo); + $this->setupVersion($mmlcVersionInfo); + $this->verifyUpdate($mmlcVersionInfo); + $this->removeRestore($mmlcVersionInfo); - private function getFileNameByVersion(string $version): string - { - $versionInfos = $this->getVersionInfos(); - foreach ($versionInfos as $versionInfo) { - if ($versionInfo['version'] == $version) { - return $versionInfo['fileName']; - } - } - return ''; + opcache_reset(); } - public function update(string $installVersion): void + public function postUpdate(): bool { - $installFileName = $this->getFileNameByVersion($installVersion); - if (!$installFileName) { - return; + if (file_exists($this->appRoot . '/config/postUpdate')) { + return false; } - $this->download($installFileName); - $this->backup($installFileName); - $this->untar($installFileName); - $this->install(); - $this->setupConfig(); - opcache_reset(); + $this->postUpdateSteps(); + system('rm -rf ' . $this->appRoot . '/backup'); + + file_put_contents($this->appRoot . '/config/postUpdate', "SelfUpdate::postUpdate() DONE"); + + return true; } - public function download(string $fileName): bool + private function download(MmlcVersionInfo $mmlcVersionInfo): bool { - $remoteAddress = $this->remoteUpdateServer . $fileName; + $remoteAddress = $this->remoteUpdateServer . $mmlcVersionInfo->fileName; $httpRequest = new HttpRequest(); $tarBall = $httpRequest->sendGetRequest($remoteAddress); if (!$tarBall) { - die("Error: Can not download $remoteAddress file. back"); + $this->showSoftError("Can not download file: $remoteAddress"); return false; } - file_put_contents($this->appRoot . '/' . $fileName, $tarBall); + file_put_contents($this->appRoot . '/' . $mmlcVersionInfo->fileName, $tarBall); + return true; } - public function backup(string $installFileName): void + private function backup(MmlcVersionInfo $mmlcVersionInfo): bool { $srcPath = $this->appRoot; $destPath = $this->appRoot . '/backup'; - @mkdir($destPath); + + if (!file_exists($destPath)) { + mkdir($destPath); + } + + if (!file_exists($destPath)) { + $this->showSoftError("Can not create directory $destPath."); + return false; + } $exclude = [ '/Archives', '/Modules', '/backup', - '/' . $installFileName + '/restore.php', + '/' . $mmlcVersionInfo->fileName ]; $files = FileHelper::scanDir($srcPath, FileHelper::FILES_AND_DIRS, true); FileHelper::moveFilesTo($files, $srcPath, $destPath, $exclude); + + return true; + } + + private function createRestore(MmlcVersionInfo $mmlcVersionInfo): bool + { + $rootPath = $this->appRoot; + + $restoreTemplateFilePath = $rootPath . '/src/Templates/restore.php.tmpl'; + $restoreFilePath = $rootPath . '/restore.php'; + + if (!file_exists($restoreTemplateFilePath)) { + $this->showSoftError("Can not find file: $restoreTemplateFilePath"); + return false; + } + + copy($restoreTemplateFilePath, $restoreFilePath); + + if (!file_exists($restoreFilePath)) { + $this->showSoftError("Can not create the automatic restore file at: $restoreFilePath"); + return false; + } + + return true; + } + + private function removeRestore(MmlcVersionInfo $mmlcVersionInfo) + { + $rootPath = $this->appRoot; + $restoreFilePath = $rootPath . '/restore.php'; + + system('rm -rf ' . $restoreFilePath); } - public function untar(string $installFileName): void + private function untar(MmlcVersionInfo $mmlcVersionInfo): void { - $tarFilePath = $this->appRoot . '/' . $installFileName; + $tarFilePath = $this->appRoot . '/' . $mmlcVersionInfo->fileName; - $tarBall = new \PharData($installFileName); + $tarBall = new \PharData($mmlcVersionInfo->fileName); $tarBall->extractTo($this->appRoot, null, true); system('rm -rf ' . $tarFilePath); } - public function install(): void + private function install(MmlcVersionInfo $mmlcVersionInfo): void { $srcPath = $this->appRoot . '/ModifiedModuleLoaderClient'; $destPath = $this->appRoot; @@ -220,27 +220,72 @@ public function install(): void system('rm -rf ' . $srcPath); } - public function setupConfig(): void + private function setupConfig(MmlcVersionInfo $mmlcVersionInfo): void { @unlink($this->appRoot . '/config/config.php'); @copy($this->appRoot . '/backup/config/config.php', $this->appRoot . '/config/config.php'); } + private function setupVersion(MmlcVersionInfo $mmlcVersionInfo): void + { + $versionFilePath = $this->appRoot . '/config/version.json'; + if (!file_exists($versionFilePath)) { + file_put_contents($this->appRoot . '/config/version.json', '{"version": "' . $mmlcVersionInfo->version . '"}'); + } + } - public function checkAndDoPostUpdate(): bool + private function verifyUntar(MmlcVersionInfo $mmlcVersionInfo) { - if (file_exists($this->appRoot . '/config/postUpdate')) { + $checkPaths = [ + $this->appRoot . '/ModifiedModuleLoaderClient/index.php', + $this->appRoot . '/ModifiedModuleLoaderClient/src', + $this->appRoot . '/ModifiedModuleLoaderClient/vendor', + $this->appRoot . '/ModifiedModuleLoaderClient/config' + ]; + + $missingPaths = []; + foreach ($checkPaths as $path) { + if (!file_exists($path)) { + $missingPaths[] = $path; + } + } + + if ($missingPaths) { + $missingPathsString = implode("
\n", $missingPaths); + $this->showError("verify untar - can not find:
\n $missingPathsString"); return false; } - $this->postUpdate(); - system('rm -rf ' . $this->appRoot . '/backup'); + return true; + } + + private function verifyUpdate(MmlcVersionInfo $mmlcVersionInfo): bool + { + $checkPaths = [ + $this->appRoot . '/index.php', + $this->appRoot . '/src', + $this->appRoot . '/vendor', + $this->appRoot . '/config', + $this->appRoot . '/config/config.php', + ]; + + $missingPaths = []; + foreach ($checkPaths as $path) { + if (!file_exists($path)) { + $missingPaths[] = $path; + } + } + + if ($missingPaths) { + $missingPathsString = implode("
\n", $missingPaths); + $this->showError("Verify update - can not find:
\n $missingPathsString"); + return false; + } - file_put_contents($this->appRoot . '/config/postUpdate', "SelfUpdate::postUpdate() DONE"); return true; } - public function postUpdate(): void + private function postUpdateSteps(): void { // Vor der Version 1.12.0 haben sich die config.php und die version.json // im Root-Verzeichnis befunden und der alte SelfUpdater hat nicht alle @@ -262,7 +307,7 @@ public function postUpdate(): void } // *** config/version.json *** - // Wenn die config/version.json Datei fehlt, gibt es 3 Möglichkeiten diese + // Wenn die config/version.json Datei fehlt, gibt es 2 Möglichkeiten diese // zu erzeugen. $dest = $this->appRoot . '/config/version.json'; @@ -273,11 +318,71 @@ public function postUpdate(): void if (!file_exists($dest) && file_exists($this->appRoot . '/version.json')) { rename($this->appRoot . 'version.json', $dest); } + } - if (!file_exists($dest)) { - $latest = Config::getSelfUpdate() == 'latest'; - $newestVersionInfo = $this->getNewestVersionInfo($latest); - file_put_contents($this->appRoot . '/config/version.json', '{"version": "' . $newestVersionInfo['version'] . '"}'); + private function getRomteUpdateServer(): string + { + $remoteAddress = Config::getRemoteAddress() ?? ''; + + if (empty(Config::getRemoteAddress())) { + throw new \RuntimeException('Unable to connect. RemoteAddress is empty or not set.'); } + + $remoteUpdateServer = str_replace('/api.php', '/Downloads/', $remoteAddress); + return $remoteUpdateServer; + } + + private function showSoftError(string $message): void + { + $errorMessage = "" + . "

ATTENTION: DO NOT RELOAD THIS PAGE

\n" + . "Because this message will disappear and probably no longer be displayed after a reload " + . "or leads to further errors.
\n" + . "You can close the window if you don't want to read the message anymore." + . "

The MMLC update was interrupted

\n" + . "

ERROR

" + . "$message
\n" + . "

WHAT CAN YOU DO

\n" + . "You can go back with the following url: " + . "Back to the MMLC System Page
\n"; + + $errorMessage = '' + . '
' + . $errorMessage + . '
'; + + die($errorMessage); + } + + private function showError(string $message): void + { + $rootPath = $this->appRoot; + $backupPath = $this->appRoot . '/backup'; + + $errorMessage = "" + . "

ATTENTION: DO NOT RELOAD THIS PAGE

\n" + . "Because this message will disappear and probably no longer be displayed after a reload " + . "or leads to further errors.
\n" + . "You can close the window if you don't want to read the message anymore." + . "

The MMLC update was interrupted

\n" + . "Your MMLC is now in an unsave and unusable state.
\n" + . "

ERROR

" + . "$message
\n" + . "

WHAT CAN YOU DO

\n" + . "Variant a) Try the following restore link, it will open in a new window. " + . "To the MMLC restore script in a new window
\n" + . "
\n" + . "Variant b) Try to restore you MMLC by moving all files/directories from $backupPath to $rootPath
\n" + . "and delete directory $rootPath/ModifiedModuleLoaderClient if exists
\n" + . "
\n" + . "Variant c) Go to module-loader.de and load the installer to reinstall the MMLC. " + . "The installer will try to keep your settings and module data.
\n"; + + $errorMessage = '' + . '
' + . $errorMessage + . '
'; + + die($errorMessage); } } diff --git a/src/Classes/Semver/Comparator.php b/src/Classes/Semver/Comparator.php index a56c467d..86b1598e 100644 --- a/src/Classes/Semver/Comparator.php +++ b/src/Classes/Semver/Comparator.php @@ -144,18 +144,6 @@ public function notEqualTo(string $versionString1, string $versionString2): bool return false; } - public function highest(array $versionStrings): string - { - $versionStrings = $this->rsort($versionStrings); - return $versionStrings[0]; - } - - public function lowest(array $versionStrings): string - { - $versionStrings = $this->sort($versionStrings); - return $versionStrings[0]; - } - // Testet ob Version1 mindestens das kann, was auch Version2 kann. // Version1 darf auch mehr können als das was Version2 kann, // aber nicht weniger. @@ -180,6 +168,9 @@ public function satisfies(string $versionString1, string $constrain): bool if ($constrain[0] == '^') { // Ist Buchstabe an Index 0 = ^ $versionString2 = str_replace('^', '', $constrain); return $this->isCompatible($versionString1, $versionString2); + } elseif ($constrain[0] == '<' && $constrain[1] == '=') { + $versionString2 = str_replace('<=', '', $constrain); + return $this->lessThanOrEqualTo($versionString1, $versionString2); } else { $versionString2 = $constrain; return $this->equalTo($versionString1, $versionString2); @@ -202,34 +193,4 @@ public function satisfiesOr(string $versionString1, string $constraintOrExpressi } return false; } - - public function sort(array $versionStrings): array - { - usort($versionStrings, [$this, 'compareAsc']); - return $versionStrings; - } - - public function rsort(array $versionStrings): array - { - usort($versionStrings, [$this, 'compareDes']); - return $versionStrings; - } - - private function compareAsc(string $versionString1, string $versionString2): int - { - if ($this->greaterThan($versionString1, $versionString2)) { - return 1; - } - - return -1; - } - - private function compareDes(string $versionString1, string $versionString2): int - { - if ($this->greaterThan($versionString1, $versionString2)) { - return -1; - } - - return 1; - } } diff --git a/src/Classes/Semver/Filter.php b/src/Classes/Semver/Filter.php new file mode 100644 index 00000000..d89b9abe --- /dev/null +++ b/src/Classes/Semver/Filter.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Semver; + +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser; + +class Filter +{ + private $sorter; + + private $comparator; + + private $parser; + + public function __construct(Parser $parser, Comparator $comparator, Sorter $sorter) + { + $this->parser = $parser; + $this->comparator = $comparator; + $this->sorter = $sorter; + } + + public function latest(array $versionStrings): string + { + if (!$versionStrings) { + return ''; + } + $versionStrings = $this->sorter->rsort($versionStrings); + return $versionStrings[0]; + } + + public function oldest(array $versionStrings): string + { + if (!$versionStrings) { + return ''; + } + $versionStrings = $this->sorter->sort($versionStrings); + return $versionStrings[0]; + } + + public function byConstraint(string $constraint, array $versions): array + { + $fileredVersions = []; + foreach ($versions as $version) { + if ($this->comparator->satisfiesOr($version, $constraint)) { + $fileredVersions[] = $version; + } + } + return $fileredVersions; + } + + public function latestByConstraint(string $constraint, array $versions): string + { + $filteredVersions = $this->byConstraint($constraint, $versions); + return $this->latest($filteredVersions); + } + + public function oldestByConstraint(string $constraint, array $versions): string + { + $filteredVersions = $this->byConstraint($constraint, $versions); + return $this->oldest($filteredVersions); + } + + public function stable(array $versionStrings): array + { + $fileredVersionStrings = []; + foreach ($versionStrings as $versionString) { + $version = $this->parser->parse($versionString); + if (!$version->getTag()) { + $fileredVersionStrings[] = $versionString; + } + } + return $fileredVersionStrings; + } +} diff --git a/src/Classes/Semver/Sorter.php b/src/Classes/Semver/Sorter.php new file mode 100644 index 00000000..66a0bb47 --- /dev/null +++ b/src/Classes/Semver/Sorter.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Semver; + +class Sorter +{ + /** @var Comparator */ + private $comparator; + + public function __construct(Comparator $comparator) + { + $this->comparator = $comparator; + } + + public function sort(array $versionStrings): array + { + usort($versionStrings, [$this, 'compareAsc']); + return $versionStrings; + } + + public function rsort(array $versionStrings): array + { + usort($versionStrings, [$this, 'compareDes']); + return $versionStrings; + } + + private function compareAsc(string $versionString1, string $versionString2): int + { + if ($this->comparator->greaterThan($versionString1, $versionString2)) { + return 1; + } + + return -1; + } + + private function compareDes(string $versionString1, string $versionString2): int + { + if ($this->comparator->greaterThan($versionString1, $versionString2)) { + return -1; + } + + return 1; + } +} diff --git a/src/Classes/Semver/Version.php b/src/Classes/Semver/Version.php index 392d754f..5b93d4eb 100644 --- a/src/Classes/Semver/Version.php +++ b/src/Classes/Semver/Version.php @@ -47,4 +47,23 @@ public function getTag(): string { return $this->tag; } + + public function nextMinor(): Version + { + return new Version( + $this->major, + $this->minor + 1, + 0, + '' + ); + } + + public function __toString() + { + $versionString = $this->major . '.' . $this->minor . '.' . $this->patch; + if ($this->tag) { + $versionString .= '-' . $this->tag; + } + return $versionString; + } } diff --git a/src/Classes/SendMail.php b/src/Classes/SendMail.php index 9115f89d..c7aba7f0 100644 --- a/src/Classes/SendMail.php +++ b/src/Classes/SendMail.php @@ -39,8 +39,7 @@ public static function sendIssue(): void $subject = 'MMLC Report Issue'; $shopVersion = ShopInfo::getModifiedVersion(); - $selfUpdater = new SelfUpdater(); - $mmlcVersion = $selfUpdater->getInstalledVersion(); + $mmlcVersion = App::getMmlcVersion(); $message .= '
Message sent from: ' . $_SERVER['HTTP_HOST'] . diff --git a/src/Templates/SelfUpdate.tmpl.php b/src/Templates/SelfUpdate.tmpl.php index 0f5b0264..206d3eb4 100644 --- a/src/Templates/SelfUpdate.tmpl.php +++ b/src/Templates/SelfUpdate.tmpl.php @@ -17,11 +17,11 @@

MMLC - Modified Module Loader Client

-

+

- greaterThan($version['version'], $installedVersion)) { ?> - Version verfügbar

- + + Version version ?> verfügbar

+
Update installieren diff --git a/src/Templates/restore.php.tmpl b/src/Templates/restore.php.tmpl new file mode 100644 index 00000000..6bc65f4a --- /dev/null +++ b/src/Templates/restore.php.tmpl @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient; + +class Restore +{ + private const ACTION_RESTORE = 'restore'; + + /** @var string */ + private $mmlcRootPath; + + /** @var string */ + private $backupRootPath; + + /** @var string[] */ + private $checkFiles = []; + + /** @var string[] */ + private $restoreFiles = []; + + public function __construct() + { + $this->mmlcRootPath = __DIR__; + $this->backupRootPath = __DIR__ . '/backup'; + + $this->restoreFiles = [ + '/config', + '/scripts', + '/src', + '/vendor', + '/index.php' + ]; + + $this->checkFiles = [ + '/config', + '/src', + '/vendor', + '/index.php' + ]; + } + + public function invoke(): void + { + $action = $_GET['action'] ?? ''; + + if ($action === self::ACTION_RESTORE) { + $this->invokeRestore(); + } else { + $this->invokeIndex(); + } + } + + private function invokeIndex(): void + { + $restoreUrl = '?action=' . self::ACTION_RESTORE; + + if ($this->isRestorePossilbe()) { + $this->outDie( + "Click on the following link to restore the MMLC. Restore MMLC now" + ); + } elseif ($this->isRestored()) { + $this->outDie( + "Looks like your MMLC is restored. Go to MMLC" + ); + } elseif (!$this->isRestored() && $this->isRestorePossilbe()) { + $filesString = implode("
\n", $this->getMissingBackupFiles()); + $this->outDie( + "Restore is not possible. Can not find the following files:
\n" + . "
\n" + . "$filesString
\n" + ); + } elseif (!$this->isRestored() && !$this->isRestorePossilbe()) { + $filesString = implode("
\n", $this->getMissingBackupFiles()); + $this->outDie( + "Your MMLC is NOT restored but a Restore is not possible. Can not find the following files:
\n" + . "
\n" + . "$filesString
\n" + ); + } + + $this->outDie( + "You can try to restore the MMLC. Restore MMLC now" + ); + } + + private function invokeRestore(): void + { + $message = ''; + foreach ($this->restoreFiles as $restoreFile) { + $fromFile = $this->backupRootPath . $restoreFile; + $toFile = $this->mmlcRootPath . $restoreFile; + + rename($fromFile, $toFile); + + if (!file_exists($this->mmlcRootPath . $restoreFile)) { + $message .= "Something went wrong. Can not copy $fromFile to $toFile
\n"; + } + } + + if ($message) { + $this->outDie($message); + } + + header("Location: index.php"); + die(); + } + + private function isRestored(): bool + { + if ($this->getMissungFiles()) { + return false; + } + return true; + } + + private function isRestorePossilbe(): bool + { + if ($this->getMissingBackupFiles()) { + return false; + } + return true; + } + + private function getMissingBackupFiles(): array + { + $missingPaths = []; + foreach ($this->checkFiles as $path) { + if (!file_exists($this->backupRootPath . $path)) { + $missingPaths[] = $this->backupRootPath . $path; + } + } + + return $missingPaths; + } + + private function getMissungFiles(): array + { + $missingPaths = []; + foreach ($this->checkFiles as $path) { + if (!file_exists($this->mmlcRootPath . $path)) { + $missingPaths[] = $this->backupRootPath . $path; + } + } + + return $missingPaths; + } + + private function render(string $html): string + { + $html = "" + . "

MMLC Restore

\n" + . "$html
\n"; + + $html = '' + . '
' + . $html + . '
'; + + return $html; + } + + private function outDie(string $message): void + { + echo $this->render($message); + die(); + } +} + +$restore = new Restore(); +$restore->invoke(); diff --git a/tests/unit/AppTest.php b/tests/unit/AppTest.php index ab272b97..fa5c96e4 100644 --- a/tests/unit/AppTest.php +++ b/tests/unit/AppTest.php @@ -18,7 +18,6 @@ class AppTest extends TestCase { - public function testRootDirectoryEndsWidthModifiedModuleLoaderClient() { $this->assertStringEndsWith('ModifiedModuleLoaderClient', App::getRoot()); diff --git a/tests/unit/MmlcVersionInfoLoaderTest.php b/tests/unit/MmlcVersionInfoLoaderTest.php new file mode 100644 index 00000000..277a67bf --- /dev/null +++ b/tests/unit/MmlcVersionInfoLoaderTest.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit; + +use PHPUnit\Framework\TestCase; +use RobinTheHood\ModifiedModuleLoaderClient\Api\V1\ApiRequest; +use RobinTheHood\ModifiedModuleLoaderClient\MmlcVersionInfo; +use RobinTheHood\ModifiedModuleLoaderClient\MmlcVersionInfoLoader; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Filter; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Sorter; + +class MmlcVersionInfoLoaderTest extends TestCase +{ + public function testCanGetAll() + { + $mmlcVersionInfoLoader = $this->getMmlcVersionInfoLoader(); + + $mmlcVersionInfos = $mmlcVersionInfoLoader->getAll(); + + $this->assertContainsOnlyInstancesOf(MmlcVersionInfo::class, $mmlcVersionInfos); + $this->assertCount(13, $mmlcVersionInfos); + $this->assertEquals('1.1.1-alpha', $mmlcVersionInfos[0]->version); + $this->assertEquals('ModifiedModuleLoaderClient_v1.1.1-alpha.tar', $mmlcVersionInfos[0]->fileName); + } + + public function testCanGetNewestStable() + { + $mmlcVersionInfoLoader = $this->getMmlcVersionInfoLoader(); + + $mmlcVersionInfo = $mmlcVersionInfoLoader->getNewest(); + $this->assertEquals('2.1.0', $mmlcVersionInfo->version); + } + + public function testCanGetNewestUnstable() + { + $mmlcVersionInfoLoader = $this->getMmlcVersionInfoLoader(); + + $mmlcVersionInfo = $mmlcVersionInfoLoader->getNewest(true); + $this->assertEquals('2.2.0-beta', $mmlcVersionInfo->version); + } + + public function testCanGetNextNewestStable() + { + $mmlcVersionInfoLoader = $this->getMmlcVersionInfoLoader(); + + $mmlcVersionInfo = $mmlcVersionInfoLoader->getNextNewest('1.2.1'); + $this->assertEquals('1.3.0', $mmlcVersionInfo->version); + } + + public function testCanGetNextNewestUnstable() + { + $mmlcVersionInfoLoader = $this->getMmlcVersionInfoLoader(); + + $mmlcVersionInfo = $mmlcVersionInfoLoader->getNextNewest('1.2.1', true); + $this->assertEquals('1.3.0', $mmlcVersionInfo->version); + + $mmlcVersionInfo = $mmlcVersionInfoLoader->getNextNewest('2.1.0', true); + $this->assertEquals('2.2.0-beta', $mmlcVersionInfo->version); + } + + private function getMmlcVersionInfoLoader(): MmlcVersionInfoLoader + { + $parser = new Parser(); + $comparator = new Comparator($parser); + $sorter = new Sorter($comparator); + $filter = new Filter($parser, $comparator, $sorter); + + $mmlcVersionInfoLoader = new MmlcVersionInfoLoader( + $this->getMockedApiRequest(), + $parser, + $filter + ); + + return $mmlcVersionInfoLoader; + } + + private function getMockedApiRequest(): ApiRequest + { + /** @var MockObject $mockedApiRequest */ + $mockedApiRequest = $this->createMock(ApiRequest::class); + $mockedApiRequest->method('getAllVersions')->willReturn([ + 'content' => [ + [ + 'version' => '1.1.1-alpha', + 'fileName' => 'ModifiedModuleLoaderClient_v1.1.1-alpha.tar' + ], + [ + 'version' => '1.1.2-alpha', + 'fileName' => 'ModifiedModuleLoaderClient_v1.1.2-alpha.tar' + ], + [ + 'version' => '1.1.2', + 'fileName' => 'ModifiedModuleLoaderClient_v1.1.2.tar' + ], + [ + 'version' => '1.1.3-alpha', + 'fileName' => 'ModifiedModuleLoaderClient_v1.1.3-alpha.tar' + ], + [ + 'version' => '1.2.0', + 'fileName' => 'ModifiedModuleLoaderClient_v1.2.0.tar' + ], + [ + 'version' => '1.2.1', + 'fileName' => 'ModifiedModuleLoaderClient_v1.2.1.tar' + ], + [ + 'version' => '1.3.0-beta.1', + 'fileName' => 'ModifiedModuleLoaderClient_v1.3.0-beta.1.tar' + ], + [ + 'version' => '1.3.0', + 'fileName' => 'ModifiedModuleLoaderClient_v1.3.0.tar' + ], + [ + 'version' => '1.3.1', + 'fileName' => 'ModifiedModuleLoaderClient_v1.3.1.tar' + ], + [ + 'version' => '2.0.0', + 'fileName' => 'ModifiedModuleLoaderClient_v2.0.0.tar' + ], + [ + 'version' => '2.1.0', + 'fileName' => 'ModifiedModuleLoaderClient_v2.1.0.tar' + ], + [ + 'version' => '2.1.1-beta', + 'fileName' => 'ModifiedModuleLoaderClient_v2.1.1-beta.tar' + ], + [ + 'version' => '2.2.0-beta', + 'fileName' => 'ModifiedModuleLoaderClient_v2.2.0-beta.tar' + ] + ] + ]); + + /** @var ApiRequest $mockedApiRequest */ + return $mockedApiRequest; + } +} diff --git a/tests/unit/SelfUpdaterTest.php b/tests/unit/SelfUpdaterTest.php index feca0f9c..ade13877 100644 --- a/tests/unit/SelfUpdaterTest.php +++ b/tests/unit/SelfUpdaterTest.php @@ -47,51 +47,19 @@ public function getStubedApiRequest() return $stubApiRequest; } - public function testGetInstalledVersion() + public function testNothing() { - $selfUpdater = new SelfUpdater(); - $version = $selfUpdater->getInstalledVersion(); - - $this->assertNotEmpty($version); - $this->assertIsString($version); - } - - public function testGetNewestVersionInfo() - { - // Arrage - $stubApiRequest = $this->getStubedApiRequest(); - $selfUpdater = new SelfUpdater($stubApiRequest); - - // Act - $newestLatestVersionInfo = $selfUpdater->getNewestVersionInfo(true); - $newestStabelVersionInfo = $selfUpdater->getNewestVersionInfo(false); - - // Assert - $expectsLatest = [ - 'version' => '1.1.3-alpha', - 'fileName' => 'ModifiedModuleLoaderClient_v1.1.3-alpha.tar' - ]; - - $expectsStable = [ - 'version' => '1.1.2', - 'fileName' => 'ModifiedModuleLoaderClient_v1.1.2.tar' - ]; - - $this->assertEquals($expectsLatest, $newestLatestVersionInfo); - $this->assertEquals($expectsStable, $newestStabelVersionInfo); + $this->assertTrue(true); } - public function testGetVersionInfos() + public function getStubedSelfUpdater() { - // Arrage - $stubApiRequest = $this->getStubedApiRequest(); - $selfUpdater = new SelfUpdater($stubApiRequest); - - // Act - $versionInfos = $selfUpdater->getVersionInfos(true); + /** @var MockObject $stubSelfUpdater */ + $stubSelfUpdater = $this->getMockBuilder(SelfUpdater::class) + ->onlyMethods(['getVersionInfos', 'getInstalledVersion']) + ->getMock(); - // Assert - $expects = [ + $stubSelfUpdater->method('getVersionInfos')->willReturn([ [ 'version' => '1.1.1-alpha', 'fileName' => 'ModifiedModuleLoaderClient_v1.1.1-alpha.tar' @@ -108,8 +76,138 @@ public function testGetVersionInfos() 'version' => '1.1.3-alpha', 'fileName' => 'ModifiedModuleLoaderClient_v1.1.3-alpha.tar' ], - ]; + [ + 'version' => '1.2.0', + 'fileName' => 'ModifiedModuleLoaderClient_v1.2.0.tar' + ], + [ + 'version' => '1.2.1', + 'fileName' => 'ModifiedModuleLoaderClient_v1.2.1.tar' + ], + [ + 'version' => '1.3.0-beta.1', + 'fileName' => 'ModifiedModuleLoaderClient_v1.3.0-beta.1.tar' + ], + [ + 'version' => '1.3.0', + 'fileName' => 'ModifiedModuleLoaderClient_v1.3.0.tar' + ], + [ + 'version' => '1.3.1', + 'fileName' => 'ModifiedModuleLoaderClient_v1.3.1.tar' + ], + [ + 'version' => '2.0.0', + 'fileName' => 'ModifiedModuleLoaderClient_v2.0.0.tar' + ], + [ + 'version' => '2.1.0', + 'fileName' => 'ModifiedModuleLoaderClient_v2.1.0.tar' + ] + ]); - $this->assertEquals($expects, $versionInfos); + return $stubSelfUpdater; } + + // public function testGetInstalledVersion() + // { + // $selfUpdater = new SelfUpdater(); + // $version = $selfUpdater->getInstalledVersion(); + + // $this->assertNotEmpty($version); + // $this->assertIsString($version); + // } + + // public function testGetNewestVersionInfo() + // { + // // Arrage + // $stubApiRequest = $this->getStubedApiRequest(); + // $selfUpdater = new SelfUpdater($stubApiRequest); + + // // Act + // $newestLatestVersionInfo = $selfUpdater->getNewestVersionInfo(true); + // $newestStabelVersionInfo = $selfUpdater->getNewestVersionInfo(false); + + // // Assert + // $expectsLatest = [ + // 'version' => '1.1.3-alpha', + // 'fileName' => 'ModifiedModuleLoaderClient_v1.1.3-alpha.tar' + // ]; + + // $expectsStable = [ + // 'version' => '1.1.2', + // 'fileName' => 'ModifiedModuleLoaderClient_v1.1.2.tar' + // ]; + + // $this->assertEquals($expectsLatest, $newestLatestVersionInfo); + // $this->assertEquals($expectsStable, $newestStabelVersionInfo); + // } + + // public function testGetVersionInfos() + // { + // // Arrage + // $stubApiRequest = $this->getStubedApiRequest(); + // $selfUpdater = new SelfUpdater($stubApiRequest); + + // // Act + // $versionInfos = $selfUpdater->getVersionInfos(true); + + // // Assert + // $expects = [ + // [ + // 'version' => '1.1.1-alpha', + // 'fileName' => 'ModifiedModuleLoaderClient_v1.1.1-alpha.tar' + // ], + // [ + // 'version' => '1.1.2-alpha', + // 'fileName' => 'ModifiedModuleLoaderClient_v1.1.2-alpha.tar' + // ], + // [ + // 'version' => '1.1.2', + // 'fileName' => 'ModifiedModuleLoaderClient_v1.1.2.tar' + // ], + // [ + // 'version' => '1.1.3-alpha', + // 'fileName' => 'ModifiedModuleLoaderClient_v1.1.3-alpha.tar' + // ], + // ]; + + // $this->assertEquals($expects, $versionInfos); + // } + + // public function testCanGetNextNewestPathVersionInfo() + // { + // // 1.1.0 to 1.2.0 + // $selfUpdater = $this->getStubedSelfUpdater(); + // $selfUpdater->method('getInstalledVersion')->willReturn('1.1.0'); + // $this->assertEquals( + // [ + // 'version' => '1.2.0', + // 'fileName' => 'ModifiedModuleLoaderClient_v1.2.0.tar' + // ], + // $selfUpdater->getNextNewestVersionInfo() + // ); + + // // 1.2.0 to 1.3.0 + // $selfUpdater = $this->getStubedSelfUpdater(); + // $selfUpdater->method('getInstalledVersion')->willReturn('1.2.0'); + // $this->assertEquals( + // [ + // 'version' => '1.3.0', + // 'fileName' => 'ModifiedModuleLoaderClient_v1.3.0.tar' + // ], + // $selfUpdater->getNextNewestVersionInfo() + // ); + + // // 1.3.0 to 1.3.1 + // $selfUpdater = $this->getStubedSelfUpdater(); + // $selfUpdater->method('getInstalledVersion')->willReturn('1.3.0'); + // $this->assertEquals( + // [ + // 'version' => '1.3.1', + // 'fileName' => 'ModifiedModuleLoaderClient_v1.3.1.tar' + // ], + // $selfUpdater->getNextNewestVersionInfo() + // ); + // } } diff --git a/tests/unit/SemverTests/SemverComparatorTest.php b/tests/unit/SemverTests/SemverComparatorTest.php index 70d0f5fc..9b4137f7 100644 --- a/tests/unit/SemverTests/SemverComparatorTest.php +++ b/tests/unit/SemverTests/SemverComparatorTest.php @@ -107,74 +107,6 @@ public function testSemverCanHandleNotEqualTo() $this->assertFalse($this->comparator->notEqualTo('1.2.3', '1.2.3')); } - public function testSemverCanSortVersions() - { - $versions = [ - '17.111.9', - '1.2.3', - '18.22.10', - '18.33.10', - '18.22.9' - ]; - - $expected = [ - '1.2.3', - '17.111.9', - '18.22.9', - '18.22.10', - '18.33.10' - ]; - - $this->assertEquals($expected, $this->comparator->sort($versions)); - } - - public function testSemverCanSortReverseVersions() - { - $versions = [ - '17.111.9', - '1.2.3', - '18.22.10', - '18.33.10', - '18.22.9' - ]; - - $expected = [ - '18.33.10', - '18.22.10', - '18.22.9', - '17.111.9', - '1.2.3' - ]; - - $this->assertEquals($expected, $this->comparator->rsort($versions)); - } - - public function testSemverGetsHighestVersionString() - { - $versions = [ - '17.111.9', - '1.2.3', - '18.22.10', - '18.33.10', - '18.22.9' - ]; - - $this->assertEquals('18.33.10', $this->comparator->highest($versions)); - } - - public function testSemverGetsLowestVersionString() - { - $versions = [ - '17.111.9', - '1.2.3', - '18.22.10', - '18.33.10', - '18.22.9' - ]; - - $this->assertEquals('1.2.3', $this->comparator->lowest($versions)); - } - public function testThatVersionAIsCompatibleWithVersionB() { $this->assertTrue($this->comparator->isCompatible('auto', '3.3.3')); @@ -188,7 +120,7 @@ public function testThatVersionAIsCompatibleWithVersionB() $this->assertFalse($this->comparator->isCompatible('3.3.3', '3.3.4')); } - public function testThatVersionASatisfiesContraint() + public function testThatVersionASatisfiesConstraint() { $this->assertTrue($this->comparator->satisfies('3.3.3', '^3.3.3')); $this->assertTrue($this->comparator->satisfies('3.3.3', '^3.2.3')); @@ -196,7 +128,7 @@ public function testThatVersionASatisfiesContraint() $this->assertFalse($this->comparator->satisfies('3.3.3', '3.2.3')); } - public function testThatVersionASatisfiesOrContraint() + public function testThatVersionASatisfiesOrConstraint() { $this->assertTrue($this->comparator->satisfiesOr('3.3.3', '^2.2.2 || ^3.3.3')); $this->assertFalse($this->comparator->satisfiesOr('4.4.4', '^2.2.2 || ^3.3.3')); diff --git a/tests/unit/SemverTests/SemverFilterTest.php b/tests/unit/SemverTests/SemverFilterTest.php new file mode 100644 index 00000000..078fb2e5 --- /dev/null +++ b/tests/unit/SemverTests/SemverFilterTest.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit\SemverTests; + +use PHPUnit\Framework\TestCase; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Filter; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Sorter; + +class SemverFilterTest extends TestCase +{ + /** @var Filter */ + public $filter; + + protected function setUp(): void + { + $parser = new Parser(); + $comparator = new Comparator($parser); + $this->filter = new Filter($parser, new Comparator($parser), new Sorter($comparator)); + } + + public function testSemverGetsHighestVersionString() + { + $versions = [ + '17.111.9', + '1.2.3', + '18.22.10', + '18.33.10', + '18.22.9' + ]; + + $this->assertEquals('18.33.10', $this->filter->latest($versions)); + } + + public function testSemverGetsLowestVersionString() + { + $versions = [ + '17.111.9', + '1.2.3', + '18.22.10', + '18.33.10', + '18.22.9' + ]; + + $this->assertEquals('1.2.3', $this->filter->oldest($versions)); + } + + public function testCanFilterByConstraint() + { + $versions = [ + '17.111.9', + '1.2.3', + '18.22.10', + '18.33.10', + '18.22.9', + '19.1.0', + '19.0.0' + ]; + + $resultVersions = [ + '18.22.10', + '18.33.10', + '18.22.9' + ]; + + $this->assertEquals($resultVersions, $this->filter->byConstraint('^18.0.0', $versions)); + + $resultVersions = [ + '19.1.0', + '19.0.0' + ]; + + $this->assertEquals($resultVersions, $this->filter->byConstraint('^19.0.0', $versions)); + } + + public function testCanGetLatestByConstraint() + { + $versions = [ + '17.111.9', + '1.2.3', + '18.22.10', + '18.33.10', + '18.22.9', + '19.1.0', + '19.0.0', + '19.0.0-beta.1' + ]; + + $this->assertEquals('', $this->filter->latestByConstraint('^0.0.0', $versions)); + $this->assertEquals('', $this->filter->latestByConstraint('^2.0.0', $versions)); + $this->assertEquals('18.33.10', $this->filter->latestByConstraint('^18.0.0', $versions)); + $this->assertEquals('19.1.0', $this->filter->latestByConstraint('^19.0.0', $versions)); + } + + public function testCanGetOldestByConstraint() + { + $versions = [ + '17.111.9', + '1.2.3', + '18.22.10', + '18.33.10', + '18.22.9', + '19.1.0', + '19.0.0', + '19.0.0-beta.1' + ]; + + $this->assertEquals('', $this->filter->oldestByConstraint('^0.0.0', $versions)); + $this->assertEquals('', $this->filter->oldestByConstraint('^2.0.0', $versions)); + $this->assertEquals('18.22.9', $this->filter->oldestByConstraint('^18.0.0', $versions)); + $this->assertEquals('19.0.0-beta.1', $this->filter->oldestByConstraint('^19.0.0-beta', $versions)); + $this->assertEquals('19.0.0', $this->filter->oldestByConstraint('^19.0.0', $versions)); + } + + public function testCanFilterStable() + { + $versions = [ + '18.33.10', + '19.0.0-beta.1', + '18.22.9', + '19.1.0-alpha.1', + '19.0.0' + ]; + + $resultVersions = [ + '18.33.10', + '18.22.9', + '19.0.0' + ]; + + $this->assertEquals($resultVersions, $this->filter->stable($versions)); + } +} diff --git a/tests/unit/SemverTests/SemverSorterTest.php b/tests/unit/SemverTests/SemverSorterTest.php new file mode 100644 index 00000000..92063b43 --- /dev/null +++ b/tests/unit/SemverTests/SemverSorterTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit\SemverTests; + +use PHPUnit\Framework\TestCase; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Sorter; + +class SemverSorterTest extends TestCase +{ + /** @var Sorter */ + public $sorter; + + protected function setUp(): void + { + $comparator = new Comparator(new Parser()); + $this->sorter = new Sorter($comparator); + } + + public function testSemverCanSortVersions() + { + $versions = [ + '17.111.9', + '1.2.3', + '18.22.10', + '18.33.10', + '18.22.9' + ]; + + $expected = [ + '1.2.3', + '17.111.9', + '18.22.9', + '18.22.10', + '18.33.10' + ]; + + $this->assertEquals($expected, $this->sorter->sort($versions)); + } + + public function testSemverCanSortReverseVersions() + { + $versions = [ + '17.111.9', + '1.2.3', + '18.22.10', + '18.33.10', + '18.22.9' + ]; + + $expected = [ + '18.33.10', + '18.22.10', + '18.22.9', + '17.111.9', + '1.2.3' + ]; + + $this->assertEquals($expected, $this->sorter->rsort($versions)); + } +} diff --git a/tests/unit/SemverTests/SemverVersionTest.php b/tests/unit/SemverTests/SemverVersionTest.php new file mode 100644 index 00000000..6e3062fe --- /dev/null +++ b/tests/unit/SemverTests/SemverVersionTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RobinTheHood\ModifiedModuleLoaderClient\Tests\Unit\SemverTests; + +use PHPUnit\Framework\TestCase; +use RobinTheHood\ModifiedModuleLoaderClient\Semver\Version; + +class SemverVersionTest extends TestCase +{ + public function testCanConvertToArray() + { + $version = new Version(1, 2, 3, 'beta.1'); + $this->assertEquals('1.2.3-beta.1', $version); + + $version = new Version(10, 3, 5, ''); + $this->assertEquals('10.3.5', $version); + } + + public function testCanGetNextMinor() + { + $version = new Version(1, 2, 3, 'beta.1'); + $this->assertEquals('1.2.3-beta.1', $version); + + $nextMinorVersion = $version->nextMinor(); + $this->assertEquals('1.3.0', $nextMinorVersion); + } +}