Skip to main content

Customer groups & visibility — the site's access model

Audience: developers & AI agents · Scope: the customer-group model and the group-based visibility gating across products, news, brochures, powerpoints and diagrams · Last reviewed: 2026-07-20

TL;DR — Every site user belongs to exactly one customer group, and that group decides their currency, whether they see prices, what they may do (checkout / save order / request a quote), their group discount, whether they count as a BC agent, and — through per-entity xref tables — which products and files exist for them at all. A product with no matching group row is not "hidden", it is absent from every query. This is the single most common cause of "why can't anyone see this?".

What it is & why it exists

Beta-Calco sells into several markets (US, Canada, UK, EU, export) and to two very different audiences (agents who quote and see pricing, specifiers who browse and request). Rather than model those as roles plus a separate currency setting, ConfigBox's customer group carries all of it at once — so "US Agent" and "UK Specifier" are groups, and moving a user between groups changes their whole experience.

The group lives on the user record (#__configbox_users.group_id); everything else reads it from there via ConfigboxUserHelper::getGroupId().

How it fits together

┌──────────────────────────────┐
registration default │ #__configbox_users.group_id │
region picker │ (one group per user) │
admin (Joomla client) └──────────────┬───────────────┘
│ getGroupId()
┌───────────────────┼────────────────────┐
▼ ▼ ▼
┌───────────────────┐ ┌─────────────────┐ ┌──────────────────────┐
│ group record │ │ permission.php │ │ xref_*_group tables │
│ #__configbox_ │ │ canSeePricing │ │ product / news / │
│ groups (+ │ │ canCheckout… │ │ brochure / powerpoint│
│ _external_ │ │ canRequest… │ │ / diagram │
│ groups_appends) │ │ canGetB2BMode │ │ │
│ currency, is_agent│ └─────────────────┘ │ → filters every list │
│ discounts 1–5 │ │ query │
└─────────┬─────────┘ └──────────────────────┘
│ is_agent

ObserverBcCustomerGroups → syncs the user's **Joomla** user group
FileRole
helpers/user.php:291ConfigboxUserHelper::getGroupId() — the entry point everything else uses
helpers/user.phpgetGroupData(), initUserRecord() (applies the default group at registration)
helpers/permission.phpcanSeePricing / canSaveOrder / canCheckoutOrder / canRequestQuotation / canGetB2BMode — thin reads of the group's booleans
helpers/customergroup.php:18,78getDiscount() / getDiscountRecurring() — the group's 5 discount levels
models/admincustomergroups.php (core)the group model; data/customization/model_property_customization/admincustomergroups.php adds the BC fields
data/customization/system_overrides/ObserverBcCustomerGroups.phpon customer save, mirrors is_agent into the Joomla user group
data/customization/controllers/bcregions.php:44-61region picker → UPDATE #__configbox_users SET group_id = …
data/customization/models/bcconfigurator.php:174-181injects xref_adminproducts_admincustomergroups.group_id into loadProductData() — the product gate
data/customization/system_overrides/BcHelper.php:510userIsAgent() — reads is_agent

Admin settings

SettingWhere setDefaultBlank / off
Default Customer Group (default_customer_group_id)core Settings (adminconfig)New registrations get no usable group; treat as required
Joomla group for specifiers (joomla_group_id_specifiers)Custom Settings → Joomla group assignmentObserver logs an error and skips the sync entirely
Joomla group for agents (joomla_group_id_agents)Custom Settings → Joomla group assignmentSame — sync skipped
Joomla group for quote makers (joomla_group_id_quote_makers)Custom Settings → Joomla group assignmentNot used by the observer; read where quote-maker rights are checked

Per-group settings live on the group record itself (Customer Groups screen), not in Custom Settings: Name, Currency (required), Analytics Name, Group members are BC-Agents (is_agent), Platform Group (joomla_user_group_id, required), Tax display mode (b2b_mode), the six Permissions booleans, five Regular + five Recurring discount levels, and the three Email recipients for requests textareas (blank ⇒ falls back to the site-wide addresses in Custom Settings).

The plain-language operator how-to for all of this is Admin Guide → Customer groups (admin-guide/quotes-sales/customer-groups.md).

Control / data flow

How a user acquires a group — three real paths:

  1. RegistrationConfigboxUserHelper::initUserRecord() applies Default Customer Group.
  2. Region pickercontrollers/bcregions.php writes the chosen region's group_id onto the user and calls resetUserCache(). An anonymous visitor gets a user record created first. This means a visitor can move themselves between groups, by design — it is the currency/market switch.
  3. Admin — the customer edit form's group dropdown. See the gotcha below: it does not render in the frontend-routed backend.

How the group gates content — each gated entity has an xref table, and the list query filters on the visitor's group:

EntityXref tableAdmin field labelCreated by
Products#__configbox_external_xref_product_groupGroup Assignmentsupdates/0.0.1.php
News#__configbox_external_xref_news_groupVisible for these Groupsupdates/0.0.45.php
Brochures#__configbox_external_xref_brochure_groupAllowed Customer Groupsupdates/0.0.8.php
Powerpoints#__configbox_external_xref_powerpoint_groupAllowed Customer Groupsupdates/0.0.29.php
Diagrams#__configbox_external_xref_diagram_groupAllowed Customer Groupsupdates/0.0.9.php

The product filter is applied in ConfigboxModelBcconfigurator::loadProductData(), so every product surface inherits it: catalog, family pages, downloads, related products, the discontinued page, and the quote-maker's product picker.

The Joomla group mirrorObserverBcCustomerGroups::onAfterStoreRecord fires on admincustomers saves: if the CB group has is_agent, the user is added to the Joomla agents group and removed from specifiers; otherwise the reverse. It bails out (logging an error) when either setting is missing or the customer has no platform_user_id.

Data model

TableOwns
#__configbox_groups (core)name, currency, joomla_user_group_id, b2b_mode, the permission booleans, discount_*_1..5, discount_recurring_*_1..5
#__configbox_external_groups_appendsthe BC additions — currency_id, analytics_name, is_agent, emails_registration, emails_rfq, emails_brochures · created by updates/0.0.17.php, extended by 0.0.86, 0.0.172, 0.4.56, 0.4.57
#__configbox_users.group_idthe user → group link
xref_*_group (five tables above)entity → group visibility

Deployment runbook (manual steps)

No migration or deploy step is needed for the mechanism itself — it ships with the code. To stand up the model on a fresh environment:

  1. Run migrations (automatic on next page load) so the appends and xref tables exist.
  2. Create the groups (Customer Groups screen), each with its Currency and Platform Group.
  3. Set Default Customer Group in core Settings.
  4. Set Joomla group for specifiers and Joomla group for agents in Custom Settings → Joomla group assignment, or the Joomla-group mirror silently does nothing.
  5. Create the regions and point each at its Default Customer Group (see the Admin Guide).
  6. Tick Group Assignments on products — a product with none is invisible to everyone.
  7. Smoke test: sign in as an account in one group, confirm currency, price visibility, and that a group-restricted product appears; switch region and confirm the currency changes.

Turning it off: not applicable — the group is mandatory on every user; there is no bypass.

Testing

No dedicated spec today. The e2e agent account's group underpins most agent specs — see testing/guide.md. Worth adding: a spec asserting that a product with a non-matching group assignment is absent from the catalog and from the quote-maker picker.

Gotchas & caveats

  • Empty group assignments = invisible to everyone. Not an error, not a warning — the row simply never matches. The first thing to check for any "missing product/brochure" report.
  • The customer edit form's group dropdown does not render in the normal backend. It lives in templates/customerform/admin_only_fields.php, gated on views/customerform/view.html.php:162isAdminArea() && isAuthorized('com_configbox.core.manage'). isAdminArea() is true only under Joomla's /administrator client, and Beta-Calco's ConfigBox backend is frontend-routed (index.php?option=com_configbox&controller=…). So in the admin operators actually use, the field is absent, along with Platform User ID, User claims to be an agent and Custom field 4. Reaching the same screen via /administrator/index.php?option=com_configbox&… does render them.
  • Currency belongs to the group, not the user. There is no per-user currency override.
  • A visitor can change their own group via the region picker. Anything that assumes a stable group across a session is wrong.
  • is_agent is duplicated into Joomla groups asynchronously. If the two settings are unset the mirror is skipped and only an error-log line records it — the CB group and the Joomla group then disagree, which shows up as agent-only pages misbehaving.
  • Group deletion is dangerous — five xref tables plus regions, users and Pipedrive-facing data reference groups. The group edit screen lists its current members at the bottom; prefer renaming.

Possible follow-ups

  • A spec covering group-gated product visibility (see Testing).
  • The unused Joomla group for quote makers setting — confirm whether anything still reads it.
  • Admin Guide → Customer groups (admin-guide/quotes-sales/customer-groups.md) — the operator how-to
  • Admin Guide → Find and update a customer (admin-guide/quotes-sales/manage-customers.md)
  • Discontinued products — the other reason a product disappears
  • ConfigBox architecture — the override model these customizations use