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