| 1 |
<?php |
| 2 |
if ( ! defined( 'ABSPATH' ) ) { |
| 3 |
exit; // Exit if accessed directly |
| 4 |
} |
| 5 |
|
| 6 |
/** |
| 7 |
* This file is to initiate WooFunnel core and to run some common methods and decide which WooFunnels core should run |
| 8 |
*/ |
| 9 |
if ( ! class_exists( 'WooFunnel_Loader' ) ) { |
| 10 |
#[AllowDynamicProperties] |
| 11 |
|
| 12 |
class WooFunnel_Loader { |
| 13 |
|
| 14 |
public static $plugins = array(); |
| 15 |
public static $loaded = false; |
| 16 |
public static $ultimate_path = ''; |
| 17 |
public static $version = null; |
| 18 |
|
| 19 |
|
| 20 |
public static function include_core() { |
| 21 |
|
| 22 |
$get_configuration = self::get_the_latest(); |
| 23 |
if ( false === self::$loaded && $get_configuration && is_array( $get_configuration ) && isset( $get_configuration['class'] ) ) { |
| 24 |
|
| 25 |
if ( is_callable( array( $get_configuration['class'], 'load_files' ) ) ) { |
| 26 |
self::$version = $get_configuration['version']; |
| 27 |
self::$ultimate_path = $get_configuration['plugin_path'] . '/woofunnels/'; |
| 28 |
self::$loaded = true; |
| 29 |
call_user_func( array( $get_configuration['class'], 'load_files' ) ); |
| 30 |
|
| 31 |
} |
| 32 |
} |
| 33 |
} |
| 34 |
|
| 35 |
public static function register( $configuration ) { |
| 36 |
array_push( self::$plugins, $configuration ); |
| 37 |
} |
| 38 |
|
| 39 |
public static function get_the_latest() { |
| 40 |
$get_all = self::$plugins; |
| 41 |
uasort( |
| 42 |
$get_all, |
| 43 |
function ( $a, $b ) { |
| 44 |
if ( version_compare( $a['version'], $b['version'], '=' ) ) { |
| 45 |
return 0; |
| 46 |
} else { |
| 47 |
return ( version_compare( $a['version'], $b['version'], '<' ) ) ? - 1 : 1; |
| 48 |
} |
| 49 |
} |
| 50 |
); |
| 51 |
|
| 52 |
$get_most_recent_configuration = end( $get_all ); |
| 53 |
|
| 54 |
return $get_most_recent_configuration; |
| 55 |
} |
| 56 |
} |
| 57 |
} |
| 58 |
|
| 59 |
|
| 60 |
#[AllowDynamicProperties] |
| 61 |
|
| 62 |
class WooFunnel_WFFN { |
| 63 |
|
| 64 |
public static $version = WFFN_BWF_VERSION; |
| 65 |
|
| 66 |
/** |
| 67 |
* Newest full (UN-dismantled) WooFunnels core that counts as abandoned. |
| 68 |
* |
| 69 |
* Some plugins bundled a WooFunnels core once and then stopped shipping |
| 70 |
* updates for it. CartHopper (woofunnels-carthopper-skip-cart) is the known |
| 71 |
* case: its core is frozen at exactly 1.9.90 while every maintained core is on |
| 72 |
* the 1.10.x line. Yielding to a core that old would downgrade the whole site, |
| 73 |
* so any full core at or below this version is dropped from the loader pool |
| 74 |
* and our dismantled core runs instead. |
| 75 |
* |
| 76 |
* Compared with '<=', not '<' -- 1.9.90 is CartHopper's own version, the one we |
| 77 |
* are excluding, not the first version we accept. |
| 78 |
* |
| 79 |
* @var string |
| 80 |
*/ |
| 81 |
const MAX_ABANDONED_FULL_CORE_VERSION = '1.10.0'; |
| 82 |
|
| 83 |
public static function register() { |
| 84 |
|
| 85 |
$configuration = array( |
| 86 |
'basename' => plugin_basename( WFFN_PLUGIN_FILE ), |
| 87 |
'version' => self::$version, |
| 88 |
'plugin_path' => dirname( WFFN_PLUGIN_FILE ), |
| 89 |
'class' => __CLASS__, |
| 90 |
/** |
| 91 |
* Marks this as a dismantled core (the FunnelKit common-core split: |
| 92 |
* contact / compatibilities / usage-tracking / includes + fb/). Cores |
| 93 |
* shipped by un-dismantled plugins do not carry this flag; that is how |
| 94 |
* maybe_yield_to_full_core() tells them apart during the migration window. |
| 95 |
*/ |
| 96 |
'dismantled' => true, |
| 97 |
); |
| 98 |
WooFunnel_Loader::register( $configuration ); |
| 99 |
} |
| 100 |
|
| 101 |
/** |
| 102 |
* Transition arbitration. |
| 103 |
* |
| 104 |
* While any other plugin still ships an UN-dismantled (full) WooFunnels core, |
| 105 |
* our minimal core must not win: it no longer ships every class those plugins |
| 106 |
* hardcode-require (e.g. WooFunnels_Notifications, WooFunnels_Deactivate). So if |
| 107 |
* any registered core lacks the 'dismantled' marker, withdraw our own |
| 108 |
* registration and let the full core win include_core() -- we ride it (our fb/ |
| 109 |
* classes still load directly on woofunnels_loaded). Once every core is |
| 110 |
* dismantled (all carry the marker) nobody withdraws and normal newest-wins |
| 111 |
* arbitration resumes. |
| 112 |
* |
| 113 |
* The yield is version-gated: only full cores newer than |
| 114 |
* MAX_ABANDONED_FULL_CORE_VERSION are worth riding. Anything at or below it is |
| 115 |
* an abandoned leftover (see the constant) and is evicted from the pool so it |
| 116 |
* cannot load at all. So there are three outcomes: |
| 117 |
* - no full core at all -> stay registered, newest-wins as usual |
| 118 |
* - maintained full core -> withdraw, it loads, we ride it |
| 119 |
* - abandoned full core -> evict it, stay registered, our core loads |
| 120 |
* |
| 121 |
* Hooked on plugins_loaded at PHP_INT_MIN, ahead of every include_core() call in |
| 122 |
* the request (ours at -1, and siblings that call it themselves -- CartHopper |
| 123 |
* does, from WFCH_Common::plugins_loaded, also -1). Lives here (our registrant), |
| 124 |
* never in the shared WooFunnel_Loader class. |
| 125 |
*/ |
| 126 |
public static function maybe_yield_to_full_core() { |
| 127 |
if ( ! class_exists( 'WooFunnel_Loader' ) || ! is_array( WooFunnel_Loader::$plugins ) || WooFunnel_Loader::$loaded ) { |
| 128 |
return; |
| 129 |
} |
| 130 |
|
| 131 |
/** |
| 132 |
* Full cores shipped by plugins that register LATE are not in the pool yet. |
| 133 |
* FunnelKit Automations, for example, requires its start.php on |
| 134 |
* plugins_loaded:1 -- after this hook and after include_core (-1). If we |
| 135 |
* only looked at the pool we would not see it, win with our minimal core, and |
| 136 |
* it would fail (its connector/ framework, WFCO_Common, only exists in its own |
| 137 |
* core). So force those late cores to register NOW by requiring their start.php, |
| 138 |
* so include_core (-1) can pick the full core and load it before our own |
| 139 |
* load_classes (also plugins_loaded:1) needs it. Requiring them also puts their |
| 140 |
* version on the table, which is what the eviction below needs. |
| 141 |
*/ |
| 142 |
if ( false === self::full_core_in_pool() ) { |
| 143 |
foreach ( self::undismantled_core_start_files() as $start_file ) { |
| 144 |
require_once $start_file; // runs the sibling's WooFunnel_*::register() |
| 145 |
} |
| 146 |
} |
| 147 |
|
| 148 |
self::drop_abandoned_full_cores(); |
| 149 |
|
| 150 |
if ( false === self::full_core_in_pool() ) { |
| 151 |
return; // Nothing left to yield to -- compete on version as usual. |
| 152 |
} |
| 153 |
|
| 154 |
foreach ( WooFunnel_Loader::$plugins as $key => $config ) { |
| 155 |
if ( isset( $config['class'] ) && __CLASS__ === $config['class'] ) { |
| 156 |
unset( WooFunnel_Loader::$plugins[ $key ] ); |
| 157 |
} |
| 158 |
} |
| 159 |
WooFunnel_Loader::$plugins = array_values( WooFunnel_Loader::$plugins ); |
| 160 |
} |
| 161 |
|
| 162 |
/** |
| 163 |
* @return bool Whether any already-registered core lacks the 'dismantled' marker. |
| 164 |
*/ |
| 165 |
private static function full_core_in_pool() { |
| 166 |
foreach ( WooFunnel_Loader::$plugins as $config ) { |
| 167 |
if ( empty( $config['dismantled'] ) ) { |
| 168 |
return true; |
| 169 |
} |
| 170 |
} |
| 171 |
|
| 172 |
return false; |
| 173 |
} |
| 174 |
|
| 175 |
/** |
| 176 |
* Remove every registered full (UN-dismantled) core at or below |
| 177 |
* MAX_ABANDONED_FULL_CORE_VERSION from the loader pool. |
| 178 |
* |
| 179 |
* Dropping the configuration is what stops it: include_core() only ever calls |
| 180 |
* load_files() on the winner it picks out of WooFunnel_Loader::$plugins, so a |
| 181 |
* core that is not in the pool never boots. Timing works because this runs on |
| 182 |
* plugins_loaded at PHP_INT_MIN, ahead of every include_core() call in the |
| 183 |
* request -- ours at -1 and the siblings' own (CartHopper calls it directly from |
| 184 |
* WFCH_Common::plugins_loaded, also -1). And if the evicted plugin registers |
| 185 |
* again afterwards, that registration is inert: include_core has already run and |
| 186 |
* flipped WooFunnel_Loader::$loaded. |
| 187 |
* |
| 188 |
* Configurations with no version string are left alone: we cannot tell how old |
| 189 |
* they are, and wrongly evicting a live full core is the more expensive mistake. |
| 190 |
*/ |
| 191 |
private static function drop_abandoned_full_cores() { |
| 192 |
$changed = false; |
| 193 |
foreach ( WooFunnel_Loader::$plugins as $key => $config ) { |
| 194 |
if ( ! empty( $config['dismantled'] ) || empty( $config['version'] ) ) { |
| 195 |
continue; |
| 196 |
} |
| 197 |
if ( version_compare( $config['version'], self::MAX_ABANDONED_FULL_CORE_VERSION, '<=' ) ) { |
| 198 |
unset( WooFunnel_Loader::$plugins[ $key ] ); |
| 199 |
$changed = true; |
| 200 |
} |
| 201 |
} |
| 202 |
|
| 203 |
if ( true === $changed ) { |
| 204 |
WooFunnel_Loader::$plugins = array_values( WooFunnel_Loader::$plugins ); |
| 205 |
} |
| 206 |
} |
| 207 |
|
| 208 |
/** |
| 209 |
* Folders an UN-dismantled WooFunnels core ships that our dismantled core does NOT. |
| 210 |
* |
| 211 |
* The dismantle stripped these from FB's woofunnels/: the connector framework |
| 212 |
* (connector/, home of WFCO_Common), the custom Action Scheduler store |
| 213 |
* (as-data-store/), the pre-kernel bootstrap loader (bootstrap/), and the licence |
| 214 |
* UI (legacy/). A plugin whose woofunnels/ ships ANY of them is running a full, |
| 215 |
* un-dismantled core -- and MUST win arbitration so those classes exist. We probe |
| 216 |
* on the whole set (not a single folder) because different un-dismantled core |
| 217 |
* versions carry different subsets: e.g. FunnelKit Automations' current core has |
| 218 |
* connector/ + as-data-store/ but NO bootstrap/ (kernel already folded elsewhere), |
| 219 |
* so a bootstrap-only probe misses it and our minimal core wrongly wins, fataling |
| 220 |
* WFCO_Common. |
| 221 |
*/ |
| 222 |
private static function full_core_folder_markers() { |
| 223 |
return array( 'connector', 'as-data-store', 'bootstrap', 'legacy' ); |
| 224 |
} |
| 225 |
|
| 226 |
/** |
| 227 |
* Active plugins (other than us) that ship an UN-dismantled WooFunnels core which |
| 228 |
* has not registered yet (e.g. FunnelKit Automations registers late, on |
| 229 |
* plugins_loaded:1). Detected by the full-core folder markers above; their start.php |
| 230 |
* sits at the plugin root. |
| 231 |
* |
| 232 |
* @return string[] Absolute paths to those start.php files. |
| 233 |
*/ |
| 234 |
private static function undismantled_core_start_files() { |
| 235 |
$files = array(); |
| 236 |
|
| 237 |
$plugins = array(); |
| 238 |
if ( function_exists( 'wp_get_active_and_valid_plugins' ) ) { |
| 239 |
$plugins = (array) wp_get_active_and_valid_plugins(); |
| 240 |
} |
| 241 |
if ( function_exists( 'wp_get_active_network_plugins' ) ) { |
| 242 |
$plugins = array_merge( $plugins, (array) wp_get_active_network_plugins() ); |
| 243 |
} |
| 244 |
|
| 245 |
$our_dir = dirname( WFFN_PLUGIN_FILE ); |
| 246 |
$markers = self::full_core_folder_markers(); |
| 247 |
foreach ( $plugins as $plugin_file ) { |
| 248 |
$dir = dirname( $plugin_file ); |
| 249 |
if ( $dir === $our_dir || ! is_readable( $dir . '/start.php' ) ) { |
| 250 |
continue; |
| 251 |
} |
| 252 |
foreach ( $markers as $marker ) { |
| 253 |
if ( is_dir( $dir . '/woofunnels/' . $marker ) ) { |
| 254 |
$files[] = $dir . '/start.php'; |
| 255 |
break; |
| 256 |
} |
| 257 |
} |
| 258 |
} |
| 259 |
|
| 260 |
return $files; |
| 261 |
} |
| 262 |
|
| 263 |
|
| 264 |
public static function load_files() { |
| 265 |
|
| 266 |
$get_global_path = __DIR__ . '/woofunnels/'; |
| 267 |
|
| 268 |
if ( false === @file_exists( $get_global_path . 'includes/class-woofunnels-api.php' ) ) { |
| 269 |
_doing_it_wrong( __FUNCTION__, esc_html__( 'WooFunnels Core should be present in folder \'woofunnels\' in order to run this properly. ', 'funnel-builder' ), self::$version ); |
| 270 |
die( 0 ); |
| 271 |
} |
| 272 |
|
| 273 |
/** |
| 274 |
* Loading Core XL Files |
| 275 |
*/ |
| 276 |
require_once dirname( WFFN_PLUGIN_FILE ) . '/woofunnels/' . 'includes/class-woofunnels-dashboard-loader.php'; |
| 277 |
|
| 278 |
if ( self::$version === BWF_VERSION ) { |
| 279 |
do_action( 'woofunnels_loaded', $get_global_path ); |
| 280 |
} elseif ( ( defined( 'WFFN_IS_DEV' ) && true === WFFN_IS_DEV ) || ( defined( 'BWF_DEV' ) && true === BWF_DEV ) ) { |
| 281 |
_doing_it_wrong( __FUNCTION__, esc_html__( 'WooFunnels Core should be at the same version as declared in your start.php', 'funnel-builder' ), self::$version ); |
| 282 |
die( 0 ); |
| 283 |
} |
| 284 |
} |
| 285 |
} |
| 286 |
|
| 287 |
WooFunnel_WFFN::register(); |