xref: /web-php/include/branches.inc (revision e367a4ac)
1<?php
2
3include_once __DIR__ . '/releases.inc';
4include_once __DIR__ . '/version.inc';
5
6/* Branch overrides. For situations where we've changed the exact dates for a
7 * branch's active support and security fix EOLs, these can be reflected here.
8 *
9 * Supported keys are:
10 *    - stable:   the end of active support (usually two years after release).
11 *    - security: the end of security support (usually release + 3 years).
12 */
13$BRANCHES = [
14    /* 3.0 is here because version_compare() can't handle the only version in
15     * $OLDRELEASES, and it saves another special case in
16     * get_branch_security_eol_date(). */
17    '3.0' => [
18        'security' => '2000-10-20',
19    ],
20    '5.3' => [
21        'stable' => '2013-07-11',
22        'security' => '2014-08-14',
23    ],
24    '5.4' => [
25        'stable' => '2014-09-14',
26        'security' => '2015-09-03',
27    ],
28    '5.5' => [
29        'stable' => '2015-07-10',
30        'security' => '2016-07-21',
31    ],
32    '5.6' => [
33        'stable' => '2017-01-19',
34        'security' => '2018-12-31',
35    ],
36    '7.0' => [
37        'stable' => '2018-01-04',
38        'security' => '2019-01-10',
39    ],
40];
41
42/* Time to keep EOLed branches in the array returned by get_active_branches(),
43 * which is used on the front page download links and the supported versions
44 * page. (Currently 28 days.) */
45$KEEP_EOL = new DateInterval('P28D');
46
47function format_interval($from, DateTime $to) {
48    try {
49        $from_obj = $from instanceof DateTime ? $from : new DateTime($from);
50        $diff = $to->diff($from_obj);
51
52        $times = [];
53        if ($diff->y) {
54            $times[] = [$diff->y, 'year'];
55            if ($diff->m) {
56                    $times[] = [$diff->m, 'month'];
57            }
58        } elseif ($diff->m) {
59            $times[] = [$diff->m, 'month'];
60        } elseif ($diff->d) {
61            $times[] = [$diff->d, 'day'];
62        } else {
63            $eolPeriod = 'midnight';
64        }
65        if ($times) {
66            $eolPeriod = implode(', ',
67                                        array_map(
68                                            function ($t) {
69                                                return "$t[0] $t[1]" .
70                                                        ($t[0] != 1 ? 's' : '');
71                                            },
72                                            $times,
73                                        ),
74                                );
75
76            if ($diff->invert) {
77                $eolPeriod = "$eolPeriod ago";
78            } else {
79                $eolPeriod = "in $eolPeriod";
80            }
81        }
82    } catch (Exception $e) {
83        $eolPeriod = 'unknown';
84    }
85
86    return $eolPeriod;
87}
88
89function version_number_to_branch(string $version): ?string {
90    $parts = explode('.', $version);
91    if (count($parts) > 1) {
92        return "$parts[0].$parts[1]";
93    }
94
95    return null;
96}
97
98function get_all_branches() {
99    $branches = [];
100
101    foreach ($GLOBALS['OLDRELEASES'] as $major => $releases) {
102        foreach ($releases as $version => $release) {
103            $branch = version_number_to_branch($version);
104
105            if ($branch) {
106                if (!isset($branches[$major][$branch]) || version_compare($version, $branches[$major][$branch]['version'], 'gt')) {
107                    $branches[$major][$branch] = $release;
108                    $branches[$major][$branch]['version'] = $version;
109                }
110            }
111        }
112    }
113
114    foreach ($GLOBALS['RELEASES'] as $major => $releases) {
115        foreach ($releases as $version => $release) {
116            $branch = version_number_to_branch($version);
117
118            if ($branch) {
119                if (!isset($branches[$major][$branch]) || version_compare($version, $branches[$major][$branch]['version'], 'gt')) {
120                    $branches[$major][$branch] = $release;
121                    $branches[$major][$branch]['version'] = $version;
122                }
123            }
124        }
125    }
126
127    krsort($branches);
128    foreach ($branches as &$branch) {
129        krsort($branch);
130    }
131
132    return $branches;
133}
134
135function get_active_branches($include_recent_eols = true) {
136    $branches = [];
137    $now = new DateTime();
138
139    foreach ($GLOBALS['RELEASES'] as $major => $releases) {
140        foreach ($releases as $version => $release) {
141            $branch = version_number_to_branch($version);
142
143            if ($branch) {
144                $threshold = get_branch_security_eol_date($branch);
145                if ($threshold === null) {
146                    // No EOL date available, assume it is ancient.
147                    continue;
148                }
149                if ($include_recent_eols) {
150                    $threshold->add($GLOBALS['KEEP_EOL']);
151                }
152                if ($now < $threshold) {
153                    $branches[$major][$branch] = $release;
154                    $branches[$major][$branch]['version'] = $version;
155                }
156            }
157        }
158        if (!empty($branches[$major])) {
159            ksort($branches[$major]);
160        }
161    }
162
163    ksort($branches);
164    return $branches;
165}
166
167/* If you provide an array to $always_include, note that the version numbers
168 * must be in $RELEASES _and_ must be the full version number, not the branch:
169 * ie provide array('5.3.29'), not array('5.3'). */
170function get_eol_branches($always_include = null) {
171    $always_include = $always_include ?: [];
172    $branches = [];
173    $now = new DateTime();
174
175    // Gather the last release on each branch into a convenient array.
176    foreach ($GLOBALS['OLDRELEASES'] as $major => $releases) {
177        foreach ($releases as $version => $release) {
178            $branch = version_number_to_branch($version);
179
180            if ($branch) {
181                if (!isset($branches[$major][$branch]) || version_compare($version, $branches[$major][$branch]['version'], 'gt')) {
182                    $branches[$major][$branch] = [
183                        'date' => strtotime($release['date']),
184                        'link' => "/releases#$version",
185                        'version' => $version,
186                    ];
187                }
188            }
189        }
190    }
191
192    /* Exclude releases from active branches, where active is defined as "in
193     * the $RELEASES array and not explicitly marked as EOL there". */
194    foreach ($GLOBALS['RELEASES'] as $major => $releases) {
195        foreach ($releases as $version => $release) {
196            $branch = version_number_to_branch($version);
197
198            if ($branch) {
199                if ($now < get_branch_security_eol_date($branch)) {
200                    /* This branch isn't EOL: remove it from our array. */
201                    if (isset($branches[$major][$branch])) {
202                        unset($branches[$major][$branch]);
203                    }
204                } else {
205                    /* Add the release information to the EOL branches array, since it
206                     * should be newer than the information we got from $OLDRELEASES. */
207                    $always_include[] = $version;
208                }
209            }
210        }
211    }
212
213    // Include any release in the always_include list that's in $RELEASES.
214    if ($always_include) {
215        foreach ($always_include as $version) {
216            $parts = explode('.', $version);
217            $major = $parts[0];
218
219            if (isset($GLOBALS['RELEASES'][$major][$version])) {
220                $release = $GLOBALS['RELEASES'][$major][$version];
221                $branch = version_number_to_branch($version);
222
223                if ($branch) {
224                    $branches[$major][$branch] = [
225                        'date' => strtotime($release['source'][0]['date']),
226                        'link' => "/downloads#v$version",
227                        'version' => $version,
228                    ];
229                }
230            }
231        }
232    }
233
234    krsort($branches);
235    foreach ($branches as &$branch) {
236        krsort($branch);
237    }
238
239    return $branches;
240}
241
242/* $branch is expected to have at least two components: MAJOR.MINOR or
243 * MAJOR.MINOR.REVISION (the REVISION will be ignored if provided). This will
244 * return either null (if no release exists on the given branch), or the usual
245 * version metadata from $RELEASES for a single release. */
246function get_initial_release($branch) {
247    $branch = version_number_to_branch($branch);
248    if (!$branch) {
249        return null;
250    }
251    $major = substr($branch, 0, strpos($branch, '.'));
252
253    if (isset($GLOBALS['OLDRELEASES'][$major]["$branch.0"])) {
254        return $GLOBALS['OLDRELEASES'][$major]["$branch.0"];
255    }
256
257    /* If there's only been one release on the branch, it won't be in
258     * $OLDRELEASES yet, so let's check $RELEASES. */
259    if (isset($GLOBALS['RELEASES'][$major]["$branch.0"])) {
260        // Fake a date like we have on the oldreleases array.
261        $release = $GLOBALS['RELEASES'][$major]["$branch.0"];
262        $release['date'] = $release['source'][0]['date'];
263        return $release;
264    }
265
266    // Shrug.
267    return null;
268}
269
270function get_final_release($branch) {
271    $branch = version_number_to_branch($branch);
272    if (!$branch) {
273        return null;
274    }
275    $major = substr($branch, 0, strpos($branch, '.'));
276
277    $last = "$branch.0";
278    foreach ($GLOBALS['OLDRELEASES'][$major] as $version => $release) {
279        if (version_number_to_branch($version) == $branch && version_compare($version, $last, '>')) {
280            $last = $version;
281        }
282    }
283
284    if (isset($GLOBALS['OLDRELEASES'][$major][$last])) {
285        return $GLOBALS['OLDRELEASES'][$major][$last];
286    }
287
288    /* If there's only been one release on the branch, it won't be in
289     * $OLDRELEASES yet, so let's check $RELEASES. */
290    if (isset($GLOBALS['RELEASES'][$major][$last])) {
291        // Fake a date like we have on the oldreleases array.
292        $release = $GLOBALS['RELEASES'][$major][$last];
293        $release['date'] = $release['source'][0]['date'];
294        return $release;
295    }
296
297    // Shrug.
298    return null;
299}
300
301function get_branch_bug_eol_date($branch): ?DateTime
302{
303    if (isset($GLOBALS['BRANCHES'][$branch]['stable'])) {
304        return new DateTime($GLOBALS['BRANCHES'][$branch]['stable']);
305    }
306
307    $date = get_branch_release_date($branch);
308
309    $date = $date?->add(new DateInterval('P2Y'));
310
311    // Versions before 8.2 do not extend the release cycle to the end of the year
312    if (version_compare($branch, '8.2', '<')) {
313        return $date;
314    }
315
316    // Extend the release cycle to the end of the year
317    return $date?->setDate($date->format('Y'), 12, 31);
318}
319
320function get_branch_security_eol_date($branch): ?DateTime
321{
322    if (isset($GLOBALS['BRANCHES'][$branch]['security'])) {
323        return new DateTime($GLOBALS['BRANCHES'][$branch]['security']);
324    }
325
326    /* Versions before 5.3 are based solely on the final release date in
327     * $OLDRELEASES. */
328    if (version_compare($branch, '5.3', '<')) {
329        $release = get_final_release($branch);
330
331        return $release ? new DateTime($release['date']) : null;
332    }
333
334    $date = get_branch_release_date($branch);
335
336    // Versions before 8.1 have 3-year support since the initial release
337    if (version_compare($branch, '8.1', '<')) {
338        return $date?->add(new DateInterval('P3Y'));
339    }
340
341    $date = $date?->add(new DateInterval('P4Y'));
342
343    // Extend the release cycle to the end of the year
344    return $date?->setDate($date->format('Y'), 12, 31);
345}
346
347function get_branch_release_date($branch): ?DateTime
348{
349    $initial = get_initial_release($branch);
350
351    return $initial ? new DateTime($initial['date']) : null;
352}
353
354function get_branch_support_state($branch) {
355    $initial = get_branch_release_date($branch);
356    $bug = get_branch_bug_eol_date($branch);
357    $security = get_branch_security_eol_date($branch);
358
359    if ($initial && $bug && $security) {
360        $now = new DateTime();
361
362        if ($now >= $security) {
363            return 'eol';
364        }
365
366        if ($now >= $bug) {
367            return 'security';
368        }
369
370        if ($now >= $initial) {
371            return 'stable';
372        }
373
374        return 'future';
375    }
376
377    return null;
378}
379
380function compare_version(array $arrayA, string $versionB)
381{
382    $arrayB = version_array($versionB, count($arrayA));
383
384    foreach ($arrayA as $index => $componentA) {
385        $componentA = $arrayA[$index];
386        $componentB = $arrayB[$index];
387        if ($componentA != $componentB) {
388            return $componentA > $componentB ? 1 : -1;
389        }
390    }
391
392    return 0;
393}
394
395function version_array(string $version, ?int $length = null)
396{
397    $versionArray = array_map(
398        'intval',
399        explode('.', $version),
400    );
401
402    if (is_int($length)) {
403        $versionArray = count($versionArray) < $length
404            ? array_pad($versionArray, $length, 0)
405            : array_slice(
406                $versionArray,
407                0,
408                $length,
409            );
410    }
411
412    return $versionArray;
413}
414
415function get_current_release_for_branch(int $major, ?int $minor): ?string {
416    global $RELEASES, $OLDRELEASES;
417
418    $prefix = "{$major}.";
419    if ($minor !== null) {
420        $prefix .= "{$minor}.";
421    }
422
423    foreach (($RELEASES[$major] ?? []) as $version => $_) {
424        if (!strncmp($prefix, $version, strlen($prefix))) {
425            return $version;
426        }
427    }
428
429    foreach (($OLDRELEASES[$major] ?? []) as $version => $_) {
430        if (!strncmp($prefix, $version, strlen($prefix))) {
431            return $version;
432        }
433    }
434
435    return null;
436}
437