Source Code
The entire project, readable in your browser. No hidden JavaScript, no sneaky beacons. If we claimed we don't track you, this is where you check.
|
Pages/
Feeds/
Backend/
Includes/
Stylesheet/
JavaScript/
data/counter.txt and data/guestbook.txt hold visitor-supplied content
(a number and signed messages), so they are not shown here.
|
1 <?php 2 /** 3 * functions.php 4 * ----------------------------------------------------------------------------- 5 * Shared helper library for "What Can I Learn About You?" 6 * 7 * Pure PHP, no dependencies. Everything here is safe to call on plain shared 8 * hosting. Functions are guarded with function_exists() so double-includes are 9 * harmless. 10 * 11 * Contents: 12 * - Output helpers ......... e() , eattr() 13 * - Time helpers ........... server_time(), utc_time(), http_date() 14 * - Visitor counter ........ get_visit_count(), bump_visit_count() 15 * - HTTP header tools ...... collect_request_headers(), client_ip(), 16 * http_protocol(), is_https() 17 * - ASCII / retro art ...... ascii_logo(), pixel_gif(), spacer_gif(), 18 * button_88x31(), section_icon() 19 * - Guestbook helpers ...... read_guestbook(), append_guestbook() 20 * - Misc ................... rand_seeded(), human_bytes() 21 * ----------------------------------------------------------------------------- 22 */ 23 24 require_once __DIR__ . '/config.php'; 25 26 // ============================================================================= 27 // OUTPUT HELPERS 28 // ============================================================================= 29 30 /** 31 * Escape text for safe HTML output (element content). 32 */ 33 if (!function_exists('e')) { 34 function e($value): string 35 { 36 return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); 37 } 38 } 39 40 /** 41 * Escape text for safe use inside a double-quoted HTML attribute. 42 * (Same routine as e() but named for intent/readability at call sites.) 43 */ 44 if (!function_exists('eattr')) { 45 function eattr($value): string 46 { 47 return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); 48 } 49 } 50 51 // ============================================================================= 52 // TIME HELPERS 53 // ============================================================================= 54 55 /** 56 * Current server local time, formatted BBS-style. 57 */ 58 if (!function_exists('server_time')) { 59 function server_time(): string 60 { 61 return date('Y-m-d H:i:s T'); 62 } 63 } 64 65 /** 66 * Current UTC time, formatted BBS-style. 67 */ 68 if (!function_exists('utc_time')) { 69 function utc_time(): string 70 { 71 return gmdate('Y-m-d H:i:s') . ' UTC'; 72 } 73 } 74 75 /** 76 * RFC-822 style date, used by the RSS feed. 77 */ 78 if (!function_exists('http_date')) { 79 function http_date($timestamp = null): string 80 { 81 $timestamp = $timestamp === null ? time() : (int)$timestamp; 82 return gmdate('D, d M Y H:i:s', $timestamp) . ' +0000'; 83 } 84 } 85 86 // ============================================================================= 87 // VISITOR COUNTER (flat file; degrades gracefully if not writable) 88 // ============================================================================= 89 90 /** 91 * Read the current visitor count without incrementing. 92 * Returns COUNTER_SEED if the file cannot be read. 93 */ 94 if (!function_exists('get_visit_count')) { 95 function get_visit_count(): int 96 { 97 if (is_readable(COUNTER_FILE)) { 98 $raw = @file_get_contents(COUNTER_FILE); 99 if ($raw !== false && $raw !== '') { 100 $n = (int)trim($raw); 101 if ($n > 0) { 102 return $n; 103 } 104 } 105 } 106 return COUNTER_SEED; 107 } 108 } 109 110 /** 111 * Increment and persist the visitor count, returning the new value. 112 * Uses an exclusive lock so concurrent hits do not corrupt the file. If the 113 * data directory is not writable, we simply return the read-only count so the 114 * page never breaks. 115 * 116 * NOTE: This is an aggregate tally only. We do NOT record IPs, timestamps, 117 * user agents, or anything that could identify a visitor. It is a single 118 * integer in a text file, in keeping with the site's "we store nothing about 119 * you" promise. 120 */ 121 if (!function_exists('bump_visit_count')) { 122 function bump_visit_count(): int 123 { 124 // Make sure the data directory exists (first run on a fresh upload). 125 if (!is_dir(DATA_DIR)) { 126 @mkdir(DATA_DIR, 0755, true); 127 } 128 129 // If we cannot write, just report the current number. 130 if ((file_exists(COUNTER_FILE) && !is_writable(COUNTER_FILE)) 131 || (!file_exists(COUNTER_FILE) && !is_writable(DATA_DIR))) { 132 return get_visit_count(); 133 } 134 135 $fp = @fopen(COUNTER_FILE, 'c+'); 136 if ($fp === false) { 137 return get_visit_count(); 138 } 139 140 $count = COUNTER_SEED; 141 if (@flock($fp, LOCK_EX)) { 142 $raw = stream_get_contents($fp); 143 $current = ($raw !== false && trim($raw) !== '') ? (int)trim($raw) : COUNTER_SEED; 144 if ($current < COUNTER_SEED) { 145 $current = COUNTER_SEED; 146 } 147 $count = $current + 1; 148 149 rewind($fp); 150 ftruncate($fp, 0); 151 fwrite($fp, (string)$count); 152 fflush($fp); 153 @flock($fp, LOCK_UN); 154 } 155 fclose($fp); 156 157 return $count; 158 } 159 } 160 161 /** 162 * Render a count as a zero-padded "odometer" string, e.g. 000013337. 163 */ 164 if (!function_exists('odometer')) { 165 function odometer(int $n, int $width = 9): string 166 { 167 return str_pad((string)$n, $width, '0', STR_PAD_LEFT); 168 } 169 } 170 171 // ============================================================================= 172 // HTTP HEADER TOOLS 173 // ============================================================================= 174 175 /** 176 * Best-effort real client IP. We look at a few common proxy headers but always 177 * fall back to REMOTE_ADDR. This is only used to enrich the on-screen report 178 * for the visitor themselves -- it is never stored or logged. 179 */ 180 if (!function_exists('client_ip')) { 181 function client_ip(): string 182 { 183 $candidates = array(); 184 185 if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) { 186 $candidates[] = $_SERVER['HTTP_CF_CONNECTING_IP']; 187 } 188 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { 189 // May be a comma-separated list; the left-most is the origin client. 190 foreach (explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']) as $part) { 191 $candidates[] = trim($part); 192 } 193 } 194 if (!empty($_SERVER['HTTP_X_REAL_IP'])) { 195 $candidates[] = $_SERVER['HTTP_X_REAL_IP']; 196 } 197 if (!empty($_SERVER['REMOTE_ADDR'])) { 198 $candidates[] = $_SERVER['REMOTE_ADDR']; 199 } 200 201 foreach ($candidates as $ip) { 202 $ip = trim($ip); 203 if (filter_var($ip, FILTER_VALIDATE_IP)) { 204 return $ip; 205 } 206 } 207 208 return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'; 209 } 210 } 211 212 /** 213 * True if the request arrived over HTTPS (directly or via a trusted proxy). 214 */ 215 if (!function_exists('is_https')) { 216 function is_https(): bool 217 { 218 if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') { 219 return true; 220 } 221 if (isset($_SERVER['SERVER_PORT']) && (int)$_SERVER['SERVER_PORT'] === 443) { 222 return true; 223 } 224 if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) 225 && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') { 226 return true; 227 } 228 if (!empty($_SERVER['HTTP_CF_VISITOR']) 229 && stripos($_SERVER['HTTP_CF_VISITOR'], 'https') !== false) { 230 return true; 231 } 232 return false; 233 } 234 } 235 236 /** 237 * The HTTP protocol version string, e.g. "HTTP/1.1" or "HTTP/2". 238 */ 239 if (!function_exists('http_protocol')) { 240 function http_protocol(): string 241 { 242 return isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'; 243 } 244 } 245 246 /** 247 * Reconstruct the raw request headers from the $_SERVER superarray. 248 * Returns an associative array of Header-Name => value, with keys normalised to 249 * canonical "Title-Case" form. Uses getallheaders() when available (Apache) and 250 * falls back to parsing HTTP_* keys everywhere else. 251 */ 252 if (!function_exists('collect_request_headers')) { 253 function collect_request_headers(): array 254 { 255 $headers = array(); 256 257 if (function_exists('getallheaders')) { 258 $raw = getallheaders(); 259 if (is_array($raw)) { 260 foreach ($raw as $name => $value) { 261 $headers[normalize_header_name($name)] = $value; 262 } 263 } 264 } 265 266 // Always merge the $_SERVER view too (covers non-Apache and CLI-ish SAPIs). 267 foreach ($_SERVER as $key => $value) { 268 if (strpos($key, 'HTTP_') === 0) { 269 $name = normalize_header_name(str_replace('_', '-', substr($key, 5))); 270 if (!isset($headers[$name])) { 271 $headers[$name] = $value; 272 } 273 } 274 } 275 276 // A few useful non-HTTP_ entries that are really request metadata. 277 if (!empty($_SERVER['CONTENT_TYPE'])) { 278 $headers['Content-Type'] = $_SERVER['CONTENT_TYPE']; 279 } 280 if (!empty($_SERVER['CONTENT_LENGTH'])) { 281 $headers['Content-Length'] = $_SERVER['CONTENT_LENGTH']; 282 } 283 284 // Sort for stable, readable display. 285 ksort($headers, SORT_STRING | SORT_FLAG_CASE); 286 287 return $headers; 288 } 289 } 290 291 /** 292 * Normalise a header name to canonical Title-Case-With-Dashes. 293 */ 294 if (!function_exists('normalize_header_name')) { 295 function normalize_header_name(string $name): string 296 { 297 $name = strtolower(trim($name)); 298 $parts = explode('-', $name); 299 foreach ($parts as $i => $p) { 300 // Keep well-known acronyms upper-cased. 301 $upper = strtoupper($p); 302 if (in_array($upper, array('IP', 'DNT', 'TE', 'UA', 'WWW', 'CF', 'ID', 'URL'), true)) { 303 $parts[$i] = $upper; 304 } else { 305 $parts[$i] = ucfirst($p); 306 } 307 } 308 return implode('-', $parts); 309 } 310 } 311 312 // ============================================================================= 313 // ASCII / RETRO ART 314 // ============================================================================= 315 316 /** 317 * The big header logo. Kept as a heredoc so the alignment is exact. Rendered 318 * inside a <pre> by header.php. 319 */ 320 if (!function_exists('ascii_logo')) { 321 function ascii_logo(): string 322 { 323 return <<<'ART' 324 __ __ _ ____ _ 325 \ \ / /_ _ ___| |_ / ___|__ _ _ __ _ | | 326 \ \ /\ / / _` |/ __| __| \___ / _` | '_ \ _| || | 327 \ V V / (_| | (__| |_ ___) | (_| | | | ||_ _| 328 \_/\_/ \__,_|\___|\__| |____/ \__,_|_| |_| |_| 329 330 _ _ _ _ 331 | | ___ __ _ _ __ _ __ | | / \ _ __ ___ _| |_ 332 | | / _ \/ _` | '__| '_ \| | / _ \ | '_ \/ _ \|_ _| 333 | |__| __/ (_| | | | | | |_|/ ___ \| |_) | (_) | |_| 334 |_____\___|\__,_|_| |_| |_(_)_/ \_\ .__/ \___/ (_) 335 |_| 336 W H A T C A N I L E A R N ? 337 ART; 338 } 339 } 340 341 /** 342 * Return a 1x1 transparent GIF as a data URI. Used for classic spacer.gif 343 * layout tricks and as a base for coloured pixel blocks. 344 */ 345 if (!function_exists('spacer_gif')) { 346 function spacer_gif(): string 347 { 348 // Standard 1x1 transparent GIF89a. 349 return 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; 350 } 351 } 352 353 /** 354 * Emit a <img> tag for the transparent spacer at a given size. The web-1.0 way 355 * to do layout and padding. 356 */ 357 if (!function_exists('spacer')) { 358 function spacer(int $w = 1, int $h = 1): string 359 { 360 return '<img src="' . spacer_gif() . '" width="' . $w . '" height="' . $h 361 . '" alt="" border="0">'; 362 } 363 } 364 365 /** 366 * A solid coloured "pixel GIF" block, produced with CSS on top of the spacer so 367 * we never have to upload any binaries. Handy for status LEDs and bar charts. 368 */ 369 if (!function_exists('pixel_gif')) { 370 function pixel_gif(string $color, int $w = 10, int $h = 10, string $title = ''): string 371 { 372 $t = $title !== '' ? ' title="' . eattr($title) . '"' : ''; 373 return '<img src="' . spacer_gif() . '" width="' . $w . '" height="' . $h 374 . '" alt=""' . $t . ' style="background:' . eattr($color) 375 . ';line-height:0;font-size:0;border:0;vertical-align:middle">'; 376 } 377 } 378 379 /** 380 * Render one classic 88x31 button as a pure-CSS box (no image upload needed). 381 * $btn = [top-text, bottom-text, fg-color, bg-color] 382 */ 383 if (!function_exists('button_88x31')) { 384 function button_88x31(array $btn): string 385 { 386 list($top, $bottom, $fg, $bg) = $btn; 387 $style = 'display:inline-block;width:88px;height:31px;' 388 . 'background:' . eattr($bg) . ';color:' . eattr($fg) . ';' 389 . 'border:1px solid #808080;text-align:center;' 390 . 'font-family:\'Courier New\',monospace;line-height:1;' 391 . 'overflow:hidden;vertical-align:middle;margin:2px;' 392 . 'box-sizing:border-box;'; 393 $out = '<span class="btn88" style="' . $style . '">'; 394 $out .= '<span style="display:block;font-size:9px;padding-top:5px;letter-spacing:1px">' 395 . e($top) . '</span>'; 396 $out .= '<span style="display:block;font-size:12px;font-weight:bold;letter-spacing:1px">' 397 . e($bottom) . '</span>'; 398 $out .= '</span>'; 399 return $out; 400 } 401 } 402 403 /** 404 * Tiny monochrome pixel-art icon for a section, drawn as an inline SVG data-URI 405 * so it stays crisp and needs no binary upload. Supported names: 406 * monitor, globe, chip, lock, warning, folder, eye, key, disk, plug 407 * Falls back to a small square for unknown names. 408 */ 409 if (!function_exists('section_icon')) { 410 function section_icon(string $name, string $color = '#00FF00', int $size = 16): string 411 { 412 // 8x8 pixel grids. '1' = filled pixel. Deliberately chunky/retro. 413 $grids = array( 414 'monitor' => array( 415 '11111111', 416 '10000001', 417 '10111101', 418 '10100101', 419 '10111101', 420 '11111111', 421 '00111100', 422 '01111110', 423 ), 424 'globe' => array( 425 '00111100', 426 '01100110', 427 '11011011', 428 '10111101', 429 '10111101', 430 '11011011', 431 '01100110', 432 '00111100', 433 ), 434 'chip' => array( 435 '00100100', 436 '11111111', 437 '10111101', 438 '10100101', 439 '10100101', 440 '10111101', 441 '11111111', 442 '00100100', 443 ), 444 'lock' => array( 445 '00111100', 446 '01100110', 447 '01100110', 448 '11111111', 449 '11011011', 450 '11000011', 451 '11011011', 452 '11111111', 453 ), 454 'warning' => array( 455 '00011000', 456 '00011000', 457 '00111100', 458 '00111100', 459 '01100110', 460 '01111110', 461 '11100111', 462 '11111111', 463 ), 464 'folder' => array( 465 '00000000', 466 '11100000', 467 '11111110', 468 '10000010', 469 '10000010', 470 '10000010', 471 '11111110', 472 '00000000', 473 ), 474 'eye' => array( 475 '00000000', 476 '00111100', 477 '01000010', 478 '10011001', 479 '10011001', 480 '01000010', 481 '00111100', 482 '00000000', 483 ), 484 'key' => array( 485 '00111000', 486 '01000100', 487 '01000100', 488 '00111000', 489 '00010000', 490 '00011000', 491 '00010000', 492 '00011000', 493 ), 494 'disk' => array( 495 '11111110', 496 '10011010', 497 '10011010', 498 '11111110', 499 '10000010', 500 '10111010', 501 '10111010', 502 '11111110', 503 ), 504 'plug' => array( 505 '00100100', 506 '00100100', 507 '01111110', 508 '01111110', 509 '00111100', 510 '00011000', 511 '00011000', 512 '00111100', 513 ), 514 ); 515 516 $grid = isset($grids[$name]) ? $grids[$name] : array( 517 '11111111','10000001','10000001','10000001', 518 '10000001','10000001','10000001','11111111', 519 ); 520 521 // Build a compact SVG with one <rect> per filled pixel. 522 $rects = ''; 523 foreach ($grid as $y => $row) { 524 $len = strlen($row); 525 for ($x = 0; $x < $len; $x++) { 526 if ($row[$x] === '1') { 527 $rects .= '<rect x="' . $x . '" y="' . $y . '" width="1" height="1"/>'; 528 } 529 } 530 } 531 $svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' . $size . '" height="' . $size 532 . '" viewBox="0 0 8 8" shape-rendering="crispEdges" fill="' . $color . '">' 533 . $rects . '</svg>'; 534 535 $uri = 'data:image/svg+xml;base64,' . base64_encode($svg); 536 return '<img src="' . $uri . '" width="' . $size . '" height="' . $size 537 . '" alt="' . eattr($name) . '" title="' . eattr($name) 538 . '" border="0" style="vertical-align:middle;image-rendering:pixelated">'; 539 } 540 } 541 542 // ============================================================================= 543 // GUESTBOOK HELPERS (flat file; append-only; no database) 544 // ============================================================================= 545 546 /** 547 * Read guestbook entries, newest first. Each entry is one line stored as 548 * pipe-separated: epoch|name|message (name/message are already sanitised on 549 * write). Returns an array of ['time'=>int,'name'=>string,'message'=>string]. 550 */ 551 if (!function_exists('read_guestbook')) { 552 function read_guestbook(int $limit = 100): array 553 { 554 $entries = array(); 555 if (!is_readable(GUESTBOOK_FILE)) { 556 return $entries; 557 } 558 $lines = @file(GUESTBOOK_FILE, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 559 if ($lines === false) { 560 return $entries; 561 } 562 foreach ($lines as $line) { 563 $parts = explode('|', $line, 3); 564 if (count($parts) === 3) { 565 $entries[] = array( 566 'time' => (int)$parts[0], 567 'name' => guestbook_decode($parts[1]), 568 'message' => guestbook_decode($parts[2]), 569 ); 570 } 571 } 572 // Newest first. 573 $entries = array_reverse($entries); 574 if ($limit > 0 && count($entries) > $limit) { 575 $entries = array_slice($entries, 0, $limit); 576 } 577 return $entries; 578 } 579 } 580 581 /** 582 * Encode a field for single-line flat-file storage (strip pipes/newlines). 583 */ 584 if (!function_exists('guestbook_encode')) { 585 function guestbook_encode(string $s): string 586 { 587 // Store as URL-encoded so pipes and newlines can never break the format. 588 return rawurlencode($s); 589 } 590 } 591 592 /** 593 * Decode a stored guestbook field. 594 */ 595 if (!function_exists('guestbook_decode')) { 596 function guestbook_decode(string $s): string 597 { 598 return rawurldecode($s); 599 } 600 } 601 602 /** 603 * Append a sanitised entry to the guestbook. Returns [ok(bool), message(str)]. 604 * Enforces length limits, strips control characters, and caps total entries. 605 * Stores time + name + message only -- never IP, never user agent. 606 */ 607 if (!function_exists('append_guestbook')) { 608 function append_guestbook(string $name, string $message): array 609 { 610 $name = trim($name); 611 $message = trim($message); 612 613 if ($name === '' || $message === '') { 614 return array(false, 'Both a handle and a message are required.'); 615 } 616 617 // Collapse whitespace / strip control chars. 618 $name = preg_replace('/[\x00-\x1F\x7F]+/u', ' ', $name); 619 $message = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/u', ' ', $message); 620 621 if (function_exists('mb_substr')) { 622 $name = mb_substr($name, 0, GUESTBOOK_MAX_NAME); 623 $message = mb_substr($message, 0, GUESTBOOK_MAX_MESSAGE); 624 } else { 625 $name = substr($name, 0, GUESTBOOK_MAX_NAME); 626 $message = substr($message, 0, GUESTBOOK_MAX_MESSAGE); 627 } 628 629 if (!is_dir(DATA_DIR)) { 630 @mkdir(DATA_DIR, 0755, true); 631 } 632 if ((file_exists(GUESTBOOK_FILE) && !is_writable(GUESTBOOK_FILE)) 633 || (!file_exists(GUESTBOOK_FILE) && !is_writable(DATA_DIR))) { 634 return array(false, 'Guestbook is read-only on this server (data/ not writable).'); 635 } 636 637 $line = time() . '|' . guestbook_encode($name) . '|' . guestbook_encode($message) . "\n"; 638 639 $ok = @file_put_contents(GUESTBOOK_FILE, $line, FILE_APPEND | LOCK_EX); 640 if ($ok === false) { 641 return array(false, 'Could not write to the guestbook.'); 642 } 643 644 // Trim to the last GUESTBOOK_MAX_ENTRIES lines to keep the file bounded. 645 trim_guestbook(); 646 647 return array(true, 'Signed. Thanks for stopping by.'); 648 } 649 } 650 651 /** 652 * Keep the guestbook file bounded to GUESTBOOK_MAX_ENTRIES lines. 653 */ 654 if (!function_exists('trim_guestbook')) { 655 function trim_guestbook(): void 656 { 657 if (!is_readable(GUESTBOOK_FILE) || !is_writable(GUESTBOOK_FILE)) { 658 return; 659 } 660 $lines = @file(GUESTBOOK_FILE, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 661 if ($lines === false) { 662 return; 663 } 664 if (count($lines) > GUESTBOOK_MAX_ENTRIES) { 665 $lines = array_slice($lines, -GUESTBOOK_MAX_ENTRIES); 666 @file_put_contents(GUESTBOOK_FILE, implode("\n", $lines) . "\n", LOCK_EX); 667 } 668 } 669 } 670 671 // ============================================================================= 672 // MISC 673 // ============================================================================= 674 675 /** 676 * Deterministic pseudo-random int in [min,max] from a string seed. Used for 677 * flavour text that should stay stable within a request. 678 */ 679 if (!function_exists('rand_seeded')) { 680 function rand_seeded(string $seed, int $min, int $max): int 681 { 682 if ($max <= $min) { 683 return $min; 684 } 685 $h = crc32($seed); 686 return $min + ($h % ($max - $min + 1)); 687 } 688 } 689 690 /** 691 * Human-readable byte size, e.g. 1536 -> "1.5 KB". 692 */ 693 if (!function_exists('human_bytes')) { 694 function human_bytes($bytes): string 695 { 696 $bytes = (float)$bytes; 697 $units = array('B', 'KB', 'MB', 'GB', 'TB'); 698 $i = 0; 699 while ($bytes >= 1024 && $i < count($units) - 1) { 700 $bytes /= 1024; 701 $i++; 702 } 703 return ($i === 0 ? (string)(int)$bytes : number_format($bytes, 1)) . ' ' . $units[$i]; 704 } 705 } 706 707 /** 708 * Pick the "newest" feature/date from the update log, used by the NEW! easter 709 * egg and the RSS <lastBuildDate>. 710 */ 711 if (!function_exists('latest_update_timestamp')) { 712 function latest_update_timestamp(): int 713 { 714 $latest = 0; 715 if (!empty($GLOBALS['UPDATE_LOG']) && is_array($GLOBALS['UPDATE_LOG'])) { 716 foreach ($GLOBALS['UPDATE_LOG'] as $row) { 717 $ts = strtotime($row[0] . ' 12:00:00 UTC'); 718 if ($ts && $ts > $latest) { 719 $latest = $ts; 720 } 721 } 722 } 723 return $latest > 0 ? $latest : time(); 724 } 725 } 726 You are reading the real file being executed on the server. What you see is what runs. |