PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / trunk
WCPOS – Point of Sale (POS) plugin for WooCommerce vtrunk
1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 1.9.13 1.9.12 1.9.11 1.9.10 All 159 releases
woocommerce-pos / includes / Services / Feature_Flags.php

Feature_Flags.php in WCPOS – Point of Sale (POS) plugin for WooCommerce trunk, at includes/Services/Feature_Flags.php

160 lines 5.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Feature flag resolver for the wp-admin landing page.
4 *
5 * Resolves the `landing-variant` A/B experiment server-side so the value can
6 * be injected into `window.wcpos.landing` and bootstrapped into the landing
7 * bundle's PostHog client at first paint — no network round-trip, no flicker,
8 * and no dependency on the PostHog `/flags` endpoint (which the self-hosted
9 * proxy does not authenticate for the public project token).
10 *
11 * Assignment uses PostHog's standard consistent-hashing algorithm — the same
12 * one the server SDKs use for local evaluation — so a given distinct id always
13 * maps to the same variant, with the configured rollout split. This keeps the
14 * recorded `$feature_flag_response` stable and the experiment denominators
15 * valid (landing-experiments spec §5.1).
16 *
17 * @package WCPOS\WooCommercePOS\Services
18 */
19
20 namespace WCPOS\WooCommercePOS\Services;
21
22 /**
23 * Feature_Flags service.
24 */
25 class Feature_Flags {
26 /**
27 * The landing-page experiment flag key. Program-wide identifier shared with
28 * the wp-admin-landing bundle (`FLAG_KEY` in variant-loader.ts); do not rename.
29 *
30 * @var string
31 */
32 const LANDING_FLAG_KEY = 'landing-variant';
33
34 /**
35 * PostHog's hashing constant: 0xFFFFFFFFFFFFFFF (15 hex digits, 2^60 - 1).
36 * The first 15 hex digits of the SHA-1 are scaled by this to a [0, 1) float.
37 *
38 * @var int
39 */
40 const LONG_SCALE = 0xFFFFFFFFFFFFFFF;
41
42 /**
43 * Multivariate variants for `landing-variant`, in PostHog config order.
44 *
45 * The order and rollout percentages mirror the flag definition in PostHog so
46 * local assignment matches what `get_all_feature_flags()` resolves there.
47 * `free-plus` is the reference arm and the bundle's fallback variant.
48 *
49 * KEEP IN SYNC: this is the source of truth for assignment while the
50 * self-hosted `/flags` endpoint is unusable. If the flag's variants, order,
51 * or split change in the PostHog UI, update this AND the wp-admin-landing
52 * bundle (`VALID_VARIANTS` / `FALLBACK_VARIANT` in variant-loader.ts), or the
53 * experiment denominators will silently skew. See wcpos/wp-admin-landing#39.
54 *
55 * @var array<int, array{key: string, rollout: int}>
56 */
57 const LANDING_VARIANTS = array(
58 array(
59 'key' => 'indie',
60 'rollout' => 50,
61 ),
62 array(
63 'key' => 'free-plus',
64 'rollout' => 50,
65 ),
66 );
67
68 /**
69 * Build the feature-flag bootstrap map for the landing page.
70 *
71 * Returns a map of resolved flags keyed for PostHog's `bootstrap.featureFlags`
72 * (e.g. `array( 'landing-variant' => 'indie' )`). Empty when no distinct id is
73 * available, in which case the bundle resolves the variant itself.
74 *
75 * @param string $distinct_id Stable anonymous identifier for the visitor/site.
76 *
77 * @return array<string, string>
78 */
79 public function get_landing_bootstrap_flags( string $distinct_id ): array {
80 $flags = array();
81 $variant = $this->get_landing_variant( $distinct_id );
82
83 if ( null !== $variant ) {
84 $flags[ self::LANDING_FLAG_KEY ] = $variant;
85 }
86
87 /**
88 * Filters the feature flags bootstrapped into the landing page.
89 *
90 * Allows forcing a variant (for QA) or extending the bootstrap with
91 * additional flags. Values must be strings or booleans, matching
92 * PostHog's `bootstrap.featureFlags` contract.
93 *
94 * @since 1.9.7
95 *
96 * @param array<string, string|bool> $flags Resolved flag map.
97 * @param string $distinct_id The visitor distinct id.
98 */
99 return apply_filters( 'woocommerce_pos_landing_bootstrap_flags', $flags, $distinct_id );
100 }
101
102 /**
103 * Resolve the `landing-variant` value for a distinct id.
104 *
105 * @param string $distinct_id Stable anonymous identifier.
106 *
107 * @return null|string The variant key, or null when no distinct id is given.
108 */
109 public function get_landing_variant( string $distinct_id ): ?string {
110 if ( '' === $distinct_id ) {
111 return null;
112 }
113
114 return $this->match_variant( self::LANDING_FLAG_KEY, $distinct_id, self::LANDING_VARIANTS );
115 }
116
117 /**
118 * Map a distinct id to a variant using PostHog's consistent-hash lookup.
119 *
120 * The variant hash is salted with `variant` and compared against the
121 * cumulative rollout ranges built from the variant order.
122 *
123 * @param string $key Flag key.
124 * @param string $distinct_id Distinct id.
125 * @param array<int, array{key: string, rollout: int}> $variants Ordered variants.
126 *
127 * @return null|string
128 */
129 private function match_variant( string $key, string $distinct_id, array $variants ): ?string {
130 $hash_value = $this->hash( $key, $distinct_id, 'variant' );
131
132 $value_min = 0.0;
133 foreach ( $variants as $variant ) {
134 $value_max = $value_min + ( $variant['rollout'] / 100 );
135 if ( $hash_value >= $value_min && $hash_value < $value_max ) {
136 return $variant['key'];
137 }
138 $value_min = $value_max;
139 }
140
141 return null;
142 }
143
144 /**
145 * PostHog consistent hash: SHA-1 of `key.distinct_id+salt`, first 15 hex
146 * digits scaled to a deterministic [0, 1) float.
147 *
148 * @param string $key Flag key.
149 * @param string $distinct_id Distinct id.
150 * @param string $salt Hash salt (`variant` for variant selection).
151 *
152 * @return float
153 */
154 private function hash( string $key, string $distinct_id, string $salt = '' ): float {
155 $hex = substr( sha1( $key . '.' . $distinct_id . $salt ), 0, 15 );
156
157 return (float) ( hexdec( $hex ) / self::LONG_SCALE );
158 }
159 }
160