Pro handoff. * * PHP hoists top-level function declarations at COMPILE time, so a runtime * early-return cannot prevent a "cannot redeclare" fatal when the free and Pro * builds are both momentarily active. Keeping the whole bootstrap inside the * else branch means the declarations are skipped entirely for the duplicate * copy. */ if ( function_exists( 'addon_boss_fs' ) ) { if ( addon_boss_fs() ) { addon_boss_fs()->set_basename( false, __FILE__ ); } else { add_action( 'admin_notices', static function () { echo '

'; echo esc_html__( 'Two copies of AddOnBoss are active (the free version and AddOnBoss Premium). Please keep only one active on the Plugins screen. Your settings are safe.', 'addon-boss' ); echo '

'; } ); } } else { define( 'ADDON_BOSS_VERSION', '2.0.3' ); define( 'ADDON_BOSS_PATH', plugin_dir_path( __FILE__ ) ); define( 'ADDON_BOSS_URL', plugin_dir_url( __FILE__ ) ); define( 'ADDON_BOSS_FILE', __FILE__ ); // The options-engine version this build ships (see AddOn_Boss_Engine). define( 'ADDON_BOSS_ENGINE_VERSION', '0.4.0' ); /** * Cache-busting asset version: the plugin version in production, the file's * mtime under WP_DEBUG so dev edits are picked up without a version bump. */ function addon_boss_asset_version( string $relative_path ): string { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { $file = ADDON_BOSS_PATH . $relative_path; $mtime = file_exists( $file ) ? filemtime( $file ) : 0; if ( $mtime ) { return ADDON_BOSS_VERSION . '.' . $mtime; } } return ADDON_BOSS_VERSION; } /** * Stand-down guard: does the active BundleBoss still provide the standalone * product-options surface itself? * * BundleBoss 4.15.0 through 4.18.x ships this same surface under its own * bundle_boss_* names. Booting the AddOnBoss engine next to such a build * would double everything: two rendered option panels, two charges, two * admin tabs. bundle_boss_render_standalone_options() exists exactly when * BundleBoss provides that surface (its Pro slot-options module declares * it from bundle_boss_init on plugins_loaded priority 20); a post-split * BundleBoss no longer declares it. The probe deliberately references the * OTHER plugin's function name. */ function addon_boss_bundleboss_provides_surface(): bool { return function_exists( 'bundle_boss_render_standalone_options' ); } /** * Boot the options engine, or stand down while BundleBoss still provides * the surface. Runs on plugins_loaded priority 25: after every plugin's * own init (BundleBoss declares its surface at priority 20), and before * any of the engine's admin or storefront hooks can fire, so the decision * is made once, at the only safe moment, for the whole request. */ function addon_boss_boot_engine(): void { if ( addon_boss_bundleboss_provides_surface() ) { add_action( 'admin_notices', 'addon_boss_stand_down_notice' ); return; } require_once ADDON_BOSS_PATH . 'shared/options-engine/engine.php'; } add_action( 'plugins_loaded', 'addon_boss_boot_engine', 25 ); /** * One dismissible notice while standing down, pointing at the fix. */ function addon_boss_stand_down_notice(): void { echo '

'; echo esc_html__( 'AddOnBoss is waiting: your BundleBoss version still provides product options itself. Update BundleBoss to hand these features to AddOnBoss.', 'addon-boss' ); echo '

'; } /** * Leave a marker at the moment of activation so the next admin screens can * point at the AddOnBoss landing (see includes/onboarding.php). add_option, * never update: once the pointer was dismissed or has done its job, a * deactivate and reactivate must not resurrect it. No redirect is involved * anywhere; the merchant stays exactly where WordPress put them. */ function addon_boss_note_activation(): void { add_option( 'addon_boss_activation_notice', '1', '', false ); } register_activation_hook( __FILE__, 'addon_boss_note_activation' ); // Licensing (and the Freemius SDK behind it) initialises at include time. require ADDON_BOSS_PATH . 'includes/licensing.php'; addon_boss_fs(); // Declare WooCommerce compatibility. add_action( 'before_woocommerce_init', function () { if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__, true ); } } ); // Feature init AFTER the engine boot at priority 25. add_action( 'plugins_loaded', 'addon_boss_init', 30 ); function addon_boss_init() { if ( ! class_exists( 'WooCommerce' ) ) { add_action( 'admin_notices', function () { echo '

AddOnBoss requires WooCommerce to be active.

'; } ); return; } // Translations: wp.org language packs load automatically and, living in // WP_LANG_DIR, win over these. The in-plugin files under /languages are // the fallback for locales the polyglots have not covered, and for the // premium build, which wp.org does not distribute and therefore never // ships a pack for; both need this explicit registration. Registered // before the stand-down check below, because a locale is not a feature. // On init, not earlier: WP 6.7 flags any translation loaded before init. add_action( 'init', function () { load_plugin_textdomain( 'addon-boss', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); } ); // The engine stood down because BundleBoss still provides the surface // (see addon_boss_boot_engine). Every AddOnBoss feature lives on the // engine, so there is nothing to init; the stand-down notice is already // queued and BundleBoss keeps working unchanged. if ( ! class_exists( 'AddOn_Boss_Engine', false ) ) { return; } require ADDON_BOSS_PATH . 'includes/admin-menu.php'; require ADDON_BOSS_PATH . 'includes/frontend.php'; require ADDON_BOSS_PATH . 'includes/onboarding.php'; // Loaded here, inside the engine-present branch, because the review // clock reads the engine's product-options meta constant. Its paid-build // silencer is a separate module that Freemius strips from the free // build, so this asks whether the file is there, never what the plan is. require ADDON_BOSS_PATH . 'includes/review-prompt.php'; if ( file_exists( ADDON_BOSS_PATH . 'includes/review-prompt__premium_only.php' ) ) { require ADDON_BOSS_PATH . 'includes/review-prompt__premium_only.php'; } } }