Skip to main content
Version: 3.x

Custom Calc Term Types

Version
3.x
Updated
View markdown

How to add your own calculation term type to CBX — a new kind of building block an admin can drop into a formula calculation, which the pricing engine evaluates at configure-time. CBX ships six term types (number, operator, function, calculation, question property, customer group); this guide shows how to add one without touching core.

This is the natural companion to com_configbox_custom_question_types.md: a custom question type that stores a composite value (a width/height/quantity triple, say) cannot be priced by the shipped terms, because they reduce a selection with floatval(). A custom term is how that value reaches a price.

Read com_configbox_customization_overview.md first, and technical/com_configbox_calculation_engine.md for how calculations are stored and evaluated. All paths are relative to the component root docroot/components/com_configbox/; getDirCustomization() is KenedoPlatform::p()->getDirCustomization(). Source references are point-in-time — verify against the code.


1. How a term type resolves to your class

Unlike question types, term classes are loaded eagerly. ConfigboxCalcTerm::loadTermClasses() (classes/ConfigboxCalcTerm.php:79) include_onces every .php file in core classes/calc_term_types/ and in getDirCustomization()/calc_term_types/. There is no autoloader involvement and no registration step — dropping the file in is the installation.

The type name is derived from the class name by stripping either accepted prefix (getTermTypeNames(), classes/ConfigboxCalcTerm.php:131):

$className = str_replace('ConfigboxCalcTerm', '', $className); // built-ins
$className = str_replace('CustomCalcTerm', '', $className); // yours

So both prefixes work, and the convention is:

classes/calc_term_types/ConfigboxCalcTermNumber.php ← shipped → type "Number"
getDirCustomization()/calc_term_types/CustomCalcTermDimensions.php ← yours → type "Dimensions"

That suffix is what gets stored as "type":"Dimensions" in the formula's JSON, so renaming the class breaks every calculation already using it. Treat the suffix as a published identifier.


2. The contract — three abstract methods

ConfigboxCalcTerm is abstract with exactly three methods to implement (classes/ConfigboxCalcTerm.php:169-186). Two of them are admin-side; only one runs on the storefront.

MethodRunsJob
getTermsPanelHtml($calculationId, $productId)Formula editorThe panel an admin uses to insert your term — pickers, inputs, whatever the term needs
getTermHtml($termData, $forEditing = true)Formula editorRender one existing term. $forEditing false means a read-only display
getTermResult($termData, $selections, $regardingQuestionId = null, $regardingAnswerId = null, $allowNonNumeric = false)Every price calculationThe value the term contributes

$termData is your own shape — whatever getTermsPanelHtml() wrote into the formula JSON, handed back to you. Keep it small and stable; it is persisted.


3. getTermResult() runs a lot

A formula is evaluated on every selection change, for every position in the cart, and again on cart and checkout pages. getTermResult() therefore sits in the hottest path CBX has.

  • No queries per call where a cached read will do. Question data is already cached; reach for ConfigboxQuestion::getQuestion() rather than SQL.
  • Return a number. Unless $allowNonNumeric is true, the engine is composing an arithmetic expression; returning a string or null propagates into the whole formula.
  • $selections is the current configuration. Read from it rather than re-fetching state.

4. The editor methods have a failure mode worth knowing

getTermHtml() renders terms an admin saved earlier — including terms that point at a question which has since been deleted. If your implementation resolves a question id without checking, the lookup throws, and the whole formula editor fails to render: the admin cannot open the calculation, and cannot delete the stale term either, because they cannot reach it.

Guard it:

if (!ConfigboxQuestion::questionExists($questionId)) {
return '<span class="cb-term cb-term-broken">' . KText::_('Deleted question') . '</span>';
}

ConfigboxQuestion::questionExists() (classes/ConfigboxQuestion.php:155) exists for exactly this. A placeholder keeps the editor usable and lets the admin remove the term.


5. Worked shape — pricing a composite question

For a dimensions question storing {"w":1200,"h":800,"qty":2}, a term that exposes one sub-value:

defined('CB_VALID_ENTRY') or die();

class CustomCalcTermDimensions extends ConfigboxCalcTerm {

/** The sub-values this term can pull out of a dimensions selection. */
protected function getSubValues() {
return ['w' => KText::_('Width'), 'h' => KText::_('Height'), 'qty' => KText::_('Quantity')];
}

public function getTermResult($termData, $selections, $regardingQuestionId = null, $regardingAnswerId = null, $allowNonNumeric = false) {

if (empty($termData['questionId']) || empty($selections[$termData['questionId']])) {
return 0;
}

$decoded = json_decode($selections[$termData['questionId']], true);
$key = $termData['subValue'] ?? 'w';

return isset($decoded[$key]) ? (float) $decoded[$key] : 0;
}

// getTermsPanelHtml() — a question picker plus a sub-value dropdown
// getTermHtml() — "Width of «Frame size»", guarded per §4
}

The point is the decode: the selection is JSON, so the shipped question-property term would read 0. That decode is the entire reason the type exists.


6. Deployment checklist

  1. getDirCustomization()/calc_term_types/CustomCalcTerm<Type>.php — implement all three methods.
  2. Clear the CBX cache; on some platforms CBX keeps its own file cache the host's cache-clear does not touch.
  3. Open a formula calculation in the admin — your panel should be offered alongside the shipped terms. If it is absent, the file is not in the folder the platform resolves (§1).
  4. Insert the term, save, and check a price on the storefront — getTermsPanelHtml() working does not prove getTermResult() does.

7. Gotchas

  • The class-name suffix is a stored identifier. Renaming the class orphans every formula using it (§1).
  • Both prefixes are stripped, so ConfigboxCalcTermFoo and CustomCalcTermFoo both yield type Foo — do not ship both.
  • Guard question lookups in getTermHtml() or a deleted question takes the formula editor down with it (§4).
  • Loading is eager and unconditional: a parse error in your file breaks calculation loading everywhere, not just where the term is used.
  • getTermResult() is hot — see §3.

See also

  • com_configbox_custom_question_types.md — the composite values these terms usually exist to read
  • com_configbox_customization_overview.md — the layer, and where it lives per platform
  • technical/com_configbox_calculation_engine.md — storage, the three calculation types, evaluation
  • com_configbox_custom_rule_conditions.md — the same pattern for the rule engine