PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
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 1 year ago Comparator.php 1 year ago CompilingMatcher.php 1 year ago Interval.php 1 year ago Intervals.php 1 year ago Semver.php 7 months ago VersionParser.php 1 year ago
VersionParser.php
592 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\MatchAllConstraint;
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 string */
42 private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev';
43
44 /**
45 * Returns the stability of a version.
46 *
47 * @param string $version
48 *
49 * @return string
50 * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev'
51 */
52 public static function parseStability($version)
53 {
54 $version = (string) preg_replace('{#.+$}', '', (string) $version);
55
56 if (strpos($version, 'dev-') === 0 || '-dev' === substr($version, -4)) {
57 return 'dev';
58 }
59
60 preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match);
61
62 if (!empty($match[3])) {
63 return 'dev';
64 }
65
66 if (!empty($match[1])) {
67 if ('beta' === $match[1] || 'b' === $match[1]) {
68 return 'beta';
69 }
70 if ('alpha' === $match[1] || 'a' === $match[1]) {
71 return 'alpha';
72 }
73 if ('rc' === $match[1]) {
74 return 'RC';
75 }
76 }
77
78 return 'stable';
79 }
80
81 /**
82 * @param string $stability
83 *
84 * @return string
85 * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev'
86 */
87 public static function normalizeStability($stability)
88 {
89 $stability = strtolower((string) $stability);
90
91 if (!in_array($stability, array('stable', 'rc', 'beta', 'alpha', 'dev'), true)) {
92 throw new \InvalidArgumentException('Invalid stability string "'.$stability.'", expected one of stable, RC, beta, alpha or dev');
93 }
94
95 return $stability === 'rc' ? 'RC' : $stability;
96 }
97
98 /**
99 * Normalizes a version string to be able to perform comparisons on it.
100 *
101 * @param string $version
102 * @param ?string $fullVersion optional complete version string to give more context
103 *
104 * @throws \UnexpectedValueException
105 *
106 * @return string
107 */
108 public function normalize($version, $fullVersion = null)
109 {
110 $version = trim((string) $version);
111 $origVersion = $version;
112 if (null === $fullVersion) {
113 $fullVersion = $version;
114 }
115
116 // strip off aliasing
117 if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
118 $version = $match[1];
119 }
120
121 // strip off stability flag
122 if (preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) {
123 $version = substr($version, 0, strlen($version) - strlen($match[0]));
124 }
125
126 // normalize master/trunk/default branches to dev-name for BC with 1.x as these used to be valid constraints
127 if (\in_array($version, array('master', 'trunk', 'default'), true)) {
128 $version = 'dev-' . $version;
129 }
130
131 // if requirement is branch-like, use full name
132 if (stripos($version, 'dev-') === 0) {
133 return 'dev-' . substr($version, 4);
134 }
135
136 // strip off build metadata
137 if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) {
138 $version = $match[1];
139 }
140
141 // match classical versioning
142 if (preg_match('{^v?(\d{1,5}+)(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) {
143 $version = $matches[1]
144 . (!empty($matches[2]) ? $matches[2] : '.0')
145 . (!empty($matches[3]) ? $matches[3] : '.0')
146 . (!empty($matches[4]) ? $matches[4] : '.0');
147 $index = 5;
148 // match date(time) based versioning
149 } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3}){0,2})' . self::$modifierRegex . '$}i', $version, $matches)) {
150 $version = (string) preg_replace('{\D}', '.', $matches[1]);
151 $index = 2;
152 }
153
154 // add version modifiers if a version was matched
155 if (isset($index)) {
156 if (!empty($matches[$index])) {
157 if ('stable' === $matches[$index]) {
158 return $version;
159 }
160 $version .= '-' . $this->expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? ltrim($matches[$index + 1], '.-') : '');
161 }
162
163 if (!empty($matches[$index + 2])) {
164 $version .= '-dev';
165 }
166
167 return $version;
168 }
169
170 // match dev branches
171 if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) {
172 try {
173 $normalized = $this->normalizeBranch($match[1]);
174 // a branch ending with -dev is only valid if it is numeric
175 // if it gets prefixed with dev- it means the branch name should
176 // have had a dev- prefix already when passed to normalize
177 if (strpos($normalized, 'dev-') === false) {
178 return $normalized;
179 }
180 } catch (\Exception $e) {
181 }
182 }
183
184 $extraMessage = '';
185 if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))?$}', $fullVersion)) {
186 $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
187 } elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))? +as +}', $fullVersion)) {
188 $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
189 }
190
191 throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage);
192 }
193
194 /**
195 * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison.
196 *
197 * @param string $branch Branch name (e.g. 2.1.x-dev)
198 *
199 * @return string|false Numeric prefix if present (e.g. 2.1.) or false
200 */
201 public function parseNumericAliasPrefix($branch)
202 {
203 if (preg_match('{^(?P<version>(\d++\\.)*\d++)(?:\.x)?-dev$}i', (string) $branch, $matches)) {
204 return $matches['version'] . '.';
205 }
206
207 return false;
208 }
209
210 /**
211 * Normalizes a branch name to be able to perform comparisons on it.
212 *
213 * @param string $name
214 *
215 * @return string
216 */
217 public function normalizeBranch($name)
218 {
219 $name = trim((string) $name);
220
221 if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) {
222 $version = '';
223 for ($i = 1; $i < 5; ++$i) {
224 $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x';
225 }
226
227 return str_replace('x', '9999999', $version) . '-dev';
228 }
229
230 return 'dev-' . $name;
231 }
232
233 /**
234 * Normalizes a default branch name (i.e. master on git) to 9999999-dev.
235 *
236 * @param string $name
237 *
238 * @return string
239 *
240 * @deprecated No need to use this anymore in theory, Composer 2 does not normalize any branch names to 9999999-dev anymore
241 */
242 public function normalizeDefaultBranch($name)
243 {
244 if ($name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') {
245 return '9999999-dev';
246 }
247
248 return (string) $name;
249 }
250
251 /**
252 * Parses a constraint string into MultiConstraint and/or Constraint objects.
253 *
254 * @param string $constraints
255 *
256 * @return ConstraintInterface
257 */
258 public function parseConstraints($constraints)
259 {
260 $prettyConstraint = (string) $constraints;
261
262 $orConstraints = preg_split('{\s*\|\|?\s*}', trim((string) $constraints));
263 if (false === $orConstraints) {
264 throw new \RuntimeException('Failed to preg_split string: '.$constraints);
265 }
266 $orGroups = array();
267
268 foreach ($orConstraints as $orConstraint) {
269 $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $orConstraint);
270 if (false === $andConstraints) {
271 throw new \RuntimeException('Failed to preg_split string: '.$orConstraint);
272 }
273 if (\count($andConstraints) > 1) {
274 $constraintObjects = array();
275 foreach ($andConstraints as $andConstraint) {
276 foreach ($this->parseConstraint($andConstraint) as $parsedAndConstraint) {
277 $constraintObjects[] = $parsedAndConstraint;
278 }
279 }
280 } else {
281 $constraintObjects = $this->parseConstraint($andConstraints[0]);
282 }
283
284 if (1 === \count($constraintObjects)) {
285 $constraint = $constraintObjects[0];
286 } else {
287 $constraint = new MultiConstraint($constraintObjects);
288 }
289
290 $orGroups[] = $constraint;
291 }
292
293 $parsedConstraint = MultiConstraint::create($orGroups, false);
294
295 $parsedConstraint->setPrettyString($prettyConstraint);
296
297 return $parsedConstraint;
298 }
299
300 /**
301 * @param string $constraint
302 *
303 * @throws \UnexpectedValueException
304 *
305 * @return array
306 *
307 * @phpstan-return non-empty-array<ConstraintInterface>
308 */
309 private function parseConstraint($constraint)
310 {
311 // strip off aliasing
312 if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $constraint, $match)) {
313 $constraint = $match[1];
314 }
315
316 // strip @stability flags, and keep it for later use
317 if (preg_match('{^([^,\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) {
318 $constraint = '' !== $match[1] ? $match[1] : '*';
319 if ($match[2] !== 'stable') {
320 $stabilityModifier = $match[2];
321 }
322 }
323
324 // get rid of #refs as those are used by composer only
325 if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraint, $match)) {
326 $constraint = $match[1];
327 }
328
329 if (preg_match('{^(v)?[xX*](\.[xX*])*$}i', $constraint, $match)) {
330 if (!empty($match[1]) || !empty($match[2])) {
331 return array(new Constraint('>=', '0.0.0.0-dev'));
332 }
333
334 return array(new MatchAllConstraint());
335 }
336
337 $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?(?:' . self::$modifierRegex . '|\.([xX*][.-]?dev))(?:\+[^\s]+)?';
338
339 // Tilde Range
340 //
341 // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
342 // version, to ensure that unstable instances of the current version are allowed. However, if a stability
343 // suffix is added to the constraint, then a >= match on the current version is used instead.
344 if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) {
345 if (strpos($constraint, '~>') === 0) {
346 throw new \UnexpectedValueException(
347 'Could not parse version constraint ' . $constraint . ': ' .
348 'Invalid operator "~>", you probably meant to use the "~" operator'
349 );
350 }
351
352 // Work out which position in the version we are operating at
353 if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) {
354 $position = 4;
355 } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
356 $position = 3;
357 } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
358 $position = 2;
359 } else {
360 $position = 1;
361 }
362
363 // when matching 2.x-dev or 3.0.x-dev we have to shift the second or third number, despite no second/third number matching above
364 if (!empty($matches[8])) {
365 $position++;
366 }
367
368 // Calculate the stability suffix
369 $stabilitySuffix = '';
370 if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
371 $stabilitySuffix .= '-dev';
372 }
373
374 $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
375 $lowerBound = new Constraint('>=', $lowVersion);
376
377 // For upper bound, we increment the position of one more significance,
378 // but highPosition = 0 would be illegal
379 $highPosition = max(1, $position - 1);
380 $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev';
381 $upperBound = new Constraint('<', $highVersion);
382
383 return array(
384 $lowerBound,
385 $upperBound,
386 );
387 }
388
389 // Caret Range
390 //
391 // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple.
392 // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for
393 // versions 0.X >=0.1.0, and no updates for versions 0.0.X
394 if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) {
395 // Work out which position in the version we are operating at
396 if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) {
397 $position = 1;
398 } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) {
399 $position = 2;
400 } else {
401 $position = 3;
402 }
403
404 // Calculate the stability suffix
405 $stabilitySuffix = '';
406 if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
407 $stabilitySuffix .= '-dev';
408 }
409
410 $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
411 $lowerBound = new Constraint('>=', $lowVersion);
412
413 // For upper bound, we increment the position of one more significance,
414 // but highPosition = 0 would be illegal
415 $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
416 $upperBound = new Constraint('<', $highVersion);
417
418 return array(
419 $lowerBound,
420 $upperBound,
421 );
422 }
423
424 // X Range
425 //
426 // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple.
427 // A partial version range is treated as an X-Range, so the special character is in fact optional.
428 if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) {
429 if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
430 $position = 3;
431 } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
432 $position = 2;
433 } else {
434 $position = 1;
435 }
436
437 $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev';
438 $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
439
440 if ($lowVersion === '0.0.0.0-dev') {
441 return array(new Constraint('<', $highVersion));
442 }
443
444 return array(
445 new Constraint('>=', $lowVersion),
446 new Constraint('<', $highVersion),
447 );
448 }
449
450 // Hyphen Range
451 //
452 // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range,
453 // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in
454 // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but
455 // nothing that would be greater than the provided tuple parts.
456 if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) {
457 // Calculate the stability suffix
458 $lowStabilitySuffix = '';
459 if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) {
460 $lowStabilitySuffix = '-dev';
461 }
462
463 $lowVersion = $this->normalize($matches['from']);
464 $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix);
465
466 $empty = function ($x) {
467 return ($x === 0 || $x === '0') ? false : empty($x);
468 };
469
470 if ((!$empty($matches[12]) && !$empty($matches[13])) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) {
471 $highVersion = $this->normalize($matches['to']);
472 $upperBound = new Constraint('<=', $highVersion);
473 } else {
474 $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]);
475
476 // validate to version
477 $this->normalize($matches['to']);
478
479 $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev';
480 $upperBound = new Constraint('<', $highVersion);
481 }
482
483 return array(
484 $lowerBound,
485 $upperBound,
486 );
487 }
488
489 // Basic Comparators
490 if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
491 try {
492 try {
493 $version = $this->normalize($matches[2]);
494 } catch (\UnexpectedValueException $e) {
495 // recover from an invalid constraint like foobar-dev which should be dev-foobar
496 // except if the constraint uses a known operator, in which case it must be a parse error
497 if (substr($matches[2], -4) === '-dev' && preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) {
498 $version = $this->normalize('dev-'.substr($matches[2], 0, -4));
499 } else {
500 throw $e;
501 }
502 }
503
504 $op = $matches[1] ?: '=';
505
506 if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') {
507 $version .= '-' . $stabilityModifier;
508 } elseif ('<' === $op || '>=' === $op) {
509 if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
510 if (strpos($matches[2], 'dev-') !== 0) {
511 $version .= '-dev';
512 }
513 }
514 }
515
516 return array(new Constraint($matches[1] ?: '=', $version));
517 } catch (\Exception $e) {
518 }
519 }
520
521 $message = 'Could not parse version constraint ' . $constraint;
522 if (isset($e)) {
523 $message .= ': ' . $e->getMessage();
524 }
525
526 throw new \UnexpectedValueException($message);
527 }
528
529 /**
530 * Increment, decrement, or simply pad a version number.
531 *
532 * Support function for {@link parseConstraint()}
533 *
534 * @param array $matches Array with version parts in array indexes 1,2,3,4
535 * @param int $position 1,2,3,4 - which segment of the version to increment/decrement
536 * @param int $increment
537 * @param string $pad The string to pad version parts after $position
538 *
539 * @return string|null The new version
540 *
541 * @phpstan-param string[] $matches
542 */
543 private function manipulateVersionString(array $matches, $position, $increment = 0, $pad = '0')
544 {
545 for ($i = 4; $i > 0; --$i) {
546 if ($i > $position) {
547 $matches[$i] = $pad;
548 } elseif ($i === $position && $increment) {
549 $matches[$i] += $increment;
550 // If $matches[$i] was 0, carry the decrement
551 if ($matches[$i] < 0) {
552 $matches[$i] = $pad;
553 --$position;
554
555 // Return null on a carry overflow
556 if ($i === 1) {
557 return null;
558 }
559 }
560 }
561 }
562
563 return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4];
564 }
565
566 /**
567 * Expand shorthand stability string to long version.
568 *
569 * @param string $stability
570 *
571 * @return string
572 */
573 private function expandStability($stability)
574 {
575 $stability = strtolower($stability);
576
577 switch ($stability) {
578 case 'a':
579 return 'alpha';
580 case 'b':
581 return 'beta';
582 case 'p':
583 case 'pl':
584 return 'patch';
585 case 'rc':
586 return 'RC';
587 default:
588 return $stability;
589 }
590 }
591 }
592