MediaWiki master
VersionChecker.php
Go to the documentation of this file.
1<?php
21use Composer\Semver\Constraint\Constraint;
22use Composer\Semver\VersionParser;
23
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 );
164 if ( $mwError !== false ) {
165 $errors[] = [
166 'msg' =>
167 "{$extension} is not compatible with the current MediaWiki "
168 . "core (version {$this->coreVersion->getPrettyString()}), "
169 . "it requires: $values.",
170 'type' => 'incompatible-core',
171 ];
172 }
173 break;
174 case 'platform':
175 foreach ( $values as $dependency => $constraint ) {
176 if ( $dependency === 'php' ) {
177 // PHP version
178 $phpError = $this->handleDependency(
179 $this->phpVersion,
180 $constraint
181 );
182 if ( $phpError !== false ) {
183 $errors[] = [
184 'msg' =>
185 "{$extension} is not compatible with the current PHP "
186 . "version {$this->phpVersion->getPrettyString()}), "
187 . "it requires: $constraint.",
188 'type' => 'incompatible-php',
189 ];
190 }
191 } elseif ( substr( $dependency, 0, 4 ) === 'ext-' ) {
192 // PHP extensions
193 $phpExtension = substr( $dependency, 4 );
194 if ( $constraint !== '*' ) {
195 throw new UnexpectedValueException( 'Version constraints for '
196 . 'PHP extensions are not supported in ' . $extension );
197 }
198 if ( !in_array( $phpExtension, $this->phpExtensions, true ) ) {
199 $errors[] = [
200 'msg' =>
201 "{$extension} requires {$phpExtension} PHP extension "
202 . "to be installed.",
203 'type' => 'missing-phpExtension',
204 'missing' => $phpExtension,
205 ];
206 }
207 } elseif ( substr( $dependency, 0, 8 ) === 'ability-' ) {
208 // Other abilities the environment might provide.
209 $ability = substr( $dependency, 8 );
210 if ( !isset( $this->abilities[$ability] ) ) {
211 throw new UnexpectedValueException( 'Dependency type '
212 . $dependency . ' unknown in ' . $extension );
213 }
214 if ( !is_bool( $constraint ) ) {
215 throw new UnexpectedValueException( 'Only booleans are '
216 . 'allowed to to indicate the presence of abilities '
217 . 'in ' . $extension );
218 }
219
220 if ( $constraint &&
221 $this->abilities[$ability] !== true
222 ) {
223 // add custom error message for missing ability if specified
224 $customMessage = '';
225 if ( isset( $this->abilityErrors[$ability] ) ) {
226 $customMessage = ': ' . $this->abilityErrors[$ability];
227 }
228
229 $errors[] = [
230 'msg' =>
231 "{$extension} requires \"{$ability}\" ability"
232 . $customMessage,
233 'type' => 'missing-ability',
234 'missing' => $ability,
235 ];
236 }
237 } else {
238 // add other platform dependencies here
239 throw new UnexpectedValueException( 'Dependency type ' . $dependency .
240 ' unknown in ' . $extension );
241 }
242 }
243 break;
244 case 'extensions':
245 case 'skins':
246 foreach ( $values as $dependency => $constraint ) {
247 $extError = $this->handleExtensionDependency(
248 $dependency, $constraint, $extension, $dependencyType
249 );
250 if ( $extError !== false ) {
251 $errors[] = $extError;
252 }
253 }
254 break;
255 default:
256 throw new UnexpectedValueException( 'Dependency type ' . $dependencyType .
257 ' unknown in ' . $extension );
258 }
259 }
260 }
261
262 return $errors;
263 }
264
273 private function handleDependency( $version, $constraint ) {
274 if ( $version === false ) {
275 // Couldn't parse the version, so we can't check anything
276 return false;
277 }
278
279 // if the installed and required version are compatible, return an empty array
280 if ( $this->versionParser->parseConstraints( $constraint )
281 ->matches( $version ) ) {
282 return false;
283 }
284
285 return true;
286 }
287
297 private function handleExtensionDependency( $dependencyName, $constraint, $checkedExt,
298 $type
299 ) {
300 // Check if the dependency is even installed
301 if ( !isset( $this->loaded[$dependencyName] ) ) {
302 return [
303 'msg' => "{$checkedExt} requires {$dependencyName} to be installed.",
304 'type' => "missing-$type",
305 'missing' => $dependencyName,
306 ];
307 }
308 if ( $constraint === '*' ) {
309 // short-circuit since any version is OK.
310 return false;
311 }
312 // Check if the dependency has specified a version
313 if ( !isset( $this->loaded[$dependencyName]['version'] ) ) {
314 $msg = "{$dependencyName} does not expose its version, but {$checkedExt}"
315 . " requires: {$constraint}.";
316 return [
317 'msg' => $msg,
318 'type' => "incompatible-$type",
319 'incompatible' => $checkedExt,
320 ];
321 } else {
322 // Try to get a constraint for the dependency version
323 try {
324 $installedVersion = new Constraint(
325 '==',
326 $this->versionParser->normalize( $this->loaded[$dependencyName]['version'] )
327 );
328 } catch ( UnexpectedValueException $e ) {
329 // Non-parsable version, output an error message that the version
330 // string is invalid
331 return [
332 'msg' => "$dependencyName does not have a valid version string.",
333 'type' => 'invalid-version',
334 ];
335 }
336 // Check if the constraint actually matches...
337 if (
338 !$this->versionParser->parseConstraints( $constraint )->matches( $installedVersion )
339 ) {
340 $msg = "{$checkedExt} is not compatible with the current "
341 . "installed version of {$dependencyName} "
342 . "({$this->loaded[$dependencyName]['version']}), "
343 . "it requires: " . $constraint . '.';
344 return [
345 'msg' => $msg,
346 'type' => "incompatible-$type",
347 'incompatible' => $checkedExt,
348 ];
349 }
350 }
351
352 return false;
353 }
354}
Check whether extensions and their dependencies meet certain version requirements.
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=[])