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