PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.1.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.1.2
5.13.0 5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / vendor / composer / semver / src / VersionParser.php
matomo / app / vendor / composer / semver / src Last commit date
Constraint 6 years ago Comparator.php 6 years ago Semver.php 6 years ago VersionParser.php 6 years ago
VersionParser.php
546 lines
1 <?php
2
3 /*
4 * This file is part of composer/semver.
5 *
6 * (c) Composer <https://github.com/composer>
7 *
8 * For the full copyright and license information, please view
9 * the LICENSE file that was distributed with this source code.
10 */
11
12 namespace Composer\Semver;
13
14 use Composer\Semver\Constraint\ConstraintInterface;
15 use Composer\Semver\Constraint\EmptyConstraint;
16 use Composer\Semver\Constraint\MultiConstraint;
17 use Composer\Semver\Constraint\Constraint;
18
19 /**
20 * Version parser.
21 *
22 * @author Jordi Boggiano <j.boggiano@seld.be>
23 */
24 class VersionParser
25 {
26 /**
27 * Regex to match pre-release data (sort of).
28 *
29 * Due to backwards compatibility:
30 * - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted.
31 * - Only stabilities as recognized by Composer are allowed to precede a numerical identifier.
32 * - Numerical-only pre-release identifiers are not supported, see tests.
33 *
34 * |--------------|
35 * [major].[minor].[patch] -[pre-release] +[build-metadata]
36 *
37 * @var string
38 */
39 private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?';
40
41 /** @var array */
42 private static $stabilities = array('stable', 'RC', 'beta', 'alpha', 'dev');
43
44 /**
45 * Returns the stability of a version.
46 *
47 * @param string $version
48 *
49 * @return string
50 */
51 public static function parseStability($version)
52 {
53 $version = preg_replace('{#.+$}i', '', $version);
54
55 if ('dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4)) {
56 return 'dev';
57 }
58
59 preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match);
60 if (!empty($match[3])) {
61 return 'dev';
62 }
63
64 if (!empty($match[1])) {
65 if ('beta' === $match[1] || 'b' === $match[1]) {
66 return 'beta';
67 }
68 if ('alpha' === $match[1] || 'a' === $match[1]) {
69 return 'alpha';
70 }
71 if ('rc' === $match[1]) {
72 return 'RC';
73 }
74 }
75
76 return 'stable';
77 }
78
79 /**
80 * @param string $stability
81 *
82 * @return string
83 */
84 public static function normalizeStability($stability)
85 {
86 $stability = strtolower($stability);
87
88 return $stability === 'rc' ? 'RC' : $stability;
89 }
90
91 /**
92 * Normalizes a version string to be able to perform comparisons on it.
93 *
94 * @param string $version
95 * @param string $fullVersion optional complete version string to give more context
96 *
97 * @throws \UnexpectedValueException
98 *
99 * @return string
100 */
101 public function normalize($version, $fullVersion = null)
102 {
103 $version = trim($version);
104 if (null === $fullVersion) {
105 $fullVersion = $version;
106 }
107
108 // strip off aliasing
109 if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
110 $version = $match[1];
111 }
112
113 // strip off build metadata
114 if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) {
115 $version = $match[1];
116 }
117
118 // match master-like branches
119 if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
120 return '9999999-dev';
121 }
122
123 if ('dev-' === strtolower(substr($version, 0, 4))) {
124 return 'dev-' . substr($version, 4);
125 }
126
127 // match classical versioning
128 if (preg_match('{^v?(\d{1,5})(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) {
129 $version = $matches[1]
130 . (!empty($matches[2]) ? $matches[2] : '.0')
131 . (!empty($matches[3]) ? $matches[3] : '.0')
132 . (!empty($matches[4]) ? $matches[4] : '.0');
133 $index = 5;
134 // match date(time) based versioning
135 } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) {
136 $version = preg_replace('{\D}', '.', $matches[1]);
137 $index = 2;
138 }
139
140 // add version modifiers if a version was matched
141 if (isset($index)) {
142 if (!empty($matches[$index])) {
143 if ('stable' === $matches[$index]) {
144 return $version;
145 }
146 $version .= '-' . $this->expandStability($matches[$index]) . (!empty($matches[$index + 1]) ? ltrim($matches[$index + 1], '.-') : '');
147 }
148
149 if (!empty($matches[$index + 2])) {
150 $version .= '-dev';
151 }
152
153 return $version;
154 }
155
156 // match dev branches
157 if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) {
158 try {
159 return $this->normalizeBranch($match[1]);
160 } catch (\Exception $e) {
161 }
162 }
163
164 $extraMessage = '';
165 if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) {
166 $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
167 } elseif (preg_match('{^' . preg_quote($version) . ' +as +}', $fullVersion)) {
168 $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
169 }
170
171 throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage);
172 }
173
174 /**
175 * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison.
176 *
177 * @param string $branch Branch name (e.g. 2.1.x-dev)
178 *
179 * @return string|false Numeric prefix if present (e.g. 2.1.) or false
180 */
181 public function parseNumericAliasPrefix($branch)
182 {
183 if (preg_match('{^(?P<version>(\d++\\.)*\d++)(?:\.x)?-dev$}i', $branch, $matches)) {
184 return $matches['version'] . '.';
185 }
186
187 return false;
188 }
189
190 /**
191 * Normalizes a branch name to be able to perform comparisons on it.
192 *
193 * @param string $name
194 *
195 * @return string
196 */
197 public function normalizeBranch($name)
198 {
199 $name = trim($name);
200
201 if (in_array($name, array('master', 'trunk', 'default'))) {
202 return $this->normalize($name);
203 }
204
205 if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) {
206 $version = '';
207 for ($i = 1; $i < 5; ++$i) {
208 $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x';
209 }
210
211 return str_replace('x', '9999999', $version) . '-dev';
212 }
213
214 return 'dev-' . $name;
215 }
216
217 /**
218 * Parses a constraint string into MultiConstraint and/or Constraint objects.
219 *
220 * @param string $constraints
221 *
222 * @return ConstraintInterface
223 */
224 public function parseConstraints($constraints)
225 {
226 $prettyConstraint = $constraints;
227
228 if (preg_match('{^([^,\s]*?)@(' . implode('|', self::$stabilities) . ')$}i', $constraints, $match)) {
229 $constraints = empty($match[1]) ? '*' : $match[1];
230 }
231
232 if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraints, $match)) {
233 $constraints = $match[1];
234 }
235
236 $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints));
237 $orGroups = array();
238 foreach ($orConstraints as $constraints) {
239 $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $constraints);
240 if (count($andConstraints) > 1) {
241 $constraintObjects = array();
242 foreach ($andConstraints as $constraint) {
243 foreach ($this->parseConstraint($constraint) as $parsedConstraint) {
244 $constraintObjects[] = $parsedConstraint;
245 }
246 }
247 } else {
248 $constraintObjects = $this->parseConstraint($andConstraints[0]);
249 }
250
251 if (1 === count($constraintObjects)) {
252 $constraint = $constraintObjects[0];
253 } else {
254 $constraint = new MultiConstraint($constraintObjects);
255 }
256
257 $orGroups[] = $constraint;
258 }
259
260 if (1 === count($orGroups)) {
261 $constraint = $orGroups[0];
262 } elseif (2 === count($orGroups)
263 // parse the two OR groups and if they are contiguous we collapse
264 // them into one constraint
265 && $orGroups[0] instanceof MultiConstraint
266 && $orGroups[1] instanceof MultiConstraint
267 && ($a = (string) $orGroups[0])
268 && substr($a, 0, 3) === '[>=' && (false !== ($posA = strpos($a, '<', 4)))
269 && ($b = (string) $orGroups[1])
270 && substr($b, 0, 3) === '[>=' && (false !== ($posB = strpos($b, '<', 4)))
271 && substr($a, $posA + 2, -1) === substr($b, 4, $posB - 5)
272 ) {
273 $constraint = new MultiConstraint(array(
274 new Constraint('>=', substr($a, 4, $posA - 5)),
275 new Constraint('<', substr($b, $posB + 2, -1)),
276 ));
277 } else {
278 $constraint = new MultiConstraint($orGroups, false);
279 }
280
281 $constraint->setPrettyString($prettyConstraint);
282
283 return $constraint;
284 }
285
286 /**
287 * @param string $constraint
288 *
289 * @throws \UnexpectedValueException
290 *
291 * @return array
292 */
293 private function parseConstraint($constraint)
294 {
295 if (preg_match('{^([^,\s]+?)@(' . implode('|', self::$stabilities) . ')$}i', $constraint, $match)) {
296 $constraint = $match[1];
297 if ($match[2] !== 'stable') {
298 $stabilityModifier = $match[2];
299 }
300 }
301
302 if (preg_match('{^v?[xX*](\.[xX*])*$}i', $constraint)) {
303 return array(new EmptyConstraint());
304 }
305
306 $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?' . self::$modifierRegex . '(?:\+[^\s]+)?';
307
308 // Tilde Range
309 //
310 // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
311 // version, to ensure that unstable instances of the current version are allowed. However, if a stability
312 // suffix is added to the constraint, then a >= match on the current version is used instead.
313 if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) {
314 if (substr($constraint, 0, 2) === '~>') {
315 throw new \UnexpectedValueException(
316 'Could not parse version constraint ' . $constraint . ': ' .
317 'Invalid operator "~>", you probably meant to use the "~" operator'
318 );
319 }
320
321 // Work out which position in the version we are operating at
322 if (isset($matches[4]) && '' !== $matches[4]) {
323 $position = 4;
324 } elseif (isset($matches[3]) && '' !== $matches[3]) {
325 $position = 3;
326 } elseif (isset($matches[2]) && '' !== $matches[2]) {
327 $position = 2;
328 } else {
329 $position = 1;
330 }
331
332 // Calculate the stability suffix
333 $stabilitySuffix = '';
334 if (!empty($matches[5])) {
335 $stabilitySuffix .= '-' . $this->expandStability($matches[5]) . (!empty($matches[6]) ? $matches[6] : '');
336 }
337
338 if (!empty($matches[7])) {
339 $stabilitySuffix .= '-dev';
340 }
341
342 if (!$stabilitySuffix) {
343 $stabilitySuffix = '-dev';
344 }
345
346 $lowVersion = $this->manipulateVersionString($matches, $position, 0) . $stabilitySuffix;
347 $lowerBound = new Constraint('>=', $lowVersion);
348
349 // For upper bound, we increment the position of one more significance,
350 // but highPosition = 0 would be illegal
351 $highPosition = max(1, $position - 1);
352 $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev';
353 $upperBound = new Constraint('<', $highVersion);
354
355 return array(
356 $lowerBound,
357 $upperBound,
358 );
359 }
360
361 // Caret Range
362 //
363 // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple.
364 // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for
365 // versions 0.X >=0.1.0, and no updates for versions 0.0.X
366 if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) {
367 // Work out which position in the version we are operating at
368 if ('0' !== $matches[1] || '' === $matches[2]) {
369 $position = 1;
370 } elseif ('0' !== $matches[2] || '' === $matches[3]) {
371 $position = 2;
372 } else {
373 $position = 3;
374 }
375
376 // Calculate the stability suffix
377 $stabilitySuffix = '';
378 if (empty($matches[5]) && empty($matches[7])) {
379 $stabilitySuffix .= '-dev';
380 }
381
382 $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
383 $lowerBound = new Constraint('>=', $lowVersion);
384
385 // For upper bound, we increment the position of one more significance,
386 // but highPosition = 0 would be illegal
387 $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
388 $upperBound = new Constraint('<', $highVersion);
389
390 return array(
391 $lowerBound,
392 $upperBound,
393 );
394 }
395
396 // X Range
397 //
398 // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple.
399 // A partial version range is treated as an X-Range, so the special character is in fact optional.
400 if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) {
401 if (isset($matches[3]) && '' !== $matches[3]) {
402 $position = 3;
403 } elseif (isset($matches[2]) && '' !== $matches[2]) {
404 $position = 2;
405 } else {
406 $position = 1;
407 }
408
409 $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev';
410 $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
411
412 if ($lowVersion === '0.0.0.0-dev') {
413 return array(new Constraint('<', $highVersion));
414 }
415
416 return array(
417 new Constraint('>=', $lowVersion),
418 new Constraint('<', $highVersion),
419 );
420 }
421
422 // Hyphen Range
423 //
424 // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range,
425 // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in
426 // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but
427 // nothing that would be greater than the provided tuple parts.
428 if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) {
429 // Calculate the stability suffix
430 $lowStabilitySuffix = '';
431 if (empty($matches[6]) && empty($matches[8])) {
432 $lowStabilitySuffix = '-dev';
433 }
434
435 $lowVersion = $this->normalize($matches['from']);
436 $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix);
437
438 $empty = function ($x) {
439 return ($x === 0 || $x === '0') ? false : empty($x);
440 };
441
442 if ((!$empty($matches[11]) && !$empty($matches[12])) || !empty($matches[14]) || !empty($matches[16])) {
443 $highVersion = $this->normalize($matches['to']);
444 $upperBound = new Constraint('<=', $highVersion);
445 } else {
446 $highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]);
447 $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[11]) ? 1 : 2, 1) . '-dev';
448 $upperBound = new Constraint('<', $highVersion);
449 }
450
451 return array(
452 $lowerBound,
453 $upperBound,
454 );
455 }
456
457 // Basic Comparators
458 if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
459 try {
460 $version = $this->normalize($matches[2]);
461
462 if (!empty($stabilityModifier) && $this->parseStability($version) === 'stable') {
463 $version .= '-' . $stabilityModifier;
464 } elseif ('<' === $matches[1] || '>=' === $matches[1]) {
465 if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
466 if (substr($matches[2], 0, 4) !== 'dev-') {
467 $version .= '-dev';
468 }
469 }
470 }
471
472 return array(new Constraint($matches[1] ?: '=', $version));
473 } catch (\Exception $e) {
474 }
475 }
476
477 $message = 'Could not parse version constraint ' . $constraint;
478 if (isset($e)) {
479 $message .= ': ' . $e->getMessage();
480 }
481
482 throw new \UnexpectedValueException($message);
483 }
484
485 /**
486 * Increment, decrement, or simply pad a version number.
487 *
488 * Support function for {@link parseConstraint()}
489 *
490 * @param array $matches Array with version parts in array indexes 1,2,3,4
491 * @param int $position 1,2,3,4 - which segment of the version to increment/decrement
492 * @param int $increment
493 * @param string $pad The string to pad version parts after $position
494 *
495 * @return string The new version
496 */
497 private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0')
498 {
499 for ($i = 4; $i > 0; --$i) {
500 if ($i > $position) {
501 $matches[$i] = $pad;
502 } elseif ($i === $position && $increment) {
503 $matches[$i] += $increment;
504 // If $matches[$i] was 0, carry the decrement
505 if ($matches[$i] < 0) {
506 $matches[$i] = $pad;
507 --$position;
508
509 // Return null on a carry overflow
510 if ($i === 1) {
511 return;
512 }
513 }
514 }
515 }
516
517 return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4];
518 }
519
520 /**
521 * Expand shorthand stability string to long version.
522 *
523 * @param string $stability
524 *
525 * @return string
526 */
527 private function expandStability($stability)
528 {
529 $stability = strtolower($stability);
530
531 switch ($stability) {
532 case 'a':
533 return 'alpha';
534 case 'b':
535 return 'beta';
536 case 'p':
537 case 'pl':
538 return 'patch';
539 case 'rc':
540 return 'RC';
541 default:
542 return $stability;
543 }
544 }
545 }
546