View Categories

Developers

13 min read

The public PHP API, REST fields, Store API data, CSV columns, WP-CLI commands, hooks and template overrides of Sales Igniter Bundles & Composites.

Words you need #

  • A bundle is a product of type si_bundle, labelled “Bundle”.
  • A bundle has items. An item is a fixed product (kind = product) or a choice (kind = choice).
  • A configuration is what a customer picked.
  • The bundle line in a cart or order is the container. Its item lines are its children.

Public PHP API: Sibundles\Api #

A static class. Method names are camelCase. Signatures are stable. New methods may be added; existing ones keep their signatures.

A configuration can be given in either of two shapes:

  • Rows, as the Store API and REST take them: “ [ { item, product_id, quantity, selected, variation_id, attributes } ] ` attributes is { “attribute_size”: “Large” } or [ { name, option } ]. product_id` is the option picked for a choice. Items you leave out take their defaults.
  • The stored shape, as order lines carry it: “ { item id: { product_id, qty, selected, variation_id, attributes } } “

Hidden items always keep their defaults. Every method checks the bundle as it is now.

Bundles and configurations #

Api::isBundle( $product ): bool

True when the product (or product id) is a bundle.

Api::configuration( $bundle, array $config = array() ): array|null

A configuration in its stored shape, with defaults filled in. Null when $bundle is not a bundle. $bundle is a product or an id.

Api::validate( $bundle, array $config = array(), $qty = 1, $stock = true ): string[]

Checks a configuration and returns the problems, in customer words. An empty array means it is fine. $stock = false skips the stock check. Filter: sibundles_configuration_errors.

Api::rules( $bundle ): array[]

A bundle’s rules (see “Rules” below). Empty when none.

Cart #

Api::addToCart( $bundle, $qty = 1, array $config = array(), array $cart_item_data = array() ): string|false

Adds a bundle with its items. Returns the bundle line’s cart key, or false with the usual error notices when refused. $cart_item_data is kept on the bundle line.

Api::isChildCartItem( array $cart_item ): bool      // an item line of a bundle
Api::isContainerCartItem( array $cart_item ): bool  // a bundle line
Api::containerCartItem( array $cart_item ): array|null  // the bundle line of an item line

Orders #

Api::addToOrder( $order, $bundle, $qty = 1, array $config = array() ): int|WP_Error

Adds a bundle line and its item lines to an order. Stock is not checked. Items that cannot be resolved are named in an order note. Totals are recalculated. Returns the bundle line’s id.

Api::configureOrderItem( $item, array $config = array(), $qty = null ): true|WP_Error

Reconfigures the bundle line $item (a WC_Order_Item_Product at the top of its bundle). $qty null keeps the quantity. Returns WP_Error if the configuration breaks the bundle’s rules.

Api::isChildOrderItem( $item ): bool
Api::isContainerOrderItem( $item ): bool
Api::orderLineDialect( $item ): string   // 'sib' | 'pb' | 'cp' | ''
Api::orderContainer( $item, $order = null ): WC_Order_Item_Product|null
Api::orderChildren( $item, $order = null ): WC_Order_Item_Product[]   // line id => line
Api::orderItemInfo( $item, $order = null ): array|null
Api::orderConfiguration( $item ): array

These read an order line whatever plugin wrote it: this plugin (sib), or an order placed before an import from another bundle plugin (pb for bundles, cp for composites). Old lines are never rewritten. Always read them through these methods, not through their meta.

orderItemInfo() returns: dialect, role (container or child), nested (a child that is also a bundle line), ours, bundle_id, product_id, variation_id, item_id, group_mode (grouped, flat, items_only). For a child it adds priced_individually, discount (percent at order time), title (the staff name), hidden, price_hidden.

orderConfiguration() returns item id => [ qty, selected, variation_id, attributes, title ]. Feed it to configuration() to check it against the bundle as it is now.

Items of a line #

Api::partItem( $line ): array|null     // the item behind an item line, as an array
Api::isChoice( $line ): bool           // the line comes from a choice
Api::partOptions( $line, $limit = 50 ): array[]   // other options of its choice: { product_id, name, type, variable }

$line is an order line or a cart line. Used by “Swap item” suggestions.

Cart and order meta #

Cart line data: sib_parent (container key), sib_children, sib_item (item id), sib_bundle (bundle id), sib_config.

Bundle (container) order line meta: _sib_key, _sib_children, _sib_config, _sib_group_mode, _sib_weight, _sib_needs_processing.

Item (child) order line meta: _sib_key, _sib_parent (the container’s key), _sib_item, _sib_bundle, _sib_hidden, _sib_price_hidden, _sib_own_price (yes or no), _sib_discount, _sib_ships_alone, _sib_title, and a visible “Part of” row for shown items.

These are for reading. Use the API to write.

REST API #

Products (v3) #

Bundles are created with "type": "si_bundle". A bundle’s product object has a bundle field, read and write:

"bundle": {
  "settings": { "layout": "default", "group_mode": "default", "shipping": "separately", "min_size": "", "max_size": "", "...": "..." },
  "items": [ { "id": 12, "kind": "product", "product_id": 345, "quantity_min": 1, "quantity_default": 1, "quantity_max": 1, "priced_individually": false, "discount": "", "visible_product": true, "...": "..." } ],
  "rules": [],
  "stock_status": "instock",
  "stock_quantity": 4,
  "price_range": { "min": "...", "max": "..." }
}
  • settings: the keys are layout, form_location, group_mode, min_size, max_size, shipping, weight, sold_individually, edit_in_cart, catalog_price, qty_tiers, qty_tiers_base. Writing merges.
  • items: writing replaces the list. An entry with the id of one of the bundle’s items keeps it (settings merged). An entry without an id is new. Items left out are removed. A new item may carry a ref (write only) that rules can use before it has an id.
    • Fixed item: kind = product, product_id.
    • Choice: kind = choice, sources = { product, product_cat, product_tag, exclude } (lists of ids), plus choice settings: default_pick, options_style, per_page, pagination, sort, filters, filter_attributes, select_action, details_*, option_price_display, image_id.
    • Item settings: quantity_min, quantity_default, quantity_max, optional, optional_checked, allowed_variations, default_attributes, priced_individually, discount, discount_base, shipped_individually, visible_product, visible_cart, visible_order, price_visible_product, price_visible_cart, price_visible_order, title, description, hide_thumbnail.
  • rules: writing replaces them.
  • stock_status, stock_quantity, price_range: read only.

Errors: 400 sibundles_rest_invalid_item (an item is not valid, for example a grouped, external or bundle product), 400 sibundles_rest_not_a_bundle (bundle data on another type), 400 sibundles_rest_invalid_bundle, 400 sibundles_rest_invalid_rules.

Orders (v3) #

Each line item gets:

  • bundle_parent: id of the bundle line this line is an item of, or null;
  • bundle_children: ids of a bundle line’s item lines;
  • bundle_config: the bundle line’s configuration.

Posting a bundle line, with or without bundle_config (rows or the stored shape), builds its item lines on the server. Stock is not checked, and it is reduced when a paid order reduces stock. Posting bundle_config on an existing bundle line reconfigures it. Errors: 400 sibundles_rest_invalid_config, sibundles_rest_not_a_bundle, sibundles_rest_not_a_bundle_line.

A bundle line’s quantity change scales its totals and its item lines follow. Item lines whose quantity the same request sets keep that quantity. The internal v4 order routes are not handled yet.

Read aliases for the previous bundle plugins #

For stores that moved from another bundle plugin, read-only copies of the old field names are served so that integrations keep working.

  • Products: bundled_by; on bundles bundle_stock_status, bundle_stock_quantity, bundle_virtual, bundle_layout, bundle_add_to_cart_form_location, bundle_editable_in_cart, bundle_sold_individually_context, bundle_item_grouping, bundle_min_size, bundle_max_size, bundle_price, bundled_items, and composite_virtual, composite_layout, composite_add_to_cart_form_location, composite_editable_in_cart, composite_sold_individually_context, composite_shop_price_calc, composite_components, composite_scenarios.
  • Order lines: bundled_by, bundled_items, bundled_item_title, composite_parent, composite_children.

They are read only, and only keys that are missing are added. New integrations should use bundle. Setting: WooCommerce > Bundles > Settings > Previous REST field names: Automatic (on once bundles were imported), On, Off. Imported items keep their old ids in these fields.

Store API #

  • Cart items carry extensions.sibundles.
    • Bundle line: { role: "container", mode, children, editable, saveable }.
    • Item line: { role: "child", mode, parent, item, removable, price_hidden, last, choice, depth, saveable }.
  • Products carry extensions.sibundles with items (the shown ones only), layout, group_mode, min_size, max_size, edit_in_cart, needs_configuration, stock_quantity, rules, rule_parents. Hidden items are never listed.
  • Add to cart: POST /wc/store/v1/cart/add-item accepts a sibundles parameter: a list of rows { item, quantity, selected, variation_id, attributes }. An invalid one answers 400 sibundles_add_to_cart.
  • Cart responses leave out hidden items and, in “items only” mode, the bundle line. A grouped bundle line carries the price and totals of the whole bundle. Item quantities are read-only.

Plugin routes #

GET /wp-json/sibundles/v1/options #

Public. The options of a choice, for the product page. Parameters:

Parameter
bundleBundle id (required)
itemThe choice’s item id (required)
modepage (default), all (1,000 a page, with more when there are more) or details
pagePage number
searchOnly options whose name or SKU holds this text
orderbydefault, popularity, rating, date, price, price-desc, title
filtersJSON: taxonomy => slugs
picksJSON: the picks so far, so rules apply
pickWith details: the option to describe

404 sibundles_not_found for a missing bundle, a draft, a fixed or hidden item.

GET /wp-json/sibundles/v1/reports #

Needs view_woocommerce_reports. Feeds Analytics > Bundles.

Parameter
viewbundles (default), items or stock
after, beforeDates (YYYY-MM-DD, store time)
compare_after, compare_beforeA period to compare with. Its totals come back as previous.
orderby, orderSort
page, per_pagePaging (per_page up to 100)

Sales come from the lookup table, with refunds already taken off, for the order statuses the Analytics settings count.

Abilities API #

sibundles/query-bundles, in the category sibundles (“Bundles”). Read only. Needs edit_products. Registered where the Abilities API exists (WordPress 6.9 and later).

Input (all optional): id, contains_product_id, search, sku, status (any, publish, private, draft, pending, future), stock_status (instock, outofstock, onbackorder), page, per_page (up to 100).

Output: each bundle’s settings, items (with product name and SKU), rules, stock and price range.

Product CSV #

Three columns on products:

ColumnKeyContent
Bundle itemssibundles_itemsJSON list of items in order
Bundle settingssibundles_settingsJSON of the settings that differ from the defaults
Bundle rulessibundles_rulesJSON of the rules

Items name their product by SKU, or id:<id> when it has none. A product further down the file, or not imported yet, is filled in when its row comes. A choice is written with kind = choice and sources = { products, categories, tags, exclude }: products by SKU or id:<id>, categories and tags by slug. Every item carries a ref (i1, i2, …) that the rules use. id keeps an item when the same store imports over its own bundles. Only settings that differ from the defaults are written.

While the previous bundle plugins are switched off, their CSV columns are read on import too.

WP-CLI #

wp sibundles sync [--all] [--product=<id>]

Recompute prices, stock and guard rails. --product takes a bundle id, or an item product id (then every bundle that uses it).

wp sibundles migrate --detect
wp sibundles migrate --dry-run (--all | --product=<ids>) [--source=<pb|cp>] [--format=<table|csv|json>]
wp sibundles migrate (--all | --product=<ids>) [--source=<pb|cp>] [--reimport] [--no-backfill]
wp sibundles migrate --backfill
wp sibundles rollback (--all | --product=<ids>) [--source=<pb|cp>] [--delete-rows]

pb is bundles of the Product Bundles plugin, cp is composites of the Composite Products plugin. See the two switching guides. Run WP-CLI with a memory cap, for example ulimit -v 3000000; wp sibundles sync --all.

Hooks #

Filters #

HookArgumentsUse
sibundles_form_data$data, $bundleChange the data the product page form’s script receives
sibundles_editor_price_line$line, $bundleThe price line under the bundle settings in the editor (HTML, ” for none). The rental bridge puts a rental bundle’s rates there
sibundles_show_prices$show, $bundleReturn false to hide every price of a bundle’s form from this visitor: item and option prices, the total, “You save”, quantity discounts, the rental total (Request a Quote uses it for hidden prices)
sibundles_share_link$show, $bundleShow or hide “Copy link to these picks”
sibundles_configuration_errors$errors, $bundle, $config, $bundle_qtyAdd or change problems found in a configuration
sibundles_cart_check_errors$errors, $bundle, $config, $qtyAdd problems at add to cart
sibundles_option_types$typesProduct types that may be options of a choice (default simple, variable, subscription, variable-subscription, si_bundle)
sibundles_choice_options$ids, $itemChange the option ids of a choice
sibundles_nestable_bundle$ok, $bundleWhether a bundle may be an option
sibundles_item_unit_price$unit, $item, $source, $contextChange an item’s unit price
sibundles_price_range$out, $bundle, $inputsChange the computed shop price range
sibundles_catalog_range$range, $productSupply the catalog range yourself (return an array to override)
sibundles_display_range$out, $productChange the range shown
sibundles_attention$out, $bundleAdd or change guard-rail notes
sibundles_order_item_priced_individually$yes, $bundle_item, $bundle, $itemWhether an order item line counts as charged on its own
sibundles_order_dialects$listRegister another order line reader
sibundles_pb_plugin_active$boolTell this plugin if the previous bundle plugin should draw its own lines
sibundles_cp_plugin_active$boolThe same for composites
sibundles_import_scan_paths$pathsFolders the dry run scans for template overrides and code
sibundles_admin_tabs$tabsAdd a tab to WooCommerce > Bundles
sibundles_new_item_defaults$settings, $productSettings of a product item when it is added in the editor
sibundles_new_choice_defaults$settingsSettings of a new choice
sibundles_item_card_data$data, $item, $productAdd data attributes to an item card
sibundles_load_rental_bridge$loadTurn the rental bridge off

Actions #

HookArgumentsWhen
sibundles_loadedThe plugin finished booting
sibundles_before_items$productIn the product page form, before the items
sibundles_items_saved$bundle_id, $items, $removed_idsA bundle’s items were saved
sibundles_bundle_synced$bundleA bundle’s price and stock were re-counted
sibundles_order_items_followed$order_idItem lines followed a bundle line’s new quantity
sibundles_items_panel_settings$bundleInside the Bundle items tab, to print extra settings
sibundles_admin_tab_{slug}Render a tab added with sibundles_admin_tabs
sibundles_rental_bridge_loadedThe rental bridge booted
sibundles_initialize_failed$error, $class_nameA component failed to start

JavaScript #

The product form dispatches sibundles:update on the form element after every change of picks. event.detail is the form object. The rental calendar listens to it.

Templates #

Copy a file from wp-content/plugins/sibundles/templates/ to your-theme/sibundles/ and edit it. Keep the same path below sibundles/.

TemplateDraws
single-product/add-to-cart/si-bundle.phpThe whole form
single-product/bundle/item.phpA fixed item
single-product/bundle/choice.phpA choice
single-product/bundle/options.phpThe option list of a choice
single-product/bundle/choice-details.phpThe picked option’s details
single-product/bundle/summary.phpThe summary

Templates are versioned by the plugin; check them after an update. The scripts and styles are sibundles-form (with readable files when SCRIPT_DEBUG is on).

Compatibility notes #

  • HPOS and the Cart and Checkout blocks are supported and declared.
  • PHP 7.4 to 8.4.
  • The plugin sends no telemetry and never locks the editor.