Upload: route through Civi extension multipart endpoint
Every JSON-based upload path on this Civi stores the `content` field
verbatim on disk — confirmed against both APIv4 File.create AND APIv3
Attachment.create (both came back as base64 text in hex dumps). The
multipart `file` part to /civicrm/ajax/rest is also a dead end: APIv3
Attachment.create on this install doesn't see $_FILES (rejected with
"Mandatory key(s) missing: id or content or options.move-file").
The one path Civi honors is APIv3 Attachment.create + options.move-file
— pointing at a filesystem path the Civi server can read. So expose a
tiny multipart endpoint in the WebForm-mw Civi extension that copies
PHP's $_FILES['file']['tmp_name'] into the API call, then return the
new file id as JSON. PHP's $_FILES preserves binary natively.
Civi extension (requires admin deploy):
- CRM/WebformMw/Page/Upload.php : multipart POST handler. Validates
the upload, requires `access CiviCRM`, whitelists entity_table to
civicrm_contact|civicrm_activity, calls Attachment.create with
move-file pointing at the tmp upload, returns {id, name} JSON.
- xml/Menu/webform_mw.xml : registers civicrm/webform-mw/upload.
WebForm-mw side:
- lib/civicrm.ts : new civiMultipart() helper. POSTs multipart to an
arbitrary Civi path (not /civicrm/ajax/rest) with the same AuthX
headers. Returns the parsed JSON body.
- app/api/upload/route.ts : send the upload's bytes via civiMultipart
to civicrm/webform-mw/upload. Comment-block now records all four
upload paths we tried so a future reader doesn't repeat the cycle.
Deploy: admin syncs the updated civi-extension/webform-mw/ directory
and Disable/Re-enables the extension (or runs cv flush) so the new
menu route is registered.
This commit is contained in:
@@ -244,6 +244,58 @@ export async function civi3Upload<T = unknown>(
|
||||
return { values, count: json.count };
|
||||
}
|
||||
|
||||
/**
|
||||
* POST a multipart request directly to an arbitrary Civi route. Used for
|
||||
* extension endpoints that handle multipart uploads natively (the v3/v4
|
||||
* ajax/rest path silently drops $_FILES on this install, and JSON+base64
|
||||
* stores the literal base64 text on disk).
|
||||
*
|
||||
* The caller's `path` is appended to CIVI_BASE_URL. AuthX headers are sent
|
||||
* the same way as the other helpers. Returns the parsed JSON body.
|
||||
*/
|
||||
export async function civiMultipart<T = unknown>(
|
||||
path: string,
|
||||
fields: Record<string, string>,
|
||||
file: { bytes: Uint8Array; filename: string; mime: string },
|
||||
opts: CiviApiOptions = {},
|
||||
): Promise<T> {
|
||||
if (isStubMode()) {
|
||||
console.warn(`${STUB_LOG_PREFIX} multipart ${path} — env not set`);
|
||||
return {} as T;
|
||||
}
|
||||
const base = opts.baseUrl ?? process.env.CIVI_BASE_URL!;
|
||||
const url = `${base.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
||||
const form = new FormData();
|
||||
for (const [k, v] of Object.entries(fields)) form.append(k, v);
|
||||
const fileBuf = file.bytes.buffer.slice(
|
||||
file.bytes.byteOffset,
|
||||
file.bytes.byteOffset + file.bytes.byteLength,
|
||||
) as ArrayBuffer;
|
||||
form.append("file", new Blob([fileBuf], { type: file.mime }), file.filename);
|
||||
const headers: Record<string, string> = {
|
||||
"X-Civi-Auth": `Bearer ${process.env.CIVI_API_KEY}`,
|
||||
"X-Civi-Key": process.env.CIVI_SITE_KEY!,
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
};
|
||||
if (process.env.CIVI_HTTP_AUTH_USER && process.env.CIVI_HTTP_AUTH_PASS) {
|
||||
const creds = Buffer.from(
|
||||
`${process.env.CIVI_HTTP_AUTH_USER}:${process.env.CIVI_HTTP_AUTH_PASS}`,
|
||||
).toString("base64");
|
||||
headers["Authorization"] = `Basic ${creds}`;
|
||||
}
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: form,
|
||||
cache: "no-store",
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`Civi POST ${path} failed (${res.status}): ${text}`);
|
||||
}
|
||||
return (await res.json()) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a contact checksum (cid + cs) against CiviCRM.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user