Rule
- Version
- 3.x
- Updated
A visibility or pricing rule, authored on a drag-and-drop surface and evaluated by the engine.
Storage kind: column · Column: text
Use it wherever the engine expects a rule — a question's or answer's visibility condition, a
shipping or discount rule. It is not a general-purpose structured-data type: the stored JSON is the
rule engine's own vocabulary, and only the rule engine reads it. For an arbitrary JSON document use
json.
Settings
| Setting | Meaning |
|---|---|
textWhenNoRule | What the form shows when no rule is set |
Everything else is the shared settings. The editing surface, the condition types and the vocabulary come from the rule engine, not from the property definition.
Validation is not optional here
The engine turns the stored JSON into a PHP boolean expression and eval()s it
(ConfigboxRulesHelper::getConditionsCode). The failure mode of a rule that does not hold together
is therefore not "behaves oddly":
| Malformed how | What happens at runtime |
|---|---|
| Conditions not alternating with AND/OR | a ParseError |
| An unknown condition type | an exception in every list that renders the rule |
| A reference to a deleted question, answer or calculation | a rule that quietly hides whatever it governs |
That last row is the dangerous one — it is silent. check() refuses malformed rules on the way in,
and because it is the standard Kenedo validation hook, one gate covers both write paths: the admin
form and the MCP storeData, since both go through KenedoModel::validateData().
Every complaint about one rule travels as a single refusal carrying all of them, so the author fixes the whole thing at once rather than one condition per round trip.
Only a changed rule is judged
check() first asks whether this save actually changes the rule, and skips validation if not.
That is load-bearing. KenedoController::store() and the MCP write path both seed the request from
the existing record, so an untouched rule is posted back verbatim on every partial update.
Without the check, a rule written by an older editor — or against an earlier vocabulary — would
block edits to fields that have nothing to do with it: you could not fix a typo in a question's
title because its rule had gone stale.
The comparison reads the column directly rather than through getRecord(), whose memoized cache
differs between the web process and the CLI. An insert, or any value that cannot be read back, is
treated as changed and validated.
Notes
- It publishes a structured sub-schema to MCP rather than being described as free text, so an
assistant authors a rule structurally instead of hand-building the engine's infix token stream.
ConfigboxMcpHelperspecial-cases'rule'and substitutesConfigboxMcpRulesHelper::getVocabularySchema()for the field (helpers/mcp.php). That is the exception to the rule that atextcolumn is opaque. copyRule()runs during a record copy, re-pointing references to the copied questions and answers rather than leaving the copy pointing at the original's — the same shape ascalculation'scopyCalculation(). It honoursstoreExternally.textis 64 KB. A very large rule needs an explicit widerdataType.
@see json.md — the counter-example, where no structure is imposed @see ../com_configbox_rule_engine.md @see ../com_configbox_mcp_server.md — the sub-schema an assistant authors against @see ../../admin-guide/rules/build-a-rule.md — what the operator sees