# Technical documentation — supplier.magus.events

Evidence date: 2026-10-01. Inspected tree: `master` at `ccfc664`. Branch tips: `qa` `e0414fb`, `prod` `5c382ec`.

Credentials are described by location only. Values are not repeated.

## Stack

| Item | Evidence |
|---|---|
| Language | PHP. `public/index.php` loads `Zend/Application.php` |
| Framework | Zend Framework 1, vendored in `library/Zend` |
| Layout and UI | Limitless theme assets under `public/assets`, `public/global_assets`, `public/limitless`. Bootstrap. jQuery |
| PDF | dompdf under `public/dompdf` |
| Database | `pdo_mysql` via `Zend_Application_Resource_Db` |
| Web server | Apache. CodeDeploy hook `scripts/install_dependencies` runs `yum install -y httpd`. `scripts/start_server` runs `service httpd start` |
| Timezone | `Africa/Johannesburg`, set in `public/index.php` |
| Default locale | `af_ZA` in `application.ini`. The views that were read are English |
| Random ids | `mcrypt_create_iv` with `MCRYPT_DEV_URANDOM`. `mcrypt` was removed from PHP after 7.1 |

`application/Bootstrap.php` defines `_initDbAdapter` and the method body is empty. The database adapter is created from `application.ini` by the framework resource, not by this method.

## Request path

1. Root `.htaccess` rewrites any URI that does not already start with `/public/` to `public/{uri}`. The HTTPS and www redirect rules in that file are commented out.
2. `public/.htaccess` serves real files and directories, and rewrites everything else to `index.php`.
3. `public/index.php` defines `APPLICATION_PATH`, `APPLICATION_ENV` (environment variable, otherwise `production`), and `UPDATE_PATH` (`public/archives/`). It enables `display_errors`, builds a `Zend_Application` from `application/configs/application.ini`, and runs it.
4. Module directory is `application/modules`. The only module with controllers is `default`.
5. Plugin `PG_Controller_Plugin_LayoutPicker` sets the layout to the module name, so the layout file is `application/layouts/default.phtml`. Login and registration call `disableLayout()`.
6. `application.ini` also sets `resources.layout.layout = default`. Keys `admin`, `union`, `referee`, `gir`, and `accreditations` name other layouts. Those layout files are not in `application/layouts` (only `default.phtml` and `layout.phtml` are).

Root `index.html` is a Plesk "Domain Default page" (copyright line says 1999-2018). It is not routed by the PHP application. If Apache serves `index.html` ahead of the rewrite, visitors see the Plesk page. Which file wins on the server is **Unknown / Requires Confirmation**.

## Application structure

```text
public/index.php                          front controller
application/configs/application.ini       environments and database
application/Bootstrap.php                 empty db init
application/layouts/default.phtml         authenticated chrome
application/modules/default/controllers   page actions
application/modules/default/views         phtml templates
application/models                        Zend_Db_Table gateways
library/Zend                              framework
library/PG/Controller/Plugin              LayoutPicker and unused helpers
scripts/                                  CodeDeploy hooks
appspec.yml
bitbucket-pipelines.yml
codedeploy_deploy.py
```

Controllers that Zend will load from the file name:

| File | Class | Auth in `init` |
|---|---|---|
| `RegisterController.php` | `RegisterController` | No. Layout off |
| `LoginController.php` | `LoginController` | No. Layout off |
| `IndexController.php` | `IndexController` | Redirect to `/login/` |
| `CompanyController.php` | `CompanyController` | Redirect to `/login/` |
| `UsersController.php` | `UsersController` | Redirect to `/login/` |
| `PermitsController.php` | `PermitsController` | Redirect to `/login/` |
| `OrganisationController.php` | `OrganisationController` | No. Actions empty |
| `AuthonController.php` | `AuthonController` | No. Actions empty |
| `ErrorController.php` | `ErrorController` | Standard Zend error action |

Files whose class name does not match, so the default dispatcher will not use them for that URL:

| File | Class declared |
|---|---|
| `EventsController.php` | `Companies_EventsController` |
| `OrgController.php` | `Company_OrgController` |

`application/modules/default/controllers/Bootstrap.php` is a second bootstrap class inside the controllers directory. It was not the class named in `application.ini` (`Bootstrap` from `application/Bootstrap.php`). Whether the module bootstrap runs is **Unknown**. It was not read in full.

## Routes in use

Zend default routing: `/{controller}/{action}/{param}/{value}`.

| URL | Action |
|---|---|
| `/register` | Registration form |
| `/register/newcompany` | POST create company. JSON. Exits |
| `/register/validateemail/id/{key}` | Confirm email |
| `/login` | Sign in |
| `/login/forgot` | Request reset |
| `/login/reset/user/{key}` | Set a new password |
| `/login/logout` | Sign out |
| `/login/companypic/id/{id}` | Company image bytes |
| `/login/uploadremotecompany` | Unauthenticated logo write |
| `/` | Dashboard |
| `/index/getpermitpdf/id/{id}` | PDF stream |
| `/company/profile` | Company form |
| `/company/upload` | Logo write. Exits |
| `/users` | Staff list |
| `/users/add` | Create staff. JSON on POST |
| `/users/profile/id/{id}` | Edit staff |
| `/permits/create` | New permit form |
| `/permits/addpermit` | POST create. Exits |
| `/permits/profile/key/{id}` | Edit form |
| `/permits/savepermit` | POST update. Exits |
| `/permits/duplicatepermit` | POST copy. Exits |
| `/permits/cancel` | POST status 5. Exits |
| `/permits/questions` | FAQ |

## Data

There is no SQL migration directory. Tables and views are whatever the shared MySQL database already has. Names below are the `$_name` values or the SQL fragments in the models.

| Name | Kind | Used for |
|---|---|---|
| `Companies_Profile` | table | Company row. Primary key used as `CID` |
| `CompaniesLogin` | table or view | Auth identity `Email`, credential `password`. Also read by `adminLogin` |
| `Company_Members` | table | People. Key `MID` |
| `Company_Events` | table | `CID`, `EID`, `Status` |
| `Company_Events_view` | view | `getListByEvent`. Not used by the registration flow |
| `Accreditation_List` | table | Event / accreditation. Key column `AC` |
| `Accreditation_Permits` | table | Permit write model. Key `PID` |
| `Permits_view` | view | Dashboard and permit profile |
| `Permit_Status` | table | `getStatus`, ordered by `SID` |
| `Accreditation_Loading_Zones` | table | Zones. Filtered by `AID` |
| `Accreditation_Load_Services_Types` | table | Service types |
| `EventGates` | table | Gates model. Queried and not assigned to the view in the actions that were read |
| `vehicletypes` | table | Vehicle types |
| `BBEE_Level` | table | B-BBEE levels |
| `B_BBEE_Types` | table | Model exists. Registration uses `Accre_Company_Types` instead |
| `Accre_Company_Types` | table | Company type dropdown |
| `countries` | table | Country dropdown |
| `public/archives/companies/{CID}_image.jpg` | file | Company logo. `UPDATE_PATH` |
| `public/archives/accreditation/{MID}_image.jpg` | file | Staff photo |

`public/archives/` and `public/files/pics/` are gitignored.

JSON columns on the permit or accreditation row, as decoded by the controllers: `Accupants`, `LoadinActiveDate`, `LoadOutActiveDate`.

Permit columns cleared on duplicate, which shows they exist on the table: `Gate`, `Zones`, `DateModified`, `AdminModifiedBy`, `UserModified`, `ConfirmmedBy`, `ConfirmedDate`, `ApprovedBy`, `ApprovedDate`, `DeclinedBy`, `DeclinedDate`, `DeclinedMessage`, `CancelledBy`, `CancelledDate`, `CancelMessage`.

Other models map rugby tables (`tournament`, `Fixture`, `squad`, `gir_profile`, `profile`, and others). They are part of the shared database surface. See `features.md`.

## Authentication

- `Zend_Auth` with `Zend_Auth_Adapter_DbTable`.
- Table `CompaniesLogin`, columns `Email` and `password`.
- Passwords written by this application are `md5` of the plain string. There is no salt in the PHP.
- Password bypass: a specific posted password causes `adminLogin` to load the stored hash and submit that hash as the credential. Location: `LoginController::indexAction`. Value omitted.
- After success, identity row is stored in the auth session. `EVENT` is a separate `Zend_Session_Namespace` holding `EID`.
- `resources.frontController.params.displayExceptions = 0` in every environment section.

## Mail

`Zend_Mail_Transport_Smtp`.

| Setting | Value in code |
|---|---|
| Host | `mail.sarugby.co.za` |
| Port | `25` |
| Auth | `login` |
| Username | `accreditation@sarugby.co.za` |
| Password | Present as a string literal in `RegisterController`, `LoginController`, `IndexController`, and `PermitsController`. Omitted here |
| From | `accreditation@sarugby.co.za`, name `Registration at sarugby.co.za` |
| Subject | `SARugby Notification` |
| Body | HTML |

`RegisterController` and `LoginController` set `SMTPSecure = tls` on the mail object while connecting to port 25. `PermitsController::sendMail` leaves that line commented out. Which combination the server accepts was not tested.

`PermitsController::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.

## PDF

`IndexController::getpermitpdfAction` includes `dompdf/dompdf_config.inc.php` (resolved from the PHP include path, which includes `library/` and the working directory of the request; the library copy that is present in the tree is `public/dompdf`). It HTTP GETs `https://www.footprintapp.net/pdf/getpermit/id/{id}`, converts UTF-8 to Windows-1250, renders A4 landscape, writes the bytes to `AC_PATH . "/loadin/pdf/permit_{id}.pdf"`, and streams the download.

`AC_PATH` is not defined in `index.php` or `application.ini`. **Unknown / Requires Confirmation.**

## Deployment

`bitbucket-pipelines.yml` image: `python:3.5.1`.

Branches `qa` and `prod` each run one step:

1. Export `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION`, `APPLICATION_NAME`, `DEPLOYMENT_GROUP_NAME`, `S3_BUCKET` from Bitbucket variables prefixed `QA_` or `PROD_`.
2. `apt-get install zip`, `pip install boto3==1.3.0`.
3. `zip -r /tmp/artifact.zip *` from the repository root. That archive includes `default.zip`, `library/Zend`, theme assets, and `application.ini` with its password.
4. `python codedeploy_deploy.py` uploads the zip to `s3://{S3_BUCKET}/{APPLICATION_NAME}/{timestamp}-bitbucket_builds.zip` and calls `create_deployment`. It polls until status `Succeeded` or `Failed`. `ignoreApplicationStopFailures` is true. `deploymentConfigName` is `DEPLOYMENT_CONFIG`. That variable is not exported in the pipeline file. **Unknown** whether it is set in the Bitbucket environment. If it is empty, the AWS call fails.

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

- Copy `/` to `/mnt/data/www/html/sarugby.online/supplier.sarugby.online/`.
- Owner and group `apache`.
- `BeforeInstall`: `scripts/install_dependencies` as root, timeout 300.
- `ApplicationStop`: `scripts/remove_archives.sh` as root. Deletes CodeDeploy deployment-root directories that are not the last successful install.
- `ApplicationStart`: `scripts/start_server` as root.

`scripts/stop_server` exists and is not referenced by `appspec.yml`. The script assigns `isExistApp = \`pgrep httpd\`` with spaces around `=`, which is not valid bash for that purpose.

There is no pipeline for `master`, and no test step.

## Configuration environments

`application.ini` sections: `production`, `staging`, `testing`, `development`. Staging, testing, and development inherit production. Testing and development only repeat the display_errors flags. They do not point at a different database. The `qa` git branch is the thing that changes the host to `qadb.footprintapp.net`.

| Key | On `master` and `prod` |
|---|---|
| `resources.db.params.host` | `db.footprintapp.net` |
| `resources.db.params.dbname` | `teamwizz_SARU` |
| `resources.db.params.username` | Present. Omitted |
| `resources.db.params.password` | Present. Omitted |
| `qa` branch host | `qadb.footprintapp.net` |

`APPLICATION_ENV` defaults to `production` when the variable is unset, so a server that does not set it uses the production section even if the files came from the `qa` branch. The `qa` branch file itself contains the QA host inside the production section.

## Files that are not the application

| Path | What it is |
|---|---|
| `index.html`, `css/`, `img/`, `favicon.ico` | Plesk default site |
| `test/` | Plesk FastCGI, PHP, and SSI test pages linked from that default site |
| `default.zip` | About 27 MB at the repository root. Not opened |
| `.well-known/pki-validation/fileauth.txt` | A domain-control token. The token is not copied here |
| `public/html`, `public/starters`, `public/wp-content`, `public/wp-includes` | Theme demos and a partial WordPress tree used as static assets. Not a second application entry point that was found |
| `public/files/*.json` | Fixture and result JSON dumps (`Fixtures*.json`, `Results_*.json`). Not read by the supplier controllers that were traced |
| `sonar-project.properties`, `sonarqube_scan.bat` | Named in the onboarding task's directory listing from the scan host. Not present in git at `ccfc664` |

## Security notes for reviewers

These are observations, not a remediation plan.

- Database password and SMTP password are committed in source.
- A login bypass password is committed in `LoginController`.
- Password reset emails include the new password.
- Passwords are MD5.
- Several queries concatenate request parameters into SQL.
- `uploadremotecompany`, permit save, permit cancel, permit duplicate, staff profile, and the PDF action do not bind the record to the caller's company. Some of them do not require login.
- `display_errors` is enabled in the front controller and the controllers.
- `PermitsController::emailtestAction` contains a personal mailbox.
- The pipeline zip includes `application.ini`.

## Development and testing

No README, Composer project file for the application itself (dompdf has its own `composer.json`), PHPUnit configuration, or application test suite was found. `public/index.php` is the way the app runs. Automated checks in the pipeline are limited to "did CodeDeploy report Succeeded".

## Monitoring

No application log configuration, metrics agent, or health endpoint was found. `ErrorController` uses Zend's default error handling. Deploy failures are printed by `codedeploy_deploy.py`.
