Product documents — spec sheets & PDFs
Audience: developers & AI agents · Scope: the spec-sheet PDFs and the pipeline that renders them · Last reviewed: 2026-07-20
TL;DR — Every product offers a spec sheet PDF, and it comes in two flavours: a generic one (four cached variants per product) and a configuration-specific one generated on demand and thrown away. Both render HTML through dompdf at 300 DPI on letter paper. The generic ones are cached on disk with no automatic invalidation — the only things that refresh them are a CLI run, an admin button, or deleting the files.
| Doc | What it covers |
|---|---|
| This hub | the two spec sheets, the render pipeline, the cache |
| downloads.md | the public downloads page, the material types, delivery and the (weak) gating |
| performance-data.md | the photometric data behind the sheets — import, lookup, and the vocabulary |
The two spec sheets
| Generic | Configured | |
|---|---|---|
| Tied to a configuration? | no | yes — a cart position |
| Variants | four: {short, long} × {imperial, metric} | always short |
| Cached? | yes, on disk | no — written to a temp file, streamed, deleted |
| Entry point | view=bcspecsheetpdf&task=display&productId=N&useShort=0|1&ms=imperial|metric, with a SEF route <product>/<full|short>/<imperial|metric> | AJAX controller=bcspecsheetpdf&task=generateSpecsheetPdf |
| Extra content | — | fixture type, joined code segments, quantity, and the looked-up performance data + disclaimer |
Old product labels resolve through a lookup table and 301 to the current SEF URL; a product that is gone
answers 410 with a link to /products.
Both go through the same model method, which picks between two designs: a newer one and a legacy one, per
the product's Use new Design flag. The legacy design additionally branches on a design_variation into
three collage layouts and has no TM-30 or decorative-performance-data sections.
The render pipeline
view->getHtml() a complete HTML document, stylesheet linked by FILESYSTEM path
│
├─ (new design only) post-processing: strip U+00A0, wrap superscripts and degree signs
▼
BcHelper::getDomPdfVersion1()
│ html5 parser · 300 DPI · letter · remote enabled · PHP enabled
▼
render() → file_put_contents(<destination>)
| File | Role |
|---|---|
data/customization/models/productspecsheet.php | generateSpecSheet(), the cache, and the CLI fan-out |
data/customization/controllers/bcspecsheetpdf.php | the public route, the SEF segments, the configured-sheet tasks |
data/customization/views/bcspecsheetpdf/ | the new design |
data/customization/views/specsheetpdf/ | the legacy design |
data/customization/system_overrides/BcHelper.php | getDomPdfVersion1() — the dompdf factory |
dompdf 1.2.2, vendored under data/customization/libs/vendor/. Note there is a second, independent copy
of the same version in the ConfigBox core tree, used only by the candela-curve PDFs — with a different chroot
and a different font cache, so identical fonts get compiled twice.
Two dependencies that are easy to miss:
isPhpEnabledmust stay on. The running header/footer (logo, product title, page N of M) is drawn by a<script type="text/php">block executed inside dompdf. Turning that option off silently removes the header and footer.- GD with FreeType is a hard runtime dependency of the new design. It measures text by calling
imagettfbbox()against the Gilroy TTFs directly — once per heading, and once per code and title for every answer of every question. On a product with hundreds of options that is thousands of GD calls per render.
The cache
docroot/cache/configbox/specsheets/{productId}-{short|long}-{imperial|metric}.pdf
- Refreshing one product generates all four variants into a temp directory and only moves them into place once all four succeed.
- Refreshing everything (
cli/cb_specsheets_refresh_cache.phpwith no argument) loads every published product and re-exec()s itself once per product. Bounded memory, lots of wall clock. A single product's failure is collected and the loop continues. - The admin "Refresh cached specsheet PDF" button does the four renders synchronously inside the web request.
- A cache miss generates on demand — it does not 404. The visitor waits for four full renders, and there is no lock, so N concurrent misses on the same product do N×4 renders and race on the final move.
There is no automatic invalidation. No observer on product save, no TTL, no expiry header. Editing a product does not refresh its spec sheet — only the CLI run, the admin button, or deleting the file does. If a spec sheet looks stale, that is why.
🔴 The cache can be poisoned by an anonymous request. The design choice is read from a request parameter (
new), and the same code path runs on a cache miss in a normal public request. So hitting a cold spec-sheet URL with&new=1renders all four variants in the new design and writes them to the shared cache, where every later visitor gets them. No authentication, no allow-list. Recorded indocs/_known-issues.md.
Admin settings
No global settings — there is no cache TTL, DPI, paper size or on/off switch for spec sheets. Everything is per product, in the product form:
| Group | Settings |
|---|---|
| Spec Sheet Design | Use new Design; Spec Sheet Cover Image; diagram display mode (grid vs full page); and eight independent page-break toggles (before features, design options, how-to-order full/short, technical data, performance data, TM-30, diagrams) |
| Performance Data Display | Enable Performance Data Display plus a primary and secondary disclaimer — see performance-data.md |
| Dimensional Diagrams | a grid column count, and 20 full-page image slots |
The only global settings in this area are the two Google Sheets that feed the performance-data import — see performance-data.md.
The other PDFs
| Cached? | Notes | |
|---|---|---|
| Performance data | no — regenerated every download | reachable only as a download material, not by its own URL |
| Candela curves (offered as IES/LDT) | yes, permanently | no invalidation at all — update a curve image and the old PDF is served forever |
| Dimensional diagrams | no — the cache hit is commented out | re-renders and overwrites on every download |
The candela-curves material is labelled "IES/LDT" but delivers a PDF of curve images, not
.iesor.ldtphotometric files. Anyone downloading it expecting photometric files gets pictures.
Deployment runbook (manual steps)
- Ensure PHP has GD with FreeType (the new design measures text with it) and that the dompdf font directory under the private store is writable.
- Make sure the temp path used for staging (
<tmp>/configbox/specsheets) exists — the code creates its per-run subdirectory non-recursively, so a fresh box fails without it. - Set the per-product Spec Sheet Design options.
- Populate performance data — see performance-data.md.
- Warm the cache: run
cli/cb_specsheets_refresh_cache.php(no argument) once, and add it to cron. - Smoke test: open a product's spec-sheet URL in all four variants, and download a configured sheet from the configurator.
Turning it off: not applicable — spec sheets are core to the product pages. Removing the cron just means sheets are generated on demand, slowly.
Monitoring
CloudWatch namespace Specsheet-Cache-Refresh, with counts for starts, generations, successes, fails and
ends plus a total-generation timer. A separate front-end metric counts configured-sheet downloads under
Website-User-Behavior with a login-status dimension. Log type custom_specsheets_cache_refresh; dompdf has
its own log file under the Joomla log path. See monitoring.md.
Gotchas & caveats
- A failed generation leaves nothing cached, so every later visitor re-attempts the same four renders.
- The refresh summary throws when anything fails. The error path builds its message by
implode()ing an array of exception objects, which is itself a fatal — so a partial failure reports the wrong error and masks which products failed. - Rendering is heavy: 300 DPI letter with full-page bitmaps (up to 20 diagram images at 1200px wide).
- The standalone diagrams PDF reads only 10 of the 20 page-image slots the admin form offers; the spec sheet reads all 20. Images 11–20 silently differ between the two.
projectNameis collected but never rendered in the new design — the configurator sends it, the controller and view carry it, and no template prints it. Only the legacy design shows it.- Both dompdf instances disable TLS verification and enable remote loading; the core copy additionally
sets its chroot to
/.
Possible follow-ups
- Ignore the
newrequest parameter on the cache-write path (or gate it), so a public request cannot choose what gets cached. - Invalidate a product's cached sheets when the product is saved.
- Restore the diagrams-PDF cache, and give the candela-curves PDF an invalidation path.
- Fix the failure-summary
implode()so a partial failure reports usefully.
Related docs
- downloads.md · performance-data.md
- Scheduled jobs — the refresh CLI · Monitoring
- Discontinued products — why a retired product keeps its spec sheet
- Admin Guide:
admin-guide/products-configurator/import-performance-data.md