# Small Modules — Messages, ISO, Complaints, GHG, Lookups & Entry Points

**Folders:** `CRUD/messages/`, `CRUD/iso/`, `CRUD/complaints/`, `CRUD/ghg/`, `CRUD/audit_history/`, `CRUD/main/`, the lookup tables, and the site entry points.

The remaining small modules. Most are simple CRUD over a lookup table and follow the standard `*_index / _create / _read / _update / _delete` shape described in the [README](../README.md#common-page-naming-pattern).

> Documented to the format set by [sites.md](sites.md).

---

## Messages / Call Log — `CRUD/messages/` (14 files)

![Phone Call History](../screenshots/small__phone_calls.png)

Logging incoming phone calls and tracking whether they've been actioned.

| Page | Purpose |
|---|---|
| `phone_call_history.php` | Main call log (574 lines) |
| `phone_call_dashboard.php` | Outstanding calls |
| `phone_call_index.php` | Call list |
| `phone_call_create.php` | Log a call |
| `phone_call_edit.php` / `phone_call_edit_index.php` | Edit a call (`?id=`) |
| `phone_call_complete.php` | Mark actioned |
| `phone_call_delete.php` / `_delete_index.php` | Remove a call |
| `phone_call_mark_read.php` | Mark read (API) |
| `phone_call_summary_bar.php` | Summary strip included by other pages |
| `phone_call_history_api.php` / `phone_call_view_api.php` | JSON feeds |

> `phone_call_complete.php` was **unauthenticated and acted on a plain GET** until the [security review](../security-audit-2026-07.md) — it now requires login and rejects cross-site requests.

## ISO — `CRUD/iso/` (7 files)

![ISO Dashboard](../screenshots/small__iso_dashboard.png)

ISO accreditation and the internal audit programme.

| Page | Purpose |
|---|---|
| `iso_dashboard.php` | ISO overview |
| `audit/audit_index.php` | Audit runs |
| `audit/audit_wizard.php` | Work through an audit (`?id_run=`) |
| `audit/audit_quarterly.php` | Quarterly audits |
| `audit/audit_template_builder.php` | Build an audit template |
| `audit/audit_printable.php` | Printable audit report |
| `audit/audit_helpers.php` | Shared logic (include) |

![ISO Audit](../screenshots/small__iso_audit.png)

Backed by `audit_templates`, `audit_template_questions`, `audit_runs`, `audit_run_answers` and `audit_run_answer_files`. Related compliance reporting lives in `fitter_iso_report.php` and `complaints_iso_report.php`.

## Complaints — `CRUD/complaints/` (5 files)

![Complaints](../screenshots/small__complaints.png)

`complaints_index.php`, `complaints_add.php`, `complaints_view.php` (`?id=`), `complaints_delete.php`, plus `complaints_iso_report.php` which feeds the ISO evidence pack.

## GHG — `CRUD/ghg/` (1 file)

![GHG](../screenshots/small__ghg.png)

`scope2_index.php` — Scope 2 greenhouse-gas (electricity) emissions tracking. Uses the `emission_factor` table maintained by `fleet/setup_wtw_factor.php`.

## Audit History — `CRUD/audit_history/` (3 files)

![Audit History](../screenshots/small__audit_history.png)

Browses the change-audit tables. `audit_history_index.php` lists changes, `audit_history_record.php` shows one record (`?id=`), `audit_history_helpers.php` holds shared logic.

> Reads `hire_list_audit` / `plant_list_audit`, which store **post-change (NEW) snapshots** — the value *before* a change is in the preceding snapshot. See [hire-list.md](hire-list.md#database).

## Main — `CRUD/main/` (2 files)

`mainpage.php` is the portal home page (the tile grid you land on after login). `maintenance.php` is the maintenance overview.

![Maintenance](../screenshots/small__maintenance.png)

---

## Lookup tables

Small CRUD modules maintaining the dropdown values used across the portal. All follow the standard pattern.

| Module | Maintains | Used by |
|---|---|---|
| `CRUD/category/` | Plant categories (`plant_category`, 242 rows) | Plant list, specs |
| `CRUD/category_type/` | Category types + weight categories (52 / 19 rows), and `plant_weight_links.php` maps machines to weight classes | Plant list, yard list |
| `CRUD/job_type/` | Job types — Operated, Self drive, Site move, Operator only, Sale | Hire list |
| `CRUD/delivery_type/` | Delivery types | Hire list |
| `CRUD/additions/` | Hire additions/attachments (`hire_plant_additions`, 11 rows) | Hire forms |
| `CRUD/holiday_dates/` | Bank holidays used by charging logic | Rate calculations |

![Holiday Dates](../screenshots/small__holidays.png)

> **Job types are load-bearing.** Renaming one changes behaviour: `hire_rules.php` matches `Operated` / `Operator only` (case-insensitively) to decide whether a hire keeps a driver. See [hire-list.md](hire-list.md#editing-a-hire--hire_updates).

> **Bank holidays:** the working-day calculation used by the monthly reports does **not** read `holiday_dates` — each report file carries its own `number_of_working_days()`. See [hire-support.md](hire-support.md#known-issues) issue 2.

---

## Entry points & plumbing

Files reachable without being linked from another page, or shared by every page.

| File | Role |
|---|---|
| `login.php` / `authenticate.php` / `logout.php` | Login flow. `authenticate.php` enforces brute-force throttling (10 failures / 15 min per IP) |
| `unauthorised.php` | Shown when a role is refused a page — correctly returns **HTTP 403** |
| `404.php` | Not-found page |
| `CRUD/session.php` | Session + auth guard, included by every page. Enforces the role model and the 8-hour idle timeout |
| `CRUD/env.php` | `portal_is_local_request()` — the non-spoofable local-dev check |
| `CRUD/bootstrap.php` / `config.php` / `database.php` | Environment, configuration, PDO connection |
| `CRUD/auth.php` | `require_role()`, field-role allowlist, `block_cross_site_request()` |
| `CRUD/keepalive.php` | Pinged every 5 min by `scripts.html`. Deliberately does **not** include `session.php`, so it cannot reset the idle timer |
| `CRUD/uploads.php` / `legacy_uploads.php` | The uploads hub and its legacy page |

---

## Known issues

Reviewed 2026-07-21. 33 of 51 tested pages were clean 200s; the rest were id-required pages redirecting correctly.

| # | Issue | Status |
|---|---|---|
| 1 | **`category/category_read.php` redirected without stopping.** With no id it called `header("Location: create_index.php")` but had **no `exit;`**, so it carried on rendering the whole page with `$data` never set — 22 KB of output and **12 warnings** per request. The redirect target `create_index.php` also **does not exist** (the real page is `category_index.php`). | **Fixed** — correct target plus `exit;`. Now a clean 0-byte 302, and 200 with a valid id |
| 2 | A portal-wide sweep for `header('Location: …')` without a following `exit`/`die` flags **~20 further candidates**. **Treat that number as approximate** — the common `if (…) { header(…); } else { header(…); } exit;` idiom still trips the detector, so several are false positives (e.g. `hs/risk_assessment/gra_edit.php` does exit correctly). | **Open** — worth checking individually; the failure mode is silent (browsers follow the redirect, so the wasted render and any warnings go unseen) |
| 3 | Unreferenced: `category/category_read.php`, `messages/phone_call_view.php`. | **Open** |

**Everything else was clean** — no PHP errors across messages, ISO, complaints, GHG, audit history, main, the six lookup modules or the entry points.
