PluginProbe ʕ •ᴥ•ʔ
WP Chat App / trunk
WP Chat App vtrunk
3.8.1 3.8.2 trunk 2.6 3.4 3.4.1 3.4.2 3.4.4 3.4.5 3.4.6 3.5 3.6 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0
wp-whatsapp / cross-sell / registry.php
wp-whatsapp / cross-sell Last commit date
filebird-dashboard-cross-sell 3 months ago filebird-notification-cross-sell 3 months ago filebird-sidebar-popup-cross-sell 3 months ago .source 3 months ago loader.php 3 months ago registry.php 3 months ago
registry.php
111 lines
1 <?php
2 /**
3 * YayCrossSell module Registry — runtime version arbitration.
4 *
5 * FROZEN ABI. Additive changes only. Older copies of this file shipped by
6 * other consumer plugins may load first; if they do, this copy bails before
7 * redeclaring the class. Removing or renaming a public method WILL break
8 * sites where an older registry is the one that won the load race.
9 *
10 * Loaded by cross-sell/loader.php in each consumer plugin.
11 */
12
13 declare(strict_types=1);
14
15 namespace YayCrossSell;
16
17 defined('ABSPATH') || exit;
18
19 // Wrap the class declaration INSIDE the if-block so PHP treats it as
20 // conditional. Otherwise PHP can early-bind unconditional top-level
21 // classes during compile/load — registering the class BEFORE any runtime
22 // statement (including a guard above the class) executes. With opcache
23 // enabled in WP, this early-binding can fatal on the second consumer's
24 // include even if a runtime `if (class_exists()) return;` precedes it.
25 //
26 // Putting the class inside `if (!class_exists())` forces late binding:
27 // PHP only registers the class when the if-body actually runs at runtime,
28 // so the guard works as expected.
29 if (!class_exists(__NAMESPACE__ . '\\Registry', false)) {
30
31 defined('YAY_CROSS_SELL_REGISTRY_VERSION') || define('YAY_CROSS_SELL_REGISTRY_VERSION', '1.0.0');
32
33 class Registry
34 {
35 /** @var array<string, array<int, array{version: string, path: string}>> */
36 private static $candidates = [];
37
38 /** @var array<string, array{version: string, path: string, error?: string}> */
39 private static $loaded = [];
40
41 /** @var bool */
42 private static $hooked = false;
43
44 /**
45 * Each consumer's bundled module calls this from its register.php.
46 * Multiple candidates per module name are collected; load_winners() arbitrates.
47 */
48 public static function register(string $name, string $version, string $init_path): void
49 {
50 // Bail loud if a consumer's loader.php is required after plugins_loaded:0 already fired —
51 // load_winners() ran and won't fire again, so this candidate would be silently dropped.
52 if (function_exists('did_action') && did_action('plugins_loaded') > 0) {
53 if (function_exists('error_log')) {
54 error_log("[YayCrossSell] late register() for '$name' — already past plugins_loaded; module will not load");
55 }
56 return;
57 }
58
59 self::$candidates[$name][] = [
60 'version' => $version,
61 'path' => $init_path,
62 ];
63
64 if (!self::$hooked && function_exists('add_action')) {
65 self::$hooked = true;
66 // Priority 0 so module init runs before any other plugin's plugins_loaded handlers.
67 add_action('plugins_loaded', [self::class, 'load_winners'], 0);
68 }
69 }
70
71 /**
72 * Pick newest version per module and require_once its init.php.
73 * Wraps each require in try/catch so a single bad module doesn't block the rest.
74 */
75 public static function load_winners(): void
76 {
77 foreach (self::$candidates as $name => $candidates) {
78 if (isset(self::$loaded[$name])) {
79 continue;
80 }
81 usort(
82 $candidates,
83 static function (array $a, array $b): int {
84 return version_compare($b['version'], $a['version']);
85 }
86 );
87 $winner = $candidates[0];
88 try {
89 require_once $winner['path'];
90 self::$loaded[$name] = $winner;
91 } catch (\Throwable $e) {
92 self::$loaded[$name] = $winner + ['error' => $e->getMessage()];
93 if (function_exists('error_log')) {
94 error_log("[YayCrossSell] failed to load module '{$name}': " . $e->getMessage());
95 }
96 }
97 }
98 }
99
100 public static function active_version(string $name): ?string
101 {
102 return self::$loaded[$name]['version'] ?? null;
103 }
104
105 public static function active_path(string $name): ?string
106 {
107 return self::$loaded[$name]['path'] ?? null;
108 }
109 }
110 }
111