# Technical documentation — Kyalami9Hour2019

Evidence date: 2026-10-10. Source: `git@bitbucket.org:overdrive/kyalami9hour2019.git`, `master` at `5b60e52` (2020-10-28). Differences on `qa` (`e0f870a`) and `prod` (`0950599`, both 2020-12-04) are marked where the tree differs.

Passwords and access keys are not copied here.

## Stack

| Piece | Evidence |
|---|---|
| Language | PHP. Front controller `public/index.php`. No `composer.json` |
| Framework | Zend Framework 1.11.10, vendored in `library/Zend`. `Zend_Version::VERSION` |
| Modules | Zend modular layout. `resources.frontController.moduleDirectory` = `application/modules` |
| Database | `pdo_mysql`. Database name `teamwizz_SARU` |
| Views | `.phtml`. jQuery 3.1.1, Bootstrap, jQuery Steps, jQuery Validate, Cropper, Inspinia assets under `public/assets` |
| Mail | `Zend_Mail` SMTP |
| Deploy | Bitbucket Pipelines, Python 3.5.1 image, boto3 1.3.0, AWS CodeDeploy |
| Tests | None found |
| Time zone | `Africa/Johannesburg` in `public/index.php` and `ApplicationController` |

**Unknown / Requires Confirmation:** the PHP version on the host. `ApplicationController::random_id` calls `mcrypt_create_iv`, which PHP 7.2 and later do not ship.

## Request path

1. `.htaccess` at the repository root rewrites every URI that does not already start with `/public/` to `public/{uri}`.
2. `public/index.php` defines `APPLICATION_PATH`, `APPLICATION_ENV` (environment variable, otherwise `production`), and `UPDATE_PATH` (`public/archives`). It puts `library/` on the include path, turns `display_errors` on, and runs `Zend_Application` with `application/configs/application.ini`.
3. `application/Bootstrap.php` exists. `_initDbAdapter` is empty. Database setup comes from the ini resource.
4. `PG_Controller_Plugin_LayoutPicker::preDispatch` sets the layout name to the module name.
5. Only `application/layouts/default.phtml` is in Git. A request whose module is `supplier` asks for a layout file that is not there, unless the action disables the layout. `Supplier_LoginController` does disable it.

Commented lines at the top of `index.php` would have stopped the app with a maintenance message. They are not active.

## Configuration

`master` tracks `application/configs/application.ini.bak` and gitignores `application/configs/application.ini`. `public/index.php` loads the gitignored name. A fresh `master` clone does not contain the file the front controller requires.

`qa` and `prod` track `application/configs/application.ini` (the bak file renamed). `qa` host is `qadb.footprintapp.net`. `prod` host is `db.footprintapp.net`. Username and database name match the bak file: user `teamwizz_ADMIN`, database `teamwizz_SARU`. The password is in that file and is not reproduced.

Production section (same shape on the bak file and on `prod`):

- `phpSettings.display_errors` = 1 and `display_startup_errors` = 1
- `resources.frontController.params.displayExceptions` = 1
- Layout path `application/layouts`, default layout `default`
- Named layouts that have no file in this repo: `admin`, `union`, `referee`, `gir`, `accreditations`, `supplier`
- `resources.locale.default` = `af_ZA`
- `resources.session.cookie_lifetime` = 864000
- Autoload namespace `PG_`
- `resources.db.adapter` = `pdo_mysql`

`testing` and `development` inherit production and turn display of errors and exceptions off. Nothing in the repo selects those environments. The front controller defaults to `production`.

## Application structure

```text
public/index.php                          front controller
application/Bootstrap.php                 empty DB init
application/configs/application.ini.bak   tracked config on master
application/layouts/default.phtml         only layout
application/models/                       Zend_Db_Table models
application/modules/default/              public accreditation portal
application/modules/supplier/             company / permit code
library/Zend/                             Zend Framework 1.11.10
library/PG/Controller/Plugin/LayoutPicker.php
scripts/                                  CodeDeploy hooks
appspec.yml
bitbucket-pipelines.yml
codedeploy_deploy.py
```

`default` controllers:

| Class | Role |
|---|---|
| `IndexController` | Home wizard for the hard-coded code. Also `checkemailAction` |
| `ApplicationController` | Apply, login, profile, update, attendance, mail, image forward |
| `ErrorController` | Zend error view |

`supplier` controllers are listed in `features.md`. Several class names do not match the module (`Company_OrgController` in `OrgController.php`, `Companies_EventsController` in `EventsController.php`). Zend's dispatcher would look for `Supplier_OrgController` and `Supplier_EventsController`. **Inferred:** those two controllers do not route. This was not executed.

## Data

There are no migrations. Models name these tables and views. **Inferred** from the shared database name and the Footprint photo host: the schema is owned outside this repository.

| Model | Table or view |
|---|---|
| `Model_AccreditationList` | `Accreditation_List`. `getProfile` reads view `Accreditations_view` by `aCode` |
| `Model_AccreditationsApplications` | `AccreditationApplycation` (spelled that way) |
| `Model_AccreditationApplicationCategory` | `Accreditation_Application_Category`. Public list reads `AccreditationCategory_view` |
| `Model_AccreditationRoles` | `AccreditationRoles`. Live list reads `AccreditationRoles_view` |
| `Model_AccreditationLinks` | `LinkedAccreditations` |
| `Model_AccreditationZone` | `AccreditationsZones` |
| `Model_AccreditationApplicantsServices` | `Accreditation_Applicants_Services` |
| `Model_Accreditation` | `Accreditation`. Some methods read `Accreditations_view` |
| `Model_CompanyProfile` | `Companies_Profile` |
| `Model_UserProfile` | `Company_Members` |
| `Model_CompanyEvents` | `Company_Events` |
| `Model_AccreditationPermits` | `Accreditation_Permits` |
| `Model_Country` | `countries` |
| `Model_BBEELevel` | `BBEE_Level` |

Other accreditation models exist for types, services, loading zones, loading services, gates (`EventGates`), history, event services, administrators, application roles, queuing (`AccriditationApplicationQueing`), and company types. The public wizard's controllers do not call most of them. They are available to the supplier controllers and to any code that constructs them.

Columns the public path reads or writes include `aCode`, `AC`, `TID`, `FID`, `CloseDate`, `DateFrom`, `DateTo`, `OpenDate` (selected, not enforced), `EmailNotes`, `EmailRejected`, `EmailAttandanceConfirmed`, `TermsAndConditions`, `AccreBanner`, `MediaOnly`, `ContactName`, `ContactEmail`, `ContactTelephone`, `Ref`, `AAID`, `Status`, `Pending`, `AttendanceKey`, `aZones`, `User_pic`, `History`.

## Auth

Applicants: Zend session namespace `user_session`. Login matches `AccreditationApplycation.email`, `Ref`, and `AC`, then stores `AAID` and `aCode`. No password, no `Zend_Auth` on this path.

Supplier module: `Zend_Auth` plus MD5 comparison against `Companies_Profile`. A hard-coded password in `Supplier_LoginController::indexAction` substitutes the stored hash. The value is not reproduced. Reset sets `AllowLogin` and `Email_Confirmed`.

`updateAction` and `updateimageAction` do not read the session.

## Mail

`ApplicationController::sendMail`:

- Host `smtp.gmail.com`, port 587, SSL mode `tls`, auth `login`.
- `master` username and From: `accreditation@kyalami9hour.com`.
- `qa` and `prod` username and From: `accreditation@magus.events`.
- Display name on all three: "Accreditation - Kyalami 9 Hour 2020".
- Subject: "Kyalami 9 Hour 2020 Notification".
- Body: HTML.
- Failure: `print_r` of the exception.

Commented blocks still mention `accreditation@footprintapp.net`, `mail.footprintapp.net`, and port 465. They are not the active config.

## External systems

| System | Call | Auth in this repo |
|---|---|---|
| MySQL `teamwizz_SARU` | Zend_Db from ini | Username in ini. Password not reproduced |
| `https://www.footprintapp.net/accreditations/application/updateimage` | POST `ac`, `ref`, `pic`. Timeout option 86400 seconds. `Content-type: multipart/form-data` is set, then `POSTFIELDS` is set again to the same array | No token in the PHP |
| `https://www.footprintapp.net/accre/photos/{Ref}.jpg` | HEAD-style curl (`CURLOPT_NOBODY`) then `file_get_contents` when status is 200 | None |
| Gmail SMTP | `Zend_Mail` | Username in source. Password not reproduced |
| AWS CodeDeploy and S3 | `codedeploy_deploy.py` | Environment variables `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION`, `APPLICATION_NAME`, `DEPLOYMENT_GROUP_NAME`, `S3_BUCKET`. Pipeline also reads `DEPLOYMENT_CONFIG` inside `create_deployment` and the yaml does not export it. **Unknown / Requires Confirmation** whether that variable is set in Bitbucket |

`supplier.sarugby.online` appears only as a link.

## Deployment

`bitbucket-pipelines.yml` defines one step on branch `qa` ("Deploy on QA 1") and one step on branch `prod` ("Deploy on PROD"). There is no `master` step and no test step.

The step installs `zip` and `boto3==1.3.0`, runs `zip -r /tmp/artifact.zip *`, then `python codedeploy_deploy.py`. The script uploads the zip to `s3://{S3_BUCKET}/{APPLICATION_NAME}/{timestamp}-bitbucket_builds.zip` and calls `create_deployment` with `ignoreApplicationStopFailures` true. It polls the deployment. The poll loop is in the rest of `codedeploy_deploy.py`.

`appspec.yml`:

- OS `linux`
- Destination `/mnt/data/www/html/magus.events/kyalami9hour2019.magus.events/`
- Owner and group `apache`
- BeforeInstall: `scripts/install_dependencies` (`yum install -y httpd`), 300 seconds, root
- ApplicationStop: `scripts/remove_archives.sh`, 300 seconds, root. The script deletes CodeDeploy deployment-root directories other than the last successful install
- ApplicationStart: `scripts/start_server` (`service httpd start`), 300 seconds, root

`scripts/stop_server` remains in Git and is not referenced after `afcd3f3`.

The zip is built from `*`, so it includes `library/` and `public/` and excludes dotfiles such as `.git` and `.gitignore` only to the extent the shell glob does. **Inferred:** `application.ini` is inside the artifact when it exists in the build checkout. On a `prod` build it is tracked. On a `master` build it is not in Git, and `master` is not a pipeline branch.

## Front-end notes

- Wizard: jQuery Steps on `#Mainform`, ajax submit via `ajaxForm`.
- Assets are committed. `public/assets_old/` holds a second copy of the plugin tree. The layout links `/assets/...`, not `assets_old`.
- Duplicate `style.css` and `animate.css` links are in the layout head.
- Loader image path is `/img/ajax-loader.jif` (extension `jif`).

## Development and testing

No README, editor config, PHPUnit file, or seed script was found. Views named `test`, `applytest`, and `testattendance` are extra HTTP actions.

`barcodeAction` includes `Barcode39.php` with a relative include. Where that file resolves on the include path was not confirmed. The action draws the fixed value `FD5T67`.

## Security notes for operators (no procedures)

These are observations from the source, not a test report.

- Database password and SMTP password are stored in Git on `qa` and `prod` (`application.ini`, `ApplicationController::sendMail`). `master` gitignores `application.ini` and still contains the SMTP password in `ApplicationController.php`, and the database password in `application.ini.bak`.
- Production shows errors and exception details to the browser.
- Login, email check, and several updates concatenate request parameters into SQL.
- Profile update and image forward accept an id or a reference with no session check.
- Applicant sign-in is email plus a 6-hex-character reference.
- Supplier login contains a hard-coded password that loads the stored hash, and password reset emails the new password.
- The Footprint image POST carries no application credential.
- Session cookie lifetime is 10 days. No CSRF token was found on the wizard or the profile form.

## Files the task scan mentioned that are not in Git

The onboarding issue listed `sonar-project.properties` and `sonarqube_scan.bat` at the repository root. Neither file is in the `master` tree, and the diff from `master` to `qa` and `prod` does not add them. **Unknown / Requires Confirmation** whether they existed only on the scan host outside Git.
