# Technical documentation — permits.magus.events

Evidence date: 2026-10-07. Source revision: `master` at `4617ab1` (2022-06-01). Only branch: `master`.

Secrets in `application.ini` and in the mail methods are omitted.

## Stack

| Item | Evidence |
|---|---|
| Language | PHP. `mcrypt_create_iv` is called. That extension is not part of PHP 7.2 or later. Server version is **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` |
| Modules | One module, `application/modules/default` |
| Database | PDO MySQL via `resources.db`. Database `teamwizz_SARU`, host `teamwizz.com`, user `teamwizz_ADMIN` |
| Mail | `Zend_Mail_Transport_Smtp` to `mail.sarugby.co.za`, port 25, auth `login` |
| PDF | dompdf under `public/dompdf`, called from `IndexController::getpermitpdfAction` |
| Timezone | `Africa/Johannesburg` set in `public/index.php` |
| Locale | `af_ZA` in `application.ini` |
| Session | `resources.session.cookie_lifetime = 864000` |
| Package manager | No `composer.json` |
| Tests | No PHPUnit, no fixture database, no README |

Custom code lives in `library/PG` (namespace `PG_`, registered as `autoloaderNamespaces.pg`) and `library/Other`.

## Request path

Root `.htaccess`:

1. `RewriteCond !{HTTPS} off` then, when the host does not start with `www.`, redirect to `https://www.{host}{uri}`. The condition is written without a `%` before `{HTTPS}`. Whether Apache treats that as intended is **Unknown / Requires Confirmation**.
2. If the URI is not already under `/public/`, rewrite into `public/`.

`public/.htaccess` serves existing files and directories, and otherwise rewrites to `index.php`.

`public/index.php`:

- Forces `display_errors` and `E_ALL|E_STRICT`. The production ini sets `display_errors = 0` and `displayExceptions = 0`. The entry script wins for `display_errors`.
- A maintenance message and image are commented out, so they do not stop the site.
- Defines `APPLICATION_PATH` as `application/` and `UPDATE_PATH` as `public/archives/`.
- Does not define `AC_PATH`. The PDF action concatenates `AC_PATH`.

`Bootstrap::_initDbAdapter` is empty. The database adapter comes from the ini resource.

`PG_Controller_Plugin_LayoutPicker` is registered as `resources.frontController.Plugins.LayoutPicker`. On `preDispatch` it sets the layout to the module name. The only module is `default`, so the layout file is `application/layouts/default.phtml`. The ini also names layouts `admin`, `union`, `referee`, `gir`, and `accreditations`. Those files are not in `application/layouts/`. `layout.phtml` is present and is not selected by the plugin.

Login, register, walk-in search, and several permit posts call `disableLayout()`.

## Configuration

`application/configs/application.ini` sections:

| Section | Behaviour |
|---|---|
| `production` | Errors hidden in the ini, library path, layout `default`, locale, session, module directory, MySQL |
| `staging` | Inherits production with no overrides |
| `testing` | Inherits production and repeats `display_errors = 0` |
| `development` | Inherits production, `display_errors = 0`, `displayExceptions = 0` |

There is no `.env`. The entry script defaults `APPLICATION_ENV` to `production` when the environment variable is absent.

`appnamespace` is empty. Models are included by Zend's conventional `Model_` autoload from `application/models` only if the module bootstrap or the application resource loads them. Controllers `new Model_*` directly. A duplicate class `Model_Fixture` exists in both `Fixture.php` and `Copy of Fixture.php`. Which file PHP loads depends on autoload order. **Inferred** the copy is accidental. No controller constructs `Model_Fixture`.

## Controllers and routes

Zend default routing: `/{controller}/{action}/{param}/{value}`. No custom routes file was found.

| File | Class Zend will load | Gate |
|---|---|---|
| `LoginController.php` | `LoginController` | None. Layout disabled in init |
| `IndexController.php` | `IndexController` | Always redirects |
| `WalkinsController.php` | `WalkinsController` | `EVENT.EID` or `/login/` |
| `PermitsController.php` | `PermitsController` | `EVENT.EID` or `/login/` |
| `RegisterController.php` | `RegisterController` | None. Layout disabled |
| `UsersController.php` | `UsersController` | `Zend_Auth` or `/login/` |
| `CompanyController.php` | `CompanyController` | `Zend_Auth` or `/login/` |
| `ErrorController.php` | `ErrorController` | Zend error handler |
| `AuthonController.php` | `AuthonController` | Empty actions |
| `OrganisationController.php` | `OrganisationController` | Empty `profileAction` |
| `OrgController.php` | Class is `Company_OrgController` | Not the class name Zend expects for this file |
| `EventsController.php` | Class is `Companies_EventsController` | Not the class name Zend expects. Body redirects to `/companies/login/` |

`ErrorController::errorAction` assigns the error handler message. `getLog` is present. No log path is configured in the ini file.

## Data

Tables and views named by the models and queries that the permit and registration code actually uses:

| Name | Used for |
|---|---|
| `Accreditation_Permits` | Permit rows. Primary key used as `PID` |
| `Permits_view` | Dashboard, profile, and the post-insert email lookup |
| `Permit_Status` | `getStatus`. Not called by the walk-in actions |
| `Accreditation_List` | Event row, load-in and load-out date JSON, prefix inside `getRef` |
| `Accreditation_Loading_Zones` | Zones. Queried by the unreachable dashboard and assigned on create screens |
| `Accreditation_Load_Services_Types` | Service type dropdown |
| `EventGates` | Gates model `AccreditationGates`. Assigned on create screens |
| `vehicletypes` | Vehicle type dropdown |
| `Company_Events` | Link of company to event, including `passkey`, `CEID`, `Status` |
| `Companies_Profile` | Company record |
| `Company_Members` | People, passwords, reset and validate keys |
| `CompaniesLogin` | Table named by the unused auth adapter |
| `BBEE_Level` | Registration dropdown |
| `Accre_Company_Types` | Registration and profile company types. The variable in `RegisterController` is named `$db_BBEETypes` but the class is `Model_AccreCompanyTypes` |
| `countries` | Country dropdown |

`Company_Events.Status = 2` on registration is a different column from `Accreditation_Permits.Status`. The permit status labels do not document what company-event status `2` means. **Unknown / Requires Confirmation.**

Permit columns written or cleared by this code include `PermitNr`, `PermitRefSerial`, `DateCreated`, `EID`, `CID`, `Status`, `Accupants`, `DriverID`, `ApplicantModifiedDate`, `ApplicantModifiedBy`, `CancelledDate`, `CancelledBy`, `CancelMessage`, plus the posted `data` fields (`LoadType`, `ServiceType`, `YourRefNumber`, `RequestDate`, `TimeIn`, `TimeOut`, `Job_description`, vehicle and trailer fields, `TotalStaff`). Duplicate also unsets gate, zones, and the approval, decline, and cancel columns before insert.

`Accupants` is a JSON list. The column name is spelled that way in the code.

Views and tables that must already exist in MySQL are not created by this repository. There are no migrations.

## Files written at runtime

| Path | Writer |
|---|---|
| `public/archives/companies/{CID}_image.jpg` | `CompanyController::uploadAction`, `LoginController::uploadremotecompanyAction` |
| `public/archives/accreditation/{MID}_image.jpg` | `UsersController` add and profile |
| `{AC_PATH}/loadin/pdf/permit_{id}.pdf` | `getpermitpdfAction`. `AC_PATH` is undefined in the entry script |

`UPDATE_PATH` is `public/archives`. `companypicAction` reads `{UPDATE_PATH}/companies/{id}_image.jpg`.

## Mail

Shared settings in `RegisterController`, `LoginController`, and `PermitsController`:

- Host `mail.sarugby.co.za`, port `25`, auth `login`, username `accreditation@sarugby.co.za`.
- From address `accreditation@sarugby.co.za`, name "Registration at sarugby.co.za".
- Subject "SARugby Notification".
- HTML body.

`LoginController::sendMail` and `RegisterController::sendMail` set `SMTPSecure = tls` on the mail object while the transport port is 25. `PermitsController::sendMail` leaves that line commented out. Whether the server accepts this combination is **Unknown / Requires Confirmation**.

`postEmailToFp` would POST `Email`, `FullName`, and `Content` to `https://footprintapp.net/email/send` with a timeout of 86400 seconds. The call in `addpermitAction` is commented out.

## Integrations

See the integrations table in `prd.md`. There is no queue, cron entry, or background worker in this repository.

`library/PG/Controller/Plugin` contains image upload, PDF, and match-report helpers. The walk-in controllers do not construct them. **Inferred** they belong to the larger application this tree was cut from.

## Auth model

Two mechanisms exist:

| Mechanism | Storage | Used by the login form |
|---|---|---|
| Shared secret | `Zend_Session_Namespace('EVENT')->EID = 45` | Yes |
| `Zend_Auth_Adapter_DbTable` on `CompaniesLogin` | Auth identity, then `Companies_Profile.LastLogin` updated by `MID` | No |

Passwords that are stored are `md5` with no salt (`RegisterController`, `LoginController::resetAction`, `UsersController`).

`LoginController::resetAction` interpolates `ResetKey` into SQL. `forgotAction` interpolates `Email`. `CompanyEvents::getListByEvents` interpolates the search string into `LIKE`. `GetMemberDetails` interpolates the ID number. This is how the code is written.

## Front-end assets

The default layout loads local CSS from `/global_assets` and `/assets` (Limitless / Bootstrap). `login/index.phtml` loads Roboto from `fonts.googleapis.com` and sets the title "SA Rugby - Load-in and Load-out". It does not use the default layout.

`walkins/new.phtml` uses jQuery Validate and the jQuery Steps wizard, then posts the serialized form.

`public/` also contains `archives`, `assets`, `assets_old`, `bootstrap2`, `dompdf`, `files`, `fonts`, `html`, `images`, `limitless`, `starters`, `wp-content`, `wp-includes`, `178.json`, `Background4_2018_maintenance.jpg`, `error_log`, and `FILESTO DOWNLOAD`. **Inferred** mixed leftovers. The Zend app does not bootstrap WordPress.

Some match-report PDFs and two tournament JSON files under `public/archives/` have names long enough that a Windows checkout without long-path support cannot create them. They are in git. They are not part of the permit flow. Eight such paths failed to check out in this investigation; the application PHP was readable.

## Development and testing

No README, no test suite, no seed data, no `.env`. Configuration is the ini file.

`IndexController::testAction` and `PermitsController::emailtestAction` are manual probes left in the controllers. They are not a test suite. One reads a real identity number. The other sends mail to a hardcoded personal mailbox. Both values are omitted here.

`IndexController::testAction` cannot run on a normal request because init redirects first. `emailtestAction` can run when `EVENT.EID` is set, because `PermitsController::init` only checks that session value.

Display of errors is forced on in the entry script.

The task that created this onboarding listed `sonar-project.properties` and `sonarqube_scan.bat` at the repository root on the scan host. Those paths are not in `git ls-tree` at `4617ab1`. They are not documented as product files.

## Deployment

No `appspec.yml`, `bitbucket-pipelines.yml`, Dockerfile, or `scripts/` directory is in the committed tree.

Hosting, virtual host, TLS termination, and whether any server still runs this revision are **Unknown / Requires Confirmation**.

The root `.htaccess` assumes Apache and a host that should be forced onto `www` and `https`.

Silicon Ark's `permits.mypass.zone` technical document describes CodeDeploy to `/mnt/data/www/html/mypass.zone/permits.mypass.zone/`. That manifest is not in this repository. Do not treat that path as the deploy target for `permits.magus.events`.

## Security notes for reviewers

These are properties of the code, not a recommended attack.

- Database and SMTP passwords and the shared login secret are in the repository.
- `/login/external` sets the event id from the query string.
- Company passkeys are rendered into the company-list HTML.
- Search and several `fetchRow` calls concatenate request strings into SQL.
- Passwords are `md5` without a salt. Reset email includes the new password.
- `uploadremotecompanyAction` writes an uploaded image with no session check.
- `display_errors` is on. `AccreditationPermits::updateData` prints the row it is about to save.
- `emailtestAction` is reachable when the event session exists.

No rate limit, captcha, or row-level security layer was found. No TLS configuration beyond the root rewrite is in this repository.
