# PRD — efiling

Evidence date: 2026-10-15. Source: `git@bitbucket.org:overdrive/efiling.git`, `master` at `67e722d` (2019-05-22, "#119 First Commit").

There is no prior PRD in the repository or in Silicon Ark. The README is only `# efiling`.

Two bodies of evidence are kept apart below.

- **In git.** Maintenance pages, theme assets, a partial dompdf tree, uploaded documents, and log files. These can be opened in the commit.
- **Observed on a server, source absent.** Stack traces and PHP messages inside `error_log` and `Efiling_Grnd_Error*`. They name controllers and SQL that this commit does not contain. They describe behaviour that happened. They are not a specification someone signed.

Anything the logs do not show is marked **Unknown / Requires Confirmation**.

## Product purpose

The pages that exist in git title themselves `SARU:: Efiling` and, on `index_test.php`, tell the visitor the site is down for maintenance.

The logs show a Zend Framework 1 site at `/home/saruefiling/public_html/` whose work is grant requests, project plans, member and union lists, tournaments, PDF output, and file upload. Uploaded filenames are rugby funding applications, invoices, reports, and budgets for provincial unions, dated in the names from about 2015 through 2019.

Provenance: title and maintenance copy are **Documented**. The grants purpose is **Inferred** from log symbols (`GrantsController`, table `grantsrequest`, view `plan/listprojects.phtml`) and from upload filenames. No product brief states the purpose in prose.

Who the contracting organisation is, beyond the title prefix `SARU::`, is **Unknown / Requires Confirmation**. The ordinary reading is South African Rugby Union. The repository does not define the acronym.

## Users and roles

| Role | What the evidence shows | Provenance |
|---|---|---|
| Visitor during maintenance | Sees the wrench page in `index_test.php` | Documented in that file. Whether this file was the live front door is Unknown |
| Signed-in user | `Zend_Auth` and `Zend_Auth_Storage_Session` appear on stack traces | Observed. Login form fields, password rules, and role names are Unknown |
| Union user | Query parameter `unionid` on the PDF print URL. Model `Model_Unions`. Notices for missing `UnionsArray` and `listUnions` | Observed names. What a union user is allowed to see is Unknown |
| Member | `MembersController`. `Model_Profile::getAllMembers` and `getUsername`. Notices for missing `MembersArray` | Observed names. Member fields are Unknown |
| Person saving a grant | `GrantsController::savegrantAction` inserts through `Model_GrantsRequests` | Observed. The screen and the actor's role are Unknown |
| Person working a plan | View `plan/listprojects.phtml` expects cost and applicant totals | Observed. The actor is Unknown |
| Approver | Notices for a missing `ApproveArray` | Observed key name only. An approval workflow is **Inferred**, and its steps are Unknown |
| Mail recipient | `PG_Controller_Plugin_EmailLayout::EmailTemplate3` is called and is not defined | Observed failure. Template contents and recipients are Unknown |
| zimrugby operator | The same `error_log` also records `/home/zimrugby/public_html/` | Observed path. Relationship to SARU users is Unknown |

No role table, ACL, or security-group source file is in the commit.

## Main workflows

### W1 — Read the maintenance page

Trigger: a browser opens `index_test.php`.

The file is HTML with a `.php` extension and contains no PHP tags. It loads Font Awesome 5.0.9 from the Font Awesome CDN and shows a red wrench, then:

- "Sorry we're down for maintenance"
- "We'll be back shortly"

Title: `SARU:: Efiling`. Favicon link: `/favicon.ico` (file `favicon.ico` is at the repository root).

There is no form, no retry, and no stated return time. Both this file and `down/index.phtml` end with the HTML comment `Localized`. What that comment means is **Unknown**.

Provenance: **Documented**.

### W2 — Open the down-page shell

Trigger: a browser opens `down/index.phtml`.

Implemented behaviour in the file:

1. The head sets the same title and favicon.
2. It links `/css/anon.css`, `/oldcss/styling.css`, `/css/stylesheets.css`, `/font-awesome-4.4.0/css/font-awesome.min.css`, `/dist/cropper.css`, `/css/ie7.css` for IE below 8, `/css/fullcalendar.print.css` for print, and `/css/als_style.css`.
3. It loads jQuery, jQuery UI, jQuery Form, jQuery Migrate, mousewheel, a cookie plugin, and Bootstrap from paths under `/js/`. Those files are in `js/` and `js/plugins/`, except where noted below.
4. Inside the head, the file runs `<?=header('Content-Type: text/html; charset=utf-8');?>`. That calls PHP `header()` after HTML has already started, and it also prints the function's return value.
5. The body element is empty.

`/oldcss/styling.css` is not in the tree. `css/styling.css` is. The other stylesheets named above are present, as are the scripts.

Provenance: **Implemented** in `down/index.phtml`. This is not the Zend layout. The logs name a different layout file, `application/layouts/default.phtml`, which is not in git.

### W3 — Save a grant

Trigger, from traces: `GrantsController::savegrantAction`.

This controller is not in the repository. The traces are.

Observed behaviour when the save fails:

1. Front controller dispatch reaches `savegrantAction`.
2. The action calls `Model_GrantsRequests->insertData`.
3. The model calls `Zend_Db_Table_Abstract->insert` on table `grantsrequest`.
4. MySQL either rejects the insert or, on other requests, the PHP process runs out of memory inside `Zend_Db_Statement_Pdo`.

Two recurring SQL failures, both on `savegrantAction` around line 1265 (file dated 2018-05-03) and line 1270 (files dated February to May 2019):

- `RequiredGrant` is given the string `undefined`, and MySQL reports incorrect integer value. The column is an integer. The literal `undefined` is the JavaScript value, which means a field was posted without a number.
- MySQL reports unknown column, and the supposed column name is a stretch of form text (school and age-grade words appear in the 2019-05-14 dump). The insert is treating submitted text as SQL column names. That is a defect observed in the logs, not a rule that those words are columns.

The successful path, the form fields, validation messages shown to the user, and who may save are **Unknown / Requires Confirmation**. The action does not appear to return a handled error page: the dumps are uncaught `PDOException` wrapped as `Zend_Db_Statement_Exception`.

Provenance: **Observed** in `Efiling_Grnd_Error*`.

### W4 — List projects on a plan

Trigger: rendering `plan/listprojects.phtml` (zimrugby path `app/views/scripts/plan/listprojects.phtml`, and the same script name under saruefiling).

On 14 March 2018 the view prints notices because these variables are not set: `TotalBudgetedCosts`, `TotalRequestCosts`, `TotalPaymentCosts`, `TotalAllocatedCosts`, `TotalApplicants`. Line numbers in the notice are 279, 281, 282, 283, and 288.

Provenance: **Observed**. It is **Inferred** that the project list is meant to show budgeted, requested, paid, and allocated totals plus an applicant count. The controller that should set those variables is not in git, and in this log it did not set them. A second view, `plan/getprojects.phtml`, is named in traces. Its contents are Unknown.

### W5 — Print a paged list to PDF

Trigger: something in the application calls `file_get_contents` on a URL of this shape (warning text in `error_log`, line 1451 of the calling file):

```text
http://saruefiling.com/pdf/print/page/{page}/start/{start}/length/{length}/search/{search}/column/{column}/unionid/{unionid}/current/{current}/dir/{dir}
```

Examples in the log use `length/20`, `current/2`, `dir/asc`, an empty `unionid`, and page values `0`, `5`, and the literal `undefined`. The HTTP request failed. A separate fatal error names `PdfController` only by file path, and a dompdf class `public/dompdf/include/cellmap.cls.php`.

`start`, `length`, `search`, `column`, and `dir` match the parameter names used by DataTables server-side paging. That match is **Inferred** from the names. `js/plugins/dataTables` is in the theme pack, which supports the inference and does not prove this URL is built by that plugin.

Provenance: **Observed** URL and failure. The PDF layout is Unknown. `unionid` empty in the failing calls does not prove the filter is optional; it proves these calls sent it empty.

### W6 — Upload a supporting file

Evidence is split.

- The commit contains `uploads/` (1,020 files) and `uploads.zip` (738 files, no PHP). Extensions in `uploads/` are mostly `.pdf` (491), `.docx` (316), `.xlsx` (118), then `.doc`, `.xls`, and a few images.
- Filenames include the phrases "SARU ONLINE FUNDING APPLICATIONS", "SARU FUNDING APPLICATION", invoices, reports, budgets, and strategic plans. Years 2015–2019 appear in names. Provincial names appear (Boland, Border, Limpopo, KZN, SWD, Griffons, Griquas, and others). This is evidence of what people stored, not a coded list of unions or categories.
- Logs name `public/uploads` on the server, a missing `FileUpload` index, a missing `file` index, an unset `fileNameNew`, and `unlink` of the uploads directory itself (`unlink(.../public/uploads/): Is a directory`).

The upload screen, allowed types, size limit, and virus scanning are **Unknown / Requires Confirmation**. Provenance of the folder is **Implemented** as stored files. Provenance of the server path and the unlink mistake is **Observed**.

### W7 — Other screens named only by a path

These files are on stack traces or in notice paths. None of them is in git. No step list can be recovered.

| Path under the saruefiling application | What the name suggests | Confidence |
|---|---|---|
| `controllers/IndexController.php`, `views/scripts/index/index.phtml` | Front page after login or the public index | Name only |
| `controllers/MembersController.php` | Member administration | Name only, plus `Model_Profile` |
| `controllers/OrganisationsController.php` | Organisations | Name only |
| `controllers/TournamentsController.php`, `views/scripts/dashboard/tournaments.phtml` | Tournaments on a dashboard | Name only |
| `controllers/EpgController.php`, `views/scripts/epg/formfiles.phtml`, `views/scripts/epg/loadform.phtml` | A form called EPG, with files | Name only. Expansion of EPG is Unknown |
| `controllers/TestController.php` | A test action that printed output before headers (line 9) | Observed warning |
| `controllers/PlanController.php` | Plan module that owns the project views | Inferred from the view folder `plan/` |
| `application/layouts/default.phtml` | Shared layout | Path only |
| `application/models/Profile.php`, `application/models/Unions.php` | Profile and union data | Paths, plus method names below |

## Functional requirements

### FR-001 — The maintenance page identifies the product and says it is down

Requirement: Opening `index_test.php` shows title `SARU:: Efiling` and the two maintenance sentences, with no login form.

Who: any visitor who opens that file.

Trigger: request for `index_test.php`.

Inputs: none.

Expected behaviour: the HTML described in W1.

Outputs: an HTML document.

Validation: none in the file.

Error conditions: none handled in the file. If Font Awesome's CDN is unreachable, the wrench icon does not load. The sentences remain.

Dependencies: Font Awesome 5.0.9 CDN script.

Implementation: **Implemented** in `index_test.php`.

Confidence: High for the file contents. Low that this was the production index. Production `public/index.php` in the logs is line 38 calling `Zend_Application->run()`, which this file does not do.

### FR-002 — The down page loads the admin skin and has an empty body

Requirement: `down/index.phtml` emits the SARU Efiling title, the stylesheets and scripts listed in W2, and an empty body.

Implementation: **Implemented**, with the `header()` call inside the head and a missing `/oldcss/styling.css`.

Confidence: High for the file. The production layout is a different file.

### FR-003 — Saving a grant inserts a row into `grantsrequest`

Requirement recovered from failures, not from source: `savegrantAction` persists the posted grant by `Model_GrantsRequests::insertData` into table `grantsrequest`.

Inputs observed by failure: an integer column `RequiredGrant`. Other columns are Unknown. One failure mode shows submitted text used as column identifiers, so the column list is built from user input in the code that was running. That behaviour is a defect.

Expected behaviour on success: **Unknown / Requires Confirmation**.

Error behaviour observed: uncaught database exceptions written to `Efiling_Grnd_Error*` files. No user-facing message is in those files.

Implementation: **Observed** in logs. **Not in this repository.**

Confidence: High that this insert ran on the server. Low as a complete requirement, because the happy path was not captured.

### FR-004 — The plan project list shows funding totals

Requirement recovered from unset variables: `plan/listprojects.phtml` reads `TotalBudgetedCosts`, `TotalRequestCosts`, `TotalPaymentCosts`, `TotalAllocatedCosts`, and `TotalApplicants`.

Expected behaviour if the variables are set: **Unknown** (labels, currency, rounding).

Error behaviour observed: PHP notices, and the page still rendered far enough to log them.

Implementation: **Observed**. Source absent.

Confidence: Medium. The variable names are explicit. The business definition of each total is not.

### FR-005 — A PDF print action reads a paged, union-scoped list

Requirement recovered from the URL the application requests: `PdfController` action `print` accepts `page`, `start`, `length`, `search`, `column`, `unionid`, `current`, and `dir`.

Validation observed: none. The value `undefined` is passed through into the URL.

Error behaviour observed: `file_get_contents` fails the HTTP call. Separately, dompdf throws `DOMText::getAttribute()` on a class that does not have that method, and PHP hits memory limits of 80 MB (`83886080`) and 800 MB (`838860800`) inside PDO statement execution.

Implementation: **Observed**. Source absent. dompdf's PHP classes are not in this commit either. Production called `public/dompdf/include/cellmap.cls.php`.

Confidence: High for the URL shape. Medium that `unionid` is an access constraint rather than a display filter.

### FR-006 — Users store supporting documents for funding activity

Requirement: the system keeps uploaded office documents for funding applications and reporting.

Evidence is the committed `uploads/` tree and the server path `public/uploads` in the logs. There is no upload validator in git.

Implementation: files are **present** in the snapshot. The upload feature's code is **absent**.

Confidence: High that documents were stored. Low for rules (type, size, who may upload, retention).

### FR-007 — Session authentication exists on the server

Requirement recovered from the stack: requests go through `Zend_Auth` with session storage before or during controller dispatch.

Login URL, credential fields, lockout, and session lifetime are **Unknown / Requires Confirmation**. Theme files `css/login.css` and a `loginBlock` function in `js/actions.js` are part of the admin skin. They are not proof of the SARU login fields.

The dompdf demo header `dompdf/www/head.inc` calls `auth_check()` after `session_start()`. That is upstream demo code. It is not the SARU login.

Implementation: Zend auth is **Observed**. Not in git.

Confidence: High that session auth ran. Low for the rules.

### FR-008 — Member listing is served by `Model_Profile::getAllMembers`

Observed signature pressure: callers on line 100 invoke `getAllMembers` without arguments 5, 6, and 7, which are defined on line 158. A later fatal calls `Model_Profile::getUsername` when that method does not exist (line 473 of the caller). Another fatal calls `Model_GrantsRequests::fetcRow` (spelled without the second `h`) at lines 764 and 769.

These are defects in the server code, recorded here so the missing source is not assumed to be consistent.

Argument meanings are **Unknown**.

Implementation: **Observed**. Source absent.

## Integrations

| System | Direction | What is known | Failure seen |
|---|---|---|---|
| MySQL via Zend PDO | Application to database | Table `grantsrequest`. Host `cp01-jhb.teamwizz.com` on one access-denied error. Adapter class `Zend_Db_Adapter_Pdo_Mysql` | Access denied, connection refused, DNS failure (`getaddrinfo`), "MySQL server has gone away", bad integer and unknown column on insert |
| `http://saruefiling.com` | Application to itself, server-side HTTP | PDF print URL in W5 | `file_get_contents` HTTP failure |
| dompdf | In-process PHP on the server | Class path under `public/dompdf/include/` | `DOMText::getAttribute()`, and this git tree does not contain that class |
| Font Awesome CDN | Browser to `use.fontawesome.com` | Script tag on the maintenance page, version 5.0.9 | Not exercised here |
| Email layout plugin | In-process | `EmailTemplate3` on `PG_Controller_Plugin_EmailLayout` | Call to undefined method. Transport, sender, and template body Unknown |
| Zend session / auth | In-process | Session storage class on the stack | "Session must be started before any output" and "headers already sent" |

No payment gateway, SMS provider, or external identity provider is named in the tree or the logs that were summarised.

## Important constraints

- The repository cannot build or boot the grants application. Constraints below that come from logs describe the 2018–2019 server, not runnable code in git.
- Timezone on log lines is `Africa/Johannesburg`.
- PHP on that server was a version that still used `Zend_Db` and `iconv_get_encoding` as a function (the zimrugby fatal is a missing iconv). Exact PHP version is **Unknown**.
- Memory ceilings recorded: 80 MB and 800 MB.
- `RequiredGrant` does not accept the string `undefined`.
- Grant insert failures in the ground-error files are uncaught.
- Committed uploads and logs are production data sitting in git. See technical documentation. This PRD does not copy document contents, personal names from filenames, or the database account name.

## Edge cases recorded in logs

| Case | What happened | Where |
|---|---|---|
| `RequiredGrant` posted as `undefined` | MySQL integer error, uncaught | Ground-error files dated 2019-02-07, 2019-03-07, 2019-03-19 |
| Form text used as a column name | MySQL unknown-column error, uncaught | Other ground-error files, 2018-05-03 through 2019-05-14 |
| Plan totals unset | Notices, page continued | `error_log` 2018-03-14 |
| PDF URL given `undefined` paging values | HTTP fetch failed | `error_log` |
| `getAllMembers` called with too few arguments | PHP warning | `error_log` |
| `fetcRow` and `getUsername` missing | Fatal | `error_log` |
| `EmailTemplate3` missing | Fatal | `error_log` |
| `unlink` of the uploads directory | Warning, directory not removed | `error_log` |
| Modules directory missing on zimrugby | `DirectoryIterator` exception for both `app/modules` and `application/modules` | `error_log` |
| Output before headers | `TestController.php` line 9, and `public/index.php` line 15 | `error_log` |
| Empty or null profile | Notices and fatals: `ToArray()` on null and on array, property of non-object, unset `profile` | `error_log` |
| Demographic and squad keys absent | Notices for indexes `Started`, `Benched`, `Coloured`, `Indian`, `White` | `error_log`. No rule in git says how these keys must be filled. Do not treat the notices as an equity policy |
| Database down or misnamed | Connection refused, DNS failure, access denied | `error_log` |

## Out of scope for this PRD

Behaviour that is not in the commit and not in the logs: registration, password reset, grant approval steps, payment of a grant, notification content, reporting periods, and any mobile client.
