Skip to main content

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.

DocWhat it covers
This hubthe two spec sheets, the render pipeline, the cache
downloads.mdthe public downloads page, the material types, delivery and the (weak) gating
performance-data.mdthe photometric data behind the sheets — import, lookup, and the vocabulary

The two spec sheets

GenericConfigured
Tied to a configuration?noyes — a cart position
Variantsfour: {short, long} × {imperial, metric}always short
Cached?yes, on diskno — written to a temp file, streamed, deleted
Entry pointview=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 contentfixture 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>)
FileRole
data/customization/models/productspecsheet.phpgenerateSpecSheet(), the cache, and the CLI fan-out
data/customization/controllers/bcspecsheetpdf.phpthe 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.phpgetDomPdfVersion1() — 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:

  • isPhpEnabled must 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.php with 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=1 renders 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 in docs/_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:

GroupSettings
Spec Sheet DesignUse 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 DisplayEnable Performance Data Display plus a primary and secondary disclaimer — see performance-data.md
Dimensional Diagramsa 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

PDFCached?Notes
Performance datano — regenerated every downloadreachable only as a download material, not by its own URL
Candela curves (offered as IES/LDT)yes, permanentlyno invalidation at all — update a curve image and the old PDF is served forever
Dimensional diagramsno — the cache hit is commented outre-renders and overwrites on every download

The candela-curves material is labelled "IES/LDT" but delivers a PDF of curve images, not .ies or .ldt photometric files. Anyone downloading it expecting photometric files gets pictures.

Deployment runbook (manual steps)

  1. 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.
  2. 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.
  3. Set the per-product Spec Sheet Design options.
  4. Populate performance data — see performance-data.md.
  5. Warm the cache: run cli/cb_specsheets_refresh_cache.php (no argument) once, and add it to cron.
  6. 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.
  • projectName is 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 new request 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.