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 / PluginReleaseChannel.php

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

100 lines 4.3 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 * Which release channel the running build belongs to: a deliberately
9 * published pre-release (`4.3.3-beta.2`, `4.4.0-rc.1`) or an ordinary stable
10 * release (`4.3.2`).
11 *
12 * The distinction exists so that instrumentation which is legitimate on a
13 * build handed to one consenting user, but NOT legitimate on the thousands of
14 * ordinary installs a wp.org release reaches, can arm itself from a fact the
15 * build already carries instead of from a switch somebody has to remember to
16 * flip. That memory is not hypothetical: the detach A/B experiment
17 * (ABJ_404_Solution_DetachAbExperiment::isEnabled(), Bruno
18 * timeout cause matrix gap G9) shipped completely inert because its filter
19 * defaulted false and nothing in the codebase or the packaging steps ever set
20 * it true. A gate whose only "on" path is a manual step is a gate that ships
21 * off.
22 *
23 * The version string is the single source of truth (the plugin header, read
24 * into ABJ404_VERSION by Loader.php), because it is the one thing that cannot
25 * be true of the source tree and false of the built artifact: packaging cannot
26 * produce a beta zip whose header does not say beta.
27 *
28 * Parsing follows semver's rule, not a substring search for "beta": a version
29 * is a pre-release exactly when a hyphen-introduced identifier follows the
30 * numeric version core. Build metadata (`+20260722`) is NOT a pre-release
31 * marker under that rule and is deliberately treated as stable.
32 *
33 * Fail-safe by construction: a version that does not parse at all reports
34 * CHANNEL_UNKNOWN, and isPreRelease() collapses both 'stable' and 'unknown' to
35 * false. An install this class cannot identify is never opted into anything.
36 * The channel itself stays a three-value answer rather than a boolean so a
37 * diagnostic record can say WHICH of "stable build" or "unparseable version"
38 * kept an experiment inert, instead of leaving a reader to infer it.
39 */
40 final class ABJ_404_Solution_PluginReleaseChannel {
41
42 /** An ordinary published release: `4.3.2`, `4.3.2+20260722`. */
43 const CHANNEL_STABLE = 'stable';
44
45 /** A deliberately published pre-release: `4.3.3-beta.2`, `4.4.0-rc.1`. */
46 const CHANNEL_PRERELEASE = 'prerelease';
47
48 /** The version string is absent or unparseable; treated as stable for gating. */
49 const CHANNEL_UNKNOWN = 'unknown';
50
51 /** Numeric version core: `4`, `4.3`, `4.3.3`. */
52 const VERSION_CORE = '\d+(?:\.\d+)*';
53
54 /** Semver identifier body, used for both the pre-release and build-metadata tails. */
55 const IDENTIFIER_TAIL = '[0-9A-Za-z.\-]+';
56
57 /**
58 * Classify one version string. Pure: takes the version rather than
59 * reading the constant, so every case (stable, pre-release, build
60 * metadata, empty, garbage) is directly assertable without a process
61 * whose ABJ404_VERSION happens to hold that value -- a constant cannot be
62 * redefined mid-process, which would otherwise make most of these cases
63 * untestable.
64 *
65 * @param mixed $version
66 */
67 public static function channelForVersion($version): string {
68 $candidate = is_scalar($version) ? trim((string)$version) : '';
69 if ($candidate === '') {
70 return self::CHANNEL_UNKNOWN;
71 }
72 $buildMetadata = '(?:\+' . self::IDENTIFIER_TAIL . ')?';
73 if (preg_match('/^' . self::VERSION_CORE . '-' . self::IDENTIFIER_TAIL . $buildMetadata . '$/', $candidate) === 1) {
74 return self::CHANNEL_PRERELEASE;
75 }
76 if (preg_match('/^' . self::VERSION_CORE . $buildMetadata . '$/', $candidate) === 1) {
77 return self::CHANNEL_STABLE;
78 }
79 return self::CHANNEL_UNKNOWN;
80 }
81
82 /**
83 * The running build's channel, reported honestly (including 'unknown')
84 * so it can be journaled as evidence rather than only acted on.
85 */
86 public static function currentChannel(): string {
87 return self::channelForVersion(defined('ABJ404_VERSION') ? ABJ404_VERSION : '');
88 }
89
90 /**
91 * Whether this build may arm pre-release-only instrumentation. Only a
92 * parsed pre-release qualifies: 'unknown' deliberately behaves like
93 * 'stable' here, because the cost of guessing wrong is instrumentation
94 * running on real installs that never opted into it.
95 */
96 public static function isPreRelease(): bool {
97 return self::currentChannel() === self::CHANNEL_PRERELEASE;
98 }
99 }
100