1<?php
2namespace TYPO3\CMS\Core\Utility;
3
4/*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17/**
18 * Class with helper functions for version number handling
19 */
20class VersionNumberUtility
21{
22    /**
23     * Returns an integer from a three part version number, eg '4.12.3' -> 4012003
24     *
25     * @param string $versionNumber Version number on format x.x.x
26     * @return int Integer version of version number (where each part can count to 999)
27     */
28    public static function convertVersionNumberToInteger($versionNumber)
29    {
30        $versionParts = explode('.', $versionNumber);
31        $version = $versionParts[0];
32        for ($i = 1; $i < 3; $i++) {
33            if (!empty($versionParts[$i])) {
34                $version .= str_pad((int)$versionParts[$i], 3, '0', STR_PAD_LEFT);
35            } else {
36                $version .= '000';
37            }
38        }
39        return (int)$version;
40    }
41
42    /**
43     * Returns the three part version number (string) from an integer, eg 4012003 -> '4.12.3'
44     *
45     * @param int $versionInteger Integer representation of version number
46     * @return string Version number as format x.x.x
47     * @throws \InvalidArgumentException if $versionInteger is not an integer
48     */
49    public static function convertIntegerToVersionNumber($versionInteger)
50    {
51        if (!is_int($versionInteger)) {
52            throw new \InvalidArgumentException(\TYPO3\CMS\Core\Utility\VersionNumberUtility::class . '::convertIntegerToVersionNumber() supports an integer argument only!', 1334072223);
53        }
54        $versionString = str_pad($versionInteger, 9, '0', STR_PAD_LEFT);
55        $parts = [
56            substr($versionString, 0, 3),
57            substr($versionString, 3, 3),
58            substr($versionString, 6, 3)
59        ];
60        return (int)$parts[0] . '.' . (int)$parts[1] . '.' . (int)$parts[2];
61    }
62
63    /**
64     * Splits a version range into an array.
65     *
66     * If a single version number is given, it is considered a minimum value.
67     * If a dash is found, the numbers left and right are considered as minimum and maximum. Empty values are allowed.
68     * If no version can be parsed "0.0.0" — "0.0.0" is the result
69     *
70     * @param string $version A string with a version range.
71     * @return array
72     */
73    public static function splitVersionRange($version)
74    {
75        $versionRange = [];
76        if (strstr($version, '-')) {
77            $versionRange = explode('-', $version, 2);
78        } else {
79            $versionRange[0] = $version;
80            $versionRange[1] = '';
81        }
82        if (!$versionRange[0]) {
83            $versionRange[0] = '0.0.0';
84        }
85        if (!$versionRange[1]) {
86            $versionRange[1] = '0.0.0';
87        }
88        return $versionRange;
89    }
90
91    /**
92     * Removes -dev -alpha -beta -RC states (also without '-' prefix) from a version number
93     * and replaces them by .0 and normalizes to a three part version number
94     *
95     * @return string
96     */
97    public static function getNumericTypo3Version()
98    {
99        $t3version = static::getCurrentTypo3Version();
100        $t3version = preg_replace('/-?(dev|alpha|beta|RC).*$/', '', $t3version);
101        $parts = GeneralUtility::intExplode('.', $t3version . '..');
102        $t3version = MathUtility::forceIntegerInRange($parts[0], 0, 999) . '.' .
103            MathUtility::forceIntegerInRange($parts[1], 0, 999) . '.' .
104            MathUtility::forceIntegerInRange($parts[2], 0, 999);
105        return $t3version;
106    }
107
108    /**
109     * Wrapper function for TYPO3_version constant to make functions using
110     * the constant unit testable
111     *
112     * @return string
113     */
114    public static function getCurrentTypo3Version()
115    {
116        return TYPO3_version;
117    }
118
119    /**
120     * This function converts version range strings (like '4.2.0-4.4.99') to an array
121     * (like array('4.2.0', '4.4.99'). It also forces each version part to be between
122     * 0 and 999
123     *
124     * @param string $versionsString
125     * @return array
126     */
127    public static function convertVersionsStringToVersionNumbers($versionsString)
128    {
129        $versions = GeneralUtility::trimExplode('-', $versionsString);
130        $versionsCount = count($versions);
131        for ($i = 0; $i < $versionsCount; $i++) {
132            $cleanedVersion = GeneralUtility::trimExplode('.', $versions[$i]);
133            $cleanedVersionCount = count($cleanedVersion);
134            for ($j = 0; $j < $cleanedVersionCount; $j++) {
135                $cleanedVersion[$j] = MathUtility::forceIntegerInRange($cleanedVersion[$j], 0, 999);
136            }
137            $cleanedVersionString = implode('.', $cleanedVersion);
138            if (static::convertVersionNumberToInteger($cleanedVersionString) === 0) {
139                $cleanedVersionString = '';
140            }
141            $versions[$i] = $cleanedVersionString;
142        }
143        return $versions;
144    }
145
146    /**
147     * Parses the version number x.x.x and returns an array with the various parts.
148     * It also forces each … 0 to 999
149     *
150     * @param string $version Version code, x.x.x
151     * @return array
152     */
153    public static function convertVersionStringToArray($version)
154    {
155        $parts = GeneralUtility::intExplode('.', $version . '..');
156        $parts[0] = MathUtility::forceIntegerInRange($parts[0], 0, 999);
157        $parts[1] = MathUtility::forceIntegerInRange($parts[1], 0, 999);
158        $parts[2] = MathUtility::forceIntegerInRange($parts[2], 0, 999);
159        $result = [];
160        $result['version'] = $parts[0] . '.' . $parts[1] . '.' . $parts[2];
161        $result['version_int'] = (int)($parts[0] * 1000000 + $parts[1] * 1000 + $parts[2]);
162        $result['version_main'] = $parts[0];
163        $result['version_sub'] = $parts[1];
164        $result['version_dev'] = $parts[2];
165        return $result;
166    }
167
168    /**
169     * Method to raise a version number
170     *
171     * @param string $raise one of "main", "sub", "dev" - the version part to raise by one
172     * @param string $version (like 4.1.20)
173     * @return string
174     * @throws \TYPO3\CMS\Core\Exception
175     */
176    public static function raiseVersionNumber($raise, $version)
177    {
178        if (!in_array($raise, ['main', 'sub', 'dev'])) {
179            throw new \TYPO3\CMS\Core\Exception('RaiseVersionNumber expects one of "main", "sub" or "dev".', 1342639555);
180        }
181        $parts = GeneralUtility::intExplode('.', $version . '..');
182        $parts[0] = MathUtility::forceIntegerInRange($parts[0], 0, 999);
183        $parts[1] = MathUtility::forceIntegerInRange($parts[1], 0, 999);
184        $parts[2] = MathUtility::forceIntegerInRange($parts[2], 0, 999);
185        switch ((string)$raise) {
186            case 'main':
187                $parts[0]++;
188                $parts[1] = 0;
189                $parts[2] = 0;
190                break;
191            case 'sub':
192                $parts[1]++;
193                $parts[2] = 0;
194                break;
195            case 'dev':
196                $parts[2]++;
197                break;
198        }
199        return $parts[0] . '.' . $parts[1] . '.' . $parts[2];
200    }
201}
202