The element → question rename — before → after reference
- Version
- 4.0 preview
- Updated
The exhaustive reference for the CBX 4 rename of the "element" entity to "question". Read this for any customization that touches the configurator question entity — its model, DB table, columns, the pricing/assignment caches, the rules/calc engine, or the calc-formula DSL. It carries the full before → after tables plus the per-file decision procedure and the shim inventory you can verify against the core.
This is the sibling of answers-collapse-deep-dive.md. That one is about the option/answer collapse; this one is about the element→question rename. A heavily-customized site is likely to have touched both.
Why this change
The core entity was called element in the code, DB and admin MVC, but question everywhere in the
UI and the frontend DOM/JS. That split was legacy debt: the same thing had two names depending on which layer
you were in. CBX 4 finishes the transition — element → question everywhere: the DB table and
every *element_id*/feature column, the admin model/controller/view classes, the pricing/assignment cache
keys, the ionCube rules/calc engine, the rule/calc condition classes and their stored JSON, and the
calc-formula DSL tokens.
The mental-model swap: wherever you thought "element" (the configurator question a customer answers), it is now "question". The entity's id is unchanged — a question keeps the same id its element had — so carts, orders, rules, calc formulas and translations all keep pointing at the right rows. Only the names changed.
Not touched (do not "fix" these — they are not ours):
- Joomla core
#__extensions.elementand#__updates.element— a Joomla concept, unrelated. - WordPress WPML translation ids read as
$translation->element_idinhelpers/wordpress.php— a WordPress concept, unrelated.
The DB migration ships with the core
The schema + stored-data half is applied automatically by the versioned update script
helpers/updates/3.5.2.php (runs on init and via php cli/joomla.php configbox:migrate). It renames the
table, all columns and the 8 foreign keys, and rewrites the stored rule/calc JSON and the calc-code DSL text.
It does not touch data/customization/ — your custom SQL, custom columns and custom code are yours to
realign using the tables below. If your customization added its own rules-bearing column, the migration's
model-driven discovery converts its stored JSON automatically (see the "stored data" section).
Before → after — DB tables & columns
The core migration renames these for you. Update any raw SQL in your customization that names them.
Table
| Before | After |
|---|---|
#__configbox_elements | #__configbox_questions |
The question-id foreign keys (every *element_id* column)
| Table | Before column | After column | FK |
|---|---|---|---|
#__configbox_answers | element_id | question_id | (index only) |
#__cbcheckout_order_configurations | element_id | question_id | (index only) |
#__configbox_cart_position_configurations | element_id | question_id | FK, CASCADE/CASCADE |
#__configbox_calculation_codes | element_id_a … element_id_d | question_id_a … question_id_d | 4 FKs |
#__configbox_calculation_matrices | column_element_id | column_question_id | FK |
#__configbox_calculation_matrices | row_element_id | row_question_id | FK |
#__configbox_calculation_matrices | multielementid | multiquestionid | FK |
Feature columns (renamed on every table that carried them, incl. frozen order history)
| Table | Before | After |
|---|---|---|
#__configbox_questions | element_custom_1 … element_custom_4 | question_custom_1 … question_custom_4 |
#__configbox_questions | element_css_classes | question_css_classes |
#__cbcheckout_order_configurations | element_custom_1 … element_custom_4 | question_custom_1 … question_custom_4 |
#__cbcheckout_order_configurations | element_type | question_type |
#__cbcheckout_order_configurations | element_code | question_code |
#__configbox_config | label_element_custom_1 … _4 | label_question_custom_1 … _4 |
#__configbox_config | label_element_custom_translatable_1 / _2 | label_question_custom_translatable_1 / _2 |
#__configbox_config | pm_regular_show_elements, pm_recurring_show_elements | pm_regular_show_questions, pm_recurring_show_questions |
#__configbox_config | pm_regular_show_elementprices, pm_recurring_show_elementprices | pm_regular_show_questionprices, pm_recurring_show_questionprices |
#__configbox_products | pm_regular_show_elements / _elementprices (+ recurring) | pm_regular_show_questions / _questionprices (+ recurring) |
The frozen order-history columns are renamed too.
cbcheckout_order_configurationsis a denormalized snapshot of placed orders — but only the column names changed; every value is preserved, and the row'squestion_idstill equals the id it had aselement_id. Custom code that reads order configs must use the new column names.
Before → after — admin model / controller / views
The model class was renamed (ConfigboxModelAdminelements → ConfigboxModelQuestions); file and
view dirs moved with it.
| Kind | Before | After | Status |
|---|---|---|---|
| Model class | ConfigboxModelAdminelements | ConfigboxModelQuestions | 🟢 getModel() aliases old→new |
| Model file | models/adminelements.php | models/adminquestions.php | — |
| Controller | controllers/adminelements.php (ConfigboxControllerAdminelements) | controllers/adminquestions.php (…Adminquestions) | 🔴 update controller=adminelements URLs |
| List view | views/adminelements/ (ConfigboxViewAdminelements) | views/adminquestions/ (…Adminquestions) | 🔴 update view=/getView() refs |
| Form view | views/adminelement/ (ConfigboxViewAdminelement) | views/adminquestion/ (…Adminquestion) | 🔴 update view=/getView() refs |
| Copy-remap bucket | $copyIds['adminelements'] | $copyIds['adminquestions'] | 🔴 rename in custom copy code |
| Answer→question join prop | element_id (join to adminelements) | question_id (join to adminquestions) | 🔴 update filter names |
| Derived join-prop names | joinedby_element_id_to_adminelements_* | joinedby_question_id_to_adminquestions_* | 🔴 rename if you read these props |
model_property_customization/ file | adminelements.php | still loads (see below) | 🟢 auto-loaded via legacy alias |
Editor sub-views for the rule/calc "question attribute" term also moved:
| Before | After |
|---|---|
views/adminruleeditor_elementattribute/ | views/adminruleeditor_questionproperty/ |
views/admincalcformula_elementattribute/ | views/admincalcformula_questionproperty/ |
Before → after — pricing API (public methods on ConfigboxPrices)
| Before | After | Status |
|---|---|---|
ConfigboxPrices::getElementPrice($id, …) | getQuestionPrice($id, …) | 🟢 legacy alias kept |
ConfigboxPrices::getElementPriceRecurring($id, …) | getQuestionPriceRecurring($id, …) | 🟢 legacy alias kept |
ConfigboxPrices::getElementWeight($id) | getQuestionWeight($id) | 🟢 legacy alias kept |
The old names still work (thin aliases, each marked @deprecated); rename your calls at leisure.
Before → after — the in-memory caches (assignment map + pricing cache)
These are the string keys of the per-request caches (ConfigboxCacheHelper::getAssignments() and the pricing
cache). They are rebuilt from the DB each request — no stored state, so this is a code-only change. The
core keeps the old keys populated as aliases, so cleanly-written cache readers keep working.
| Cache | Before key | After key | Status |
|---|---|---|---|
| assignments | element_to_product | question_to_product | 🟢 old key kept as alias |
| assignments | product_to_element | product_to_question | 🟢 old key kept as alias |
| assignments | element_to_page | question_to_page | 🟢 old key kept as alias |
| assignments | page_to_element | page_to_question | 🟢 old key kept as alias |
| assignments | answer_to_element | answer_to_question | 🟢 old key kept as alias |
| assignments | element_to_answer | question_to_answer | 🟢 old key kept as alias |
| pricing | calcModelByElement | calcModelByQuestion | 🟢 old key kept as alias |
| pricing | calcModelRecurringByElement | calcModelRecurringByQuestion | 🟢 old key kept as alias |
| pricing | calcModelWeightByElement | calcModelWeightByQuestion | 🟢 old key kept as alias |
| pricing | taxClassIdByElement | taxClassIdByQuestion | 🟢 old key kept as alias |
| pricing | taxClassIdByElementRecurring | taxClassIdByQuestionRecurring | 🟢 old key kept as alias |
| pricing | calcModel*ByAnswer[…]['regardingElement'] | ['regardingQuestion'] | 🟢 old sub-key kept as alias |
The old keys are deprecated — update your cache readers to the
question_*keys; the aliases will be removed in a future release.
Before → after — the rules / calc engine (ionCube) + stored JSON
The rule condition and calc term for "a question's attribute/selection" were renamed. This is a hard rename with no runtime alias — instead the core migration rewrites the stored JSON so the new classes receive the migrated data.
| Kind | Before | After | Status |
|---|---|---|---|
| Rule condition class | ConfigboxConditionElementAttribute | ConfigboxConditionQuestionProperty | 🔴 rename a custom subclass/ref |
| Calc term class | ConfigboxCalcTermElementAttribute | ConfigboxCalcTermQuestionProperty | 🔴 rename a custom subclass/ref |
Rule/calc JSON type value | "ElementAttribute" | "QuestionProperty" | 🟢 migrated in stored data |
| Rule/calc JSON key | "elementId" | "questionId" | 🟢 migrated in stored data |
| Method on those classes | getElementAttributes() | getQuestionProperties() | 🔴 rename if you call it |
The dispatcher builds the class name from the stored type string (ConfigboxCondition + ucfirst($type)),
so a stored "type":"QuestionProperty" resolves to ConfigboxConditionQuestionProperty. That is why the
class rename requires the stored-JSON migration — they must move together. The sentinel value
"questionId":"regarding" (meaning "the question this formula is attached to") is preserved verbatim.
The calc-formula DSL tokens (user-authored calc code)
Hard rename, no alias — the migration rewrites the stored calculation_codes.code text.
| Before token | After token |
|---|---|
ElementAttribute( | QuestionProperty( |
ElementEntry( | QuestionSelection( |
ElementPrice( | QuestionPrice( |
ElementPriceRecurring( | QuestionPriceRecurring( |
RegardingElement( | RegardingQuestion( |
DSL field paths that named a feature column also change: QuestionProperty(22.element_custom_1) →
QuestionProperty(22.question_custom_1) (the path resolves against the question's renamed property).
The field-path prefix crumbs changed too (part of the option→answer rename — see answers-collapse-deep-dive.md):
| Before prefix | After prefix |
|---|---|
selectedOption. | selectedAnswer. |
regardingOption. | regardingAnswer. |
The engine's ConfigboxQuestion::getField() now resolves the crumbs selectedAnswer / regardingAnswer
only — there is no alias for the old *Option. prefixes. The suffix after the dot (.id,
.assignment_custom_N, .option_custom_N, .weight, .basePriceStatic, …) is unchanged. If your
customization stores calc code outside the calculation_codes table (e.g. generated in custom code),
rewrite these prefixes and the Element*( tokens the same way.
Stored data — what the core migrates, and what you may still owe
The migration (3.5.2.php) transforms all stock stored rule/calc data, and — importantly — is
model-driven for rules: it discovers every model that declares a rule (or calculationOverride)
property, reads its effective property definitions (merging your model_property_customization/ overrides)
and transforms whichever table+column that property is stored in. So if your customization added its own
rule-typed property on a model, its stored JSON is converted for you. The raw calculation_formulas.calc
column and the calculation_codes.code DSL are transformed explicitly.
What you may still owe — a DATA migration the core cannot do for you:
- calc code / rule JSON your customization stores in a table or column the core does not know about
(e.g. an external append table, a JSON blob in a custom settings row). The core migrates only columns it
can discover via the model layer or the two explicit stock columns. Ship your own
data/customization/updates/migration applying the same transforms (JSON keyelementId→questionId, type valueElementAttribute→QuestionProperty; the field-path prefixesselectedOption.→selectedAnswer.andregardingOption.→regardingAnswer.; DSL tokens per the table above). See../technical/com_configbox_migrations.mdin the CBX developer docs.
Per-file decision procedure (for a heavily-customized site)
For each file under data/customization/:
- Is it loaded on this install? Custom models/controllers/views only matter if this deployment uses
them.
model_property_customization/adminelements.phpis still loaded — see the shim below. - Which bucket?
- References only renamed-but-shimmed things (the
ConfigboxModelAdminelementsclass name, theelement_*cache keys,getElementPrice*, theadminelementscustomization filename) → 🟢 works today, but update at leisure; a deprecation is logged. - References renamed things with no shim — raw SQL on
#__configbox_elements/ the*element_id*columns / feature columns,controller=adminelements/view=adminelement(s)URLs, theConfigboxConditionElementAttribute/ConfigboxCalcTermElementAttributeclasses, the DSL tokens in calc code you generate → 🔴 must edit (see the tables above for each old → new).
- References only renamed-but-shimmed things (the
- Does it also need a data migration? If it stores rule/calc JSON in a column the core migration doesn't
reach (step above), write a
data/customization/updates/script. A code rename alone won't move that data. - Prove it's clean with this grep (should return nothing but intentional legacy-alias lines):
Exclude only your own intentional back-compat lines. Do not flaggrep -rnE "configbox_elements|element_id|element_custom_|element_css_classes|ConfigboxModelAdminelements|ConfigboxConditionElementAttribute|ConfigboxCalcTermElementAttribute|joinedby_element_id_to_adminelements|column_element_id|row_element_id|multielementid|ElementAttribute\(|ElementPrice\(|ElementEntry\(|RegardingElement\(|element_to_|_to_element|ByElement|regardingElement|getElementPrice|getElementWeight" data/customization/
helpers/wordpress.php-style$translation->element_id(WPML) — that is a WordPress concept, not this entity.
Worked conversions
Raw SQL on the questions table:
// before
$db->setQuery("SELECT `id`, `element_custom_1` FROM `#__configbox_elements` WHERE `page_id` = ".$pid);
// after
$db->setQuery("SELECT `id`, `question_custom_1` FROM `#__configbox_questions` WHERE `page_id` = ".$pid);
Answers joined to their question:
// before
"LEFT JOIN `#__configbox_elements` AS e ON e.id = answer.element_id"
// after
"LEFT JOIN `#__configbox_questions` AS q ON q.id = answer.question_id"
Reading the assignment cache:
// before — still works via the alias, but deprecated
$questionIds = $assignments['product_to_element'][$productId];
// after
$questionIds = $assignments['product_to_question'][$productId];
A calc-code DSL formula:
// before
ElementAttribute(22.element_custom_1) * RegardingElement(regardingOption.basePriceStatic)
// after
QuestionProperty(22.question_custom_1) * RegardingQuestion(regardingAnswer.basePriceStatic)
An admin edit-link URL in a custom view:
// before
'index.php?option=com_configbox&controller=adminelements&task=edit&id='.$id
// after
'index.php?option=com_configbox&controller=adminquestions&task=edit&id='.$id
The core shims that do the auto-conversion (verify these)
KenedoModel::getModel()— aliasesConfigboxModelAdminelements→ConfigboxModelQuestions(same pattern as the answers-collapse aliases). Logs a legacy call. SogetModel('ConfigboxModelAdminelements')keeps returning the right model.ConfigboxModelQuestions::getLegacyCustomizationAliases()returnsarray('adminelements')— so the merge inKenedoModel::getCustomPropertyDefinitions()still loads your existingmodel_property_customization/adminelements.phpand applies it to the renamed model. No need to rename that file.ConfigboxCacheHelperassignment build — re-exposes the oldelement_*assignment keys as aliases of the newquestion_*keys ($legacyAssignmentKeyAliases), and the old*ByElement/regardingElementpricing keys as aliases ($legacyPricingKeyAliases). Cleanly-written cache readers keep working.ConfigboxPrices::getElementPrice()/getElementPriceRecurring()/getElementWeight()— thin@deprecatedaliases that call thegetQuestion*methods.
No shim (hard rename, stored data migrated instead): the rule/calc type strings and the calc DSL
tokens. There is no runtime alias for "ElementAttribute" or ElementAttribute( — the migration rewrites
the stored data so the new classes/parsers receive it. If you author rule/calc JSON or DSL code in
customization at runtime, emit the new names.