MaxtDesign

Developer Guide

Class Structure

The plugin uses a custom autoloader rooted at the MCP_ class-name prefix and the includes/ directory.

| Class | File | Responsibility |

|---|---|---|

| MCP_Core | class-mcp-core.php | Bootstrap, component init, activation / deactivation lifecycle |

| MCP_Admin | class-mcp-admin.php | Product meta box, WC Settings tab (General / Tools / System Status) |

| MCP_Frontend | class-mcp-frontend.php | Product page selection UI, asset enqueueing, AJAX endpoints for live total |

| MCP_Cart | class-mcp-cart.php | Cart item data, quantity matching, required vs extra split, removal cascade |

| MCP_Order | class-mcp-order.php | Order line item meta (_mcp_*), admin order detail relationship labels |

| MCP_Requirements | class-mcp-requirements.php | Reads per-product companion config, cached at 1-hour TTL |

| MCP_Cache | class-mcp-cache.php | Requirements cache layer + invalidation hooks |

| MCP_Export_Import | class-mcp-export-import.php | SKU-keyed JSON config import / export |

Constants

MCP_VERSION         // '1.0.0'
MCP_PLUGIN_FILE     // __FILE__ from the main plugin
MCP_PLUGIN_DIR      // plugin_dir_path(__FILE__)
MCP_PLUGIN_URL      // plugin_dir_url(__FILE__)
MCP_PLUGIN_BASENAME // plugin_basename(__FILE__)

Action Hooks

`mcp_before_add_required_product`

Fires immediately before a required companion is added to the cart.

do_action('mcp_before_add_required_product', $product_id, $selected_product_id, $quantity, $cart_item_key);

| Param | Type | Description |

|---|---|---|

| $product_id | int | Parent product ID |

| $selected_product_id | int | Companion product ID being added |

| $quantity | int | Quantity being added |

| $cart_item_key | string | Parent cart item key |

`mcp_after_add_required_product`

Fires after a required companion is added (or merged into an existing line).

do_action('mcp_after_add_required_product', $cart_item_key, $required_cart_key, $product_id, $selected_product_id, $quantity);

`mcp_order_item_meta_added`

Fires after companion relationship meta has been written to an order line item.

do_action('mcp_order_item_meta_added', $item, $values, $parent_product_id, $group_id);

$group_id is the literal string 'required' or 'optional'.

`mcp_after_clear_all_cache`

Fires after the requirements cache is fully flushed (Tools → Clear Cache).

do_action('mcp_after_clear_all_cache');

Filter Hooks

`mcp_validate_add_to_cart`

Filter the validation result before a parent product is added to the cart.

add_filter('mcp_validate_add_to_cart', function($passed, $product_id, $first_selected_id, $quantity) {
    // Return false to block add-to-cart with a wc_add_notice() already shown,
    // or true to allow.
    return $passed;
}, 10, 4);

For backward compatibility, only the first selected companion ID is passed (the field is single-select for most customers).

`mcp_required_cart_data`

Filter the cart_item_data array used when adding a required companion.

add_filter('mcp_required_cart_data', function($cart_item_data, $product_id, $selected_product_id, $quantity) {
    $cart_item_data['my_custom_flag'] = 'yes';
    return $cart_item_data;
}, 10, 4);

`mcp_optional_cart_data`

Same as above for add-on companions:

add_filter('mcp_optional_cart_data', function($cart_item_data, $product_id, $optional_product_id, $quantity) {
    return $cart_item_data;
}, 10, 4);

Cart Item Data Keys

Runtime keys set on cart line items:

| Key | Type | Set on |

|---|---|---|

| mcp_is_required | bool | required companions |

| mcp_is_optional | bool | add-on companions |

| mcp_is_standalone | bool | the same product when also bought independently |

| mcp_required_qty | int | required companions — the driven-by-parent portion |

| mcp_extra_qty | int | the customer-added-on-top portion |

| mcp_parent_product_id | int | parent product ID |

| mcp_parent_cart_key | string | parent cart item key |

These are not persisted to options; they live for the lifetime of the cart session and are promoted to order item meta at checkout.

Order Line Item Meta

Persisted on woocommerce_order_item_meta (or the HPOS equivalent):

| Meta key | Value | Notes |

|---|---|---|

| _mcp_is_required | 'yes' | leading underscore — hidden from order edit UI by default |

| _mcp_is_optional | 'yes' | |

| _mcp_group_id | 'required' or 'optional' | |

| _mcp_parent_product_id | parent product ID | used to render the "Required for [parent]" / "Add-on for [parent]" label |

Use $item->get_meta('_mcp_parent_product_id') to recover the parent from a line item.

SKU-Keyed Config Schema (Tools → Export)

{
  "version": "1.0.0",
  "exported_at": "2026-05-28T16:42:11Z",
  "global": {
    "multiselect_roles": ["wholesale", "dealer"]
  },
  "products": {
    "PARENT-SKU-001": {
      "required": {
        "enabled": true,
        "label": "Select Required Product",
        "display": "radio",
        "items": [
          { "sku": "REQ-SKU-A", "oos": "block" },
          { "sku": "REQ-SKU-B", "oos": "backorder" }
        ]
      },
      "optional": {
        "enabled": true,
        "label": "Add-ons",
        "items": [
          { "sku": "ADD-SKU-X", "conflict_key": "voltage" },
          { "sku": "ADD-SKU-Y", "conflict_key": "voltage" }
        ]
      },
      "pricing_display": "both"
    }
  }
}

SKUs are the join key on import — products on the destination site match by SKU and are skipped (with an admin notice line) if the SKU doesn't resolve.

HPOS Declaration

Declared on the before_woocommerce_init action:

AutomatticWooCommerceUtilitiesFeaturesUtil::declare_compatibility(
    'custom_order_tables',
    __FILE__,
    true
);

All order reads and writes go through WooCommerce APIs (wc_get_orders(), $order->get_meta(), $item->add_meta_data()), never get_post_meta() against the order ID.