boxcart.dev

Architecture Overview

A technical look at how BoxCart is structured — from the directory layout and boot sequence through to the class hierarchy, JavaScript layer, session handling, and security model.

Plugin Structure

BoxCart follows a conventional WordPress plugin layout with classes organised by responsibility into subdirectories under includes/.

boxcart/
├── boxcart.php              Main plugin file
├── includes/
│   ├── class-boxcart.php    Main plugin class
│   ├── admin/               Admin-facing classes
│   ├── frontend/            Frontend-facing classes
│   ├── core/                Core business logic
│   └── api/                 AJAX & REST API handlers
├── assets/
│   ├── css/                 Stylesheets
│   ├── js/                  JavaScript
│   └── fonts/               Self-hosted fonts
├── templates/               Template files
└── languages/               i18n files

Each directory has a clear purpose:

Boot Flow

When WordPress loads the plugin, the following sequence runs inside boxcart.php and class-boxcart.php:

  1. Constants defined

    BOXCART_VERSION, BOXCART_PLUGIN_FILE, BOXCART_PLUGIN_DIR, BOXCART_PLUGIN_URL, and BOXCART_PLUGIN_BASENAME are set at the top of boxcart.php.

  2. Autoloader registered

    spl_autoload_register( 'boxcart_autoloader' ) is called immediately so that any BoxCart_* class can be resolved on demand.

  3. Activation / deactivation hooks

    register_activation_hook triggers BoxCart_Activator::activate() which creates database tables, sets defaults, schedules cron events, and creates the boxcart_customer role. register_deactivation_hook triggers BoxCart_Deactivator::deactivate() which clears cron events.

  4. plugins_loaded hook

    The boxcart() function is hooked to plugins_loaded. It creates (or returns) a singleton instance of the main BoxCart class.

  5. BoxCart::__construct()

    The constructor runs the following initialisation chain:

    1. load_dependencies() — Requires all core, admin, or frontend class files.
    2. maybe_update_database() — Checks whether the DB schema needs updating.
    3. set_locale() — Hooks load_plugin_textdomain() to the init action.
    4. init_core() — Instantiates post types, taxonomies, email, webhook, and ensures the customer role exists.
    5. init_cron() — Registers cron hook callbacks.
    6. init_customer_restrictions() — Sets up admin-bar hiding and wp-admin redirect for customers.
    7. init_admin() or init_frontend() — Loads the appropriate interface classes based on is_admin(). Frontend also initialises session-based notices.
    8. init_ajax() — Instantiates BoxCart_Ajax.
    9. init_blocks() — Instantiates BoxCart_Blocks for Gutenberg block registration.
// boxcart.php — entry point
add_action( 'plugins_loaded', 'boxcart' );

function boxcart() {
    static $instance = null;
    if ( null === $instance ) {
        require_once BOXCART_PLUGIN_DIR . 'includes/class-boxcart.php';
        $instance = new BoxCart();
    }
    return $instance;
}

Autoloader

BoxCart uses a custom spl_autoload_register callback that maps every class with the BoxCart_ prefix to its corresponding file. The prefix is stripped, converted to lowercase, and underscores are replaced with hyphens to build the filename.

The autoloader searches five directories in order:

  1. includes/
  2. includes/admin/
  3. includes/frontend/
  4. includes/core/
  5. includes/api/
function boxcart_autoloader( $class_name ) {
    // Only load classes with our prefix.
    if ( 0 !== strpos( $class_name, 'BoxCart' ) ) {
        return;
    }

    $class_name = str_replace( 'BoxCart_', '', $class_name );
    $class_name = strtolower( $class_name );
    $class_name = str_replace( '_', '-', $class_name );

    $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',
    );

    foreach ( $locations as $file ) {
        if ( file_exists( $file ) ) {
            require_once $file;
            return;
        }
    }
}
spl_autoload_register( 'boxcart_autoloader' );
Tip

Although the autoloader is registered, most classes are also explicitly require_once'd inside BoxCart::load_dependencies() to guarantee load order. The autoloader acts as a safety net for any class referenced before its explicit require.

Class Reference

The table below lists the major classes organised by directory. Each class follows the BoxCart_ naming convention and maps to a file via the autoloader.

Core Classes includes/core/

ClassPurpose
BoxCart_DatabaseTable creation, schema migrations, and column checks
BoxCart_HelpersStatic utility methods — settings retrieval, formatting, validation, role management
BoxCart_ProductProduct data model
BoxCart_OrderOrder data model with status and payment management
BoxCart_LocationLocation data model
BoxCart_SlotTime-slot data model
BoxCart_CustomerCustomer data based on WordPress users
BoxCart_EmailEmail queue, template rendering, and dispatch
BoxCart_PaymentPayment constants, helpers, and status logic
BoxCart_StripeStripe API wrapper (no SDK — uses wp_remote_post)
BoxCart_WebhookStripe webhook REST endpoint with signature verification
BoxCart_TaxTax calculation (inclusive / exclusive)
BoxCart_Post_TypesCustom post type registration
BoxCart_TaxonomiesCustom taxonomy registration
BoxCart_NoticesSession-based frontend notice system
BoxCart_CSV_ImporterCSV import with validation
BoxCart_CSV_ExporterCSV export
BoxCart_Password_ResetCustom password reset flow with verification codes
BoxCart_ReorderQuick reorder from previous orders
BoxCart_Order_ModificationOrder modification within a configurable time window
BoxCart_FavouritesFavourite products stored in user meta
BoxCart_Product_QuantitiesQuantity type definitions (each, kg, per-item, etc.)

Admin Classes includes/admin/

ClassPurpose
BoxCart_AdminAdmin initialisation and asset enqueuing
BoxCart_Admin_MenusAdmin menu and sub-menu registration
BoxCart_Admin_SettingsSettings page with tabbed interface
BoxCart_Admin_ProductsProduct list and edit screens
BoxCart_Admin_OrdersOrder list, detail view, and status management
BoxCart_Admin_LocationsLocation and opening-hours management
BoxCart_Admin_CategoriesCategory CRUD and reordering
BoxCart_Admin_SlotsTime-slot management
BoxCart_Admin_Import_ExportCSV import/export UI
BoxCart_Admin_HelpContextual help content for admin screens

Frontend Classes includes/frontend/

ClassPurpose
BoxCart_FrontendFrontend initialisation and asset enqueuing
BoxCart_ShortcodesShortcode registration ([boxcart_products], [boxcart_basket], etc.)
BoxCart_BasketBasket / cart session management (singleton)
BoxCart_CheckoutCheckout processing and order creation
BoxCart_AccountCustomer account pages (dashboard, orders, edit details)
BoxCart_SidecartSlide-out sidecart panel
BoxCart_BlocksGutenberg block registration and render callbacks

API Classes includes/api/

ClassPurpose
BoxCart_AjaxCentral AJAX handler — registers all wp_ajax_ and wp_ajax_nopriv_ actions

JavaScript Architecture

BoxCart ships separate JavaScript files for each major feature area rather than a single monolithic bundle. This keeps page payloads small — only the scripts needed for the current page are enqueued.

Frontend Scripts

FilePurpose
boxcart-products.jsProduct grid/table interactions, filtering, search, and view toggle
boxcart-checkout.jsCheckout form handling, order placement, and Stripe handoff
boxcart-payment.jsStripe Payment Element mounting, payment method toggle, and payment confirmation
boxcart-customer.jsAccount page interactions — login, register, edit details, reorder, modify
boxcart-sidecart.jsSidecart open/close, auto-open on add, and item management
boxcart-slot-selector.jsSlot selection modal, date picker, and time-slot selection

Admin Scripts

FilePurpose
admin-common.jsShared admin functionality (notifications, modals, utilities)
admin-products.jsProduct management interactions
admin-categories.jsCategory management and drag-and-drop reordering
admin-locations.jsLocation, opening hours, and slot management
admin-import-wizard.jsCSV import step-by-step wizard
boxcart-blocks.jsGutenberg block editor integration

Data Passing

All scripts receive server-side data through wp_localize_script(). This injects a global JavaScript object (typically boxcart_params) containing AJAX URLs, nonces, settings, and translatable strings.

wp_localize_script( 'boxcart-products', 'boxcart_params', array(
    'ajax_url' => admin_url( 'admin-ajax.php' ),
    'nonce'    => wp_create_nonce( 'boxcart_ajax_nonce' ),
    'currency' => BoxCart_Helpers::get_currency_settings(),
    // ... additional settings and i18n strings
) );

Build Process

Source files are minified via npm run build using terser (JavaScript) and clean-css-cli (CSS). Both source and .min.js / .min.css files are shipped so that SCRIPT_DEBUG can toggle between them.

Session Handling

BoxCart uses its own database-backed session system rather than PHP native sessions. This avoids conflicts with object caching, load balancers, and other plugins.

PropertyDetail
Storage table{prefix}_boxcart_baskets
Session identifier32-character random string generated by wp_generate_password()
Cookie nameboxcart_session
Cookie lifetime30 days
Basket expiry7 days (server-side)
Guest supportYes — baskets are linked by session_id when no user is logged in
Logged-in supportYes — baskets are linked by user_id; session is updated on login
InitialisationHooked to init at priority 5 via BoxCart_Basket::init_session()

When a logged-in user has an existing basket tied to their user_id, the session ID is updated to match the current cookie. For guest users, the basket row is looked up by session_id. Expired baskets are cleaned up automatically by a cron job (see Cron Jobs below).

// Session initialisation (simplified)
public function init_session() {
    $this->session_id = $this->get_session_id();
}

public function get_session_id() {
    if ( ! empty( $this->session_id ) ) {
        return $this->session_id;
    }

    if ( isset( $_COOKIE[ self::COOKIE_NAME ] ) ) {
        $this->session_id = sanitize_text_field( wp_unslash( $_COOKIE[ self::COOKIE_NAME ] ) );
    } else {
        $this->session_id = wp_generate_password( 32, false );
        setcookie( self::COOKIE_NAME, $this->session_id, time() + self::COOKIE_EXPIRATION, '/' );
    }

    return $this->session_id;
}

Email Queue

BoxCart never sends emails synchronously during an HTTP request. Instead, every email is queued and dispatched in the background so that checkout and status changes remain fast.

  1. Queue

    When an event fires (e.g. boxcart_order_created), BoxCart_Email schedules a single cron event via wp_schedule_single_event() and immediately calls spawn_cron() to trigger processing.

  2. Process

    The boxcart_send_queued_email cron hook fires BoxCart_Email::process_queued_email(), which renders the HTML template, applies filters, and sends the email via wp_mail().

  3. Templates

    HTML templates live in includes/emails/ and support placeholder tokens like {customer_name}, {order_number}, and {collection_date}. Templates include inline CSS and support admin-configurable branding (logo, colours, footer text).

Tip

Admins can preview, test-send, and reset email templates from BoxCart → Settings → Emails without placing a real order.

Cron Jobs

BoxCart registers the following scheduled tasks through the WordPress cron system:

Cron HookScheduleHandlerPurpose
boxcart_cleanup_expired_baskets Twice daily BoxCart_Basket::cleanup_expired_baskets() Remove expired guest baskets and their items from the database
boxcart_cleanup_old_logs Daily Clean up old order status log entries
boxcart_send_queued_email Single event BoxCart_Email::process_queued_email() Non-blocking email delivery — scheduled per email, fires once

Cron events are registered during plugin activation by BoxCart_Activator and cleared on deactivation by BoxCart_Deactivator.

Security

BoxCart follows WordPress security best practices across all entry points.

Nonce Verification

Every AJAX request is verified with check_ajax_referer( 'boxcart_ajax_nonce', 'nonce', false ). Admin settings forms use a separate boxcart_admin_nonce. Nonces are generated via wp_create_nonce() and passed to JavaScript through wp_localize_script().

Capability Checks

Admin-only AJAX actions verify current_user_can( 'manage_options' ) before executing. Customer-only actions check that the user is logged in. The custom boxcart_customer role has only the read capability, preventing access to wp-admin screens.

Input Sanitisation

Input TypeSanitisation Function
Text fieldssanitize_text_field()
Email addressessanitize_email()
Numeric valuesabsint(), intval()
Raw inputwp_unslash() before sanitisation

Output Escaping

All output uses esc_html(), esc_attr(), and wp_kses() as appropriate to prevent XSS.

Prepared SQL Statements

Every database query uses $wpdb->prepare() with parameterised placeholders. No raw user input is ever interpolated into SQL.

CSRF Protection

All state-changing requests (AJAX and form submissions) require a valid nonce. The Stripe webhook endpoint uses HMAC-SHA256 signature verification instead of nonces.

Rate Limiting

ActionLimitWindow
Login attempts5 attempts15 minutes
Password reset requestsThrottled per email hashVia transient