Architecture & conventions
Audience: developers & AI agents · Scope: how the codebase is laid out and the rules for changing it safely · Last reviewed: 2026-07-12
TL;DR: This is a Joomla 5 site whose main component is the ConfigBox configurator
(built on the Kenedo framework). Beta-Calco's own code lives almost entirely under
docroot/components/com_configbox/data/customization/. Treat Joomla core, Kenedo, and vendor
libraries as read-only third-party code — extend/override them from the customization tree
rather than editing them in place.
The golden rule
Anything outside data/customization/ is replaced when Joomla, ConfigBox, or a vendor library is
updated. So:
- Read core/Kenedo/vendor code freely to understand behavior.
- Don't modify it. To change core behavior, add code under
data/customization/and use an override mechanism (below).
Layout of com_configbox
docroot/components/com_configbox/
├── configbox.php ← front-end entry point (request dispatch)
├── router.php ← Joomla SEF router (build/parse clean URLs)
├── controllers/ models/ views/ ← ConfigBox core MVC (third-party)
├── external/kenedo/ ← the Kenedo framework (third-party)
│ ├── classes/ ← KenedoController, KenedoModel, KenedoView, KenedoDatabase, …
│ ├── platforms/joomla/ ← Joomla glue (KenedoPlatform::p() implementation)
│ └── helpers/ ← KenedoRouterHelper, init, …
└── data/customization/ ← ★ Beta-Calco's code ★ (edit here)
Inside data/customization/
| Subdir | Purpose |
|---|---|
controllers/ models/ views/ | Custom MVC. Custom pages/features (usually bc-prefixed), e.g. bcquotelandingpage, bcmyquotes. |
system_overrides/ | Replace/extend core PHP classes (esp. data objects like BcQuote). Wired via KenedoAutoload::registerClass(...). |
language_overrides/ | Override translation strings. |
model_property_customization/ properties/ | Add/adjust model fields, and add custom property types. A file properties/<type>.php with class KenedoProperty<Type> is auto-discovered (customization wins over core) and used by 'type' => '<type>' in a model's property defs — e.g. the read-only bcimported / bcimportedjoin / bcimportedboolean types that render import-owned fields as static, non-saving text. A custom type that extends a non-base built-in (…String/…Join/…Boolean) must require that base file first — property-type files load on demand, so the parent may not be loaded yet. |
rule_condition_types/ | Custom configurator rule/condition logic. |
updates/ | Numbered DB/schema migrations (auto-run). See migrations.md. |
assets/ | Custom CSS/JS (and minified builds), served via getDirCustomizationAssets(). CSS and JS behave differently here — see Minified custom assets. |
templates/ | Custom view/email templates. |
libs/ | Third-party PHP libraries used by custom code (Composer vendor/). |
Minified custom assets: CSS yes, AMD JS no
Worth knowing before you spend time regenerating a .min.js that nothing loads.
- CSS is served minified.
KenedoViewrewritesfoo.css→foo.min.csswhen one exists, so editing a custom stylesheet has no visible effect until you regenerate the.min.css(csso foo.css -o foo.min.css). - Custom AMD modules are not.
assets/main.jsappends.minto the RequireJS path config only whenuseMinifiedJsis on — and it explicitly skips theconfigboxandconfigbox/customentries, because those map to directories rather than to single files. A module underconfigbox/custom/…therefore always resolves to the plain.js; its.min.jssibling is never requested. Verified from the network log: the page loads…/customization/assets/javascript/adminbcbranchcleanup.js?version=…, never the.min.js.
Either way, bump the release number in system_overrides/releasenumber.php after touching a custom
asset. It is the <appVersion>-<releaseNumber> cache-buster RequireJS appends to every module URL; without
a bump, browsers keep serving the cached old file.
Naming conventions
- Classes:
Configbox+Controller/Model/View+Ucfirst(name)— e.g. viewbcquotelandingpage→ConfigboxControllerBcquotelandingpage,ConfigboxModelBcquotelandingpage,ConfigboxViewBcquotelandingpage. (Seeconfigbox.php→KenedoController::getControllerClass().) - Custom features are typically prefixed
bc(Beta-Calco) to distinguish them from core ConfigBox. - A view lives in
views/<name>/view.html.phpwith templates underviews/<name>/tmpl/. - Every PHP file starts with
defined('CB_VALID_ENTRY') or die();.
Request lifecycle (front end)
index.php?option=com_configbox&view=<name>&... (or controller=<name>&task=<task>)
│
▼
configbox.php
│ reads option / controller / view / task (task defaults to 'display')
│ resolves ConfigboxController<Name> and calls ->execute($task)
▼
ConfigboxController<Name>::display() → getDefaultView()->... → rendered output
view=<name>andcontroller=<name>both resolve toConfigboxController<Name>; the difference matters for SEF (onlyview=URLs get clean paths). See sef-links.md.- AJAX/tasks use
controller=<name>&task=<task>and usually return JSON (ConfigboxJsonResponse, withKenedoPlatform::p()->setDocumentMimeType('application/json')). - SEF building/parsing is centralized in
router.php(ConfigboxBuildRoute/ConfigboxParseRoute), which delegates to each controller's SEF hooks.
Override mechanisms (instead of editing core)
- Data objects / classes: add a file in
system_overrides/that registers the override viaKenedoAutoload::registerClass('<ClassName>', KenedoPlatform::p()->getDirCustomization().'/system_overrides/<File>.php')and defines a class extending the original (e.g.class BcQuote extends DataObjectAbstract). - Behavior on every request:
observers/(e.g.observers/System.phpruns pending migrations on each request viaConfigboxUpdateHelper::applyUpdates()). - Strings:
language_overrides/. Schema/data:updates/migrations.
system_overrides/load order. Files here arerequire_once'd on every request during init (onConfigboxInitialized), sorted alphabetically with_-prefixed files first. Use them for static helper classes, global function overrides, and custom data-object classes — not MVC artifacts (those go incontrollers/models/views/).
Override resolution is NOT uniform — the main footgun
"Customization overrides core" is only half true — resolution order depends on the artifact type:
| Type | Order checked | Effect |
|---|---|---|
| Controllers | customization first, then core | A same-named custom file does override core. |
| Models | core first, then customization | A same-named custom file is ignored if a core model exists. The customization slot is for new models only. |
| Views | core first, then customization | Same as models — a custom file is only used when no core view of that name exists. |
(Verified in external/kenedo/classes/KenedoController.php, KenedoModel.php, KenedoView.php.)
Consequences:
- To change a controller's behaviour, drop a same-named file in
customization/controllers/. - You cannot override a core model or view by adding a same-named file — it's silently ignored.
To alter a core model's data/behaviour, use
model_property_customization/<modelname>.php(itscustomPropertyDefinitions<Modelname>()array is merged into the core model) or asystem_overrides/file. The custombc*/adminbc*models & views here are all new artifacts, not overrides.
Cross-cutting integrations
The custom models talk to several external systems (e.g. Pipedrive CRM sync, Google Sheets,
AWS metrics). Scheduled/batch work for these lives in docroot/cli/cb_*.php (run as
php docroot/cli/cb_<name>.php) — Pipedrive sync & cold-quote prompts, Google-Sheet schedule/price/BOM
exports, Infor ERP reads (BcHelper::getInforDb()), BigQuery, GA4, and AWS CloudWatch metrics. The
integrations with their own docs are Pipedrive and
tracking; the rest aren't fully documented yet — see the relevant models/bc*.php
files and any draft notes under notes/, and promote what you learn into a dedicated doc (see
README.md).
Platform abstraction
Kenedo can run on Joomla, WordPress, Magento, or standalone. Code reaches the platform through
KenedoPlatform::p() (routing, mailer, document, dirs, language) and KenedoPlatform::getDb()
(database). Prefer these abstractions over Joomla APIs in custom code, so behavior stays consistent
with the framework.