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:
- 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. - templates/ — Override-able email templates.
- 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 five directories in order:
includes/includes/admin/includes/frontend/includes/core/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' );
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_Password_Reset | Custom password reset flow with verification codes |
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 |
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
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.
| 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) |
| Guest support | Yes — baskets are linked by session_id when no user is logged in |
| Logged-in support | Yes — baskets are linked by user_id; session is updated on login |
| Initialisation | Hooked 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.
-
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 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 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 |
|---|---|---|
| Login attempts | 5 attempts | 15 minutes |
| Password reset requests | Throttled per email hash | Via transient |