Migrations 54–55: seed South African Air Force and South African Navy OrgEntityAttachment trees from include/saafOob.json and include/sanOob.json
New saafOrganigram.php and sanOrganigram.php (Info menu) — live SVG organigrams for squadrons/bases and fleet combatants/patrol/support/bases
Shared include/organigramOob.php + organigramPage.php helpers; cross-links across Army, Air Force, Navy, and RSF organigram pages
v1.19.8Rhodesian Security Forces organigrams
2026-08-27
Migration 53: seed OrgEntityAttachment trees for RSF from include/rsfOob.json under umbrella Category "Rhodesian Security Forces (organigram)"
New rsfOrganigram.php (Info menu) — live SVG organigrams for RSF overview, Rhodesian Army, Air Force, BSAP, and Internal Affairs/Guard Force; trees rebuild from DB attachments on each request
Cross-links between SA Army and RSF organigram pages
v1.19.7Wartime division names on the organigram
2026-08-26
Migration 52: Wikipedia Template:SA Army Units piped [[1st Infantry Division (South Africa)|1]] as Name "1", which collided with leftover person Unit fragments (Lynsky/Beer/Fourie). Organigram now uses 1st/2nd/3rd South African Infantry Division, 3rd/6th South African Armoured Division, and the same expansion for numbered brigades, WWI SA Infantry / SAMR regiments, field engineers, and homeland battalions. Stub Unit="1"/"2" rows are left in place with the division Wikipedia URL removed.
v1.19.6Rhodesian Security Forces (RSF) on the roll
2026-08-26
DefenceForce catalog code RSF (Rhodesian Security Forces, 11 Nov 1965–30 Mar 1980) — distinct from SADF/SANDF
Imported/enriched Prop Geldenhuys Rhodesian War Casualties chronological roll: Force=RSF on matched Rhodesian units, new records where not already on the roll, Geldenhuys citation, OrgEntity Branch=Rhodesian
Seeded Rhodesian memorial stubs (Operation Miracle, RFMP, RLI Trooper, Warren Hills, Gweru, London RAA, Donaldson Canberra, All Rhodesian Aircrew)
v1.19.5Live SA Army organigrams
2026-08-26
Migration 51: seed Formation / Division / Command echelons and OrgEntityAttachment trees from Wikipedia Template:SA Army Units (include/saArmyOob.json)
New armyOrganigram.php under Info — search + SVG organigrams live from the database
Info menu: Formations, Divisions, Commands, Army Organigrams
v1.19.4DLI name history and unit links
2026-08-26
Migration 50: Durban Light Infantry and Natal Carbineers drop "Royal" in 1961 (Republic) — shown on org pages as a dated name history, not only the next rename
Person page Unit/Regiment links when the ID is missing but the name matches an organisation; force labels like South African Army are not linked as a regiment
v1.19.3Commando Group Commands and General Support Bases
2026-08-26
Migration 49: SADF/SANDF Commando System order of battle from Wikipedia Template:SA Commandos — numbered Group Commands and General Support Bases as organisation pages like Brigades, with commandos attached underneath
Folded duplicate GROUP N / Group N HQ / GSB KIMB rows onto the canonical Group Command / GSB entity
Info menu: Commandos, Group Commands, General Support Bases
Migration 48: folded 103 duplicate Regiment+Unit rows for the same organisation (the merge had kept both copies, so a Battalion typed as Regiment listed people whose Unit was a different battalion)
Cleared Regiment when it was a battalion-level name and Unit named a different formed unit (e.g. 1 Para on a 116 SAI / 101 Battalion record)
Stripped CWGC ", S.A. Forces" suffixes from organisation names; decoded HTML entities (Duke of Edinburgh's Own Rifles)
Split leftover "X attd. Y" primary Units onto PersonUnitAttachment (7 Medical Battalion Group attached to 44 Parachute Brigade)
1–4 Parachute Battalion sit under 44 Parachute Brigade (1978–1999) then 44 Parachute Regiment; 1940 infantry-brigade attachments from Wikipedia OOB where both names already exist
Helmet/shoulder flashes from Wikipedia can display beside the cap badge on org.php
v1.19.1Info menu for organisations
2026-08-26
New Info menu: Units, Regiments, Corps, Brigades, and Defence Forces moved off Lists onto organisation catalog/detail pages
info.php hub, listOrgs.php type catalogs (search + A–Z), and org.php detail pages (badge, Wikipedia, eras, order-of-battle parents/children, personnel)
Old listPeopleUnit/Regiment/Corps URLs redirect to org.php so person-page links keep working
Defence Force page shows the catalog description and Wikipedia link for each era
v1.19.0OrgEntity merge, Corps field, and unit-history model
2026-08-26
Migration 40-41: merged the separate Regiment and Unit tables into one OrgEntity table (EntityType: Regiment/Corps/Brigade/Battalion/Battalion Group/Commando/Category/Unit) -- PersonInfoRaw.RegimentID/Regiment and .UnitID/Unit are unchanged, they just reference OrgEntity now; folded two RegimentID mis-keyings (Cape Corps+SWATF, SW African Police+SA Police) onto their correct entity instead of preserving the collision; fixed 10 stale Unit/Regiment text spellings
Migration 42-43: added Corps/CorpsID to PersonInfoRaw -- classified 25,462 of 26,874 Force-set rows (94.7%) via roh_classify_corps(), moving generic corps-as-Regiment labels (e.g. South African Infantry Corps) into Corps with Regiment cleared, and assigning a Corps to named regiments (e.g. Natal Carbineers -> SAIC) while keeping Regiment; also cleared 81 Regiment=SANDF mis-entries
Migration 44: finished the DefenceForce catalog (table existed in code but was never created) with two new codes, Colonial and SAP
Migration 45-46: repointed 214 PersonInfoRaw rows off duplicate attd. X Unit entities onto the clean entity already resolved via PersonUnitAttachment; cleared 1,327 redundant Unit2 attd. values and retired the dead UnitID2 column
Added OrgEntityAlias (replaces UnitAlias), OrgEntityEra (Force/Corps/Role/Status over time), and OrgEntityAttachment (order of battle) tables for unit-history research
New Corps field on the person page (linked, with sibling count) and listPeopleCorps.php; Corps added to search.php
Deleted normalize_lookup_tables.php and rebuild_tables_with_fks.php (stale admin tools that would have corrupted the merged schema if run)
See .cursor/rules/military-fields.mdc for the full model
v1.18.1Ciskei Defence Force (CDF) on the Defence Force field
2026-08-20
Migration 34: Ciskei Defence Force deaths March 1981–26 April 1994 show Force CDF, not SADF and not blank
CDF was formed March 1981 from SADF 141 Battalion; Ciskei independence 4 December 1981; amalgamated into SANDF 27 April 1994
Ciskei Parachute Battalion as a unit on a non-Ciskei regiment (1 Para / SA Army) does not change Force
v1.18.0Gallantry awards and Honoris Crux / VC citations
2026-08-20
Migration 33: Award catalogue and PersonAward links for confirmed Victoria Cross and Honoris Crux recipients on the roll
Person page shows post-nominals after the surname (click for a scrollable medal modal)
Citations block filled for Honoris Crux records that previously had no citation text, from Wikipedia recipient lists and existing death narratives; London Gazette / Ogongo / Winterbottom texts were left unchanged
v1.17.0Physical memorials (SADF Wall of Remembrance)
2026-08-19
Migration 29: Memorial, MemorialPhoto and PersonMemorial tables for walls of remembrance and similar sites
SADF Wall of Remembrance seeded with address, OpenStreetMap coordinates, and links for everyone listed at CemeteryID 2194 (panel number parsed from GraveRef where present)
Person page shows a Memorial card (name, location, panel, position on the panel)
listMemorials.php and warMemorial.php list the site and its names; cemetery listings are unchanged
Panel photographs can be added later and will list the names on that panel
v1.16.0Citation page thumbnails from The Gazette
2026-08-17
Migration 28: CitationImage stores London Gazette page scans for award citations
Person page Citations card shows a thumbnail (lightbox) for each page found, with a link back to The Gazette
Gazette refs are parsed from Citation and ExtraCitation (LG/EG dates, issue numbers, gazette.co.uk URLs)
v1.15.0Related Deaths: Unknown names and Civilian category
2026-08-17
Unnamed related deaths are stored with LastName Unknown (Tshabalala wife; second Group 39 HQ casualty)
Migration 27: RelatedDeathCategory Civilian for civilians killed in military-related activity
Serrano recategorised from Other to Civilian
First TRC batch: named civilians from SADF cross-border raids and attacks on military/security-force targets
v1.14.0Old ROH person number + story attribution
2026-08-17
Migration 26: OldRohPersonNumber on PersonInfoRaw stores the pre-rohsa JustDone person number when uniquely matched
Archive stories/dedications remapped from JustDone PN to current PersonNumber (same unique-match rule as 32824)
Contributor name and email stripped from the end of imported bodies into ContributorName / ContributorEmail; new submissions are parsed the same way
v1.13.0Related Death categories Other and Unknown
2026-08-17
Migration 25: RelatedDeathCategory gains Other and Unknown so inserts do not fail when the category is missing or does not fit Relative / Former member / Associated
roh_related_death_category_id() maps a name or id and falls back to Unknown
v1.12.0Cause code (KIA / MIA / DOW) on person records
2026-08-17
Migration 24: CauseCode lookup table and PersonInfoRaw.CauseCode — short codes for statistics
KIA, MIA and DOW are first-class; also KOAS, DOAS, POW, FF, DUTY, MVA, AA, DROW, SEA, ACC, SHOT, MURD, SUIC, DIS, NAT, DISCH, UNK
Derived from ActualCause plus the raw CauseDeath narrative (e.g. knife wounds → DOW)
person.php shows Cause code next to the long cause of death; listPeopleCause.php?code=KIA lists a code; cause charts group by CauseCode
v1.11.0Related Death category lookup
2026-08-17
Migration 23: RelatedDeathCategory table with foreign key from RelatedDeath.CategoryID
Seeded values: Relative, Former member, Associated
Related Death list and detail pages join the lookup for the category name
v1.10.0Defence Force field (UDF / SADF / SANDF)
2026-08-17
Migration 22: Force column on PersonInfoRaw — UDF (1912–1957), SADF (1957–26 Apr 1994), SANDF (from 27 Apr 1994)
Derived from unit and regiment (South African military only; police, Rhodesian, Boer and foreign forces are left blank) plus date of death
5 SAI and other SADF-era army units show SADF; the same units after 27 April 1994 show SANDF
person.php Military Service card shows Defence Force next to Secondary Unit; listPeopleForce.php lists each force
v1.9.0Related Deaths — people not on the main roll
2026-08-17
Migration 21: RelatedDeath table and PersonRelatedDeath link table for relatives, former members who died after leaving the service, and other associated deaths that do not belong on the Roll of Honour
listRelatedDeaths.php lists them separately from the main roll; relatedDeath.php is the detail page
person.php shows a Related Deaths card (not mixed into the ROH record) when a link exists
Existing after-discharge PersonInfoRaw rows stay on the main roll — they already have SAWGP PersonNumbers
v1.8.0External source catalog and per-person deep links
2026-08-17
Migration 20: ExternalSource catalog and PersonExternalSource link table (PersonNumber + SourceID + Url + Reference)
Every record gets a South African War Graves Project deep link (details.php?id={PersonNumber})
ExtraCitation URLs and named sources (SADF.info, Koevoet wall, WMBR, samilhistory, Graham Last Post, Mike Sandham) moved onto the link table
Editorial leftovers (stolen service-number notes, date-placeholder notes) stay in ExtraCitation as Research notes
person.php Additional Information card lists named external sources with deep links instead of a single ExtraCitation blob
v1.7.0Stories and Dedications on person pages
2026-08-17
Migration 19: Stories and Dedications tables (PersonNumber, Body, contributor fields, DateAdded, Status approved/pending/rejected)
Imported archive scrape stories (2,049) and dedications (899) from the old justdone.co.za ROH as approved entries
person.php: Stories and Dedications cards with scrollable Add modals; new submissions go to Status=pending for review
submitMemorial.php + include/memorial.php: CSRF cookie, honeypot, validation; email is stored but never shown publicly
sql/stories_dedications_schema.sql and tools/import_stories_dedications.php for schema + re-import
v1.6.0Remembrance List view on On This Day
2026-08-16
listOnThisDay.php: added a tab selector between the existing Table view and a new Remembrance List view (view=table / view=roll); the selected day and view are preserved across the date selector and pagination
Remembrance List renders each record as a narrative sentence — year, record number, rank and name, service number, unit (regiment), age, and the circumstances of death — ordered chronologically by year, 100 per page
Added a Copy as text button that copies the page as plain text (heading, one bullet per person, and the closing line) for posting to social media; falls back to a selectable text box where the clipboard API is blocked
Added roh_clean_field() in functions.php — decodes HTML entities stored in the database (names such as King's Royal Rifle Corps were being displayed double-encoded) and treats Unknown/N/A placeholders as empty; applied to the On This Day table columns as well
Added roh_otd_formation(), roh_otd_circumstances(), roh_otd_entry() and roh_otd_entry_text() helpers for building the narrative entries; Age = 0 is treated as unknown rather than printed as aged 0
v1.5.1Search pagination and release note fix
2026-06-04
search.php: replaced hard LIMIT 100 with paginated results (100 per page, page parameter, total count, pagination nav) — drill-downs from chartConflict.php and other range-based filters now show all matching records
Release notes: fixed 1.5.0 INSERT OR IGNORE — the changes column was silently null due to PHP variable interpolation issue inside a double-quoted SQL string; consolidated to a single inline string literal
Migration 14 column count fix — INSERT SELECT in fixDatabase.php now includes NULL AS ExtraCitation for 34-column alignment
Fixed roh_cache fatal error — added roh_cache to core table guard; cacheGet() now calls ensureAnalyticsTables() before querying
Pageviews chart white-screen fix — replaced IIFE with DOMContentLoaded; wrapped every new Chart() in try-catch; restored max-height: 420px
Applied DOMContentLoaded + try-catch to all 17 chart files via chartGeneric.php roh_chart_script() and 8 inline chart pages
Moved RohData.sql3* to data/ directory; moved config.php to include/config.php with internal paths updated
Search broadened to 14 fields — search.php now searches Rank, Regiment, Unit, DateDeath, CauseDeath, ActualCause, Cemetery, Locality, Country, ServiceNo, GraveRef alongside name/number
Field-specific search parameters — search.php accepts FromYear, ToYear, AgeMin, AgeMax, Rank, Regiment, Unit, Cemetery, Cause as optional URL params that combine via AND; active filters shown as dismissible badges with Clear All
Conflict chart drill-down fixed — chartConflict.php now navigates to search.php?FromYear=&ToYear= using the conflict year range instead of dead search.php?q= route
Age chart drill-downs fixed — chartAge.php and chartAgeRank.php parse age-group labels into AgeMin/AgeMax parameters via search.php
New listPeopleAge.php — paginated sortable age-range filter page
New listPeopleRankGroup.php — paginated sortable rank-group filter page with dropdown selector
v1.3.9Migrate dead izkor.gov.il citation URLs
2026-05-18
Added ExtraCitation column to PersonInfoRaw
Migration 17: all 2,598 records whose Citation field contained a dead izkor.gov.il URL (site completely rebuilt; old .asp?t= links all return 404) have been updated — ExtraCitation is set to https://www.izkor.gov.il so users can still search the Israeli Fallen Soldiers & Victims of Terrorism database by name, and the broken URL has been removed from Citation
The t= values in the old URLs were batch source IDs shared by up to 237 records each (74 unique values across 2,598 records), so individual URL reconstruction was not possible
person.php Additional Information card now shows an 'External Source' row with the link and a note to search by name
v1.3.10South African War Graves Project link
2026-05-18
person.php Additional Information card now shows a permanent 'South African War Graves Project' link for every record, constructed from the PersonNumber as https://www.southafricawargraves.org/search/details.php?id={PersonNumber} — no database migration required as the URL is derived entirely from the existing PersonNumber field
v1.3.11Lightbox image gallery on person page
2026-05-18
Portrait images on person.php now display as clickable thumbnails (160px height, object-fit cover) in a flex row
Clicking any thumbnail opens GLightbox — a lightweight MIT-licensed overlay showing the full image with keyboard/swipe navigation, name and record number as caption
No-image placeholders are excluded from the gallery entirely
Thumbnails have a scale + cyan glow hover effect matching the linked-field button style
v1.3.12Fix image download for URLs with spaces in path
2026-05-18
Added roh_encode_url_path() helper in functions.php: percent-encodes raw spaces and special characters in URL path segments (decode-then-re-encode makes it idempotent — already-encoded URLs are not double-encoded)
Applied to roh_download_remote_image_safe() before curl_init — cURL was rejecting URLs like southafricawargraves.org image paths containing spaces and parentheses with exit code 3 (malformed URL), silently failing to download the image
Applied to GetImgSrc() fallback branch so the browser img src is also valid before the file has been downloaded locally
v1.3.13Coordinates field links to Google Maps
2026-05-18
Coordinates field on person.php is now a clickable link to Google Maps (opens in new tab) — previously it showed the lat/long as plain text with no link
Fixed map marker icon class from 'fa fa-map-marker' (Font Awesome 4 — broken in FA5/6) to 'fas fa-map-marker-alt' on both the Cemetery map button and its fallback
v1.4.0Unit info panel with Wikipedia integration
2026-05-18
Migration 18: added seven metadata columns to the Unit table — WikipediaUrl, Description, BadgeUrl, BadgeLocal, Formed, Disbanded, Branch (all TEXT, all NULL by default, idempotent)
Created UnitBadges/ directory for locally cached badge/insignia images
listPeopleUnit.php completely rewritten: unit info panel at the top shows badge image (locally cached preferred, remote URL fallback), unit name, branch/formed/disbanded pills, record count, description, and Wikipedia button; graceful 'no information on file' prompt with Add link when empty; sortable columns now use ASC/DESC toggle with dir= param; pagination style matches other list pages
Created editUnit.php — admin form to set WikipediaUrl, Description, BadgeUrl, Formed, Disbanded, Branch; on save with a BadgeUrl the image is automatically downloaded and cached in UnitBadges/ using the existing SSRF-safe download helper
Created unitWikiFetch.php — AJAX endpoint with two actions: action=search returns up to 6 Wikipedia page matches for a query; action=summary returns the full extract + thumbnail URL for a specific page title
Edit page has a live Wikipedia search panel: type the unit name, click a result, form fields auto-fill from the Wikipedia REST summary API (description, Wikipedia URL, badge thumbnail URL); badge preview button shows the image before saving
v1.4.1Fix concurrent DB lock crashes
2026-05-18
Moved PRAGMA busy_timeout to execute before PRAGMA journal_mode = WAL — previously the WAL pragma could contend for a write lock before the timeout was even active, causing the very first statement to fail
Increased busy_timeout from 5 s to 30 s
Wrapped roh_run_migrations() in try/catch(SQLite3Exception) — a transient lock during migration now logs a warning and defers to the next request rather than crashing the page with a fatal error
Added roh_applied_migrations table: each migration now records itself as complete after its first successful run; subsequent requests check a single cheap SELECT and skip immediately — eliminates the flood of concurrent write queries that caused the locking storm on a busy server
Batch migrations (2, 3, 4, 11) mark themselves done only when their batch is empty, so they continue across requests until complete then become no-ops
All migration transactions upgraded from BEGIN to BEGIN IMMEDIATE to acquire the write lock upfront and wait (respecting busy_timeout) rather than failing mid-batch
v1.4.2Harden editUnit.php against missing Unit columns
2026-05-18
editUnit.php: added an inline column-guard at page load that checks whether the seven Unit metadata columns (WikipediaUrl, Description, BadgeUrl, BadgeLocal, Formed, Disbanded, Branch) exist, and adds any missing ones immediately — prevents the 'no such column' fatal crash if migration 18 was deferred due to a DB lock
Wrapped the UPDATE Unit … execute() call in a try/catch so a column error or lock shows a user-friendly alert instead of a fatal crash
Added an error alert block in the HTML to display save errors to the user
v1.4.3Wikipedia infobox preview on unit edit page
2026-05-18
editUnit.php: when a Wikipedia search result is selected, the Wikipedia infobox table is now fetched and displayed below the search results — styled for the dark theme with appropriate colours for title rows, label/data cells, and images
unitWikiFetch.php: added action=infobox — fetches section 0 of the Wikipedia article, extracts the first table.infobox via DOMDocument, cleans up edit-section spans, citation superscripts, and internal anchor links (text kept, href removed), fixes protocol-relative image src (//upload.wikimedia.org → https://), returns the cleaned HTML
editUnit.php: added block for the 'Unknown Unit' placeholder — visiting editUnit.php for that unit now redirects back to the unit list instead of showing an editable form
listPeopleUnit.php: 'Edit Unit Info' button is hidden when the unit name is 'Unknown Unit'
v1.3.1Chart rebuilds and data cleanup
2026-05-17
Migration 13: NULLs out literal 'Unknown', 'N/A', '?', 'None', 'Not Known', 'Not Recorded' values in Unit, Cemetery, Locality, and Regiment fields
Rebuilt chartCemetery.php, chartLocality.php, chartRegiment.php from scratch — all three were broken (blank page) due to dependency on removed chartGeneric.php include
Fixed chartRank.php: removed RankID > 0 filter which silently excluded records, replaced CountNoRank()/CountTotalDeaths() helpers with inline queries
Fixed chartAge.php: replaced broken main age-group chart (was also using chartGeneric.php) with inline Chart.js bar chart; births-by-decade section unchanged
Rebuilt chartCauseOfDeathYear.php: now renders a proper stacked bar chart with the top 8 causes as separate coloured series by year, plus a full cause summary table
v1.3.2Navigation menu additions
2026-05-17
Added missing chart pages to Statistics navigation submenus
Deaths: added Cause of Death by Year (chartCauseOfDeathYear.php)
Personnel: added Age at Death (chartAge.php)
Geography: added By Regiment, By Cemetery, By Locality (chartRegiment.php, chartCemetery.php, chartLocality.php)
v1.3.3Schema corrections and Name field
2026-05-17
Migration 14: rebuilds PersonInfoRaw with correct column types — Age changed from TEXT to INTEGER (data preserved via CAST), Cemetery changed from INTEGER to TEXT; all 7 indexes recreated automatically
Migration 15: populates the Name field as 'Surname, FirstName (Initials)' for all records (falls back to 'Surname, FirstName' or 'Surname' if fields are absent); adds two SQLite triggers (trg_person_name_insert, trg_person_name_update) to keep Name current for all future inserts and edits to FirstName, LastName, or Initials
v1.3.4Fix locality/country mismatches
2026-05-17
Migration 16: corrects Country to 'South Africa' for all records where Locality is a South African province but Country was set to another country (e.g. Gauteng/Zimbabwe, Eastern Cape/Namibia) — affects ~300 records across 9 provinces
Normalises variant province spellings: 'Kwazulu Natal' to 'KwaZulu-Natal', 'Limpopo Province' to 'Limpopo', 'North West Province' to 'North West'
chartLocality.php table now shows Country column alongside Locality
v1.3.5Expanded person record page
2026-05-17
Rewrote person.php to show all fields in four grouped cards: Identity, Military Service, Death Record, Location, and Additional Information
Empty fields shown as muted italic 'Unknown' rather than hidden
Cause of Death shows normalised ActualCause as primary value with raw CauseDeath as a smaller subtitle
Cemetery name links to Google Maps when lat/long coordinates are present
Added Previous / Next navigation buttons to move between records
Date fields formatted as 'dd Month YYYY'
Record last-checked date shown in footer of the page
v1.3.6Linked fields and badges on person page
2026-05-17
Citation field now detects URLs and renders them as clickable links opening in a new tab
Date of Death links to On This Day page with badge showing deaths on that calendar day
Date of Birth links to On This Day page with badge showing deaths on that calendar day
Regiment links to By Regiment list with badge showing total records in that regiment
Unit links to new By Unit list page with badge showing total records in that unit
Estimated Birth Year links to new By Est. Birth Year list page with badge showing how many share that birth year
Surname links to search results with badge showing total records with that surname
Linked fields styled as Bootstrap outline-info buttons with cyan glow on hover
Added listPeopleUnit.php — paginated list filtered by unit with sortable columns and unit dropdown selector
Added listPeopleEstBirthYear.php — paginated list filtered by estimated birth year with year dropdown
Added both new pages to Lists menu; renamed 'By Year' to 'By Year of Death' for clarity
v1.3.7More linked fields on person page
2026-05-17
Added linked button + count badge for five more fields on person.php: Cause of Death (links to list by cause), Grave Reference (links to same cemetery filtered by plot ref), Country (links to list by country), Locality (links to list by locality), Cemetery (links to list by cemetery; map icon moved to a separate button alongside)
Added listPeopleCause.php — paginated sortable list filtered by ActualCause
Added listPeopleCountry.php — paginated sortable list filtered by Country
Added listPeopleLocality.php — paginated sortable list filtered by Locality
Added listPeopleCemetery.php — paginated sortable list filtered by CemeteryID; accepts optional grave= param to further narrow to a specific grave reference
v1.3.8Minor person page cleanup
2026-05-17
Removed 'Record last checked: never' footer from person.php — the DateChecked field is empty on all 41,924 records and was never populated, so the line served no purpose
v1.3.0Deaths by Unit chart
2026-05-16
Added chartUnit.php — horizontal bar chart of top 60 units by death count, with full ranked table showing count and percentage of records with a known unit
Added "By Unit" link to the Personnel submenu in the Statistics navigation
Rebuilt chartUnit.php from scratch replacing an old incompatible version that referenced removed helpers (UnitID filter, CountNoUnit(), chartGeneric.php)
v1.2.8Data quality migrations 5-11
2026-05-16
Migration 5 (Rank cleanup): NULLs out "Rank Unknown" / "Unknown Rank" variants, strips trailing full stop, normalises Serjeant/Serjeant Major/Lance Serjeant/Staff Serjeant to modern Sergeant spellings
Migration 6 (CauseDeath): strips trailing full stops from raw CauseDeath field
Migration 7 (Initials): replaces full stops with spaces in Initials field, collapses double spaces, NULLs out blank results
Migration 8 (Age): converts Age = 0 to NULL (placeholder values)
Migration 9 (DateDeath): converts DateDeath values starting with "-0001" to NULL (SQLite date placeholder)
Migration 10 (ServiceNo): trims leading/trailing whitespace from ServiceNo
Migration 11 (Rank/Regiment/Unit title-case): converts all-caps Rank, Regiment, and Unit fields to title case; handles Mc/Mac prefixes; processed in batches of 5,000 per page load
v1.2.9Remove spare record placeholders
2026-05-16
Added migration 12 to fixDatabase.php: deletes all PersonInfoRaw rows where LastName = 'Spare Record' — these are structural placeholder entries with no real person data
v1.2.7Title-case name fields
2026-05-16
Added migration 4 to fixDatabase.php: converts all-caps LastName and FirstName values to title case (e.g. ABBOTT to Abbott, MCDONALD to McDonald)
Handles hyphens, apostrophes, and Mc/Mac prefixes
Affects ~41,000 LastName and ~39,000 FirstName records; processed in batches of 5,000 per page load
v1.2.6Name field split migration
2026-05-16
Added migration 3 to fixDatabase.php: for records where FirstName is empty and LastName contains a space, splits the combined field at the first space into correct LastName and FirstName values
Excludes 151 records with Afrikaans/Dutch/French compound surname prefixes (DE, DU, DA, VAN, VON, TEN, DEN, TER, LE, LA) which must not be split
Where Initials is also blank, derives initials from the extracted first name by taking the first letter of each word
Affects ~421 records
v1.2.5Page title fixes
2026-05-16
Added missing entries to roh_page_display_title() for releases.php, about.php, and chart_views.php so pageviews.php shows friendly titles instead of raw filenames
v1.2.4Minor text fix
2026-05-16
Changed about.php browser tab title from "Roll of Honour — About" to "Roll of Honour — About this site"
v1.2.3ActualCause field — normalised cause of death
2026-05-16
Added ActualCause column to PersonInfoRaw — parsed from the raw CauseDeath field by stripping location/narrative suffixes after the first comma, stripping parenthetical asides, and normalising ~40 variant spellings to canonical categories (e.g. "Died of pulmonary tuberculosis" → "Died of tuberculosis", MVA variants → "Died in motor vehicle accident")
Updated chartCauseOfDeath.php and chartCauseOfDeathYear.php to group by ActualCause instead of raw CauseDeath — reduces ~13,330 distinct values to ~200 meaningful categories
Updated CountNoCause() and roh_batch_person_stats() to use ActualCause
Updated Data Completeness chart to check ActualCause for meaningful cause data
v1.2.2Line graph on On This Day
2026-05-16
Converted the yearly trend chart on listOnThisDay.php from a bar chart to a line graph with filled area, curve tension, dot markers, and dark-theme axis styling
v1.2.1Estimated birth year
2026-05-16
Added EstBirthYear column to PersonInfoRaw — calculated as death year minus age at death (accurate to ±1 year), covering 30,382 records
Added "Estimated Births by Decade" bar chart and table to chartAge.php
Added estimated birth year range column (min–max) to rank detail table in chartAgeRank.php
Added stacked "Births by Decade by Rank Group" chart to chartAgeRank.php
Split "Date of Birth" in chartCompleteness.php into two rows: recorded date of birth (6 records) and estimated birth year (30,382 records)
v1.2.0Version bump
2026-05-16
Minor version bump to 1.2.0
v1.1.0Seven new statistics pages
2026-05-16
Added chartMap.php — interactive Leaflet.js cemetery map, circle markers sized by burial count, top-20 sites table
Added chartConflict.php — deaths grouped by named conflict era (Anglo-Boer War, WW1, WW2, Korean War, Rhodesian Bush War, SA Border War, post-1990), plus per-year timeline coloured by conflict
Added chartMonth.php — deaths by calendar month with top-3 months highlighted
Added chartCompleteness.php — data quality dashboard: progress bars and radar chart for 16 fields across Identity, Military, Death Record, Location, and Other categories
Added chartAgeRank.php — average age at death by rank group (General/Flag Officer down to Other Rank), plus full age-distribution histogram
Added chartSurname.php — top-40 surname frequency horizontal bar chart with ranked table
Added chartCalendar.php — 365-day CSS heat map grid showing deaths per calendar day across all years, with top-20 days table
Reorganised Statistics nav dropdown into four headed sections: Deaths, Personnel, Geography, Data
v1.0.3Fixed duplicate release notes
2026-05-16
Added UNIQUE constraint on roh_releases.version — INSERT OR IGNORE had nothing to conflict on so inserted a new row every page load
Added deduplication DELETE before index creation to clean up existing duplicates on the server
Added CREATE UNIQUE INDEX IF NOT EXISTS for pre-existing installs
v1.0.2Removed duplicate page views page
2026-05-16
Deleted chart_views.php — pageviews.php is the superset (includes everything chart_views had plus Top 40 table and daily 3-month chart)
Updated footer Quick Links to point to pageviews.php
Removed chart_views.php entry from roh_page_display_title()
v1.0.1Bug fix
2026-05-16
Fixed Personnel with Images count on home page — was counting locally downloaded files only (returned 1); now counts distinct PersonNumber values with a real image URL, excluding the no_image.jpg placeholder (correct count: 30,183)
v1.0.0Initial versioned release
2026-05-16
Initial versioned release of the Roll of Honour website