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 (and admin/views/ partials)
│ ├── frontend/ Frontend-facing classes (and frontend/views/ partials)
│ ├── core/ Core business logic
│ ├── emails/ HTML + plain-text email templates
│ └── api/ AJAX & REST API handlers
├── assets/
│ ├── css/ Stylesheets
│ ├── js/ JavaScript
│ └── fonts/ Self-hosted fonts
└── languages/ i18n files
Each directory has a clear purpose:
- includes/core/: Data models, database helpers, payment logic, email, CSV import/export, and other business logic that is shared between admin and frontend.
- includes/admin/: Admin screens, menus, settings, and admin-only view templates (inside
admin/views/). - includes/frontend/: Frontend shortcodes, basket, checkout, account, sidecart, and frontend view templates (inside
frontend/views/). - includes/api/: The central AJAX handler class that registers all
wp_ajax_andwp_ajax_nopriv_actions. - assets/: CSS, JavaScript, images, and self-hosted font files. Both source and minified (
.min.js,.min.css) versions are shipped. - includes/emails/: HTML and plain-text email templates rendered by
BoxCart_Email, plus sharedemail-header.phpandemail-footer.phppartials. - languages/: Translation files (
.pot).
Boot Flow
When WordPress loads the plugin, the following sequence runs inside boxcart.php and class-boxcart.php:
-
Constants defined
BOXCART_VERSION,BOXCART_PLUGIN_FILE,BOXCART_PLUGIN_DIR,BOXCART_PLUGIN_URL, andBOXCART_PLUGIN_BASENAMEare set at the top ofboxcart.php. -
Autoloader registered
spl_autoload_register( 'boxcart_autoloader' )is called immediately so that anyBoxCart_*class can be resolved on demand. -
Activation / deactivation hooks
register_activation_hooktriggersBoxCart_Activator::activate()which creates database tables, sets defaults, schedules cron events, and creates theboxcart_customerrole.register_deactivation_hooktriggersBoxCart_Deactivator::deactivate()which clears cron events. -
plugins_loadedhookThe
boxcart()function is hooked toplugins_loaded. It creates (or returns) a singleton instance of the mainBoxCartclass. -
BoxCart::__construct()The constructor runs the following initialisation chain:
load_dependencies(): Requires all core, admin, or frontend class files.maybe_update_database(): Checks whether the DB schema needs updating.set_locale(): Hooksload_plugin_textdomain()to theinitaction.init_core(): Instantiates post types, taxonomies, email, webhook, and ensures the customer role exists.init_cron(): Registers cron hook callbacks.init_customer_restrictions(): Sets up admin-bar hiding and wp-admin redirect for customers.init_admin()orinit_frontend(): Loads the appropriate interface classes based onis_admin(). Frontend also initialises session-based notices.init_ajax(): InstantiatesBoxCart_Ajax.init_blocks(): InstantiatesBoxCart_Blocksfor 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 eight directories in order (the last three exist only in the Pro build):
includes/includes/admin/includes/frontend/includes/core/includes/api/includes/premium/core/includes/premium/admin/includes/premium/frontend/
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',
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',
);
foreach ( $locations as $file ) {
if ( file_exists( $file ) ) {
require_once $file;
return;
}
}
}
spl_autoload_register( 'boxcart_autoloader' );
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/
| Class | Purpose |
|---|---|
BoxCart_Database | Table creation, schema migrations, and column checks |
BoxCart_Helpers | Static utility methods: settings retrieval, formatting, validation, role management |
BoxCart_Product | Product data model |
BoxCart_Order | Order data model with status and payment management |
BoxCart_Location | Location data model |
BoxCart_Slot | Time-slot data model |
BoxCart_Customer | Customer data based on WordPress users |
BoxCart_Email | Email queue, template rendering, and dispatch |
BoxCart_Payment | Payment constants, helpers, and status logic |
BoxCart_Stripe | Stripe API wrapper (no SDK, uses wp_remote_post) |
BoxCart_Webhook | Stripe webhook REST endpoint with signature verification |
BoxCart_Tax | Tax calculation (inclusive / exclusive) |
BoxCart_Post_Types | Custom post type registration |
BoxCart_Taxonomies | Custom taxonomy registration |
BoxCart_Notices | Session-based frontend notice system |
BoxCart_CSV_Importer | CSV import with validation |
BoxCart_CSV_Exporter | CSV export |
BoxCart_Reorder | Quick reorder from previous orders |
BoxCart_Order_Modification | Order modification within a configurable time window |
BoxCart_Favourites | Favourite products stored in user meta |
BoxCart_Product_Quantities | Quantity type definitions (each, kg, per-item, etc.) |
Admin Classes includes/admin/
| Class | Purpose |
|---|---|
BoxCart_Admin | Admin initialisation and asset enqueuing |
BoxCart_Admin_Menus | Admin menu and sub-menu registration |
BoxCart_Admin_Settings | Settings page with tabbed interface |
BoxCart_Admin_Products | Product list and edit screens |
BoxCart_Admin_Orders | Order list, detail view, and status management |
BoxCart_Admin_Locations | Location and opening-hours management |
BoxCart_Admin_Categories | Category CRUD and reordering |
BoxCart_Admin_Slots | Time-slot management |
BoxCart_Admin_Import_Export | CSV import/export UI |
BoxCart_Admin_Help | Contextual help content for admin screens |
Frontend Classes includes/frontend/
| Class | Purpose |
|---|---|
BoxCart_Frontend | Frontend initialisation and asset enqueuing |
BoxCart_Shortcodes | Shortcode registration ([boxcart_products], [boxcart_basket], etc.) |
BoxCart_Basket | Basket / cart session management (singleton) |
BoxCart_Checkout | Checkout processing and order creation |
BoxCart_Account | Customer account pages (dashboard, orders, edit details) |
BoxCart_Sidecart | Slide-out sidecart panel |
BoxCart_Blocks | Gutenberg block registration and render callbacks |
API Classes includes/api/
| Class | Purpose |
|---|---|
BoxCart_Ajax | Central AJAX handler: registers all wp_ajax_ and wp_ajax_nopriv_ actions |
Database Tables
BoxCart creates custom database tables on activation, all prefixed with {$wpdb->prefix}boxcart_: 9 in the Free build and 12 with Pro. They fall into four groups: locations, orders, baskets, and products. See Database Schema for the full column-by-column reference.
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
| File | Purpose |
|---|---|
boxcart-products.js | Product grid/table interactions, filtering, search, and view toggle |
boxcart-checkout.js | Checkout form handling, order placement, and Stripe handoff |
boxcart-payment.js | Stripe Payment Element mounting, payment method toggle, and payment confirmation |
boxcart-customer.js | Account page interactions: login, register, edit details, reorder, modify |
boxcart-sidecart.js | Sidecart open/close, auto-open on add, and item management |
boxcart-slot-selector.js | Slot selection modal, date picker, and time-slot selection |
Admin Scripts
| File | Purpose |
|---|---|
admin-common.js | Shared admin functionality (notifications, modals, utilities) |
admin-products.js | Product management interactions |
admin-categories.js | Category management and drag-and-drop reordering |
admin-locations.js | Location, opening hours, and slot management |
admin-import-wizard.js | CSV import step-by-step wizard |
boxcart-blocks.js | Gutenberg block editor integration |
Data Passing
Frontend scripts receive server-side data through wp_localize_script(). The core frontend script (handle boxcart-frontend) is localised with a global JavaScript object named boxcartData containing the AJAX URL, nonce, currency and pricing settings, and translatable strings. Its keys are camelCase, for example boxcartData.ajaxUrl and boxcartData.nonce.
wp_localize_script( 'boxcart-frontend', 'boxcartData', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'boxcart_ajax_nonce' ),
'currencySymbol' => $currency_symbol,
// ... 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.
| Property | Detail |
|---|---|
| Storage table | {prefix}_boxcart_baskets |
| Session identifier | 32-character random string generated by wp_generate_password() |
| Cookie name | boxcart_session |
| Cookie lifetime | 30 days |
| Basket expiry | 7 days (server-side) |
| User linkage | Baskets are linked by user_id to the logged-in customer and carried across devices |
| Initialisation | Hooked to init at priority 5 via BoxCart_Basket::init_session() |
When a customer has an existing basket tied to their user_id, the session ID is updated to match the current cookie. 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.
-
Queue
When an event fires (e.g.
boxcart_order_created),BoxCart_Emailschedules a single cron event viawp_schedule_single_event()and immediately callsspawn_cron()to trigger processing. -
Process
The
boxcart_send_queued_emailcron hook firesBoxCart_Email::process_queued_email(), which renders the HTML template, applies filters, and sends the email viawp_mail(). -
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).
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 Hook | Schedule | Handler | Purpose |
|---|---|---|---|
boxcart_cleanup_expired_baskets |
Twice daily | BoxCart_Basket::cleanup_expired_baskets() |
Remove expired baskets and their items from the database |
boxcart_cleanup_old_logs |
Daily | N/A | 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 Type | Sanitisation Function |
|---|---|
| Text fields | sanitize_text_field() |
| Email addresses | sanitize_email() |
| Numeric values | absint(), intval() |
| Raw input | wp_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
| Action | Limit | Window |
|---|---|---|
Post-purchase account signup (boxcart_post_purchase_signup) | 5 signups | Per hour, keyed by IP address |