# Business rules — FootprintApp.net

Evidence date: 2026-10-14. Revision: `master` `629b199a`.

Rules are **Implemented** when the code enforces them. **Inferred** when the behaviour suggests a rule the code does not name. **Unknown** when the data or a later branch would be required. No separate business-rules document exists in the repository. Do not treat an inference as a signed-off rule.

Secrets (passwords, API keys, the login bypass string) are not written here.

## Access

### BR-001 — Login password check, and a bypass

**Implemented.** `Model_Profile::UserLogig_Check_Terms` selects from `UserLogin_view` where `Username` equals the submitted email and `Password` equals `md5` of the submitted password.

**Implemented.** If the submitted password equals a fixed string in `LoginController::loginuserAction` and `checktermsAction`, the code calls `AdminLogig_Check_Terms`, which selects the same view by username only, then continues login with that row's stored password. The fixed string is omitted. This is a credential bypass in source. It needs human review. It is not documented as an approved support procedure.

The username is concatenated into the SQL string.

### BR-002 — Who may remain on the main home

**Implemented** in `IndexController::init`:

- No Zend_Auth identity: redirect to `/login/`.
- `UnionType` > 2 and `Administrator` is not 1: if `Status` is 0, redirect to `/first`; otherwise redirect to `/union/`.
- `Status` is 0 (any union type that reached this line): redirect to `/first`.
- A commented condition also required `Active == 0` for the union redirect. That condition is not executed.

**Inferred.** `Status` 0 means the account has not finished a first-time step. The `/first` form contents were not read.

### BR-003 — Menu visibility is not the same thing as an administrator flag

**Implemented.** Menu sections follow JSON keys on `profile.Role` (`organisations`, `members`, `tournament`, `fixture`, `squad`, `accreditation`, `assessment`, `scout`, `bluecard`, `cms`, `food`). Nested keys such as `Actions.index` and `Actions.add` hide individual links.

**Implemented exceptions that ignore that JSON:**

| Who | What they see |
|---|---|
| UserID 1, 582, 1007, or 989 | InSight and Efiling |
| UserID 13329, 1, 989, 582, 4156, 1007, 1014, 2153, or 13305 | Team Zone (`/union/`) |
| UserID 1 | Add Tourn |
| `UnionType` < 3 | System Settings and the online-users block |

**Inferred.** Those ids were individual staff accounts. Whether they are still the correct people is **Unknown / Requires Confirmation**.

**Implemented.** The navigation partial is printed only when `profile.Active == 1`. Profile link is omitted when `Status` is 0, and also when the host is `admin.magus.events`.

### BR-004 — Private access-control rows

**Implemented.** `Model_AccessControl::getAll` returns every `AccessControl` row for `UnionType` 1. For every other union type it adds `Private = 0`.

**Inferred.** Type 1 is a super-union or national administrator. Not confirmed by a comment or a seed list.

### BR-005 — Admin module is looser for some browsers

**Implemented.** `Admin_IndexController::init` redirects to `/login/` only when there is no identity and the browser string is not empty and not `ie`. An empty user-agent or Internet Explorer is not redirected. When a session exists, `UnionType` > 2 and not `Administrator` redirects using an undefined `$db` (`$db->getRow(...)`). That path can error rather than redirect.

This differs from `default` `IndexController::init`. **Requires Confirmation** whether `/admin` is still used.

### BR-006 — SMS requires a session

**Implemented.** `SmsController::init` calls `exit` when there is no identity. It does not check a role key before `bylistAction`, `byprofileidAction`, or `teammanagersAction`.

## People and membership

### BR-007 — Passwords are md5, and new users are told the plaintext

**Implemented.** New credentials rows store `md5` of a generated password (`MembersController`). The email body includes that plaintext password and tells the reader to sign in at a doubled URL `http://http://www.footprintapp.net` and change it. Reset stores `md5` of the chosen password.

**Implemented.** Username on create is the email address. One insert sets `FPLogin` to 1. A later insert in the same controller does not. When each path runs was not fully traced.

### BR-008 — User types

**Implemented** in `Model_UserType`:

- `getAll` returns rows with `parent` > 0, ordered by `TypeName`.
- `getAllTypesDrop` also excludes `parent` 2.
- `getForSquadsdrop` returns rows with `squad` = 1.
- `getAllNoPlayers` excludes `TypeName` `Player` for a given parent.
- `getAllN` excludes `TypeID` 200.

What parent 2 and type 200 mean is **Unknown**.

### BR-009 — Dashboard population groups

**Implemented.** Ethnicity counts request type 2 (assigned to the Players view variable), 3 (Coaches), 8 (Referees; the variable is spelled `Refereers`), and 5 (Medical).

**Inferred.** Those integers are `user_type` ids. The model method was not opened to confirm the column.

### BR-010 — Transfers on the dashboard

**Implemented.** The dashboard assigns `Model_TransferRequest::getRequestsCompleted()` to the view. The menu shows `/members/transfers` when the members role allows `add`.

**Inferred.** Only someone who may add members may open the transfer list. The controller was not checked for the same gate.

### BR-011 — Profile lookup for the season

**Implemented.** After authentication, the stored identity is `getForSeason(UserID)`, not the raw `UserLogin_view` row. Login succeeds only when that profile's `UserID` equals the authenticated id. `LastLogin` is then set to the current timestamp. `tandc` is set to 1 when `LoginNow` is called with `$terms` 1, which `loginuserAction` always does.

## Squads, fixtures, and BokSmart

### BR-012 — Squad spreadsheet BokSmart column

**Implemented.** Union squad Excel export writes the header `BokSmart` and the profile value `boksmart`. The same pattern exists in `union_old` and in test/copy controllers.

**Implemented** in save paths that were grepped: if `boksmart` is non-empty it is copied onto the saved row; other branches assign `boksmart` from the request without that empty check. The surrounding form was not read. Treat "BokSmart is stored when the form sends it" as **Implemented**, and any "must be present before a player can be named" rule as **Unknown** on this revision.

A prod commit not in `master` (`0e9e7a97`) says BokSmart was added to the member quick view. That is not a rule of this revision.

### BR-013 — Unassigned squad position (FP3)

**Implemented** only as a commit that is contained in `master`: `cbe06447` ("Changed fp3 squads to use default position and to set as unassigned if no position"). The method body was not re-read for this document. Confidence: medium.

### BR-014 — Checklist document for the current year

**Documented** in `changelog.txt` (2019-05-17) against `SquadController::competitionsquadAction`: file-type check uses the checklist document, and only the last uploaded checklist for the current year is selected. The method was not re-read. Confidence: medium that the note still matches the code.

### BR-015 — Fixture officials

**Implemented.** Fixture `MatchOfficials` is JSON. The dashboard decodes it, loads profiles, and shows position names from `MatchOfficialPositions`.

**Implemented.** `api/fixtures/getdetails` was changed on 2022-12-01 to add timer information (commit subject). Field list not extracted.

### BR-016 — Live scoring order

**Implemented.** Home and away lineups from fixture JSON `TeamH` and `TeamA` are sorted by `Jersey` ascending before display. Commentary is loaded from `Model_ComentryAndCiting`.

## Accreditation and hospitality

### BR-017 — Accreditation menu rights

**Implemented.** List link requires `role.accreditation.Actions.list`. Add link requires `Actions.add` and points at `/accreditation/addtest/`.

### BR-018 — Hospitality import service

**Implemented** as commit `2d377106` on this revision: hospitality import includes a service; hospitality service status is set to 7; the service is stored on `AccreditationApplication.services`; the service is removed from the normal import.

Status value 7's label is **Unknown**.

### BR-019 — Applicant API timestamps

**Implemented** as commit `836ac5c1`: last-updated dates on the application and the calendar event were corrected for `api/accreditation/getapplicantsandroid`.

### BR-020 — Permits

**Implemented.** PDF and HTML permit actions exist (`PdfController::getpermitAction`, `AccreditationController::getpermitpdfAction` and related names). Approval rules before a permit can be downloaded were not extracted. `track.mypass.zone` documentation says that product fetches permit HTML from this host; this repository does not state that rule.

## E-filing

### BR-021 — E-filing is limited to four user ids

**Implemented.** The Efiling menu is not tied to a role key. The `organisations` role condition above it is commented out. The block runs for user ids 1, 582, 1007, and 989.

Grant approve, reject, and reactivate actions exist on `EfgrantsController` (`approvedAction`, `rejectAction`, `reactivateAction`, `approvegrantAction`). The conditions inside them were not read. Those rules are **Unknown**.

### BR-022 — InSight is the same four user ids

**Implemented.** InSight links (home, rugby participation, Superset pivot, custom, tournaments, organisations) use the same id list, and they are skipped entirely when `SERVER_NAME` is `admin.magus.events`.

## Communication

### BR-023 — Mail identity

**Implemented.** `EmailLayout::sendMail` sends HTML through `smtp.gmail.com` port 587 TLS, from `support@footprintapp.net`, display name `Footprint`. A comment above the current config says the change was to Gmail. An older transport host `mail.footprintapp.net` is commented out.

**Implemented.** A second method sets from `accreditation@magus.events`, display name `Magus.Events`.

Password-reset links are built with `http://` and `$_SERVER["SERVER_NAME"]`.

### BR-024 — SMS is tournament-scoped in the stored log

**Implemented.** `bylistAction`, `byprofileidAction`, and `teammanagersAction` call `MyMobileAPI::sendSms` and insert a `Model_Sms` row whose `event` text is `{TournName}: Tournament SMS`. `sender` is the session user id. `total` is hardcoded to 1 even for list sends (the list actions that were read).

**Inferred.** `total` is not the recipient count. That may be a defect. **Requires Confirmation.**

## Data and locale

### BR-025 — One default database

**Implemented.** `application.ini` sets the default adapter to `pdo_mysql`, host `db.footprintapp.net`, database `teamwizz_SARU`. A second block names database `teamwizz_API` under `apilogs.db.params`. No PHP reference to the `apilogs` key was confirmed. Using that second database is **Unknown**.

### BR-026 — Locale and time

**Implemented.** `resources.locale.default` is `af_ZA`. `public/index.php` sets `Africa/Johannesburg`. `Live_ScoreController` sets the same timezone again. Session cookie lifetime in ini is 864000 seconds (10 days). The `FootPrintApp` cookie set in the home controller lasts 86400 seconds (1 day).

### BR-027 — Deployment branch is not master

**Implemented.** Pipelines run only for branches `qa` and `prod`. A change that exists only on `master` is not deployed by this file.

## Rules that are not established

- A written approval matrix for accreditation, grants, or transfers.
- Age-grade eligibility, citation, or medical-clearance rules. Models exist (`AgeGroups`, `CitingEvents`, `Medical`, `PlayerInjury`). Their gates were not read.
- POPI consent rules. A remote branch is named `popi-popup-19-01-2022`. It was not inspected. `tandc` is set to 1 on every successful `loginuser` call.
- Whether BokSmart expiry is checked before a person can be selected. `boksmartmssql.php` can query an Officials/Courses expiry for one hardcoded number. It is not called by the Zend application.
- FP4 transfer validation described in prod commits after this revision (`2b2badb5` and others). Not rules of `629b199a`.
