MediaWiki master
VersionChecker.php
Go to the documentation of this file.
1<?php
8
9use Composer\Semver\Constraint\Constraint;
10use Composer\Semver\VersionParser;
11use UnexpectedValueException;
12
25 private $coreVersion = false;
26
30 private $phpVersion = false;
31
35 private $phpExtensions;
36
40 private $abilities;
41
45 private $abilityErrors;
46
50 private $loaded = [];
51
55 private $versionParser;
56
64 public function __construct(
65 $coreVersion, $phpVersion, array $phpExtensions,
66 array $abilities = [], array $abilityErrors = []
67 ) {
68 $this->versionParser = new VersionParser();
69 $this->setCoreVersion( $coreVersion );
70 $this->setPhpVersion( $phpVersion );
71 $this->phpExtensions = $phpExtensions;
72 $this->abilities = $abilities;
73 $this->abilityErrors = $abilityErrors;
74 }
75
83 public function setLoadedExtensionsAndSkins( array $credits ) {
84 $this->loaded = $credits;
85
86 return $this;
87 }
88
94 private function setCoreVersion( $coreVersion ) {
95 try {
96 $this->coreVersion = new Constraint(
97 '==',
98 $this->versionParser->normalize( $coreVersion )
99 );
100 $this->coreVersion->setPrettyString( $coreVersion );
101 } catch ( UnexpectedValueException ) {
102 // Non-parsable version, don't fatal.
103 }
104 }
105
111 private function setPhpVersion( $phpVersion ) {
112 // normalize to make this throw an exception if the version is invalid
113 $this->phpVersion = new Constraint(
114 '==',
115 $this->versionParser->normalize( $phpVersion )
116 );
117 $this->phpVersion->setPrettyString( $phpVersion );
118 }
119
146 public function checkArray( array $extDependencies ) {
147 $errors = [];
148 foreach ( $extDependencies as $extension => $dependencies ) {
149 foreach ( $dependencies as $dependencyType => $values ) {
150 switch ( $dependencyType ) {
152 $mwError = $this->handleDependency(
153 $this->coreVersion,
154 $values
155 );
156 if ( $mwError !== false ) {
157 $errors[] = [
158 'msg' =>
159 "{$extension} is not compatible with the current MediaWiki "
160 . "core (version {$this->coreVersion->getPrettyString()}), "
161 . "it requires: $values.",
162 'type' => 'incompatible-core',
163 ];
164 }
165 break;
166 case 'platform':
167 foreach ( $values as $dependency => $constraint ) {
168 if ( $dependency === 'php' ) {
169 // PHP version
170 $phpError = $this->handleDependency(
171 $this->phpVersion,
172 $constraint
173 );
174 if ( $phpError !== false ) {
175 $errors[] = [
176 'msg' =>
177 "{$extension} is not compatible with the current PHP "
178 . "version {$this->phpVersion->getPrettyString()}), "
179 . "it requires: $constraint.",
180 'type' => 'incompatible-php',
181 ];
182 }
183 } elseif ( str_starts_with( $dependency, 'ext-' ) ) {
184 // PHP extensions
185 $phpExtension = substr( $dependency, 4 );
186 if ( $constraint !== '*' ) {
187 throw new UnexpectedValueException( 'Version constraints for '
188 . 'PHP extensions are not supported in ' . $extension );
189 }
190 if ( !in_array( $phpExtension, $this->phpExtensions, true ) ) {
191 $errors[] = [
192 'msg' =>
193 "{$extension} requires {$phpExtension} PHP extension "
194 . "to be installed.",
195 'type' => 'missing-phpExtension',
196 'missing' => $phpExtension,
197 ];
198 }
199 } elseif ( str_starts_with( $dependency, 'ability-' ) ) {
200 // Other abilities the environment might provide.
201 $ability = substr( $dependency, 8 );
202 if ( !isset( $this->abilities[$ability] ) ) {
203 throw new UnexpectedValueException( 'Dependency type '
204 . $dependency . ' unknown in ' . $extension );
205 }
206 if ( !is_bool( $constraint ) ) {
207 throw new UnexpectedValueException( 'Only booleans are '
208 . 'allowed to to indicate the presence of abilities '
209 . 'in ' . $extension );
210 }
211
212 if ( $constraint &&
213 $this->abilities[$ability] !== true
214 ) {
215 // add custom error message for missing ability if specified
216 $customMessage = '';
217 if ( isset( $this->abilityErrors[$ability] ) ) {
218 $customMessage = ': ' . $this->abilityErrors[$ability];
219 }
220
221 $errors[] = [
222 'msg' =>
223 "{$extension} requires \"{$ability}\" ability"
224 . $customMessage,
225 'type' => 'missing-ability',
226 'missing' => $ability,
227 ];
228 }
229 } else {
230 // add other platform dependencies here
231 throw new UnexpectedValueException( 'Dependency type ' . $dependency .
232 ' unknown in ' . $extension );
233 }
234 }
235 break;
236 case 'extensions':
237 case 'skins':
238 foreach ( $values as $dependency => $constraint ) {
239 $extError = $this->handleExtensionDependency(
240 $dependency, $constraint, $extension, $dependencyType
241 );
242 if ( $extError !== false ) {
243 $errors[] = $extError;
244 }
245 }
246 break;
247 default:
248 throw new UnexpectedValueException( 'Dependency type ' . $dependencyType .
249 ' unknown in ' . $extension );
250 }
251 }
252 }
253
254 return $errors;
255 }
256
266 private function handleDependency( $version, $constraint ) {
267 if ( $version === false ) {
268 // Couldn't parse the version, so we can't check anything
269 return false;
270 }
271
272 // if the installed and required version are compatible, return an empty array
273 if ( $this->versionParser->parseConstraints( $constraint )
274 ->matches( $version ) ) {
275 return false;
276 }
277
278 return true;
279 }
280
291 private function handleExtensionDependency( $dependencyName, $constraint, $checkedExt,
292 $type
293 ) {
294 // Check if the dependency is even installed
295 if ( !isset( $this->loaded[$dependencyName] ) ) {
296 return [
297 'msg' => "{$checkedExt} requires {$dependencyName} to be installed.",
298 'type' => "missing-$type",
299 'missing' => $dependencyName,
300 ];
301 }
302 if ( $constraint === '*' ) {
303 // short-circuit since any version is OK.
304 return false;
305 }
306 // Check if the dependency has specified a version
307 if ( !isset( $this->loaded[$dependencyName]['version'] ) ) {
308 $msg = "{$dependencyName} does not expose its version, but {$checkedExt}"
309 . " requires: {$constraint}.";
310 return [
311 'msg' => $msg,
312 'type' => "incompatible-$type",
313 'incompatible' => $checkedExt,
314 ];
315 } else {
316 // Try to get a constraint for the dependency version
317 try {
318 $installedVersion = new Constraint(
319 '==',
320 $this->versionParser->normalize( $this->loaded[$dependencyName]['version'] )
321 );
322 } catch ( UnexpectedValueException ) {
323 // Non-parsable version, output an error message that the version
324 // string is invalid
325 return [
326 'msg' => "$dependencyName does not have a valid version string.",
327 'type' => 'invalid-version',
328 ];
329 }
330 // Check if the constraint actually matches...
331 if (
332 !$this->versionParser->parseConstraints( $constraint )->matches( $installedVersion )
333 ) {
334 $msg = "{$checkedExt} is not compatible with the current "
335 . "installed version of {$dependencyName} "
336 . "({$this->loaded[$dependencyName]['version']}), "
337 . "it requires: " . $constraint . '.';
338 return [
339 'msg' => $msg,
340 'type' => "incompatible-$type",
341 'incompatible' => $checkedExt,
342 ];
343 }
344 }
345
346 return false;
347 }
348}
349
351class_alias( VersionChecker::class, 'VersionChecker' );
const MEDIAWIKI_CORE
"requires" key that applies to MediaWiki core
Check whether extensions and their dependencies meet certain version requirements.
__construct( $coreVersion, $phpVersion, array $phpExtensions, array $abilities=[], array $abilityErrors=[])
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...