4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
20 * @author Florian Schmidt
23 use Composer\Semver\VersionParser
;
24 use Composer\Semver\Constraint\Constraint
;
27 * Provides functions to check a set of extensions with dependencies against
28 * a set of loaded extensions and given version information.
32 class VersionChecker
{
34 * @var Constraint|bool representing $wgVersion
36 private $coreVersion = false;
39 * @var Constraint|bool representing PHP version
41 private $phpVersion = false;
44 * @var string[] List of installed PHP extensions
46 private $phpExtensions = [];
49 * @var bool[] List of provided abilities
51 private $abilities = [];
54 * @var string[] List of provided ability errors
56 private $abilityErrors = [];
59 * @var array Loaded extensions
66 private $versionParser;
69 * @param string $coreVersion Current version of core
70 * @param string $phpVersion Current PHP version
71 * @param string[] $phpExtensions List of installed PHP extensions
72 * @param bool[] $abilities List of provided abilities
73 * @param string[] $abilityErrors Error messages for the abilities
75 public function __construct(
76 $coreVersion, $phpVersion, array $phpExtensions,
77 array $abilities = [], array $abilityErrors = []
79 $this->versionParser
= new VersionParser();
80 $this->setCoreVersion( $coreVersion );
81 $this->setPhpVersion( $phpVersion );
82 $this->phpExtensions
= $phpExtensions;
83 $this->abilities
= $abilities;
84 $this->abilityErrors
= $abilityErrors;
88 * Set an array with credits of all loaded extensions and skins.
90 * @param array $credits An array of installed extensions with credits of them
91 * @return VersionChecker $this
93 public function setLoadedExtensionsAndSkins( array $credits ) {
94 $this->loaded
= $credits;
100 * Set MediaWiki core version.
102 * @param string $coreVersion Current version of core
104 private function setCoreVersion( $coreVersion ) {
106 $this->coreVersion
= new Constraint(
108 $this->versionParser
->normalize( $coreVersion )
110 $this->coreVersion
->setPrettyString( $coreVersion );
111 } catch ( UnexpectedValueException
$e ) {
112 // Non-parsable version, don't fatal.
119 * @param string $phpVersion Current PHP version. Must be well-formed.
120 * @throws UnexpectedValueException
122 private function setPhpVersion( $phpVersion ) {
123 // normalize to make this throw an exception if the version is invalid
124 $this->phpVersion
= new Constraint(
126 $this->versionParser
->normalize( $phpVersion )
128 $this->phpVersion
->setPrettyString( $phpVersion );
132 * Check all given dependencies if they are compatible with the named
133 * installed extensions in the $credits array.
135 * Example $extDependencies:
138 * 'MediaWiki' => '>= 1.25.0',
142 * 'ability-bar': true
145 * 'FooBaz' => '>= 1.25.0'
148 * 'BazBar' => '>= 1.0.0'
153 * @param array $extDependencies All extensions that depend on other ones
156 public function checkArray( array $extDependencies ) {
158 foreach ( $extDependencies as $extension => $dependencies ) {
159 foreach ( $dependencies as $dependencyType => $values ) {
160 switch ( $dependencyType ) {
161 case ExtensionRegistry
::MEDIAWIKI_CORE
:
162 $mwError = $this->handleDependency(
167 if ( $mwError !== false ) {
170 "{$extension} is not compatible with the current MediaWiki "
171 . "core (version {$this->coreVersion->getPrettyString()}), "
172 . "it requires: $values."
174 'type' => 'incompatible-core',
179 foreach ( $values as $dependency => $constraint ) {
180 if ( $dependency === 'php' ) {
182 $phpError = $this->handleDependency(
187 if ( $phpError !== false ) {
190 "{$extension} is not compatible with the current PHP "
191 . "version {$this->phpVersion->getPrettyString()}), "
192 . "it requires: $constraint."
194 'type' => 'incompatible-php',
197 } elseif ( substr( $dependency, 0, 4 ) === 'ext-' ) {
199 $phpExtension = substr( $dependency, 4 );
200 if ( $constraint !== '*' ) {
201 throw new UnexpectedValueException( 'Version constraints for '
202 . 'PHP extensions are not supported in ' . $extension );
204 if ( !in_array( $phpExtension, $this->phpExtensions
, true ) ) {
207 "{$extension} requires {$phpExtension} PHP extension "
210 'type' => 'missing-phpExtension',
211 'missing' => $phpExtension,
214 } elseif ( substr( $dependency, 0, 8 ) === 'ability-' ) {
215 // Other abilities the environment might provide.
216 $ability = substr( $dependency, 8 );
217 if ( !isset( $this->abilities
[$ability] ) ) {
218 throw new UnexpectedValueException( 'Dependency type '
219 . $dependency . ' unknown in ' . $extension );
221 if ( !is_bool( $constraint ) ) {
222 throw new UnexpectedValueException( 'Only booleans are '
223 . 'allowed to to indicate the presence of abilities '
224 . 'in ' . $extension );
227 if ( $constraint === true &&
228 $this->abilities
[$ability] !== true
230 // add custom error message for missing ability if specified
232 if ( isset( $this->abilityErrors
[$ability] ) ) {
233 $customMessage = ': ' . $this->abilityErrors
[$ability];
238 "{$extension} requires \"{$ability}\" ability"
241 'type' => 'missing-ability',
242 'missing' => $ability,
246 // add other platform dependencies here
247 throw new UnexpectedValueException( 'Dependency type ' . $dependency .
248 ' unknown in ' . $extension );
254 foreach ( $values as $dependency => $constraint ) {
255 $extError = $this->handleExtensionDependency(
256 $dependency, $constraint, $extension, $dependencyType
258 if ( $extError !== false ) {
259 $errors[] = $extError;
264 throw new UnexpectedValueException( 'Dependency type ' . $dependencyType .
265 ' unknown in ' . $extension );
274 * Handle a simple dependency to MediaWiki core or PHP. See handleMediaWikiDependency and
275 * handlePhpDependency for details.
277 * @param Constraint|bool $version The version installed
278 * @param string $constraint The required version constraint for this dependency
279 * @param string $checkedExt The Extension, which depends on this dependency
280 * @return bool false if no error, true else
282 private function handleDependency( $version, $constraint, $checkedExt ) {
283 if ( $version === false ) {
284 // Couldn't parse the version, so we can't check anything
288 // if the installed and required version are compatible, return an empty array
289 if ( $this->versionParser
->parseConstraints( $constraint )
290 ->matches( $version ) ) {
298 * Handle a dependency to another extension.
300 * @param string $dependencyName The name of the dependency
301 * @param string $constraint The required version constraint for this dependency
302 * @param string $checkedExt The Extension, which depends on this dependency
303 * @param string $type Either 'extensions' or 'skins'
304 * @return bool|array false for no errors, or an array of info
306 private function handleExtensionDependency( $dependencyName, $constraint, $checkedExt,
309 // Check if the dependency is even installed
310 if ( !isset( $this->loaded
[$dependencyName] ) ) {
312 'msg' => "{$checkedExt} requires {$dependencyName} to be installed.",
313 'type' => "missing-$type",
314 'missing' => $dependencyName,
317 if ( $constraint === '*' ) {
318 // short-circuit since any version is OK.
321 // Check if the dependency has specified a version
322 if ( !isset( $this->loaded
[$dependencyName]['version'] ) ) {
323 $msg = "{$dependencyName} does not expose its version, but {$checkedExt}"
324 . " requires: {$constraint}.";
327 'type' => "incompatible-$type",
328 'incompatible' => $checkedExt,
331 // Try to get a constraint for the dependency version
333 $installedVersion = new Constraint(
335 $this->versionParser
->normalize( $this->loaded
[$dependencyName]['version'] )
337 } catch ( UnexpectedValueException
$e ) {
338 // Non-parsable version, output an error message that the version
341 'msg' => "$dependencyName does not have a valid version string.",
342 'type' => 'invalid-version',
345 // Check if the constraint actually matches...
347 !$this->versionParser
->parseConstraints( $constraint )->matches( $installedVersion )
349 $msg = "{$checkedExt} is not compatible with the current "
350 . "installed version of {$dependencyName} "
351 . "({$this->loaded[$dependencyName]['version']}), "
352 . "it requires: " . $constraint . '.';
355 'type' => "incompatible-$type",
356 'incompatible' => $checkedExt,