# Business rules — SuperHero2020

Evidence date: 2026-10-02. Source: `master` at `ab4effc`.

No business-rules document exists in the repository. Rules below are taken from controllers, models, and views. Each rule is marked **Implemented** when the code enforces it, or **Inferred** when the behaviour suggests a rule that nobody wrote down. Rules that could not be settled are marked **Unknown / Requires Confirmation**.

Passwords are not copied into this document.

## Event selection

### BR-001 — Homepage and sign-in use one accreditation code

**Implemented.** `IndexController::indexAction` and `ApplicationController::loginAction` load `aCode` `5DEB6F11DDA93`. They do not read a code from the query string.

`applyAction` loads the `code` argument. A missing or unknown code stops the page with "Sorry this page does not exits".

Whether pinning the homepage to that code is the approved product rule is **Unknown / Requires Confirmation**.

### BR-002 — Applications close at the end of CloseDate

**Implemented.** Close time is `strtotime(CloseDate + " 24:00:00")` compared with the current time in `Africa/Johannesburg`. If the difference is less than 1 second, the closed screen is shown.

The closed screen tells the visitor to email `ContactName` at `ContactEmail`.

What happens when `CloseDate` is null or not a date is **Unknown**.

### BR-003 — Public categories exclude team categories and hidden categories

**Implemented.** `getAccreditationCategoryPublic` reads `AccreditationCategory_view` where `AID` matches, `Teams = 0`, and `hidden = 0`.

**Inferred.** `Teams = 1` categories are for squad or team applications handled somewhere else. This repository does not show a team-application screen. `GetRolesTourn` filters `AccreditationRoles_view` with `Teams = 1` and is not called by the three controllers.

### BR-004 — Roles are limited to the chosen category

**Implemented** in the browser. `ChangeSelectCat` keeps options whose `ACID` equals the selected category. The server stores whatever `data[Role]` is posted. It does not check that the role belongs to the category or the event.

## Who may apply

### BR-005 — An email should be unique per event

**Implemented** in the browser. `checkEmails` counts `AccreditationApplycation` rows with the same `email` and `AC`. The form shows "Email already in use,please use different email address."

**Implemented** gap: `insertData` does not enforce uniqueness. Two requests can store the same email. The apply view calls `/accreditations/application/checkemail`, which is not a route in this repository, so the check may not run on that view.

### BR-006 — The applicant must accept the terms

**Implemented** in the browser. `data[acceptTerms]` has class `required`. The visible sentence says the applicant accepts the terms, SA Rugby's Tournament and Competitions Accreditations Policy, and the Safety at Sports and Recreational Events Act. The popup HTML is the event's `TermsAndConditions` column.

The server does not reject a post that omits `acceptTerms`.

The accreditation-note checkbox is shown only when `AccreditationNote` is not empty and is not marked required.

### BR-007 — Media applicants supply a producer and, when the event spans days, the days they will attend

**Implemented** display rule.

- If `MediaOnly == 1`, the media block is visible.
- If `MediaOnly` is not greater than 0, category id `1` shows the block and marks `.MediaOnly` required. Other categories hide it.

**Inferred.** Category id `1` is the media category when the event itself is not flagged media-only. That mapping is not documented outside the script.

Day checkboxes exist only when `DaysDuration > 1`.

### BR-008 — Some roles require a supplier registration, as a notice only

**Implemented.** Role flag `SupplierReg == 1` shows a note that an owner, director, or manager who needs the load-in/out schedule before event day must register the company at `http://supplier.sarugby.online`. The form can still be submitted.

### BR-009 — A photo is required unless the selected role has photo = 1

**Implemented** as written in `getCurrentRole`: `photo == 1` removes the `required` class; any other role adds it. The variable `required` is not cleared when the role changes, so the first `photo == 1` selection leaves the photo optional afterwards.

**Unknown / Requires Confirmation** whether `photo == 1` was intended to mean "photo required". The condition reads as the opposite. An empty `User_pic` on the server is replaced with a built-in JPEG data-URI, so the database insert still gets an image string.

Client rule: files over 1.5 MB are rejected before upload.

## What is stored

### BR-010 — A new application is stamped with the event, the fixture, and a reference

**Implemented.** Insert sets:

- `AC`, `TID`, `FID` from the accreditation profile
- `DateApply` to the current timestamp
- `Ref` to 6 uppercase hex characters from 3 random bytes
- `History` to the string `None`
- `DaysAttending` to empty string when the field is absent

No status column is set in `applyAction`. The profile treats a row that is not Pending and not status 1, 2, or 3 as "New Application". **Inferred** that the database default for `Status` is 0 or null. The default is **Unknown / Requires Confirmation** because no schema SQL is in the repository.

### BR-011 — Linked events create extra rows, not one row with many events

**Implemented.** For each posted `apply[]` value, the same field array is inserted again with `AC` replaced by that value. The reference, photo, and personal fields are copied. `sendImage` and `EmailApplicant` run once, for the original event, before the loop.

`apply[]` is only added to the form when at least one linked row has `DateTo >= today`.

### BR-012 — The portrait for the live apply path is sent to Footprint

**Implemented.** `applyAction` does not write the file locally. It POSTs `ac`, `ref`, and `pic` to `https://www.footprintapp.net/accreditations/application/updateimage`.

`SavePhoto` writes `{AC_PATH}/photos/{Ref}.jpg` at 500×500. That call is commented out on `applyAction` and used by `applytestAction`. `AC_PATH` is not defined in this repo.

The profile displays the Footprint URL `https://www.footprintapp.net/accre/photos/{Ref}.jpg` when HTTP 200, otherwise `/accre/profile-icon.png`.

## Sign-in and editing

### BR-013 — The applicant's credential is the email plus the reference number for that event

**Implemented.** `UserLogin` requires `AC`, `Ref`, and `email` to match one row. There is no password column check. Whitespace is stripped in the browser before submit. The reference input is masked (`type="password"`).

A match stores `AAID` and `aCode` in `user_session`. Cookie lifetime in the ini backup is 864000 seconds.

### BR-014 — Profile edits are locked for Confirmed, Declined, and Approved

**Implemented** in `profile.phtml`.

| Condition | Label | Editable |
|---|---|---|
| `Pending == 1` | Pending | Yes |
| `Status == 0` and `DateApply < LastUpdate` | In Review | Yes (the lock line is commented out) |
| `Status == 1` | Confirmed | No |
| `Status == 2` | Declined | No |
| `Status == 3` | Approved | No |
| anything else | New Application | Yes |

`updateAction` does not repeat this lock. A POST can update any `AAID` that is sent, with no session check. **Implemented** behaviour. Whether that is an accepted rule or a defect is **Unknown / Requires Confirmation**.

`LastUpdate` on that action uses PHP format `Y-m-d H:s:i`, which writes seconds in the minute position and minutes in the second position. The In Review label compares `DateApply` with `LastUpdate` as strings or dates from the row. The effect of the swapped format on that comparison is **Unknown / Requires Confirmation**.

### BR-015 — Attendance is a one-time choice

**Implemented.**

- `att=1` stores `AttendanceRequest = 1`, sets `AttendanceResponseDate`, clears `AttendanceKey`, and emails `EmailAttandanceConfirmed`.
- `att=2` stores `AttendanceRequest = 2`, sets `Status = 2`, `History = "Declined by Applicant"`, `DeclinedDate`, clears `AttendanceKey`, and emails `EmailRejected` when that template is non-empty. The reason text is "Applicant confirmed NON-ATTENDANCE / CANCELLATION for this event."
- After the key is cleared, the same link renders "this link does not exist or has been used already".

**Inferred.** Clearing `AttendanceKey` is what makes the link single-use. There is no second check that the key was unused beyond the lookup.

## Mail

### BR-016 — Mail is sent only when the matching template column is non-empty

**Implemented.**

- New application: `EmailNotes`
- Decline / non-attendance: `EmailRejected`
- Attendance confirmed: `EmailAttandanceConfirmed`

Placeholders: `[ACCREDITATIONNAME]`, `[CONTACTNAME]`, `[CONTACTTELEPHONE]`, `[CONTACTEMAIL]`, `[REFNUMBER]`, `[APPLICANTNAME]`, and `[REASONNOTE]` on decline.

The message subject is always "Vodacom Super Hero Sunday 2020 Notification". The from-address is `accreditation@magus.events` with the name "Accreditation - Vodacom Super Hero Sunday 2020".

## Status values used in queries

These are **Implemented** query filters. They are not a written status policy.

| Value | Where |
|---|---|
| `Status = 3` | `getApproved`, `getAllRef` (reference list of approved rows), `getCue` (also `Cue = 1` and `AC > 26`) |
| `Status = 2` | Set by the applicant non-attendance path; profile label Declined |
| `Status = 1` | Profile label Confirmed. No writer in this repository |
| `Status = 0` with a later `LastUpdate` | Profile label In Review |
| `Pending = 1` | Profile label Pending, and it wins over the status checks |

Who may set Approved or Confirmed is **Unknown / Requires Confirmation**. It is not an action in this repository.

## Access

### BR-017 — This site does not authenticate staff

**Implemented.** There is no staff login, role, or permission check in the three controllers. `Accreditation_Administrators` can be queried by the model and is unused by those controllers.

**Inferred.** Approving an application is done in another application that shares the database. Requires confirmation.

### BR-018 — Several data-changing URLs are public

**Implemented.** `updateAction`, `updateimageAction`, `loginnowAction`, `checkemailAction`, `attendanceAction`, and `applyAction` do not require a logged-in session, except that `applyAction` redirects to the profile when a session already exists (`isloggedIN`). `applymultipleAction` does not call `isloggedIN`.

`checkemail`, `loginnow`, and the model queries concatenate request values into SQL. That is how the code is written. It is recorded as implementation behaviour, not as a recommended interface.

## Data this application will not decide

- The wording of `TermsAndConditions`, `EmailNotes`, `EmailRejected`, `EmailAttandanceConfirmed`, and `AccreditationNote` is stored on the event row. This repository does not define that copy.
- Zone, gate, permit, and load-in rules exist as tables and models only. No rule for them is enforced by a screen here.
- `can_apply($tid, $sid)` counts existing applications for a tournament and squad. Nothing in the controllers calls it, so it is not an applicant-facing rule in this site.
