# Technical documentation — sarugby (SA Rugby)

Evidence date: 2026-10-04.

Source: `git@bitbucket.org:overdrive/sarugby.git`, `master` at `6048eba` (2019-04-28, Mandus Momberg, "adding .htaccess files"). Parent: `8ef9258` (same day, "First Commit. WTF is going on here?"). Only branch: `master`. `origin/HEAD` points at `master`.

The scan-host path `~/projects/sonar_scan/repos/sarugby` was not on the machine that produced this document. Facts below come from that git revision. Passwords and bypass literals are omitted.

## Architecture

Zend Framework 1 MVC application. Apache accepts the request, the root rewrite forces HTTPS and `www` and maps the URL into `public/`, and `public/index.php` boots `Zend_Application`.

```text
.htaccess
  HTTPS + www host
  URI not under /public/  ->  public/{URI}
public/.htaccess
  existing file or directory is served
  otherwise index.php
public/index.php
  APPLICATION_ENV or "production"
  timezone Africa/Johannesburg
  UPDATE_PATH = public/archives
  Zend_Application(application/configs/application.ini)
application/Bootstrap.php
  _initDbAdapter is empty
PG_Controller_Plugin_LayoutPicker
  layout = module name
modules/{default|companies|company|bluecard|menu}
  controllers, views
application/models
  Zend_Db_Table
MySQL teamwizz_SARU
```

There is no `composer.json`, no `package.json`, no Dockerfile, and no CI file. Zend is the copy under `library/Zend`. `Zend_Version::VERSION` is `1.11.10`.

`Bootstrap::_initDbAdapter` does not create an adapter. The adapter comes from `resources.db` in `application.ini`.

## Repository layout

| Path | What it is |
|---|---|
| `Readme.md` | Restore notes, S3 backup name, untested HHVM command |
| `.htaccess` | HTTPS, www, internal rewrite to `public/` |
| `.gitignore` | `*.zip`, `*.tar`, `*.tar.gz`, `cgi-bin/`, `dev/`, `public/archives/`, `.DS_Store` |
| `public/index.php` | Front controller. Maintenance block commented out |
| `public/.htaccess` | Zend rewrite |
| `public/assets`, `bootstrap2`, `dist`, `global_assets`, `limitless`, `starters`, `fonts`, `html` | Front-end copies. The README says duplicates were left in place |
| `public/dompdf` | PDF library used by cron via `require_once("dompdf/dompdf_config.inc.php")` |
| `public/archives/` | Not in git. Runtime JSON, tournament images, accreditation photos |
| `application/configs/application.ini` | PHP, front controller, layout, locale, session, MySQL |
| `application/modules/` | Five modules |
| `application/models/` | Table classes |
| `application/layouts/` | `.phtml` layouts |
| `library/Zend` | Framework 1.11.10 |
| `library/PG/Controller/Plugin/` | Layout picker, mail layout, image upload, FPDF, match report helpers |
| `library/Other/Functions.php`, `Notifications.php` | Local helpers. Not traced action by action |

Top-level names from the task scan that are **not** in git: `sonar-project.properties`, `sonarqube_scan.bat`.

## Request routing

Zend default routing: `/{module}/{controller}/{action}/param/value`. The default module is `default`, so `/login` is `default/login/index`.

| Module | Controller class prefix | Layout file |
|---|---|---|
| `default` | none (`IndexController`) | `application/layouts/default.phtml` |
| `companies` | `Companies_` | `companies.phtml` |
| `company` | `Company_` | `company.phtml` |
| `bluecard` | `Bluecard_` | `bluecard.phtml` |
| `menu` | `Menu_` | `menu.phtml` |

`application.ini` also sets `admin`, `union`, `referee`, `gir`, and `accreditations` layout names. The layout plugin overwrites the layout with the module name on every request, so those ini keys do not select a layout unless something else reads them. No code reference to `union.resources.layout` was searched beyond the ini file. The four layout files `union`, `referee`, `gir`, and `accreditations` are not on disk. `admin.phtml`, `food.phtml`, and `repo.phtml` are on disk. `food` and `repo` are not module names, so the plugin will not select them.

## Runtime configuration

`public/index.php`:

- `display_errors` 1 and `E_ALL|E_STRICT` before the application starts.
- `APPLICATION_ENV` from the environment, else `production`.
- `UPDATE_PATH` = `realpath(public/archives)`. If that directory is missing, `realpath` is `false`.
- Include path: `library`, then the previous include path.

`application.ini` sections: `production`, `staging` (inherits production), `testing` and `development` (inherit production and turn `display_errors` off). `development` also sets `displayExceptions` to 0. Because the default environment is `production`, a normal start uses the section where errors are displayed.

| Key | Value |
|---|---|
| `resources.db.adapter` | `pdo_mysql` |
| `resources.db.params.host` | `teamwizz.com` |
| `resources.db.params.dbname` | `teamwizz_SARU` |
| `resources.db.params.username` | present in the file, not repeated |
| `resources.db.params.password` | present in the file, not repeated |
| `resources.locale.default` | `af_ZA` |
| `resources.session.cookie_lifetime` | `864000` |
| `resources.layout.layout` | `default` |
| Front-controller plugin | `PG_Controller_Plugin_LayoutPicker` |
| Autoload namespace | `PG_` |

Constants used in controllers and **not** defined in `index.php`: `UPDATES_PATH` (`NewsController`, `TournamentsController::resultoldAction`), `IMG_PATH` (`CronController::addtozip`).

## Data

No migration directory and no SQL dump are in git. Table names below are `$_name` on the model classes. Views are names in `select()->from`.

### Tables

| Model | Table |
|---|---|
| `Model_Tournament` | `tournament` |
| `Model_Squad` | `squad` |
| `Model_Fixture` | `Fixture` |
| `Model_FixtureScores` | `fixture_score` |
| `Model_MatchReport` | `Match_Report` |
| `Model_Logs` | `TournamentLogs` |
| `Model_Profile` | `profile` |
| `Model_NewsArticles` | `News_Articles_SA_Rugby` |
| `Model_Unions` | `ef_unions` |
| `Model_Ethnicity` | `Ethnicity` |
| `Model_Positions` | `positions` |
| `Model_Country` | `countries` |
| `Model_GirProfile` | `gir_profile` |
| `Model_ComentryAndCiting` | `ComentryAndCiting` |
| `Model_CompanyProfile` | `Companies_Profile` |
| `Model_UserProfile` | `Company_Members` |
| `Model_CompanyEvents` | `Company_Events` |
| `Model_BBEELevel` | `BBEE_Level` |
| `Model_BBEETypes` | `B_BBEE_Types` |
| `Model_AccreCompanyTypes` | `Accre_Company_Types` |
| `Model_AccreditationList` | `Accreditation_List` |
| `Model_AccreditationPermits` | `Accreditation_Permits` |
| `Model_AccreditationGates` | `EventGates` |
| `Model_AccreditationLoadingZones` | `Accreditation_Loading_Zones` |
| `Model_AccreditationLoadingServices` | `Accreditation_Load_Services_Types` |
| `Model_AccreVehicleTypes` | `vehicletypes` |
| `Model_TournamentFoodSession` | `Tournament_Foodsessions` |
| `Model_FoodSessionItems` | `SessionFood_Items` |
| `Model_MenuVenues` | `Menu_Venues` |
| `Model_Nutritions` | `Nutritions` |
| `Model_Allergens` | `Allergens` |

### Names used in SQL that have no `Model_*` class file

| Name | Used as |
|---|---|
| `co_credetials` | Auth table in default login. Spelling is the code's spelling |
| `CompaniesLogin` | Auth table in companies login. May be a view |
| `SARugbyListTourn_view` | `Model_Tournament::ListTourn` |
| `TournamentDetails_view` | `Tourment_min_Details` |
| `GameResults_view` | `fixturesResultsTest2` |

### Classes constructed but not defined under `application/models`

`Model_Credentials`, `Model_DbTable_Countries`, `Model_Assessments`, `Model_AccreditationsApplications`, `Model_AccreditationZone`.

A request that reaches those lines fatals with a missing class.

### File cache

| Path under `UPDATE_PATH` (`public/archives`) | Writer |
|---|---|
| `tournaments/years/{year}.json` | Home index |
| `tournaments/{TID}.json` | Home index, ajax |
| `tournaments/list.json`, `tournaments/squads.json` | `AjaxController` |
| `tournaments/latest_result.json` | `Model_Fixture::fixturesResultsTest2` |
| `images/tournaments/{TID}.jpg` and `thumb_{TID}.jpg` | Home index, ajax, from base64 columns |
| `images/squads/{SID}.jpg` | Ajax squad export |
| `Logs/{TID}.json` | Ajax and tournament profile |
| `squads/{SID}.json` | Ajax |
| `lastupdate.txt` | `AjaxController::getdataAction` |
| `accreditation/{MID}_image.jpg` | `UsersController::addAction` |
| `companies/{id}_image.jpg` | `Companies_LoginController::companypicAction` read; `uploadremotecompanyAction` write |

`NewsController` and `resultoldAction` use `UPDATES_PATH` instead, which is a different, undefined constant.

## Authentication

Two stacks share `Zend_Auth` and the same PHP session.

| Area | Adapter | Identity column | Credential column | Notes |
|---|---|---|---|---|
| `default/login` | `co_credetials` | `Username` | `Password` | Requires `FPLogin = 1` before auth. Stores `Model_Profile` row. Class `Model_Credentials` missing |
| `companies/login` | `CompaniesLogin` | `Email` | `password` | Stores the adapter row. Echoes `LOGIN` on the way through |

Company passwords are `md5`. Both login actions contain a hardcoded bypass. Values are not documented here.

Session cookie lifetime is 864000 seconds. Logout on the companies controller clears the identity and calls `Zend_Session::destroy()`.

Mail transports:

| Flow | Transport | From |
|---|---|---|
| Companies register, forgot, reset, permits | `Zend_Mail_Transport_Smtp` host `mail.sarugby.co.za`, port 25, auth login, username `accreditation@sarugby.co.za` | `accreditation@sarugby.co.za`, name "Registration at sarugby.co.za", subject "SARugby Notification" |
| Default login reset | `Zend_Mail` default transport (no SMTP array in that method) | `support@footprint.net`, name "SARU E-Filing" |

The SMTP password is in `RegisterController::sendMail` and `Companies_LoginController::sendMail`. It is not repeated here. `SMTPSecure = tls` is assigned as a public property on `Zend_Mail`, not passed in the transport config. Whether TLS is actually negotiated is **Unknown / Requires Confirmation**.

## Integrations

### AppWiz

`CronController::senddata_to_Api` POSTs `multipart/form-data` to `https://fsrugby.appwizard.co.za/scripts/AppWiz.dll`. Timeout is set to 86400 seconds. Called from `queingAction` only when `AC == 14`. Curl output is not written back in the lines inspected. Commented URL: `https://footprintapp.net/test/recievedata`.

### dompdf

`addtozip` loads HTML with `file_get_contents` of `{http|https}://{SERVER_NAME}/pdf/a6/id/{AAID}`, then renders A6 portrait at 300 dpi. There is no `pdf` controller in `application/modules`. The require path is relative: `dompdf/dompdf_config.inc.php`. The library directory on disk is `public/dompdf`. Whether PHP resolves that require depends on the working directory and include path. **Unknown / Requires Confirmation.**

### Google Charts

Menu view image `https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl=...`. No server-side call.

### Theme third parties

`default.phtml` references styles and scripts under `/wp-content/` and `demo.themeum.com` (WordPress 4.4.5 era URLs, WooCommerce 2.4.12 stylesheet names). Those files were not confirmed as a WordPress install inside this repo. The layout is static HTML served by Zend.

## Background work

There is no queue daemon, cron definition, or worker process in the repository. `default/cron/queing` and `default/cron/autorun` and `default/ajax/getdata` are ordinary web actions. Nothing in git schedules them. `autorunAction` is empty. `queingAction` has a commented `ignore_user_abort` flush block that does not run.

`set_time_limit(3000)` and `memory_limit` `800M` are set in `CronController`. Home and several other controllers set `180M`. Ajax sets `800M`.

## Security notes

These are observations from the tree, not a pentest.

- MySQL username and password are in `application/configs/application.ini` on `master`.
- SMTP password is in companies `sendMail` methods.
- Two login actions accept a fixed password string and then authenticate as the stored credential.
- `public/index.php` and the `production` ini section display errors.
- SQL fragments concatenate request parameters (`year`, email, reset key, ID number, registration number).
- `default` ajax, cron, test, and `indexdump` actions do not check `Zend_Auth`.
- Companies `testAction` methods print profile or permit rows. `EventsController::indexAction` `print_r`s events after the login check.
- Company password-reset mail contains the new password.
- Forgot-password links are `http://`, not `https://`, even though the root `.htaccess` redirects browsers to HTTPS.
- `mcrypt_create_iv` is used for references and passwords. `mcrypt` was removed from PHP after 7.1.
- A default image constant in `companypicAction` is a long base64 JPEG in source. It is not a credential. It is omitted here because of size.

## Development and testing

No PHPUnit, no `tests/` directory, no seeders, and no postman collection were found.

The README's suggested run, not executed for this document:

```bash
aws s3 cp s3://evolve-backup-assets/april-2019/sarugby-backup-28-04-19.tar.gz .
docker run --rm -ti -v $(pwd):/var/www -p 8080:80 hhvm/hhvm-proxygen:3.27-lts-latest
```

The author wrote that the database backup must also be restored and `application.ini` changed, and that they had not tried the container.

Local inspection was a read of the git tree at `6048eba`. The application was not booted. The database was not queried.

## Deployment

**Unknown / Requires Confirmation.**

Evidence that a host once existed:

- Site name `sarugby.online` and `www.sarugby.online` in the README, email links, and the menu QR target.
- Apache rewrite rules added the same day as the import.
- Database host `teamwizz.com`.
- S3 bucket `evolve-backup-assets` in `eu-west-1` named in the README.

No `appspec.yml`, no pipeline YAML, no server names beyond those, and no release after 2019-04-28.

## Duplication that affects runtime

`application/models/Copy of Fixture.php` declares `class Model_Fixture` again. Zend's class loader will load `Fixture.php` for `Model_Fixture` and will not load a file whose name is `Copy of Fixture.php`. The copy is inert unless something includes it directly.

`Copy of AjaxController.php` is not `AjaxController.php`, so it is not the routed class.

`companiess.phml` is not a `.phtml` layout the plugin will find for a module named `companies`.
