PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.2
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.2
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / src / conditionals / gradual-rollout-conditional.php

gradual-rollout-conditional.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.2, at src/conditionals/gradual-rollout-conditional.php

92 lines 3.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Yoast\WP\SEO\Conditionals;
4
5 /**
6 * Feature-flag conditional whose default state is a gradual, deterministic rollout
7 * across a share of sites.
8 *
9 * The `YOAST_SEO_<FEATURE>` constant remains an explicit override: when it is defined
10 * in wp-config.php it wins outright (`true` forces the feature on, `false` forces it off),
11 * exactly like a plain {@see Feature_Flag_Conditional}. This is the per-site testing lever.
12 *
13 * When the constant is *not* defined, the feature falls back to the gradual-rollout
14 * heuristic: it is enabled for a slowly widening share of sites. A site's bucket is derived
15 * from a stable hash of the feature name plus the site URL, so the same site stays in (or out
16 * of) the rollout consistently across plugin releases.
17 *
18 * The share is expressed in per-mille (0-1000), not percent, because at the install base this
19 * rides on (10M+ sites) a single percent is too coarse for the first rollout steps; per-mille
20 * lets a rollout start at 0.1% (a share of 1).
21 *
22 * The hash input deliberately includes the feature name, so a site that buckets low for one
23 * feature is not automatically early for every feature - there are no permanently "lucky" sites
24 * that always receive new features first.
25 *
26 * This machinery is temporary by design: once a feature reaches a 100% share with no
27 * regressions, the concrete conditional reverts to extending {@see Feature_Flag_Conditional}
28 * directly and this class can be removed.
29 */
30 abstract class Gradual_Rollout_Conditional extends Feature_Flag_Conditional {
31
32 /**
33 * The number of buckets sites are distributed across.
34 *
35 * @var int
36 */
37 private const BUCKET_COUNT = 1000;
38
39 /**
40 * Returns whether the feature is enabled.
41 *
42 * The `YOAST_SEO_<FEATURE>` constant, when defined, is an explicit override and wins.
43 * Otherwise the gradual-rollout share decides.
44 *
45 * @return bool Whether the conditional is met.
46 */
47 public function is_met() {
48 $constant = 'YOAST_SEO_' . \strtoupper( $this->get_feature_flag() );
49
50 // An explicit constant always wins (true forces on, false forces off).
51 if ( \defined( $constant ) ) {
52 return ( \constant( $constant ) === true );
53 }
54
55 return $this->is_in_rollout_cohort();
56 }
57
58 /**
59 * Returns the current rollout share in per-mille (0-1000).
60 *
61 * 0 means the feature is enabled for no sites, 1000 for all sites. The value is
62 * raised release over release as the rollout widens.
63 *
64 * @return int The rollout share in per-mille.
65 */
66 abstract protected function get_rollout_share(): int;
67
68 /**
69 * Determines whether this site falls within the current rollout share.
70 *
71 * @return bool Whether this site is in the rollout cohort.
72 */
73 private function is_in_rollout_cohort(): bool {
74 $share = \max( 0, \min( self::BUCKET_COUNT, $this->get_rollout_share() ) );
75
76 if ( $share <= 0 ) {
77 return false;
78 }
79
80 if ( $share >= self::BUCKET_COUNT ) {
81 return true;
82 }
83
84 // Hash the feature name together with the site URL so cohorts differ per feature
85 // (no permanently lucky sites). sprintf( '%u' ) reads crc32's result as unsigned,
86 // which keeps the modulo correct on 32-bit platforms where crc32 can be negative.
87 $bucket = ( (int) \sprintf( '%u', \crc32( $this->get_feature_name() . \site_url() ) ) % self::BUCKET_COUNT );
88
89 return ( $bucket < $share );
90 }
91 }
92