true; Free zip has the folder stripped -> false. // This keeps the Free build wp.org-compliant (the SDK surfaces the free // activation flow rather than the licence-entry screen). $box_is_premium_build = file_exists( __DIR__ . '/includes/premium/.premium' ); $boxcart_fs = fs_dynamic_init( array( 'id' => '28224', 'slug' => 'boxcart', 'type' => 'plugin', 'public_key' => 'pk_89d8c2e542852310c6ecfc38e8b7c', 'is_premium' => $box_is_premium_build, 'premium_suffix' => 'Pro', // If your plugin is a serviceware, set this option to false. 'has_premium_version' => true, 'has_addons' => false, 'has_paid_plans' => true, 'is_org_compliant' => true, 'trial' => array( 'days' => 7, 'is_require_payment' => false, ), 'menu' => array( 'slug' => 'boxcart', 'support' => false, // Hide Freemius's iframed "Contact Us" page — // it's off-brand and we already have hello@boxcart.dev // for Pro customers and the wp.org forum for Free. 'contact' => false, // "Upgrade ➤" is Freemius's conversion CTA aimed at // Free users. On Pro builds the customer has already // paid — they don't need to upgrade, they need to // *activate*, and our own "Activate License" submenu // (registered by BoxCart_License_Activation) is the // correct destination. Hide Freemius's upgrade item // on Pro builds; keep it on Free where it points to // boxcart.dev/pricing (filter below). 'pricing' => ! $box_is_premium_build, ), ) ); // Redirect the "Upgrade" sidebar item from Freemius's iframed // pricing page to our own marketing-site pricing page, which is // on-brand and already wired up to Freemius checkout. Users see // "Upgrade ➤" in the WP admin, click it, and land on // https://boxcart.dev/pricing/ — same window, conventional // upgrade-link UX (matches WooCommerce, Yoast, Elementor, etc). $boxcart_fs->add_filter( 'pricing_url', function () { return 'https://boxcart.dev/pricing/'; } ); // After the opt-in (or licence) screen, a new shop goes on to Guided // Setup rather than an empty dashboard. $boxcart_fs->add_filter( 'after_connect_url', 'boxcart_after_freemius_activation_url' ); $boxcart_fs->add_filter( 'after_skip_url', 'boxcart_after_freemius_activation_url' ); } return $boxcart_fs; } /** * Where Freemius sends the shop owner after its opt-in or licence screen: * Guided Setup until the shop is set up, otherwise where Freemius chose. * * @param string $url URL chosen by Freemius. * @return string */ function boxcart_after_freemius_activation_url( $url ) { if ( class_exists( 'BoxCart_Setup' ) && ! BoxCart_Setup::get_status()['complete'] ) { return admin_url( 'admin.php?page=boxcart-setup' ); } return $url; } // Init Freemius. boxcart_fs(); // Signal that SDK was initiated. do_action( 'boxcart_fs_loaded' ); } /** * Plugin uninstall cleanup. * * Hooked through Freemius' after_uninstall action (not via WP's uninstall.php) * so the SDK can run its own license deactivation / opt-out flow before we * drop plugin data. * * Only deletes data if the admin has explicitly opted in via the * "boxcart_delete_data_on_uninstall" setting. Otherwise this is a no-op and * the user can reinstall without losing products, orders, or configuration. */ function boxcart_freemius_uninstall_cleanup() { // Whatever happens to the data, don't leave WordPress registration open // if BoxCart was the one that switched it on. if ( get_option( 'boxcart_enabled_core_registration' ) ) { update_option( 'users_can_register', '0' ); delete_option( 'boxcart_enabled_core_registration' ); } if ( ! get_option( 'boxcart_delete_data_on_uninstall', false ) ) { return; } global $wpdb; // Drop the Free build's custom tables. $tables = array( $wpdb->prefix . 'boxcart_product_quantities', $wpdb->prefix . 'boxcart_basket_items', $wpdb->prefix . 'boxcart_baskets', $wpdb->prefix . 'boxcart_order_status_log', $wpdb->prefix . 'boxcart_order_items', $wpdb->prefix . 'boxcart_orders', $wpdb->prefix . 'boxcart_location_slots', $wpdb->prefix . 'boxcart_location_schedules', $wpdb->prefix . 'boxcart_locations', ); foreach ( $tables as $table ) { $wpdb->query( "DROP TABLE IF EXISTS {$table}" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared } // Pro owns the closures, capacity-override, and order-messages tables. // Let the Pro database class drop them if the Pro distribution is present. // The Free build never references those table names. $boxcart_pro_db = BOXCART_PLUGIN_DIR . 'includes/premium/core/class-boxcart-pro-database.php'; if ( file_exists( $boxcart_pro_db ) ) { require_once $boxcart_pro_db; BoxCart_Pro_Database::drop_tables(); } // Delete all boxcart_product posts. $products = get_posts( array( 'post_type' => 'boxcart_product', 'post_status' => 'any', 'posts_per_page' => -1, 'fields' => 'ids', ) ); foreach ( $products as $product_id ) { wp_delete_post( $product_id, true ); } // Delete taxonomy terms. $terms = get_terms( array( 'taxonomy' => 'boxcart_product_category', 'hide_empty' => false, 'fields' => 'ids', ) ); if ( ! is_wp_error( $terms ) ) { foreach ( $terms as $term_id ) { wp_delete_term( $term_id, 'boxcart_product_category' ); } } // Delete options. $options = array( 'boxcart_version', 'boxcart_db_version', 'boxcart_settings', 'boxcart_delete_data_on_uninstall', 'boxcart_account_page_id', 'boxcart_qty_settings_fixed', 'boxcart_columns_ensured', 'boxcart_recover_quantities', 'boxcart_recovered_quantities', 'boxcart_pro_templates_restored', 'boxcart_image_size_queue', 'boxcart_image_size_failed', 'boxcart_enabled_core_registration', 'boxcart_setup_progress', 'boxcart_retired_order_numbers', ); foreach ( $options as $option ) { delete_option( $option ); } // Clear scheduled cron events. wp_clear_scheduled_hook( 'boxcart_cleanup_expired_baskets' ); wp_clear_scheduled_hook( 'boxcart_cleanup_old_logs' ); wp_clear_scheduled_hook( 'boxcart_cleanup_awaiting_orders' ); wp_clear_scheduled_hook( 'boxcart_generate_image_sizes' ); // Delete named transients. $transients = array( 'boxcart_location_error', 'boxcart_location_success', 'boxcart_schedule_error', 'boxcart_schedule_success', 'boxcart_product_error', 'boxcart_bulk_success', 'boxcart_category_error', 'boxcart_category_success', 'boxcart_auto_detected_pages', 'boxcart_activation_redirect', 'boxcart_import_error', 'boxcart_import_results', ); foreach ( $transients as $transient ) { delete_transient( $transient ); } // Delete pattern-based transients. $transient_patterns = array( '_transient_boxcart_order_counts_%', '_transient_timeout_boxcart_order_counts_%', '_transient_boxcart_order_status_counts%', '_transient_timeout_boxcart_order_status_counts%', '_transient_boxcart_pwd_reset_%', '_transient_timeout_boxcart_pwd_reset_%', '_transient_boxcart_rate_%', '_transient_timeout_boxcart_rate_%', '_transient_boxcart_import_file_%', '_transient_timeout_boxcart_import_file_%', '_transient_boxcart_import_created_%', '_transient_timeout_boxcart_import_created_%', '_transient_boxcart_modifying_order_%', '_transient_timeout_boxcart_modifying_order_%', 'boxcart_awaiting_payment_%', 'boxcart_basket_awaiting_%', 'boxcart_give_back_%', 'boxcart_refund_pending_%', 'boxcart_refund_lock_%', 'boxcart_change_lock_%', '_transient_boxcart_card_refundable_%', '_transient_timeout_boxcart_card_refundable_%', '_transient_boxcart_cancel_refund_%', '_transient_timeout_boxcart_cancel_refund_%', '_transient_boxcart_closure_orders_%', '_transient_timeout_boxcart_closure_orders_%', ); foreach ( $transient_patterns as $pattern ) { $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $pattern ) ); } // Reassign users from boxcart_customer role to subscriber before removing it. $boxcart_users = get_users( array( 'role' => 'boxcart_customer' ) ); foreach ( $boxcart_users as $user ) { $user->set_role( 'subscriber' ); } remove_role( 'boxcart_customer' ); // Clean up user meta. $wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key = 'boxcart_phone'" ); flush_rewrite_rules(); } boxcart_fs()->add_action( 'after_uninstall', 'boxcart_freemius_uninstall_cleanup' ); /** * Canonical "is this a Pro build with an active license" check. * * Used throughout the plugin to gate Pro feature loading and UI. Returns true * only when BOTH conditions hold: the /premium/ code is physically present * (i.e. this is the Pro zip, not the wp.org free zip) AND the Freemius license * entitlement is satisfied. * * @return bool */ function boxcart_is_premium() { static $result = null; if ( null === $result ) { $result = file_exists( BOXCART_PLUGIN_DIR . 'includes/premium/.premium' ) && function_exists( 'boxcart_fs' ) && boxcart_fs()->can_use_premium_code(); } return $result; } /** * Autoloader for BoxCart classes. * * @param string $class_name The name of the class to load. */ function boxcart_autoloader( $class_name ) { // Only load classes with our prefix. if ( 0 !== strpos( $class_name, 'BoxCart' ) ) { return; } // Convert class name to file path. $class_name = str_replace( 'BoxCart_', '', $class_name ); $class_name = strtolower( $class_name ); $class_name = str_replace( '_', '-', $class_name ); // Define possible file locations. // Core (always-loaded) locations first; /premium/ locations last so that // on free builds (where /premium/ is stripped), file_exists() short-circuits // without a filesystem trip for every non-Pro class. $locations = array( BOXCART_PLUGIN_DIR . 'includes/class-boxcart-' . $class_name . '.php', BOXCART_PLUGIN_DIR . 'includes/admin/class-boxcart-' . $class_name . '.php', BOXCART_PLUGIN_DIR . 'includes/frontend/class-boxcart-' . $class_name . '.php', BOXCART_PLUGIN_DIR . 'includes/core/class-boxcart-' . $class_name . '.php', BOXCART_PLUGIN_DIR . 'includes/api/class-boxcart-' . $class_name . '.php', BOXCART_PLUGIN_DIR . 'includes/premium/core/class-boxcart-' . $class_name . '.php', BOXCART_PLUGIN_DIR . 'includes/premium/admin/class-boxcart-' . $class_name . '.php', BOXCART_PLUGIN_DIR . 'includes/premium/frontend/class-boxcart-' . $class_name . '.php', ); // Try to load the class file. foreach ( $locations as $file ) { if ( file_exists( $file ) ) { require_once $file; return; } } } spl_autoload_register( 'boxcart_autoloader' ); // Optional extensions bootstrap. The wp.org Free zip does NOT ship a // /includes/premium/ subtree; it is stripped at build time. If a separately // distributed extension bundle is present, it layers itself on top of the // base plugin via the action/filter hook points the Free codebase fires // (see /includes/premium/bootstrap.php for the full hook list). $boxcart_pro_bootstrap = BOXCART_PLUGIN_DIR . 'includes/premium/bootstrap.php'; if ( file_exists( $boxcart_pro_bootstrap ) ) { require_once $boxcart_pro_bootstrap; } unset( $boxcart_pro_bootstrap ); /** * Plugin activation hook. */ function boxcart_activate() { require_once BOXCART_PLUGIN_DIR . 'includes/class-boxcart-activator.php'; BoxCart_Activator::activate(); } register_activation_hook( __FILE__, 'boxcart_activate' ); /** * Plugin deactivation hook. */ function boxcart_deactivate() { require_once BOXCART_PLUGIN_DIR . 'includes/class-boxcart-deactivator.php'; BoxCart_Deactivator::deactivate(); } register_deactivation_hook( __FILE__, 'boxcart_deactivate' ); /** * Initialize the plugin. * * @return BoxCart The main plugin instance. */ function boxcart() { static $instance = null; if ( null === $instance ) { require_once BOXCART_PLUGIN_DIR . 'includes/class-boxcart.php'; $instance = new BoxCart(); } return $instance; } // Initialize the plugin after WordPress is fully loaded. add_action( 'plugins_loaded', 'boxcart' );