# Business rules — userModule

Evidence date: 2026-09-29. Revision: `master` `a8971a4`.

`README.md` states the phase goals. It does not state policies for duplicates, status, or who may call a page. Unless a rule is marked **Documented**, it is **Inferred** from the PHP and JavaScript at HEAD. Inferred rules describe what the code does. They are not a confirmed business policy.

The `lamp` database password is not copied here.

## Who may use the screens

**BR-001 — Any browser that can open the PHP file can create, list, update, and delete.**  
`userForm.php`, `registrationForm.php`, and `editUser.php` include the database connection and run their SQL without reading a session, cookie, or role. **Inferred.**

**BR-002 — Login does not grant a lasting identity.**  
A matching email and digest is meant to send the browser to `userForm.php`. The script never calls `session_start` and never sets a cookie. **Inferred.**

**BR-003 — The README asks for authentication.**  
Phase 4 says "build in a authtenticating on the user." **Documented.** BR-001 and BR-002 are the implementation. They do not satisfy that sentence. **Requires Confirmation** whether the open pages are a defect.

**BR-004 — User type is a label, not a permission.**  
`Administrator`, `Developer`, and `Technical user` are stored on the row and shown in the list. No branch checks `txtUserType` before insert, update, delete, or login. **Inferred.**

**BR-005 — Status does not block login.**  
Login compares `txtEmail` and `txtPassword` only. `txtStatus` is ignored. **Inferred.**

## Identity and uniqueness

**BR-006 — An ID number already stored means the person already exists.**  
**Documented** in the subject of commit `0f46e0e`: "an ID Number is unique per person."

**Implemented** only inside the create scripts, and only when `SELECT *` returns at least one row. The check is a PHP string compare on `txtIDNumber`. No `UNIQUE` constraint is in the repository, so a second insert path or a direct SQL insert is not covered by this rule. **Requires Confirmation** of the live table.

**BR-007 — Email is not required to be unique.**  
Nothing compares `txtEmail` before insert. Login treats the first row whose email and digest both match as success and stops. A second row with the same email is never considered. **Inferred.**

**BR-008 — The login name is the email.**  
**Documented** phase 4 asks for a username. **Implemented** field is `txtEmail`. There is no username column in the insert list.

**BR-009 — The ID number is the key for update and delete.**  
Both statements filter `WHERE txtIDNumber = $id` using the value posted from the form, not `intUserID`. If the operator changes the ID in the form and then updates, the `WHERE` uses the new ID. **Inferred.**

## Passwords

**BR-010 — The password is stored as MD5.**  
**Documented** in phase 4 ("encrypt (md5) the password on the post") and **Implemented** with PHP `md5`. The input is passed through `mysqli_real_escape_string` first, and the digest is of that escaped string. Create and login both do the steps in that order, so they match each other.

**BR-011 — Confirm password never reaches the database.**  
The create forms post `confirmPassword`. The insert column list has no confirm field. Equality is enforced only when `validation()` runs. **Inferred.**

**BR-012 — Minimum length 4 applies in the browser on the create forms.**  
`validation()` alerts `Minimum password lenght is 4` (spelled that way in the script). PHP does not check length. **Inferred.**

**BR-013 — Edit does not change the password.**  
The edit form has no password inputs. The `UPDATE` column list omits `txtPassword`. **Inferred.**

## Status

**BR-014 — The inactive checkbox decides the stored status.**  
The server sets a temporary value from `empty(active)`, then overwrites it from `empty(inactive)`. Because an unchecked box is missing from the request, `empty` is true when the box was not checked.

| Inactive posted | Stored `txtStatus` |
|---|---|
| No | `Active` |
| Yes | `Inactive` |

The active checkbox does not change the result. **Inferred** from the two ternary lines, which are copied in `userForm.php`, `registrationForm.php`, and `editUser.php` (`$_POST` on create, `$_GET` on edit).

**BR-015 — The browser asks for at least one status box on the create forms.**  
`validation()` alerts `User status can either be active or inactive` when neither box is checked. Both may be checked. In that case BR-014 stores `Inactive`. **Inferred.**

**BR-016 — There is no dormant status at HEAD.**  
A dormant checkbox existed in commit `3949b979` and is gone. **Inferred** from history versus the current forms.

## Names, ID, gender, type

**BR-017 — The create browser accepts the name when either first or last name is letters only.**  
The test is `firstname` match **or** `lastname` match against `/^[a-zA-Z]+$/`. Spaces, hyphens, and digits fail a field, but the other field can still let the form through. The alert `Field must only be letters!` runs only when both fail. **Inferred.**

**BR-018 — The ID must be 13 digits in the browser.**  
**Documented** phase 1 ("13 numbers") and **Implemented** in `validation()` (`length == 13` and `/^[0-9]+$/`). The input type is `number`. PHP does not check length. A numeric input cannot keep a leading zero. **Inferred** consequence of `type="number"`.

**BR-019 — Gender is required in the browser and stored as `Male` or `Female`.**  
Radio values are those two strings. The edit click checks male only when the cell text is exactly `Male`; every other value checks female. **Inferred.**

**BR-020 — User type is one of three fixed strings.**  
**Documented** phase 1 names them Admin, Developer, and Technical User. **Implemented** option text, which is also the submitted value, is `Administrator`, `Developer`, and `Technical user`. The dropdown has no empty option, so a create always submits one of the three. Phase 3's add and archive rules are **Documented** and not implemented.

**BR-021 — Saving an edit can replace the type with Administrator.**  
The row-click script does not set the select. The first option is `Administrator`. An update that does not touch the dropdown writes that default. **Inferred.**

## Dates and notes

**BR-022 — A new row's created date is the server's calendar date.**  
`date("Y-m-d")` at insert time. The disabled field on the form is not the value that is stored, though it displays the same format. Timezone follows the PHP default. That timezone is **Unknown**. **Inferred.**

**BR-023 — Edit can change the created date.**  
The edit control is `type="date"` and is included in the `UPDATE`. **Inferred.**

**BR-024 — Notes are optional.**  
`validation()` does not read `userNote`. The column is still inserted, including an empty string. **Inferred.**

**BR-025 — Date of birth is required in the browser and not interpreted.**  
It must be non-empty for `validation()` to pass. Nothing checks that it matches the ID number, that it is a real date, or that it is in the past. **Inferred.**

## Listing and delete

**BR-026 — The edit list is every row, with no filter and no paging.**  
`SELECT * FROM `user``. **Inferred.**

**BR-027 — Delete is immediate.**  
The Delete button submits the GET form. There is no second confirmation in the page. **Inferred.**

**BR-028 — The create-page table is not the register of users.**  
It prints the variables from the current request, one row, including blanks before the first submit. **Inferred.**

## Connection

**BR-029 — Only two hostnames are configured.**  
`localhost` and `lamp`. Any other `SERVER_NAME` does not select a database. **Implemented** in the `switch`. Which hostname was used outside a developer machine is **Unknown / Requires Confirmation**.
