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