[ 'security' => '2000-10-20', ], '5.3' => [ 'stable' => '2013-07-11', 'security' => '2014-08-14', ], '5.4' => [ 'stable' => '2014-09-14', 'security' => '2015-09-03', ], '5.5' => [ 'stable' => '2015-07-10', 'security' => '2016-07-21', ], '5.6' => [ 'stable' => '2017-01-19', 'security' => '2018-12-31', ], '7.0' => [ 'stable' => '2018-01-04', 'security' => '2019-01-10', ], '8.4' => [ 'date' => '2024-11-21', ], ]; /* Time to keep EOLed branches in the array returned by get_active_branches(), * which is used on the front page download links and the supported versions * page. (Currently 28 days.) */ $KEEP_EOL = new DateInterval('P28D'); function format_interval($from, DateTime $to) { try { $from_obj = $from instanceof DateTime ? $from : new DateTime($from); $diff = $to->diff($from_obj); $times = []; if ($diff->y) { $times[] = [$diff->y, 'year']; if ($diff->m) { $times[] = [$diff->m, 'month']; } } elseif ($diff->m) { $times[] = [$diff->m, 'month']; } elseif ($diff->d) { $times[] = [$diff->d, 'day']; } else { $eolPeriod = 'midnight'; } if ($times) { $eolPeriod = implode(', ', array_map( function ($t) { return "$t[0] $t[1]" . ($t[0] != 1 ? 's' : ''); }, $times, ), ); if ($diff->invert) { $eolPeriod = "$eolPeriod ago"; } else { $eolPeriod = "in $eolPeriod"; } } } catch (Exception $e) { $eolPeriod = 'unknown'; } return $eolPeriod; } function version_number_to_branch(string $version): ?string { $parts = explode('.', $version); if (count($parts) > 1) { return "$parts[0].$parts[1]"; } return null; } function get_all_branches() { $branches = []; foreach ($GLOBALS['OLDRELEASES'] as $major => $releases) { foreach ($releases as $version => $release) { $branch = version_number_to_branch($version); if ($branch) { if (!isset($branches[$major][$branch]) || version_compare($version, $branches[$major][$branch]['version'], 'gt')) { $branches[$major][$branch] = $release; $branches[$major][$branch]['version'] = $version; } } } } foreach ($GLOBALS['RELEASES'] as $major => $releases) { foreach ($releases as $version => $release) { $branch = version_number_to_branch($version); if ($branch) { if (!isset($branches[$major][$branch]) || version_compare($version, $branches[$major][$branch]['version'], 'gt')) { $branches[$major][$branch] = $release; $branches[$major][$branch]['version'] = $version; } } } } krsort($branches); foreach ($branches as &$branch) { krsort($branch); } return $branches; } function get_active_branches($include_recent_eols = true) { $branches = []; $now = new DateTime(); foreach ($GLOBALS['RELEASES'] as $major => $releases) { foreach ($releases as $version => $release) { $branch = version_number_to_branch($version); if ($branch) { $threshold = get_branch_security_eol_date($branch); if ($threshold === null) { // No EOL date available, assume it is ancient. continue; } if ($include_recent_eols) { $threshold->add($GLOBALS['KEEP_EOL']); } if ($now < $threshold) { $branches[$major][$branch] = $release; $branches[$major][$branch]['version'] = $version; } } } if (!empty($branches[$major])) { ksort($branches[$major]); } } ksort($branches); return $branches; } /* If you provide an array to $always_include, note that the version numbers * must be in $RELEASES _and_ must be the full version number, not the branch: * ie provide array('5.3.29'), not array('5.3'). */ function get_eol_branches($always_include = null) { $always_include = $always_include ?: []; $branches = []; $now = new DateTime(); // Gather the last release on each branch into a convenient array. foreach ($GLOBALS['OLDRELEASES'] as $major => $releases) { foreach ($releases as $version => $release) { $branch = version_number_to_branch($version); if ($branch) { if (!isset($branches[$major][$branch]) || version_compare($version, $branches[$major][$branch]['version'], 'gt')) { $branches[$major][$branch] = [ 'date' => strtotime($release['date']), 'link' => "/releases#$version", 'version' => $version, ]; } } } } /* Exclude releases from active branches, where active is defined as "in * the $RELEASES array and not explicitly marked as EOL there". */ foreach ($GLOBALS['RELEASES'] as $major => $releases) { foreach ($releases as $version => $release) { $branch = version_number_to_branch($version); if ($branch) { if ($now < get_branch_security_eol_date($branch)) { /* This branch isn't EOL: remove it from our array. */ if (isset($branches[$major][$branch])) { unset($branches[$major][$branch]); } } else { /* Add the release information to the EOL branches array, since it * should be newer than the information we got from $OLDRELEASES. */ $always_include[] = $version; } } } } // Include any release in the always_include list that's in $RELEASES. if ($always_include) { foreach ($always_include as $version) { $parts = explode('.', $version); $major = $parts[0]; if (isset($GLOBALS['RELEASES'][$major][$version])) { $release = $GLOBALS['RELEASES'][$major][$version]; $branch = version_number_to_branch($version); if ($branch) { $branches[$major][$branch] = [ 'date' => strtotime($release['source'][0]['date']), 'link' => "/downloads#v$version", 'version' => $version, ]; } } } } krsort($branches); foreach ($branches as &$branch) { krsort($branch); } return $branches; } /* $branch is expected to have at least two components: MAJOR.MINOR or * MAJOR.MINOR.REVISION (the REVISION will be ignored if provided). This will * return either null (if no release exists on the given branch), or the usual * version metadata from $RELEASES for a single release. */ function get_initial_release($branch) { $branch = version_number_to_branch($branch); if (!$branch) { return null; } $major = substr($branch, 0, strpos($branch, '.')); if (isset($GLOBALS['OLDRELEASES'][$major]["$branch.0"])) { return $GLOBALS['OLDRELEASES'][$major]["$branch.0"]; } if(isset($GLOBALS['BRANCHES'][$branch])) { return $GLOBALS['BRANCHES'][$branch]; } /* If there's only been one release on the branch, it won't be in * $OLDRELEASES yet, so let's check $RELEASES. */ if (isset($GLOBALS['RELEASES'][$major]["$branch.0"])) { // Fake a date like we have on the oldreleases array. $release = $GLOBALS['RELEASES'][$major]["$branch.0"]; $release['date'] = $release['source'][0]['date']; return $release; } // Shrug. return null; } function get_final_release($branch) { $branch = version_number_to_branch($branch); if (!$branch) { return null; } $major = substr($branch, 0, strpos($branch, '.')); $last = "$branch.0"; foreach ($GLOBALS['OLDRELEASES'][$major] as $version => $release) { if (version_number_to_branch($version) == $branch && version_compare($version, $last, '>')) { $last = $version; } } if (isset($GLOBALS['OLDRELEASES'][$major][$last])) { return $GLOBALS['OLDRELEASES'][$major][$last]; } /* If there's only been one release on the branch, it won't be in * $OLDRELEASES yet, so let's check $RELEASES. */ if (isset($GLOBALS['RELEASES'][$major][$last])) { // Fake a date like we have on the oldreleases array. $release = $GLOBALS['RELEASES'][$major][$last]; $release['date'] = $release['source'][0]['date']; return $release; } // Shrug. return null; } function get_branch_bug_eol_date($branch): ?DateTime { if (isset($GLOBALS['BRANCHES'][$branch]['stable'])) { return new DateTime($GLOBALS['BRANCHES'][$branch]['stable']); } $date = get_branch_release_date($branch); $date = $date?->add(new DateInterval('P2Y')); // Versions before 8.2 do not extend the release cycle to the end of the year if (version_compare($branch, '8.2', '<')) { return $date; } // Extend the release cycle to the end of the year return $date?->setDate($date->format('Y'), 12, 31); } function get_branch_security_eol_date($branch): ?DateTime { if (isset($GLOBALS['BRANCHES'][$branch]['security'])) { return new DateTime($GLOBALS['BRANCHES'][$branch]['security']); } /* Versions before 5.3 are based solely on the final release date in * $OLDRELEASES. */ if (version_compare($branch, '5.3', '<')) { $release = get_final_release($branch); return $release ? new DateTime($release['date']) : null; } $date = get_branch_release_date($branch); // Versions before 8.1 have 3-year support since the initial release if (version_compare($branch, '8.1', '<')) { return $date?->add(new DateInterval('P3Y')); } $date = $date?->add(new DateInterval('P4Y')); // Extend the release cycle to the end of the year return $date?->setDate($date->format('Y'), 12, 31); } function get_branch_release_date($branch): ?DateTime { $initial = get_initial_release($branch); return $initial ? new DateTime($initial['date']) : null; } function get_branch_support_state($branch) { $initial = get_branch_release_date($branch); $bug = get_branch_bug_eol_date($branch); $security = get_branch_security_eol_date($branch); if ($initial && $bug && $security) { $now = new DateTime(); if ($now >= $security) { return 'eol'; } if ($now >= $bug) { return 'security'; } if ($now >= $initial) { return 'stable'; } return 'future'; } return null; } function compare_version(array $arrayA, string $versionB) { $arrayB = version_array($versionB, count($arrayA)); foreach ($arrayA as $index => $componentA) { $componentA = $arrayA[$index]; $componentB = $arrayB[$index]; if ($componentA != $componentB) { return $componentA > $componentB ? 1 : -1; } } return 0; } function version_array(string $version, ?int $length = null) { $versionArray = array_map( 'intval', explode('.', $version), ); if (is_int($length)) { $versionArray = count($versionArray) < $length ? array_pad($versionArray, $length, 0) : array_slice( $versionArray, 0, $length, ); } return $versionArray; } function get_current_release_for_branch(int $major, ?int $minor): ?string { global $RELEASES, $OLDRELEASES; $prefix = "{$major}."; if ($minor !== null) { $prefix .= "{$minor}."; } foreach (($RELEASES[$major] ?? []) as $version => $_) { if (!strncmp($prefix, $version, strlen($prefix))) { return $version; } } foreach (($OLDRELEASES[$major] ?? []) as $version => $_) { if (!strncmp($prefix, $version, strlen($prefix))) { return $version; } } return null; }