Merge "Simplify HTMLTitleTextField::validate"
[lhc/web/wiklou.git] / includes / registration / VersionChecker.php
1 <?php
2
3 /**
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.
8 *
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.
13 *
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
18 *
19 * @author Legoktm
20 * @author Florian Schmidt
21 */
22
23 use Composer\Semver\VersionParser;
24 use Composer\Semver\Constraint\Constraint;
25
26 /**
27 * Provides functions to check a set of extensions with dependencies against
28 * a set of loaded extensions and given version information.
29 *
30 * @since 1.29
31 */
32 class VersionChecker {
33 /**
34 * @var Constraint|bool representing $wgVersion
35 */
36 private $coreVersion = false;
37
38 /**
39 * @var Constraint|bool representing PHP version
40 */
41 private $phpVersion = false;
42
43 /**
44 * @var array Loaded extensions
45 */
46 private $loaded = [];
47
48 /**
49 * @var VersionParser
50 */
51 private $versionParser;
52
53 /**
54 * @param string $coreVersion Current version of core
55 */
56 public function __construct( $coreVersion, $phpVersion ) {
57 $this->versionParser = new VersionParser();
58 $this->setCoreVersion( $coreVersion );
59 $this->setPhpVersion( $phpVersion );
60 }
61
62 /**
63 * Set an array with credits of all loaded extensions and skins.
64 *
65 * @param array $credits An array of installed extensions with credits of them
66 * @return VersionChecker $this
67 */
68 public function setLoadedExtensionsAndSkins( array $credits ) {
69 $this->loaded = $credits;
70
71 return $this;
72 }
73
74 /**
75 * Set MediaWiki core version.
76 *
77 * @param string $coreVersion Current version of core
78 */
79 private function setCoreVersion( $coreVersion ) {
80 try {
81 $this->coreVersion = new Constraint(
82 '==',
83 $this->versionParser->normalize( $coreVersion )
84 );
85 $this->coreVersion->setPrettyString( $coreVersion );
86 } catch ( UnexpectedValueException $e ) {
87 // Non-parsable version, don't fatal.
88 }
89 }
90
91 /**
92 * Set PHP version.
93 *
94 * @param string $phpVersion Current PHP version. Must be well-formed.
95 * @throws UnexpectedValueException
96 */
97 private function setPhpVersion( $phpVersion ) {
98 // normalize to make this throw an exception if the version is invalid
99 $this->phpVersion = new Constraint(
100 '==',
101 $this->versionParser->normalize( $phpVersion )
102 );
103 $this->phpVersion->setPrettyString( $phpVersion );
104 }
105
106 /**
107 * Check all given dependencies if they are compatible with the named
108 * installed extensions in the $credits array.
109 *
110 * Example $extDependencies:
111 * {
112 * 'FooBar' => {
113 * 'MediaWiki' => '>= 1.25.0',
114 * 'platform': {
115 * 'php': '>= 7.0.0'
116 * },
117 * 'extensions' => {
118 * 'FooBaz' => '>= 1.25.0'
119 * },
120 * 'skins' => {
121 * 'BazBar' => '>= 1.0.0'
122 * }
123 * }
124 * }
125 *
126 * @param array $extDependencies All extensions that depend on other ones
127 * @return array
128 */
129 public function checkArray( array $extDependencies ) {
130 $errors = [];
131 foreach ( $extDependencies as $extension => $dependencies ) {
132 foreach ( $dependencies as $dependencyType => $values ) {
133 switch ( $dependencyType ) {
134 case ExtensionRegistry::MEDIAWIKI_CORE:
135 $mwError = $this->handleDependency(
136 $this->coreVersion,
137 $values,
138 $extension
139 );
140 if ( $mwError !== false ) {
141 $errors[] = [
142 'msg' =>
143 "{$extension} is not compatible with the current MediaWiki "
144 . "core (version {$this->coreVersion->getPrettyString()}), "
145 . "it requires: $values."
146 ,
147 'type' => 'incompatible-core',
148 ];
149 }
150 break;
151 case 'platform':
152 foreach ( $values as $dependency => $constraint ) {
153 if ( $dependency === 'php' ) {
154 $phpError = $this->handleDependency(
155 $this->phpVersion,
156 $constraint,
157 $extension
158 );
159 if ( $phpError !== false ) {
160 $errors[] = [
161 'msg' =>
162 "{$extension} is not compatible with the current PHP "
163 . "version {$this->phpVersion->getPrettyString()}), "
164 . "it requires: $constraint."
165 ,
166 'type' => 'incompatible-php',
167 ];
168 }
169 } else {
170 // add other platform dependencies here
171 throw new UnexpectedValueException( 'Dependency type ' . $dependency .
172 ' unknown in ' . $extension );
173 }
174 }
175 break;
176 case 'extensions':
177 case 'skins':
178 foreach ( $values as $dependency => $constraint ) {
179 $extError = $this->handleExtensionDependency(
180 $dependency, $constraint, $extension, $dependencyType
181 );
182 if ( $extError !== false ) {
183 $errors[] = $extError;
184 }
185 }
186 break;
187 default:
188 throw new UnexpectedValueException( 'Dependency type ' . $dependencyType .
189 ' unknown in ' . $extension );
190 }
191 }
192 }
193
194 return $errors;
195 }
196
197 /**
198 * Handle a simple dependency to MediaWiki core or PHP. See handleMediaWikiDependency and
199 * handlePhpDependency for details.
200 *
201 * @param Constraint|bool $version The version installed
202 * @param string $constraint The required version constraint for this dependency
203 * @param string $checkedExt The Extension, which depends on this dependency
204 * @return bool false if no error, true else
205 */
206 private function handleDependency( $version, $constraint, $checkedExt ) {
207 if ( $version === false ) {
208 // Couldn't parse the version, so we can't check anything
209 return false;
210 }
211
212 // if the installed and required version are compatible, return an empty array
213 if ( $this->versionParser->parseConstraints( $constraint )
214 ->matches( $version ) ) {
215 return false;
216 }
217
218 return true;
219 }
220
221 /**
222 * Handle a dependency to another extension.
223 *
224 * @param string $dependencyName The name of the dependency
225 * @param string $constraint The required version constraint for this dependency
226 * @param string $checkedExt The Extension, which depends on this dependency
227 * @param string $type Either 'extensions' or 'skins'
228 * @return bool|array false for no errors, or an array of info
229 */
230 private function handleExtensionDependency( $dependencyName, $constraint, $checkedExt,
231 $type
232 ) {
233 // Check if the dependency is even installed
234 if ( !isset( $this->loaded[$dependencyName] ) ) {
235 return [
236 'msg' => "{$checkedExt} requires {$dependencyName} to be installed.",
237 'type' => "missing-$type",
238 'missing' => $dependencyName,
239 ];
240 }
241 if ( $constraint === '*' ) {
242 // short-circuit since any version is OK.
243 return false;
244 }
245 // Check if the dependency has specified a version
246 if ( !isset( $this->loaded[$dependencyName]['version'] ) ) {
247 $msg = "{$dependencyName} does not expose its version, but {$checkedExt}"
248 . " requires: {$constraint}.";
249 return [
250 'msg' => $msg,
251 'type' => "incompatible-$type",
252 'incompatible' => $checkedExt,
253 ];
254 } else {
255 // Try to get a constraint for the dependency version
256 try {
257 $installedVersion = new Constraint(
258 '==',
259 $this->versionParser->normalize( $this->loaded[$dependencyName]['version'] )
260 );
261 } catch ( UnexpectedValueException $e ) {
262 // Non-parsable version, output an error message that the version
263 // string is invalid
264 return [
265 'msg' => "$dependencyName does not have a valid version string.",
266 'type' => 'invalid-version',
267 ];
268 }
269 // Check if the constraint actually matches...
270 if (
271 !$this->versionParser->parseConstraints( $constraint )->matches( $installedVersion )
272 ) {
273 $msg = "{$checkedExt} is not compatible with the current "
274 . "installed version of {$dependencyName} "
275 . "({$this->loaded[$dependencyName]['version']}), "
276 . "it requires: " . $constraint . '.';
277 return [
278 'msg' => $msg,
279 'type' => "incompatible-$type",
280 'incompatible' => $checkedExt,
281 ];
282 }
283 }
284
285 return false;
286 }
287 }