MediaWiki REL1_39
VersionChecker.php
Go to the documentation of this file.
1<?php
2
23use Composer\Semver\Constraint\Constraint;
24use Composer\Semver\VersionParser;
25
36 private $coreVersion = false;
37
41 private $phpVersion = false;
42
46 private $phpExtensions;
47
51 private $abilities;
52
56 private $abilityErrors;
57
61 private $loaded = [];
62
66 private $versionParser;
67
75 public function __construct(
76 $coreVersion, $phpVersion, array $phpExtensions,
77 array $abilities = [], array $abilityErrors = []
78 ) {
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;
85 }
86
93 public function setLoadedExtensionsAndSkins( array $credits ) {
94 $this->loaded = $credits;
95
96 return $this;
97 }
98
104 private function setCoreVersion( $coreVersion ) {
105 try {
106 $this->coreVersion = new Constraint(
107 '==',
108 $this->versionParser->normalize( $coreVersion )
109 );
110 $this->coreVersion->setPrettyString( $coreVersion );
111 } catch ( UnexpectedValueException $e ) {
112 // Non-parsable version, don't fatal.
113 }
114 }
115
120 private function setPhpVersion( $phpVersion ) {
121 // normalize to make this throw an exception if the version is invalid
122 $this->phpVersion = new Constraint(
123 '==',
124 $this->versionParser->normalize( $phpVersion )
125 );
126 $this->phpVersion->setPrettyString( $phpVersion );
127 }
128
154 public function checkArray( array $extDependencies ) {
155 $errors = [];
156 foreach ( $extDependencies as $extension => $dependencies ) {
157 foreach ( $dependencies as $dependencyType => $values ) {
158 switch ( $dependencyType ) {
159 case ExtensionRegistry::MEDIAWIKI_CORE:
160 $mwError = $this->handleDependency(
161 $this->coreVersion,
162 $values,
163 $extension
164 );
165 if ( $mwError !== false ) {
166 $errors[] = [
167 'msg' =>
168 "{$extension} is not compatible with the current MediaWiki "
169 . "core (version {$this->coreVersion->getPrettyString()}), "
170 . "it requires: $values."
171 ,
172 'type' => 'incompatible-core',
173 ];
174 }
175 break;
176 case 'platform':
177 foreach ( $values as $dependency => $constraint ) {
178 if ( $dependency === 'php' ) {
179 // PHP version
180 $phpError = $this->handleDependency(
181 $this->phpVersion,
182 $constraint,
183 $extension
184 );
185 if ( $phpError !== false ) {
186 $errors[] = [
187 'msg' =>
188 "{$extension} is not compatible with the current PHP "
189 . "version {$this->phpVersion->getPrettyString()}), "
190 . "it requires: $constraint."
191 ,
192 'type' => 'incompatible-php',
193 ];
194 }
195 } elseif ( substr( $dependency, 0, 4 ) === 'ext-' ) {
196 // PHP extensions
197 $phpExtension = substr( $dependency, 4 );
198 if ( $constraint !== '*' ) {
199 throw new UnexpectedValueException( 'Version constraints for '
200 . 'PHP extensions are not supported in ' . $extension );
201 }
202 if ( !in_array( $phpExtension, $this->phpExtensions, true ) ) {
203 $errors[] = [
204 'msg' =>
205 "{$extension} requires {$phpExtension} PHP extension "
206 . "to be installed."
207 ,
208 'type' => 'missing-phpExtension',
209 'missing' => $phpExtension,
210 ];
211 }
212 } elseif ( substr( $dependency, 0, 8 ) === 'ability-' ) {
213 // Other abilities the environment might provide.
214 $ability = substr( $dependency, 8 );
215 if ( !isset( $this->abilities[$ability] ) ) {
216 throw new UnexpectedValueException( 'Dependency type '
217 . $dependency . ' unknown in ' . $extension );
218 }
219 if ( !is_bool( $constraint ) ) {
220 throw new UnexpectedValueException( 'Only booleans are '
221 . 'allowed to to indicate the presence of abilities '
222 . 'in ' . $extension );
223 }
224
225 if ( $constraint &&
226 $this->abilities[$ability] !== true
227 ) {
228 // add custom error message for missing ability if specified
229 $customMessage = '';
230 if ( isset( $this->abilityErrors[$ability] ) ) {
231 $customMessage = ': ' . $this->abilityErrors[$ability];
232 }
233
234 $errors[] = [
235 'msg' =>
236 "{$extension} requires \"{$ability}\" ability"
237 . $customMessage
238 ,
239 'type' => 'missing-ability',
240 'missing' => $ability,
241 ];
242 }
243 } else {
244 // add other platform dependencies here
245 throw new UnexpectedValueException( 'Dependency type ' . $dependency .
246 ' unknown in ' . $extension );
247 }
248 }
249 break;
250 case 'extensions':
251 case 'skins':
252 foreach ( $values as $dependency => $constraint ) {
253 $extError = $this->handleExtensionDependency(
254 $dependency, $constraint, $extension, $dependencyType
255 );
256 if ( $extError !== false ) {
257 $errors[] = $extError;
258 }
259 }
260 break;
261 default:
262 throw new UnexpectedValueException( 'Dependency type ' . $dependencyType .
263 ' unknown in ' . $extension );
264 }
265 }
266 }
267
268 return $errors;
269 }
270
280 private function handleDependency( $version, $constraint, $checkedExt ) {
281 if ( $version === false ) {
282 // Couldn't parse the version, so we can't check anything
283 return false;
284 }
285
286 // if the installed and required version are compatible, return an empty array
287 if ( $this->versionParser->parseConstraints( $constraint )
288 ->matches( $version ) ) {
289 return false;
290 }
291
292 return true;
293 }
294
304 private function handleExtensionDependency( $dependencyName, $constraint, $checkedExt,
305 $type
306 ) {
307 // Check if the dependency is even installed
308 if ( !isset( $this->loaded[$dependencyName] ) ) {
309 return [
310 'msg' => "{$checkedExt} requires {$dependencyName} to be installed.",
311 'type' => "missing-$type",
312 'missing' => $dependencyName,
313 ];
314 }
315 if ( $constraint === '*' ) {
316 // short-circuit since any version is OK.
317 return false;
318 }
319 // Check if the dependency has specified a version
320 if ( !isset( $this->loaded[$dependencyName]['version'] ) ) {
321 $msg = "{$dependencyName} does not expose its version, but {$checkedExt}"
322 . " requires: {$constraint}.";
323 return [
324 'msg' => $msg,
325 'type' => "incompatible-$type",
326 'incompatible' => $checkedExt,
327 ];
328 } else {
329 // Try to get a constraint for the dependency version
330 try {
331 $installedVersion = new Constraint(
332 '==',
333 $this->versionParser->normalize( $this->loaded[$dependencyName]['version'] )
334 );
335 } catch ( UnexpectedValueException $e ) {
336 // Non-parsable version, output an error message that the version
337 // string is invalid
338 return [
339 'msg' => "$dependencyName does not have a valid version string.",
340 'type' => 'invalid-version',
341 ];
342 }
343 // Check if the constraint actually matches...
344 if (
345 !$this->versionParser->parseConstraints( $constraint )->matches( $installedVersion )
346 ) {
347 $msg = "{$checkedExt} is not compatible with the current "
348 . "installed version of {$dependencyName} "
349 . "({$this->loaded[$dependencyName]['version']}), "
350 . "it requires: " . $constraint . '.';
351 return [
352 'msg' => $msg,
353 'type' => "incompatible-$type",
354 'incompatible' => $checkedExt,
355 ];
356 }
357 }
358
359 return false;
360 }
361}
Provides functions to check a set of extensions with dependencies against a set of loaded extensions ...
setLoadedExtensionsAndSkins(array $credits)
Set an array with credits of all loaded extensions and skins.
checkArray(array $extDependencies)
Check all given dependencies if they are compatible with the named installed extensions in the $credi...
__construct( $coreVersion, $phpVersion, array $phpExtensions, array $abilities=[], array $abilityErrors=[])