The downloads page & product materials
Audience: developers & AI agents · Scope: the public downloads matrix and the per-product materials behind it · Last reviewed: 2026-07-20
TL;DR — A public matrix of products × material types. Some materials are uploaded files, some are generated PDFs, and a couple are pulled from other admin lists. Two things to know before relying on it: the per-material "login required" flag protects nothing, and a product can only ever expose one material per type.
The matrix
Rows are products, columns are material types, plus an "All" column that zips a row together.
- Products shown are published and not discontinued.
- Customer-group gating applies to the product list: a visitor sees only products assigned to their customer group — unless they are a quote maker, who sees everything.
- Two types are removed from the columns entirely: generic Regular files and the Short Specsheet. They can exist as materials but never get a column here.
- Five columns are visible by default (full spec sheet, diagrams, brochures, images, installation guides); the rest are hidden and toggled by filter chips.
Material types
| Type | Column label | Where the bytes come from |
|---|---|---|
specsheet_full / specsheet_short | Full / Short Specsheet | the cached spec-sheet PDF — see the hub |
diagrams | Dimensional Diagrams | generated PDF, re-rendered every download |
candela_curves | IES/LDT | generated PDF of curve images — see the warning below |
performance_data | Performance Data | generated PDF |
files | Regular files | uploaded per material |
bim_files | BIM files | uploaded |
install_instructions | Installation Guides | uploaded |
global_file | Global File | uploaded once globally and reused across products |
brochures | Brochures | the published, download-type brochures (admin-guide/website-content/share-files-and-brochures.md) list |
images | Product Images | the product gallery, at 1920px |
"IES/LDT" is a misnomer. That material delivers a PDF of candela-curve images, not
.ies/.ldtphotometric files. Anyone downloading it expecting photometric data gets pictures.
"Declare" is not a type. A Declare label is just a
filesorglobal_filematerial whose title says so. The same is true of anything else not in the table above.
Delivery
- One material → streamed straight from the controller. PDFs go
inline, everything elseattachment. - "All" → the JS collects the visible material ids in that row and asks the server to prepare them, so what you get depends on which columns are currently toggled on. Multiple files are zipped.
- PDFs take a different path: the "prepare" step copies the PDF into the public web root under
docroot/cache/configbox/temp_downloads/<random>/<filename>and returns a plain URL.
⚠️ That temp directory is never cleaned. The garbage-collection CLI does not touch it and nothing else does either, so it grows without bound — and its contents are world-readable at a guessable-ish URL.
Gating — weaker than it looks
There are two mechanisms and neither does much:
1. login_required per material — decorative. It is enforced only in JavaScript, and that handler is
broken: it calls e.preventDefault() while taking no event argument, so the ReferenceError aborts the
handler and the plain <a href> navigates anyway. Even if the JS worked, the controller never checks the
flag at all — the download URL is a guessable task=getMaterial&materialId=N. Treat login_required as a
UI hint with no security value.
Note the flag deliberately does not apply to spec sheets — a comment in the model says spec sheets cannot be login-protected.
2. Customer-group gating — partial. It applies to the product list on this page, and inside the "prepare" step for brochures only. Every other material type ignores customer groups entirely.
A whole-page "agents only" gate exists in the view but is commented out, leaving its template orphaned.
If any of this material is genuinely meant to be restricted, it currently isn't. Recorded in
docs/_known-issues.md.
Data model
| Table | Holds |
|---|---|
#__configbox_external_product_materials | one row per product × material type: the type, uploaded files, ordering, published, login_required |
#__configbox_external_global_product_materials | files uploaded once and reused across products |
Uploaded files live under the public store at data/store/public/custom_media/product_material_files/{materialId}/
(and …/globalmaterials for global ones) — public, unlike the private store used for
product HTML embeds.
One material per type, per product. The lookup is keyed
[productId][materialType], so a second published material of the same type is silently dropped — which one survives depends on query order.
Admin settings
Per material (product form → Materials): the type, the files, an optional global material, ordering, published, and login required (not offered for spec sheets).
No global settings.
Deployment runbook (manual steps)
No manual steps — the page ships with the code. To make a product's downloads appear:
- Add materials to the product (product form → Materials), one per type.
- For generated types (spec sheet, diagrams, candela curves, performance data) there is nothing to upload — just add the material row of that type.
- Confirm the product is published, not discontinued, and assigned to the right customer groups.
- Smoke test as a signed-out visitor and as an agent — the product list differs by customer group.
Gotchas & caveats
- A bot filter checks for
&in the request URI on the single-material path and returns 403. It is not applied to the "prepare" path. - The "All" download is only as complete as the visible columns — a user who toggled columns off gets a smaller zip with no warning.
- Generated PDFs are rebuilt on demand, so an "All" download of a large product renders several PDFs in one request.
- Two of the three generated PDFs have broken caching — see the hub.
Related docs
- Overview · performance-data.md
- Customer groups & visibility — the gating that does apply
- Discontinued products