# Technical documentation — SaruEfiling.com

Evidence date: 2026-10-04. Source: `git@bitbucket.org:overdrive/saruefiling.com.git`, `master` at `379437f8` (2023-03-07).

Credentials, bearer tokens, and the login bypass string are described only by where they live. They are not reproduced.

## Stack

| Item | Evidence |
|---|---|
| Language | PHP. Server version **Unknown / Requires Confirmation** |
| Framework | Zend Framework 1.11.10, vendored at `library/Zend` (`Zend_Version::VERSION`) |
| Front controller | `public/index.php` builds `Zend_Application` with `APPLICATION_ENV` or `production` |
| Database | `pdo_mysql`. Production section of `application/configs/application.ini` points at host `db.footprintapp.net`, database `teamwizz_SARU` |
| Timezone | `Africa/Johannesburg` set in `public/index.php` and again in `EpgController` |
| Locale | `resources.locale.default = af_ZA` |
| UI | Server-rendered `.phtml`, jQuery, jQuery UI tabs, Font Awesome 4.4.0, an older admin theme under `public/css` |
| Spreadsheets | PHPExcel under `public/Classes/PHPExcel` |
| PDF | Dompdf under `public/dompdf`, FPDF under `library/PG/Controller/Plugin` |
| Charts | Highcharts assets and an exporting server under `public/charts` |
| HTTP client in cron scripts | Guzzle, installed under `public/cron/vendor` and `public/vendor` |

`composer.json` is not at the repository root. Vendor trees are committed.

`application/Bootstrap.php` declares an empty `Bootstrap` class. Autoloader namespace `PG_` maps to `library/PG`.

## Repository layout

```text
application/          Zend controllers, models, views, layouts, configs
library/              Zend Framework 1 and PG plugins
public/               Document root candidate: index.php, css, js, uploads, cron, vendor
scripts/              CodeDeploy hook scripts
Footprint_files/      Nested older copy of application, library, and public
appspec.yml
bitbucket-pipelines.yml
codedeploy_deploy.py
.htaccess             Rewrites into public/
```

The task that created this documentation listed `sonar-project.properties` and `sonarqube_scan.bat` at the top of a scan-host working copy. Those two files are not in `master` at `379437f8`. Provenance: **Unknown** whether they exist only as untracked local files.

Controllers are flat. There is no Zend module directory. `PG_Controller_Plugin_LayoutPicker` sets the layout name to the module name on every request. The default module name is `default`, and `application/layouts/default.phtml` is the shell. `application.ini` also mentions a `client` layout. No `client.phtml` was found under `application/layouts`. Provenance: **Implemented**.

## Request path

1. `.htaccess` rewrites a URI that does not already start with `/public/` to `public/{uri}`.
2. `public/index.php` raises memory limit to 800M, sets `set_time_limit(0)`, defines `UPLOADS_PATH`, `IMG_PATH`, and `SYS_PATH`, and runs the application.
3. Routes are the Zend default: `/{controller}/{action}/{param}/{value}`.
4. Most HTML controllers redirect to `/login/` when `Zend_Auth` has no identity. `EpgController` skips that check only when the action name is `pdf`.

`EpgController` is about 8,000 lines and contains the form, report, Excel, and upload actions. `loadform.phtml` is similarly large and encodes many HTML element types in one view.

## Data

Models are `Zend_Db_Table` subclasses. Representative tables:

| Model | Table | Used for |
|---|---|---|
| `Model_EPG` | `epg` | One filing per union and form: status, JSON `formdata`, approval columns |
| `Model_EPGForm` | `epg_forms` | Form definition, confidential flag, member and approver JSON |
| `Model_EPGHeader` | `EPG_Headers` | Section structure |
| `Model_EPGSubHeader` | `EPG_SubHeaders` | |
| `Model_EPGSections` | `EPG_Sections` | |
| `Model_EPGHtml` | `EPG_HTML` | HTML snippets on a section |
| `Model_EPGElements` | `EPG_FormElemets` | Field instances. The table name is spelled that way in the model |
| `Model_EPGDocumentUpload` | `EPG_FILES` | Uploaded form files |
| `Model_Profile` | `profile` | Person |
| `Model_Credentials` | `co_credetials` | Username, MD5 password, `EFLogin`, `FPLogin`, `Role`, `LastLogin`, `ResetKey` |
| `Model_Unions` | `ef_unions` | Organisations |
| `Model_GirProfile` | `gir_profile` | Dashboard ethnicity counts |
| `Model_GirReport` | `Gir_Report` | GIR overview |
| `Model_Projects` | `projects` | Plan and budget |
| `Model_Grants` | `grants` | Grants |
| `Model_Efiling` | `ef_efiling` | Older e-filing table, separate from `epg` |
| `Model_EmailTemplate` | `Template_email` | |
| `Model_EmailSent` | `Email_report` | |

The spelling `co_credetials` is the real table name in the adapter.

`epg.formdata` is a JSON object keyed by page. `savedataAction` replaces one page key per post. `SortEmptyData` coerces empty values to `0` on `insertData` and `updateData`.

There are no migration files. Schema changes are **Unknown / Requires Confirmation** (they are not in this repository).

Queries are built by concatenating request parameters into `where` strings. That is the data-access style throughout the controllers that were read. Provenance: **Implemented**.

## Authentication

- Adapter: `Zend_Auth_Adapter_DbTable` on `co_credetials`.
- Gate before the adapter: row must have `EFLogin` = 1, otherwise the identity string is discarded.
- A hardcoded password in `LoginController::indexAction` substitutes the stored hash. Not copied here.
- Identity stored in the session is the profile row.
- `/app/login` is a separate JSON login for scout users and does not use the `EFLogin` check. It compares `md5` of the posted password and requires `scout.Actions.app`.
- Reset links look like `http://{HTTP_HOST}/login/reset/user/{md5 key}` (not forced to HTTPS in the string).

Menu authorisation is view-side JSON checks. It is not a Zend_Acl resource map. `Model_Priviledges` reads `privileges` by `RoleID` and `ModelID` and was not seen on the e-filing path that was traced.

## File storage

`UPLOADS_PATH` is `public/uploads/`, which `.gitignore` ignores. ID copies are moved there and the profile stores an `http://{host}/uploads/{md5 name}` URL. Form uploads go through `/epg/fileupload` (`EpgController::fileuploadAction`). Allowed ID extensions on first login: jpg, png, jpeg, gif, pdf.

## Mail

`library/PG/Controller/Plugin/EmailLayout.php` sends with `Zend_Mail` from `support@saruefiling.com`, display name `SA Rugby Efiling`. Template selection is numeric (`EmailTemplate2(userId, 2, 6, formId)` on activate).

`public/emailCron/` and `public/cron/vendor` include PHPMailer. SMTP host and password changes appear in the 2022 commit messages. Current SMTP settings inside those scripts were not copied. Which sender production uses is **Unknown / Requires Confirmation**.

Approval and submit notification calls in `EpgController` are commented out. Activate still sends.

## Background work

No queue, no worker daemon, and no cron table in the repository.

`public/cron/getData.php` and `getManagers.php` are HTTP scripts (`$_GET['a']` is `file` or `db`). They POST GraphQL to `https://admin-api.globalperformance.co.za/authed/graphql` with a hardcoded bearer token whose `exp` claim is 1609372800 (2020-12-31). The `db` action connects to MySQL `127.0.0.1`, user `root`, empty password, database `saruefiling`, which is not the application database in `application.ini`.

Provenance: **Implemented** as files. Scheduling is **Unknown / Requires Confirmation**. The token should be treated as exposed and expired; it is not copied here.

`scripts/start_server` runs `service httpd start`. `scripts/install_dependencies` and `scripts/remove_archives.sh` are the other CodeDeploy hooks named in `appspec.yml`.

## Deployment

`bitbucket-pipelines.yml` image is `python:3.5.7`.

| Branch | Step | Variables |
|---|---|---|
| `development` | Deploy on QA | `QA_AWS_ACCESS_KEY_ID`, `QA_AWS_SECRET_ACCESS_KEY`, `QA_AWS_DEFAULT_REGION`, `QA_APPLICATION_NAME`, `QA_DEPLOYMENT_GROUP_NAME`, `QA_S3_BUCKET` |
| `master` | Deploy on PROD | the `PROD_` equivalents |

The step installs `zip` and `boto3==1.3.0`, writes `/tmp/artifact.zip` from `*`, and runs `codedeploy_deploy.py`. That script uploads the zip to S3 and creates a CodeDeploy deployment. The version label is a timestamp.

`appspec.yml` (version 0.0, os linux):

- Files: `/` to `/mnt/data/www/html/saruefiling.com/saruefiling.com/`
- Owner and group `apache`
- BeforeInstall: `scripts/install_dependencies`
- ApplicationStop: `scripts/remove_archives.sh`
- ApplicationStart: `scripts/start_server`

The zip is `*`, so `Footprint_files/`, `library/`, and `public/vendor/` all ship. `application.ini` is part of the tree that was cloned, so a pipeline that zips the Bitbucket checkout will include it if the file is tracked.

`.htaccess` www and HTTPS redirects are commented out. Whether TLS is terminated elsewhere is **Unknown / Requires Confirmation**.

Branches `qa` and `prod` have no pipeline step. Their tips are in 2020. `development` last moved on 2020-02-27. `master` last moved on 2023-03-07.

## Environments in application.ini

`[production]` holds the database and the settings below. `[staging]` inherits production with no overrides. `[testing]` and `[development]` turn display errors on. `[development]` also sets `displayExceptions` to 1.

Production display errors and front-controller exceptions are off.

The database password is in this file. It is not copied here.

## Security notes

These are observations from reading the code, not a penetration test.

- Production database credentials are in `application/configs/application.ini`. `.gitignore` names that file, and the file is still in the tree that was cloned.
- Login contains a hardcoded password bypass.
- Passwords are MD5.
- SQL fragments are concatenated from request parameters in the controllers that were read.
- The GraphQL bearer token is committed under `public/cron/`.
- `public/cron/getData.php` `db` mode uses `root` with an empty password against localhost.
- Reset links are built with `http://` and `$_SERVER["SERVER_NAME"]`.
- `EpgController::testAction` calls `notifications(669, 1)` with no render. `AppController` turns `display_errors` on.
- User ids 1, 582, 1007, and 74664 are special-cased in views.

No rate limit, captcha, row-level security policy, or security header configuration was found.

## Testing and quality

There is no PHPUnit (or other) test suite, no CI test step, and no coverage gate. The pipeline only deploys.

`TestController::mailAction` sends mail. Many `*test*` views are scratch screens.

`sonar-project.properties` is not in this revision.

## Operations that are not in the repository

- PHP version and Apache vhost on the CodeDeploy host.
- Whether a cron invokes `public/cron/*.php`.
- Database backups, monitoring, and log shipping.
- Who owns the AWS deployment groups.
- Whether `teamwizz_SARU` is shared with other Overdrive products. The host name `db.footprintapp.net` and the database prefix `teamwizz_` make sharing **Inferred**, not proven.

## Related trees inside the repo

`Footprint_files/` repeats controllers, models, views, and `application.ini`. The live front controller does not point `APPLICATION_PATH` at it. Deploy still copies it because the artefact is the whole repository.

`application/old/` holds earlier model copies.

Do not treat those trees as the running application unless a vhost is found that says otherwise. That vhost is not in this repository.
