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';
}
}
}