Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Classes/IndexController.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,14 @@ public function invokeModuleInfo()
return $this->redirect('/');
}

if ($error = ModuleStatus::hasValidRequire($module)) {
Notification::pushFlashMessage([
'type' => 'error',
'text' => 'Error in require in moduleinfo.json of '
. $module->getArchiveName() . ' ' . $module->getVersion() . ' - ' . $error
]);
}

return $this->render('ModuleInfo', [
'module' => $module
]);
Expand Down
4 changes: 2 additions & 2 deletions src/Classes/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ public function isCompatibleWithPhp(): bool

$phpVersionInstalled = phpversion();
$comparator = new Comparator(new Parser());
return $comparator->satisfiesOr($phpVersionInstalled, $phpVersionContraint);
return $comparator->satisfies($phpVersionInstalled, $phpVersionContraint);
}

/**
Expand All @@ -536,7 +536,7 @@ public function isCompatibleWithMmlc(): bool
}

$comparator = new Comparator(new Parser());
return $comparator->satisfiesOr($mmlcVersionInstalled, $mmlcVersionContraint);
return $comparator->satisfies($mmlcVersionInstalled, $mmlcVersionContraint);
}

public function getTemplateFiles($file): array
Expand Down
15 changes: 15 additions & 0 deletions src/Classes/ModuleStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
namespace RobinTheHood\ModifiedModuleLoaderClient;

use RobinTheHood\ModifiedModuleLoaderClient\Semver\Comparator;
use RobinTheHood\ModifiedModuleLoaderClient\Semver\ConstraintParser;
use RobinTheHood\ModifiedModuleLoaderClient\Semver\ParseErrorException;
use RobinTheHood\ModifiedModuleLoaderClient\Semver\Parser;

class ModuleStatus
Expand Down Expand Up @@ -143,4 +145,17 @@ public static function isUpdatable(Module $module): bool

return $module->isInstalled();
}

public static function hasValidRequire(Module $module): string
{
$constraintParser = new ConstraintParser(new Parser());
foreach ($module->getRequire() as $module => $constraintString) {
try {
$constraintParser->parse($constraintString);
} catch (ParseErrorException $e) {
return $e->getMessage();
}
}
return '';
}
}
65 changes: 49 additions & 16 deletions src/Classes/Semver/Comparator.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,34 +163,67 @@ public function isCompatible(string $versionString1, string $versionString2): bo
return $this->greaterThanOrEqualTo($versionString1, $versionString2);
}

public function satisfies(string $versionString1, string $constrain): bool
public function satisfies(string $versionString1, string $constrainString): 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);
$constraintParser = new ConstraintParser($this->parser);
try {
$constraint = $constraintParser->parse($constrainString);
} catch (ParseErrorException $e) {
return false;
}

if ($constraint->type === Constraint::TYPE_OR) {
return $this->satisfiesOr($versionString1, $constraint);
}

if ($constraint->type === Constraint::TYPE_AND) {
return $this->satisfiesAnd($versionString1, $constraint);
}

if ($constraint->type === Constraint::TYPE_LESS_OR_EQUAL) {
return $this->lessThanOrEqualTo($versionString1, $constraint->versionString);
} elseif ($constraint->type === Constraint::TYPE_LESS) {
return $this->lessThan($versionString1, $constraint->versionString);
} elseif ($constraint->type === Constraint::TYPE_GREATER_OR_EQUAL) {
return $this->greaterThanOrEqualTo($versionString1, $constraint->versionString);
} elseif ($constraint->type === Constraint::TYPE_GREATER) {
return $this->greaterThan($versionString1, $constraint->versionString);
} elseif ($constraint->type === Constraint::TYPE_CARET) {
return $this->isCompatible($versionString1, $constraint->versionString);
} elseif ($constraint->type === Constraint::TYPE_EQUAL) {
return $this->equalTo($versionString1, $constraint->versionString);
}

return false;
}

/**
* Can satisfy multiple constraints with OR / ||
* Can satisfy multiple constraints with OR (||)
*
* Example: ^7.4 || ^8.0
*/
public function satisfiesOr(string $versionString1, string $constraintOrExpression): bool
public function satisfiesOr(string $versionString1, Constraint $constraint): bool
{
$constraints = explode('||', $constraintOrExpression);
foreach ($constraints as $constraint) {
$constraint = trim($constraint);
if ($this->satisfies($versionString1, $constraint)) {
foreach ($constraint->constraints as $constraint) {
if ($this->satisfies($versionString1, $constraint->constraintString)) {
return true;
}
}
return false;
}

/**
* Can satisfy multiple constraints with AND (,)
*
* Example: ^7.4, ^8.0
*/
public function satisfiesAnd(string $versionString1, Constraint $constraint): bool
{
foreach ($constraint->constraints as $constraint) {
if (!$this->satisfies($versionString1, $constraint->constraintString)) {
return false;
}
}
return true;
}
}
81 changes: 81 additions & 0 deletions src/Classes/Semver/Constraint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

/*
* This file is part of MMLC - ModifiedModuleLoaderClient.
*
* (c) Robin Wieschendorf <mail@robinwieschendorf.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace RobinTheHood\ModifiedModuleLoaderClient\Semver;

class Constraint
{
public const TYPE_EQUAL = '=';
public const TYPE_OR = '||';
public const TYPE_AND = ',';
public const TYPE_CARET = '^';
public const TYPE_LESS_OR_EQUAL = '<=';
public const TYPE_LESS = '<';
public const TYPE_GREATER_OR_EQUAL = '>=';
public const TYPE_GREATER = '>';

/** @var string $type */
public $type;

/** @var string $constraintString */
public $constraintString;

/** @var Version $version */
public $version;

/** @var string $versionString */
public $versionString;

/** @var Constraint[] $constraints */
public $constraints = [];

public static function createConstraintFromConstraints(array $constraints): string
{
$constraint = '';
foreach ($constraints as $version) {
if ($constraint === '') {
$constraint = $version;
} else {
$constraint .= ', ' . $version;
}
}
return $constraint;
}

public static function resolveCaretRange(string $range): string
{
if (preg_match('/^\^(?<major>\d+)(\.(?<minor>\d+))?(\.(?<patch>\d+))?(?<suffix>.*)$/', $range, $matches)) {
$major = intval($matches['major']);
$minor = isset($matches['minor']) ? intval($matches['minor']) : 0;
$patch = isset($matches['patch']) ? intval($matches['patch']) : 0;
$suffix = $matches['suffix'];

$lower = sprintf("%d.%d.%d%s", $major, $minor, $patch, $suffix);

if ($major == 0) {
$upper = sprintf("%d.%d.%d", $major, $minor + 1, 0);
} else {
$upper = sprintf("%d.%d.%s", $major + 1, 0, '0');
}

return ">=$lower,<$upper";
}

return $range;
}

public static function resolve(string $constaint): string
{
return self::resolveCaretRange($constaint);
}
}
99 changes: 99 additions & 0 deletions src/Classes/Semver/ConstraintParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

/*
* This file is part of MMLC - ModifiedModuleLoaderClient.
*
* (c) Robin Wieschendorf <mail@robinwieschendorf.de>
*
* 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\ParseErrorException;

class ConstraintParser
{
/** @var Parser $parser */
private $parser;

public function __construct(Parser $parser)
{
$this->parser = $parser;
}

public function parse(string $constraintString): Constraint
{
$constraint = new Constraint();
$constraint->constraintString = $constraintString;

if (strpos($constraintString, '||')) {
$constraint->type = Constraint::TYPE_OR;
$subConstraintStrings = explode('||', $constraintString);
foreach ($subConstraintStrings as $subConstraintString) {
$subConstraintString = trim($subConstraintString);
$subConstraint = $this->parse($subConstraintString);
$constraint->constraints[] = $subConstraint;
}
return $constraint;
} elseif (strpos($constraintString, ',')) {
$constraint->type = Constraint::TYPE_AND;
$subConstraintStrings = explode(',', $constraintString);
foreach ($subConstraintStrings as $subConstraintString) {
$subConstraintString = trim($subConstraintString);
$subConstraint = $this->parse($subConstraintString);
$constraint->constraints[] = $subConstraint;
}
return $constraint;
} elseif (strpos($constraintString, '^') === 0) {
$constraint->type = Constraint::TYPE_CARET;
$versionString = substr($constraintString, 1);
$version = $this->parser->parse($versionString);
$constraint->version = $version;
$constraint->versionString = $versionString;
return $constraint;
} elseif (strpos($constraintString, '<=') === 0) {
$constraint->type = Constraint::TYPE_LESS_OR_EQUAL;
$versionString = substr($constraintString, 2);
$version = $this->parser->parse($versionString);
$constraint->version = $version;
$constraint->versionString = $versionString;
return $constraint;
} elseif (strpos($constraintString, '<') === 0) {
$constraint->type = Constraint::TYPE_LESS;
$versionString = substr($constraintString, 1);
$version = $this->parser->parse($versionString);
$constraint->version = $version;
$constraint->versionString = $versionString;
return $constraint;
} elseif (strpos($constraintString, '>=') === 0) {
$constraint->type = Constraint::TYPE_GREATER_OR_EQUAL;
$versionString = substr($constraintString, 2);
$version = $this->parser->parse($versionString);
$constraint->version = $version;
$constraint->versionString = $versionString;
return $constraint;
} elseif (strpos($constraintString, '>') === 0) {
$constraint->type = Constraint::TYPE_GREATER;
$versionString = substr($constraintString, 1);
$version = $this->parser->parse($versionString);
$constraint->version = $version;
$constraint->versionString = $versionString;
return $constraint;
}

try {
$constraint->type = Constraint::TYPE_EQUAL;
$versionString = $constraintString;
$version = $this->parser->parse($versionString);
$constraint->version = $version;
$constraint->versionString = $versionString;
return $constraint;
} catch (ParseErrorException $e) {
throw new ParseErrorException('Unsupported version constraint ' . $constraintString);
}
}
}
2 changes: 1 addition & 1 deletion src/Classes/Semver/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function byConstraint(string $constraint, array $versions): array
{
$fileredVersions = [];
foreach ($versions as $version) {
if ($this->comparator->satisfiesOr($version, $constraint)) {
if ($this->comparator->satisfies($version, $constraint)) {
$fileredVersions[] = $version;
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/SemverTests/SemverComparatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public function testThatVersionASatisfiesConstraint()

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'));
$this->assertTrue($this->comparator->satisfies('3.3.3', '^2.2.2 || ^3.3.3'));
$this->assertFalse($this->comparator->satisfies('4.4.4', '^2.2.2 || ^3.3.3'));
}
}