PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / core / SiteLocalTimestamp.php

SiteLocalTimestamp.php in 404 Solution trunk, at includes/core/SiteLocalTimestamp.php

102 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Renders a UTC epoch as a display string in the WordPress site's configured
9 * timezone (Settings > General), on every WordPress version this plugin
10 * declares support for.
11 *
12 * wp_date() arrived in WordPress 5.3, but 404-solution.php and readme.txt both
13 * declare `Requires at least: 5.0`, so a bare wp_date() call is a
14 * `Call to undefined function` fatal on every site the declaration invites in.
15 * Eight such calls sat in the two main admin tables (RedirectRowPresenter,
16 * View_CapturedURLsTable, AdminLogsTable) and in the n-gram scheduler's refusal
17 * reports, which is the whole plugin surface a WP 5.0-5.2 admin would open.
18 *
19 * The rest of the codebase already guards its post-5.0 core calls one by one
20 * (ABJ_404_Solution_SiteTimezone::resolve, ScheduledEventInspector,
21 * RequestEnvironmentFingerprint, WPUtils::addScriptTranslations,
22 * FrontendSuggestionLocaleScope). This class exists so the timestamp axis is
23 * guarded ONCE rather than eight times: adding a ninth inline function_exists()
24 * check would spread the same decision across nine files, and the ninth is the
25 * one somebody forgets.
26 *
27 * The WP < 5.3 path is not a degradation to UTC: it reuses
28 * ABJ_404_Solution_SiteTimezone, so the rendered wall-clock time is the same
29 * one wp_date() would produce. Only WordPress's localized month, weekday and
30 * am/pm strings are unavailable, because those live behind $wp_locale's
31 * date translation helpers that wp_date() itself introduced.
32 *
33 * Pure presentation policy: no business decisions, no data store access.
34 *
35 * @see scripts/lint/lint-wp-version-compat.php which fails the build on any
36 * shipped call to a core function newer than the declared floor.
37 */
38 final class ABJ_404_Solution_SiteLocalTimestamp {
39
40 /**
41 * Format a UTC epoch for display in the site's timezone.
42 *
43 * Argument order mirrors wp_date() so call sites read identically.
44 *
45 * A wp_date() that returns false (WordPress's documented failure return)
46 * falls through to the same fallback rather than yielding the empty string
47 * the previous `(string)wp_date(...)` casts produced: an empty date cell
48 * tells the admin nothing, and the fallback can still answer correctly.
49 *
50 * Exceptions thrown by wp_date() itself are deliberately NOT caught. On
51 * WordPress 5.3+ that means the site's timezone options are corrupt, which
52 * is a real fault the admin needs to see; swallowing it here would only
53 * hide it behind a plausible-looking timestamp.
54 *
55 * @param string $format A PHP date() format string.
56 * @param int $timestamp Seconds since the Unix epoch, UTC.
57 * @return string The formatted timestamp; never false, never null.
58 */
59 public static function format(string $format, int $timestamp): string {
60 if (function_exists('wp_date')) {
61 $rendered = wp_date($format, $timestamp);
62 if (is_string($rendered) && $rendered !== '') {
63 return $rendered;
64 }
65 }
66
67 return self::formatFallback($format, $timestamp);
68 }
69
70 /**
71 * The WordPress < 5.3 rendering path, split out (exactly as
72 * ABJ_404_Solution_SiteTimezone::resolveFallback is) so it can be unit
73 * tested directly. Once any test in a worker stubs wp_date() through
74 * Brain\Monkey, Patchwork keeps it defined for the rest of that PHP
75 * process (R6 in .claude/rules/testing.md), so function_exists('wp_date')
76 * cannot be driven to false from inside the suite.
77 *
78 * @param string $format A PHP date() format string.
79 * @param int $timestamp Seconds since the Unix epoch, UTC.
80 * @return string The formatted timestamp.
81 */
82 public static function formatFallback(string $format, int $timestamp): string {
83 try {
84 $moment = new DateTimeImmutable('@' . $timestamp);
85 return $moment->setTimezone(ABJ_404_Solution_SiteTimezone::resolve())->format($format);
86 } catch (Throwable $e) {
87 // Only reachable when the site's timezone options are corrupt
88 // enough that resolving a DateTimeZone throws at all. Rendering
89 // in UTC keeps the admin table readable; the warning says why the
90 // number may not match the site's clock.
91 if (function_exists('abj_service_optional')) {
92 $logging = abj_service_optional('logging');
93 if ($logging !== null) {
94 $logging->warn('SiteLocalTimestamp: could not resolve the site timezone (' .
95 $e->getMessage() . '); rendering "' . $format . '" in UTC instead.');
96 }
97 }
98 return gmdate($format, $timestamp);
99 }
100 }
101 }
102