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.
66 lines
2.3 KiB
PHP
66 lines
2.3 KiB
PHP
<?php
|
|
|
|
/**
|
|
* File redirect page.
|
|
*
|
|
* External callers (the WebForm-mw staff report iframe) link to this route
|
|
* with `id=<civicrm_file.id>` and rely on us to mint the `fcs` JWT that
|
|
* Civi's `/civicrm/file` handler requires. The JWT is signed with the
|
|
* site's crypto key, which we have here because we're running inside
|
|
* CiviCRM; the Next.js side doesn't.
|
|
*
|
|
* Permission: `access CiviCRM`. The user is already authenticated in the
|
|
* parent Civi tab when they click a link in the iframe; their session
|
|
* cookie travels with the new-tab navigation.
|
|
*
|
|
* URL shape:
|
|
* /civicrm/webform-mw/file?id=<fileId>[&eid=<entityId>]
|
|
*
|
|
* Behavior:
|
|
* - Resolve `eid` from civicrm_entity_file if not provided.
|
|
* - Mint a short-lived JWT with payload {exp, civi.file: <fileId>}
|
|
* matching Civi's own /civicrm/file token format.
|
|
* - 302-redirect to /civicrm/file?reset=1&id=...&eid=...&fcs=<jwt>.
|
|
*/
|
|
class CRM_WebformMw_Page_File extends CRM_Core_Page {
|
|
|
|
public function run() {
|
|
$fileId = (int) CRM_Utils_Request::retrieve('id', 'Positive', $this, TRUE);
|
|
$eid = (int) CRM_Utils_Request::retrieve('eid', 'Positive', $this, FALSE, 0);
|
|
|
|
// Resolve eid from the entity_file join if the caller didn't supply
|
|
// one. Any linked entity works for URL-fingerprint purposes; the JWT
|
|
// we mint below is what Civi actually authenticates on.
|
|
if (!$eid) {
|
|
$dao = CRM_Core_DAO::executeQuery(
|
|
"SELECT entity_id FROM civicrm_entity_file WHERE file_id = %1 LIMIT 1",
|
|
[1 => [$fileId, 'Positive']]
|
|
);
|
|
if ($dao->fetch()) {
|
|
$eid = (int) $dao->entity_id;
|
|
}
|
|
}
|
|
|
|
// Mint the fcs JWT. Payload matches the structure Civi's own file
|
|
// URL builder emits: {exp, "civi.file": "<id>"}. One-week lifetime —
|
|
// these links are typically clicked seconds after the report renders,
|
|
// but the staff report can be left open for a while in a Civi tab.
|
|
$payload = [
|
|
'exp' => time() + 60 * 60 * 24 * 7,
|
|
'civi.file' => (string) $fileId,
|
|
];
|
|
$fcs = \Civi::service('crypto.jwt')->encode($payload);
|
|
|
|
$url = CRM_Utils_System::url(
|
|
'civicrm/file',
|
|
"reset=1&id={$fileId}&eid={$eid}&fcs=" . urlencode($fcs),
|
|
FALSE,
|
|
NULL,
|
|
FALSE,
|
|
TRUE
|
|
);
|
|
CRM_Utils_System::redirect($url);
|
|
}
|
|
|
|
}
|