Path 1 (APIv4 Attachment.get + select url) shipped but didn't fix the
Firebase\JWT decode crash — the deployed Civi version either omits `url`
from Attachment.get or returns it without the fcs param. Falling back to
the bare /civicrm/file?id=X URL hits the same JWT null crash.
Path 2: route file clicks through a tiny redirect endpoint in the Civi
extension instead. The extension runs PHP on Civi, has access to the
crypto.jwt service, and mints the same shape of token Civi's own file
URL builder uses ({exp, "civi.file": <id>}) before 302-redirecting to
the canonical /civicrm/file URL.
Civi extension changes:
- New CRM/WebformMw/Page/File.php — resolves eid from civicrm_entity_file
if not supplied, signs a 7-day JWT via Civi::service('crypto.jwt'),
redirects.
- xml/Menu/webform_mw.xml — registers civicrm/webform-mw/file. Requires
`access CiviCRM` (the user is already authenticated in the parent Civi
tab when they click the link).
Frontend (StaffReportView.tsx, FieldValue):
- When Attachment.get's url is missing, fall back to the new extension
route instead of bare /civicrm/file. Attachment.get's url remains the
fast path when present.
Deploy: admin needs to push the updated extension files to the Civi
server, then Disable/Enable webform-mw (or cv flush) so the new menu
route registers in civicrm_menu.
WebForm-mw — CiviCRM extension
Adds an Engagement Report tab to Organization contact-view pages that
embeds the FCI Co-op Survey staff report
(https://survey.fci.coop/staff/report) in an iframe. The tab shows the
report for the current organization, scoped by the contact id in the URL.
The embedded app handles its own auth via a shared staff secret. The extension is otherwise read-only and adds no Civi tables, custom fields, or scheduled jobs.
Install
- Copy the entire
webform-mw/directory to your CiviCRM extensions directory (typically<civi-root>/sites/default/ext/or wherever[civicrm.extensionsDir]points in yourcivicrm.settings.php).- The directory name on disk must be
webform-mw(matching<key>ininfo.xml).
- The directory name on disk must be
- In CiviCRM:
Administer → System Settings → Extensions → Add new → Refresh, then click Install next to "WebForm-mw". - Configure (see next section). The tab will be hidden until both settings are present.
Configuration
The extension reads two values, in this order:
-
Constants in
civicrm.settings.php(preferred):define('WEBFORM_MW_APP_URL', 'https://survey.fci.coop'); define('WEBFORM_MW_STAFF_KEY', '...the STAFF_REPORT_KEY shared with the app...'); -
Or environment variables (
WEBFORM_MW_APP_URL,WEBFORM_MW_STAFF_KEY) set wherever PHP-FPM / the web server reads its environment from.
WEBFORM_MW_STAFF_KEY must match the STAFF_REPORT_KEY configured on the
Next.js app (Amplify environment / SSM Parameter Store). The two are the
same shared secret; rotate them together.
WEBFORM_MW_APP_URL is the public base URL of the WebForm-mw deployment
(no trailing slash). Production: https://survey.fci.coop.
When either value is missing, the tab body shows a help banner with the exact configuration snippet to paste, so anyone installing the extension without prior context can self-serve.
Behaviour
- Tab title: Engagement Report.
- Visible only on Organization contacts (Individuals and Households see no tab). The check happens server-side in the tabset hook.
- Tab body is an iframe pointing at
${WEBFORM_MW_APP_URL}/staff/report?org=<cid>&key=<secret>&frame=1. frame=1tells the embedded app to suppress its site header/footer and emit apostMessage({type:"webform-mw-height", height})payload on render and on resize. The tab's small inline script listens for this message and auto-sizes the iframe so there's no nested scrollbar.- The script validates the postMessage
event.originagainst the configuredWEBFORM_MW_APP_URLorigin before resizing.
Security notes
- The staff secret travels with each tab render inside the iframe
src. Anyone permitted to view the Engagement Report tab (i.e., anyone with CiviCRM access) can read the URL in their browser's DevTools and reuse the secret to view any org's report. Acceptable model if "CiviCRM access" and "should view any staff report" overlap; otherwise consider upgrading the embedded app to per-user signed tokens and minting them in the page controller. - The extension uses
access CiviCRMas its access argument — anyone with that permission sees the tab on Organization contacts. Tighten by changing the<access_arguments>value inxml/Menu/webform_mw.xmlto a more specific permission (e.g.,view all contacts) and reinstalling. - The embedded app's CSP (
frame-ancestors) must include the CiviCRM origin or the iframe will refuse to render. The Next app readsCIVI_BASE_URLat build time and adds its origin to the staff route's CSP automatically. Confirm withcurl -I <app>/staff/reportafter deploy — you should seeContent-Security-Policy: ... frame-ancestors 'self' https://<your-civi-host> ....
Files
info.xml— extension manifest. Keywebform-mw, typemodule.webform_mw.php—hook_civicrm_tabset+ config readers.CRM/WebformMw/Page/Tab.php— page controller for the tab snippet.templates/CRM/WebformMw/Page/Tab.tpl— iframe + height-listener.xml/Menu/webform_mw.xml— registers thecivicrm/contact/view/engagement-reportURL.
Versions tested
- CiviCRM 5.50+
- PHP 7.4+ / 8.x
- Tested against Backdrop-as-host and Drupal-as-host CiviCRM deployments.
Uninstalling
Administer → Extensions → Disable, then Uninstall. Removes nothing
from the database; the extension stores no Civi data of its own.