Replace the first-aid PDF links with interactive guides served in an
<iframe>. Three steps: read the published guides from the Parse REST API,
join them to the PDF rows you already list, then render an iframe where a guide exists and
keep the PDF where it doesn't.
One guide per iframe. id is the guide's slug — step 2 is how you get it.
<iframe
src="https://casualty-staging.b4a.app/embed/?id=choking"
style="width:100%;height:800px;border:0;max-width:480px"
loading="lazy"
title="First Aid: Choking"
></iframe>
Staging domain. Ask engineering for the production domain before go-live.
It must be /embed/?id=choking — with the trailing slash on /embed/
and the slug in the query string. /embed/choking returns
403 {"error":"unauthorized"} and renders that text inside your iframe: the host
serves static files only and has no single-page-app fallback, so a slug in the path matches no
file. /embed?id=… (no slash) works but costs a 301 redirect.
Height is fixed by the iframe, not the content — 700–900px suits most guides, and the guide
scrolls internally if it's taller. max-width:480px keeps the phone-shaped layout from
stretching. Guides are served noindex, so no SEO value transfers to the host page.
Everything lives in one Parse class, FirstAidItems, which holds both the old PDF rows
and the new interactive guides. Filter to the interactive ones that are live:
curl -sG https://parseapi.back4app.com/classes/FirstAidItems \
-H "X-Parse-Application-Id: $PARSE_APP_ID" \
-H "X-Parse-REST-API-Key: $PARSE_REST_KEY" \
--data-urlencode 'where={"status":"published","renderKind":"algoV2"}' \
--data-urlencode 'keys=v2Id,name,supersedes,faTypeArr' \
--data-urlencode 'limit=1000'
Returns 45 guides today. Ask engineering for the keys — never commit them.
Each result gives you the four fields that matter:
{
"objectId": "kyIw3XIpNe", // stable row id — the safe fallback key
"v2Id": "choking", // the slug you put in ?id=
"name": "Choking", // display title
"supersedes": ["x0VwLO6veY"], // objectIds of the PDF rows this replaces
"faTypeArr": ["life"] // life | medical | trauma | poison | paeds
}
limit. Parse defaults to 100 rows and silently truncates.status. The class is publicly readable at every
status, so an unfiltered query returns drafts and archived guides too. Only
published renders — anything else shows "This guide is not available."v2Id is not unique. Two published rows currently share the slug
recovery_position, and which one loads is undefined. The embed also accepts an
objectId in ?id=, so use ?id=KcrliuBslH for that guide —
or any guide, if you prefer one key for everything.Calling this from browser JavaScript works — the API sends
Access-Control-Allow-Origin: * — but prefer fetching server-side in PHP and caching,
so you aren't hitting Parse on every page view and aren't shipping a key in your theme:
function fa_guides() {
if ( $hit = get_transient( 'fa_guides' ) ) return $hit;
$qs = http_build_query( [
'where' => wp_json_encode( [ 'status' => 'published', 'renderKind' => 'algoV2' ] ),
'keys' => 'v2Id,name,supersedes,faTypeArr',
'limit' => 1000,
] );
$res = wp_remote_get( "https://parseapi.back4app.com/classes/FirstAidItems?$qs", [
'headers' => [
'X-Parse-Application-Id' => PARSE_APP_ID,
'X-Parse-REST-API-Key' => PARSE_REST_KEY,
],
'timeout' => 15,
] );
if ( is_wp_error( $res ) ) return [];
$rows = json_decode( wp_remote_retrieve_body( $res ), true )['results'] ?? [];
set_transient( 'fa_guides', $rows, HOUR_IN_SECONDS );
return $rows;
}
Your existing list is built from the legacy rows — the ones with a faFile PDF and no
renderKind. Don't match on name: only 15 of 42 titles match exactly
("Recovery position (Adults & Children)" versus "Recovery Position", and so on).
The authoritative join is supersedes: each guide lists the
objectId of every PDF row it replaces. Invert it once into
legacy objectId → slug, then look up each row you already render:
$replaces = []; // legacy objectId => guide
foreach ( fa_guides() as $g ) {
foreach ( $g['supersedes'] ?? [] as $legacy_id ) {
$replaces[ $legacy_id ] = $g;
}
}
// $item is a legacy row you already list
if ( isset( $replaces[ $item['objectId'] ] ) ) {
$slug = $replaces[ $item['objectId'] ]['v2Id'];
printf(
'<iframe src="https://casualty-staging.b4a.app/embed/?id=%s" '
. 'style="width:100%%;height:800px;border:0;max-width:480px" '
. 'loading="lazy" title="First Aid: %s"></iframe>',
rawurlencode( $slug ), esc_attr( $item['name'] )
);
} else {
fa_render_pdf_link( $item ); // no guide yet — keep the PDF
}
Where that lands against today's data:
So the fallback branch is not hypothetical — six of your current entries have no interactive replacement and must keep working as PDFs:
| PDF entry | objectId | Category | Render as |
|---|---|---|---|
| Dehydration | jXiTsbA0sZ | medical | |
| Evaluation (Babies) | zYdtBiyhj0 | paeds | |
| Fever (Babies) | xE3M00XmZa | paeds | |
| Injuries: Bites | JL19GAhCch | trauma | |
| Poisoning (Babies) | uTR5OkvX5o | paeds | |
| Rash | 68J7BtMi4J | medical |
These have no supersedes because they never existed as PDFs. The join above will never
surface them — add them to the list explicitly, or build the list from the guides rather than from
the PDF rows.
| Guide | ?id= | Category | Render as |
|---|---|---|---|
| Animal or Human Bites | animal_or_human_bites | trauma | iframe |
| Bruise | bruise | trauma | iframe |
| CPR | cpr | — | iframe |
| Helmet Removal | helmet_removal | trauma | iframe |
| Muscle Cramps | muscle_cramps | trauma | iframe |
| Newborn Resuscitation | newborn_resuscitation | paeds | iframe |
| Recovery Position | KcrliuBslH | life | iframe |
| Scorpion or Spider Bites | scorpion_or_spider_bites | trauma | iframe |
| Snake Bites | snake_bites | trauma | iframe |
Recovery Position is keyed by objectId because its slug is duplicated — see step 2.