EMS PDF Invoice

Source

First-party — auto-fetched from its GitHub release (private repo)

Purpose

Per-customer branding, billing-field labels and EFT details on the PDF invoice

Special infra

None (no writable filesystem paths)

This plugin does not generate PDFs. It is a customisation layer over WooCommerce PDF Invoices, hooking that extension’s filters to swap in a branded template, re-colour it, relabel the billing fields, and add an EFT payment card. Every customer-specific value is configuration, so one artefact serves every site.

Install — auto-fetched release

Ours, so no hand-staging. The build fetches the pinned GitHub release asset (ems-pdf-invoice-<ver>.zip, root folder ems-pdf-invoice/):

# scripts/build-image.sh — before the docker build
gh release download "${EMS_PDF_INVOICE_VERSION}" \
  -R <org>/ems-pdf-invoice --pattern 'ems-pdf-invoice-*.zip' --dir packages --clobber
unzip -q packages/ems-pdf-invoice-*.zip -d web/app/plugins   # -> web/app/plugins/ems-pdf-invoice

Activated in bootstrap after both WooCommerce and the PDF Invoices extension, which it depends on. If either is missing it registers no hooks and raises an admin notice.

Configuration

Everything lives in one option, ems_pdf_invoice_settings. Nothing in it is a secret — bank details are printed on every invoice — so unlike most plugin config it can be declared in git in full. Defaults reproduce the original single-customer output, except the bank fields, which default empty.

Because the plugin owns this option end to end, it is declared whole in the chart’s bootstrap.options, not patched key by key. That is the opposite of the vendor’s option, which must be merged — see the warning there.

Behaviour

Key Default Meaning

enabled

true

Master switch. Off leaves the vendor’s stock template and branding in place, which is the quickest way to rule this plugin out when diagnosing a rendering problem.

inject_css

true

Off suppresses the generated palette block, for a customer supplying wholly custom CSS.

use_custom_template

true

Off keeps the vendor’s template but still expands the custom placeholders.

Brand palette

Key Default Meaning

brand_colour

#1a5276

Headings, card rules, the line-item heading row.

brand_tint_colour

derived

EFT card background. Left empty it is derived by mixing brand_colour 92% toward white, so setting one colour still yields a coherent card.

brand_contrast_colour

#ffffff

Text on the brand-filled heading row.

text_colour, muted_colour, border_colour, panel_bg_colour, row_alt_bg_colour

greys

Body text, secondary labels, rules, panel fills, alternating rows.

Layout, labels and custom fields

Key Default Meaning

logo_max_width_px / logo_max_height_px

120 / 80

Logo box; the originals were tuned to one customer’s asset.

logo_path

''

Absolute filesystem path to a logo, bypassing the media library — so branding can be baked into the image and versioned with it rather than uploaded by hand.

invoice_title

''

Empty uses the vendor’s own translated "Invoice".

label_phone, label_email, label_vat

Phone:, Email:, VAT No:

Billing-field labels. An empty label renders the value unlabelled.

vat_meta_keys

VAT Number,vat_number,…

Candidate order-meta keys; first non-empty wins.

field1_*, field2_*

registration / PO number

Two configurable order-meta slots — _enabled, _meta_keys, _label each. Slot 1 renders inline, slot 2 as a label/value cell. For a third, filter ems_pdf_invoice_custom_fields.

EFT payment card

Key Default Meaning

eft_enabled

true

Off suppresses the card regardless of gateway or bank details.

eft_heading

EFT Payment Instructions

Card heading.

eft_gateways

bacs,bank_transfer,eft

Gateway IDs that trigger the card. Add the site’s own (ozow, payfast_eft, a custom wc_gateway_*) or it silently gets none.

eft_reference_text

"…use Invoice No {reference}…"

{reference} is substituted with the invoice number, or the order number when eft_reference_source is order.

eft_bank_name, eft_account_name, eft_account_number, eft_branch_code, eft_swift

empty

Bank details. Empty rows are omitted, and when all are empty the whole card is suppressed — so an unconfigured site prints nothing rather than an empty box.

How the palette is injected

The colours are not templated. The vendor exposes a pdf_template_additional_template_css filter whose return lands last inside <style>, and which nothing else uses. The plugin generates an override block there, so equal-specificity rules win by cascade order without !important, and the template keeps its original literals as a fallback if the filter never fires.

CSS custom properties (var()) are deliberately not used: dompdf, the vendor’s default generator, does not support them.

The failure worth knowing about

The template override works by handing the vendor a relative path that climbs out of its templates directory — the only route available to a plugin, since the vendor’s other override path is inside the read-only theme (see the override contract).

If that traversal breaks — a renamed vendor directory, a restructured image — the invoice still renders, just with the vendor’s stock design. Nothing errors. Two guards exist:

wp ems-pdf-invoice doctor

reports whether the override resolves, alongside dependency and palette checks; and every rendered invoice carries an <!-- ems-pdf-invoice v<version> -→ marker in <head>.

The complementary symptom is the opposite one: if a raw or appears on an invoice, the plugin is not running at all — deactivated, or one of its two dependencies missing — rather than misconfigured. When it is running it clears its own tokens even for an order it cannot resolve.

Re-run the check after any upgrade of the vendor extension.

Sites not on the EMS chart

For a WordPress site provisioned some other way, wp ems-pdf-invoice seed reads the same settings from EPI_* environment variables (brand_colourEPI_BRAND_COLOUR).

Updates

Self-updating is opt-in via EMS_PDF_INVOICE_UPDATE_REPO / EMS_PDF_INVOICE_UPDATE_TOKEN, and is skipped entirely where DISALLOW_FILE_MODS is set or under WP-CLI. On an immutable image the check cannot lead to an install, so enabling it would only make an outbound call to GitHub and raise a nag nobody can action. Upgrades follow the normal path: bump the pinned version and rebuild the image.