# PRD — clientModule

Evidence date: 2026-10-18.

Source: `git@bitbucket.org:overdrive/clientmodule.git`, `master` at `aa88a34` (2019-06-24).

There is no prior PRD in the source repository or in Silicon Ark. Requirements below are recovered from `README.md` and the HTML, PHP, CSS, and git history on that revision. Provenance is labelled. Database passwords stored for hosts `localhost` and `lamp` are omitted.

## Product purpose

**Documented.** `README.md` titles the work "Unity : clientModule" and lists three phases: a validated client form that writes to MySQL, a table of clients that can be edited, and a catalogue of client types.

**Implemented.** The running pages collect a client, insert a row into `tbl_client`, and list every row. They do not implement edit-and-save, the type catalogue, or the `api/` post described in phase 1.

**Unknown / Requires Confirmation:** who the intended operator is, whether this module was embedded in another Overdrive system, and whether it is still used. The last commit is 2019-06-24.

## Users and roles

| Actor | Evidence | Status |
|---|---|---|
| Person filling the client form | `formClient.html` posts to `insertClient.php` | **Implemented**. The README does not name this actor |
| Person viewing the list | `displayClient.php` selects every `tbl_client` row | **Implemented**. No login check |
| Person editing | `editClient.php` is linked from each row | **Documented** in phase 2. The page does not load or save a client |
| Client type on the row | Stored labels `SLA-Software`, `SLA-AWS`, `Technical` | **Implemented** as data. The label is not checked before insert or list |

No permission table, group, session, or login page appears in the application PHP. The README does not ask for authentication.

## Systems the product talks to

1. MySQL through `mysqli`, database name `db_clientmodule`, table `tbl_client`. Connection parameters depend on `$_SERVER["SERVER_NAME"]`. The insert page opens its own connection. The list page uses `dbconnection.php`.
2. The browser, for `alert()` messages and for the inline function `showInput()` in `formClient.html`.

No mail transport, HTTP API client, queue, CDN, or file store is used by the application pages. The Smart Admin template under `template/` includes its own demo pages (mail, maps, invoices). Those pages are not called by the PHP.

## Major workflows

### WF-1 — Open the form

**Implemented** in `formClient.html`:

1. The page is static HTML. It does not include PHP and does not open a database.
2. It links `style.css`.
3. The form `action` is `insertClient.php` and the method is `post`.
4. Visible fields: first name, last name, SA ID number (placeholder "SA ID ONLY", `maxlength="13"`, `required`), date of birth (`type="date"`, `required`), gender radios, an "Is Account Active?" checkbox, a type `<select>` (`required`), Save, and Clear.
5. Female is the checked gender. The active checkbox is checked. The type dropdown's first option is `--select an option--` with `value="none"`, `hidden` and `disabled`.

### WF-2 — Validate and submit

**Documented** phase 1: on submit, validate the fields. If they are correct, GET the fields and POST only the ID number to PHP under `api/`.

**Implemented** in `showInput()`, called from the Save button as `onclick="showInput();"`:

1. The function writes a preview into `#results` before it checks anything. Each click appends another block (`innerHTML+=`).
2. Gender in that preview is "male" when the radio with `id="gender"` is checked, otherwise "female". Account state in the preview is "yes" or "no".
3. Empty first name, a first name for which `isNaN` is false, empty last name, a last name for which `isNaN` is false, an ID that is empty or not numeric or not length 13, and an empty birthday each `alert` and `return false`.
4. A variable `regexp` (`/^[A-Za-z]+$/`) is declared and never used. A name such as `John1` is therefore not rejected by the letter pattern. `isNaN` rejects a value that coerces to a number (for example `123`).
5. The birthday check references `today`, which is not defined in the page, and only on the empty-birthday branch.
6. If the checks pass, the function alerts "Posting to Database" and returns true.
7. The `onclick` attribute does not return that value, so a `return false` inside `showInput()` does not cancel the browser submit by itself.
8. HTML `required` on the ID, the birthday, and the type select can still stop submit in a browser that enforces constraint validation. First name and last name have no `required` attribute.
9. There is no `api/` directory. The POST body is the whole form, sent to `insertClient.php`.

### WF-3 — Insert a client

**Implemented** in `insertClient.php`:

1. The script reads `identityNumber`, `type`, `firstName`, `lastName`, and `birthday` from `$_POST` with no `isset` check. A missing key is a PHP notice (or a warning, depending on version) and becomes an empty use.
2. `active` is optional. If it was posted, `str_status` is that value (`Yes` from the checkbox). If it was not posted, `str_status` is `No`.
3. `gender` is optional. If it was posted, `str_gender` is that value (`male` or `female`). If it was not posted, `str_gender` is `Female`.
4. The script opens mysqli with the same host switch as `dbconnection.php`. It does not include that file.
5. Failure calls `die("Connection failed: " . $conn->connect_error)`. A following `print_r` of "Connection was NOT successful" is unreachable. Success prints `******Connection was Successful********`.
6. A prepared statement inserts `str_fname`, `str_lname`, `str_idNum`, `str_dob`, `str_gender`, `str_type`, `str_status` bound as seven strings. `int_ID` and `tms_dateCreated` are not in the statement.
7. The script does not test `prepare` or `execute`. After `execute` it always prints "New record(s) inserted successfully" and echoes the seven values.
8. It then calls `header("refresh:5;url=formClient.html")`. Body output has already started, so a default PHP configuration rejects that header. Whether this host enables output buffering is **Unknown / Requires Confirmation**.
9. There is no duplicate-ID check.

### WF-4 — List clients

**Documented** phase 2: an HTML/JS table of all clients and their details.

**Implemented** in `displayClient.php`:

1. The page includes `dbconnection.php`, which prints `connection successful` before the HTML when the connect works.
2. A line of links offers `displayClient.php` ("Display") and `formClient.html` ("Insert New Record").
3. `SELECT * FROM tbl_client` is fetched with `mysqli_query` and `mysqli_fetch_assoc`. There is no `ORDER BY`, limit, or empty-state message. A failed query is not tested before the loop.
4. Each row prints `str_fname`, `str_lname`, `str_idNum`, `str_type`, `str_dob`, `str_gender`, `str_status`, `tms_dateCreated`, and a link whose text is `int_ID`.
5. The link target is `editClient.php` with no query string. The id is not submitted.
6. A script looks up `getElementById('tbl')`. The table's id is `tblClient` and its name is `tbl`, so that lookup is null on a browser that follows the HTML. The table tag also carries `action` and `method`, which are not table attributes.

### WF-5 — Edit and save

**Documented** phase 2: clicking a row edits and saves the client.

**Implemented** in `editClient.php` and the script in `displayClient.php`:

1. `editClient.php` includes `dbconnection.php` and then `displayClient.php`, so the response is the list page followed by a second `<html>` document titled "Update Client Details".
2. Inside the list page's script, PHP assigns `$selectedUser` from the tokens `this.cells[0]` through `this.cells[8]`. PHP runs before the browser, so those tokens are not the clicked cells. On PHP 7 an undefined constant is a notice and a string. On PHP 8 it is an Error. The PHP version used in 2019 is not recorded.
3. The edit page loops `for ($a = 0; $a < $selectedUser; $a++)` and echoes each element. The bound is the value from step 2, not a row count from MySQL.
4. A `SELECT * from tbl_client where int_ID = $id` block is commented out, including the read of `$_REQUEST['int_ID']`. No `UPDATE` statement exists in the tree at HEAD.
5. Commit `aa88a34` (the HEAD subject) states "can not update directly from php".

## Functional requirements

### FR-001 — Client form fields

**Documented** phase 1 requires first name, last name, a 13-digit ID number, male or female, a client-type dropdown (SLA-Software, SLA-AWS, Technical), Submit, and Cancel.

**Implemented** on `formClient.html`, with two extra fields the README does not list: date of birth and "Is Account Active?". The type option values match the README strings. Save is the submit button. Clear is `type="reset"`. There is no Cancel button.

### FR-002 — Client validation on submit

**Documented** phase 1 step 3: "On form submit it must validate the fields."

**Implemented** only in the browser, and only as described in WF-2. PHP does not reject an empty name, a short ID, an unknown type, or a missing date. Because `onclick` ignores the function result, the JavaScript checks are alerts plus a preview, not a reliable gate. HTML `required` is a separate gate for ID, date of birth, and type.

### FR-003 — Persist a client row

**Documented** phase 1 step 5: a client table with the form fields plus auto-increment `per_id` and `per_date_created`.

**Implemented** insert columns and order:

`str_fname`, `str_lname`, `str_idNum`, `str_dob`, `str_gender`, `str_type`, `str_status`.

The statement is prepared and bound as seven strings. A comment on the bind says "2 strings and 1 interger". The type string is `sssssss`.

`int_ID` and `tms_dateCreated` are read by the list and are not written here. Whether they correspond to `per_id` and `per_date_created`, and whether the database fills them, is **Unknown / Requires Confirmation**.

Earlier inserts, not executed at HEAD, targeted `clienttype (type, said, status)` and `tbltype (cli_type, cli_identityNumber, cli_status)`. A commented `mysql_*` block in `dbconnection.php` names table `clients` and columns `FirstName`, `LastName`, `SAID`.

### FR-004 — Post only the ID number to `api/`

**Documented** phase 1 step 4.

**Not implemented.** No `api/` path exists in history. `insertClient.php` receives and stores every posted field. The form method is POST, not GET.

### FR-005 — Show all clients

**Implemented** on `displayClient.php`, as WF-4. The list includes date of birth, gender, account status, and date created, which are wider than the README's phase 1 field list.

### FR-006 — Edit and save from a row click

**Documented** phase 2 step 2.

**Not implemented** as a working save. The list links to `editClient.php` without an id. The update query is commented out. See WF-5.

### FR-007 — Client types as a maintained list

**Documented** phase 3: add a type, archive a type, link it to the client module.

**Not implemented.** Types are three hardcoded options. Nothing archives a type. The historical `tbltype` insert stored a type, an ID number, and a status on one row. That is not an add/archive catalogue, and it is not the statement at HEAD.

### FR-008 — Account active flag

**Implemented.** Not in the README.

The checkbox `active` is checked by default and posts `Yes`. `insertClient.php` stores that posted value, or `No` when the key is absent. The preview text uses `yes` / `no`, which is not the value stored.

The list prints `str_status` and does not interpret it.

### FR-009 — Gender

**Documented** as a male or female radio.

**Implemented** values on the form are `male` and `female`, with female checked. The insert default when the key is missing is `Female`. The list prints the stored string as-is.

### FR-010 — Theme

**Documented:** "Theme include in template."

**Implemented:** `template/` is Smart Admin 2.0 (`template/index.html` title "Dashboard | Smart Admin 2.0"). Application pages link `style.css` and do not reference `template/assets`.

## Discrepancies

| Topic | README or commit | Code at `aa88a34` |
|---|---|---|
| Post only the ID to `api/` | Phase 1 step 4, and "Do a Get of the fields" | Full-row POST to `insertClient.php`. No `api/` path in history |
| Column names | `per_id`, `per_date_created`, "client table" | `tbl_client`, `int_ID`, `tms_dateCreated`, and the `str_*` columns above |
| Cancel | Phase 1 | Reset button labelled "Clear" |
| Edit and save | Phase 2 | List link without an id. Update SQL commented out. HEAD commit subject says update from PHP could not be done |
| Type catalogue | Phase 3 | Three hardcoded options |
| Database name | Not named in the README | `client_module` until 2019-06-21, then `db_clientmodule` |
| Validation stops a bad submit | Phase 1 step 3 | JS return value is discarded. Server accepts the POST |

## Error conditions

| Condition | Behaviour | Provenance |
|---|---|---|
| mysqli connect fails on the list or edit page | `die("Connection failed: " . $conn->connect_error)` | **Implemented** in `dbconnection.php` |
| mysqli connect fails on insert | Same `die` text. The "NOT successful" `print_r` after `die` does not run | **Implemented** in `insertClient.php` |
| mysqli connect succeeds | `connection successful` on the list path. `******Connection was Successful********` on insert | **Implemented** |
| Insert `prepare` fails | Not tested. The next call is `bind_param` on the failure result | **Implemented** as the absence of a check |
| Insert `execute` fails | The success paragraph is still printed | **Implemented** |
| List query fails | Not tested. The `while` still runs on the query result | **Implemented** as the absence of a check |
| List returns zero rows | The table prints only the header row. No message | **Implemented** |
| Client rule fails inside `showInput()` | `alert` with the messages in WF-2. The form can still POST | **Implemented** |
| HTML `required` field empty | Browser-dependent constraint validation. Not a PHP path | **Implemented** in the form attributes |
| `header("refresh:...")` after the insert echo | Header is sent after body output | **Implemented**. Effect under output buffering is **Unknown** |
| Edit URL opened | List HTML, then a second document that does not load a row | **Implemented** |

`$_SERVER["SERVER_NAME"]` other than `localhost` uses the `lamp` credentials. It does not fail closed.

## Non-functional constraints visible in the code

- Database credentials are duplicated in `dbconnection.php` and `insertClient.php`. **Implemented.** Values are omitted from this PRD.
- Insert uses a prepared statement. The list query has no user input. The commented edit query would have interpolated `$id`. **Implemented.**
- Posted values are echoed into the insert response with no encoding. **Implemented.**
- No tests, sessions, CSRF tokens, rate limits, or security headers are in the repository.
- No PHP version, framework, or deploy manifest is pinned.

## Open questions

- Is "Unity" a product this module belongs to?
- What is the live `CREATE TABLE` for `tbl_client`, and do `int_ID` and `tms_dateCreated` populate themselves?
- Was edit-and-save abandoned on 2019-06-24, or left unfinished?
- Is phase 3 still required?
- Are the committed MySQL passwords still valid anywhere?
- Should the scan-host Sonar files be in scope?
- Which tracker owns `#160`, `#161`, `#173`, and `#180`?
