# Technical documentation — Sunmet2020

Evidence date: 2026-10-02.

Source: `git@bitbucket.org:overdrive/sunmet2020.git`, default branch `master` at `10caee0` (2020-08-04, "Merge branch 'prod'"). Deploy branches: `qa` at `cf9251d` (2020-01-13), `prod` at `afeb66d` (2020-01-13).

`git diff origin/prod...origin/master` is only the rename of `application/configs/application.ini` to `application/configs/application.ini.bak`. Secrets in those files and in `sendMail` are not copied into this document.

There is no README. PHP, Apache, and MySQL versions on the server are **Unknown / Requires Confirmation** except where a file pins a version.

## Architecture

Monolithic PHP application on Zend Framework 1, module `default` only.

```text
Internet
  → Apache httpd
  → /.htaccess  (rewrite into public/)
  → public/.htaccess (front controller)
  → public/index.php
  → Zend_Application (APPLICATION_ENV or "production")
  → application/Bootstrap.php (empty DB init)
  → PG_Controller_Plugin_LayoutPicker (layout = module name)
  → ApplicationController or IndexController or ErrorController
  → Zend_Db_Table models
  → MySQL
```

Side calls from `ApplicationController`: Gmail SMTP, and HTTPS to `www.footprintapp.net` for the headshot.

Deployment is a separate path: Bitbucket Pipelines on `qa` and `prod` zips the tree, uploads it to S3, and starts an AWS CodeDeploy deployment. Hooks install and start `httpd`. They do not install PHP, configure a vhost, or copy `application.ini` into place.

## Repository layout

| Path | Role |
|---|---|
| `application/configs/application.ini.bak` | Zend config on `master`. On `prod` the same bytes are `application.ini` |
| `application/Bootstrap.php` | Empty `_initDbAdapter` |
| `application/layouts/default.phtml` | The only layout. Title, Inspinia assets, nav |
| `application/models/` | `Zend_Db_Table` classes, one file per table or view |
| `application/modules/default/controllers/` | `ApplicationController.php`, `IndexController.php`, `ErrorController.php` |
| `application/modules/default/views/scripts/` | `application/`, `index/`, `error/`, `sean/` |
| `library/Zend/` | Zend Framework 1.11.10 (`library/Zend/Version.php` `VERSION = '1.11.10'`) |
| `library/PG/` | Plugin classes, FPDF, image helpers. Layout picker is the one registered |
| `library/Other/` | `Functions.php`, `Notifications.php`. Not referenced by the module |
| `public/index.php` | Front controller |
| `public/.htaccess` | Zend rewrite |
| `public/assets/` | Inspinia, Bootstrap, jQuery 3.1.1, Steps, Validate, Cropper, Font Awesome |
| `public/accre/profile-icon.png` | Fallback headshot |
| `public/phpinfo.php`, `phpinfo.php` | `phpinfo()` |
| `appspec.yml`, `scripts/`, `codedeploy_deploy.py`, `bitbucket-pipelines.yml` | Deploy |
| `.htaccess` | Rewrite into `public/` |
| `htaccess.old` | Older rewrite file. Not referenced |
| `.gitignore` | Ignores `application/configs/application.ini` |

About 4175 tracked files. Most of the count is `library/Zend` and `public/assets`.

`sonar-project.properties` and `sonarqube_scan.bat` are not tracked. They appeared only on the onboarding scan listing.

## Runtime configuration

`public/index.php`:

- `display_errors` on, `E_ALL | E_STRICT`.
- Timezone `Africa/Johannesburg`.
- `APPLICATION_PATH` = `../application`.
- `APPLICATION_ENV` from the environment, otherwise `production`.
- `UPDATE_PATH` = `../public/archives` (directory not present in git).
- Include path adds `library/`.
- A maintenance `exit` block above this is commented out.

`application.ini` sections: `production`, `staging`, `testing`, `development`. Staging inherits production. Testing and development turn `display_errors` off. The front controller defaults to production, which has:

- `display_startup_errors`, `display_errors`, and `displayExceptions` set to 1.
- Layout path `application/layouts`, layout `default`.
- Extra layout names are declared (`admin`, `union`, `referee`, `gir`, `accreditations`) and no matching layout files exist.
- `resources.locale.default = af_ZA`. No translation catalogue is in the tree. Screens read are English.
- `resources.session.cookie_lifetime = 864000`.
- Autoload namespace `PG_`.
- Front controller module directory `application/modules`.
- Plugin `PG_Controller_Plugin_LayoutPicker`.
- Database adapter `pdo_mysql`, default table adapter.
- Host `db.footprintapp.net`, database `teamwizz_SARU`, username `teamwizz_ADMIN`. The password is in the ini file and is omitted here.

`Bootstrap::_initDbAdapter` is empty. The adapter comes from `resources.db`.

On `master`, Zend looks for `application/configs/application.ini` (`public/index.php`). That path is gitignored and the tracked file is `application.ini.bak`. A deploy of `master` does not contain the filename Zend opens unless something outside git copies it. `prod` still tracks `application.ini`. The pipeline deploys `prod` and `qa`, not `master`.

## Request routing

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

| Route | Action | Notes |
|---|---|---|
| `/` | `IndexController::indexAction` | Hard-coded event code. View `index/index.phtml` |
| `/application/apply/code/{code}` | `applyAction` | Public wizard |
| `/application/applymultiple/code/{code}` | `applymultipleAction` | Duplicate of apply |
| `/application/applytest/code/{code}` | `applytestAction` | Duplicate of apply |
| `/application/checkemail` | `checkemailAction` | JSON `Ok` count. Also on the index controller |
| `/application/login` | `loginAction` | Hard-coded event code |
| `/application/loginnow/ac/{AC}` | `loginnowAction` | JSON `OK` 0 or 1. POST |
| `/application/logout` | `logoutAction` | Clears session |
| `/application/profile/code/{code}` | `profileAction` | Requires session `AAID` |
| `/application/update/aaid/{id}` | `updateAction` | POST. No session check |
| `/application/updateimage` | `updateimageAction` | POST. No session check |
| `/application/attendance/key/{key}/att/{1\|2}` | `attendanceAction` | |
| `/application/testattendance/...` | `testattendanceAction` | Same, prints exceptions |
| `/application/barcode` | `barcodeAction` | Includes missing `Barcode39.php` |
| `/application/test` | `testAction` | Sends a test email |
| `/application/image` | `imageAction` | Uses unset `$data` |
| `/application/partners` | `partnersAction` | Empty view |
| `/application` | `indexAction` | Role list view |
| Error | `ErrorController` | `error.phtml` prints message, exception, and request parameters when display exceptions is on |

`LayoutPicker::preDispatch` sets the layout to the module name. The module is `default`, and `layouts/default.phtml` exists, so the plugin matches the ini layout.

## Controllers

`ApplicationController` (about 910 lines, plus a large commented base64 image inside `applyAction`) is the product. Memory limit `512M`. `display_errors` on.

`IndexController` repeats the home-page load and a POST insert that calls `random_id`, `SavePhoto`, and `EmailApplicant`, which are not methods of `IndexController`. The form's `action` is `/application/apply/...`, so that POST block is unused by the form.

`ErrorController` is the stock Zend error controller (about 50 lines).

## Persistence

Adapter: PDO MySQL. No migrations, no schema SQL, no seed.

Table name used for applications: `AccreditationApplycation` (class `Model_AccreditationsApplications`). The spelling is in `_name`.

Other `_name` values:

| Class | Table or name |
|---|---|
| `Model_Accreditation` | `Accreditation` |
| `Model_AccreditationList` | `Accreditation_List` |
| `Model_AccreditationCategory` | `Accreditation_Category` |
| `Model_AccreditationApplicationCategory` | `Accreditation_Application_Category` |
| `Model_AccreditationRoles` | `AccreditationRoles` |
| `Model_AccreditationApplicationRole` | `Accreditation_Application_Roles` |
| `Model_AccreditationZone` | `AccreditationsZones` |
| `Model_AccreditationLinks` | `LinkedAccreditations` |
| `Model_AccreditationHistory` | `Accreditation_History` |
| `Model_AccreditationTypes` | `AccreditationTypes` |
| `Model_AccreditationServices` | `accreditation_Services` |
| `Model_AccreditationEventServices` | `accreditation_Event_Services` |
| `Model_AccreditationApplicantsServices` | `Accreditation_Applicants_Services` |
| `Model_AccreditationAdministrators` | `Accreditation_Administrators` |
| `Model_AccreditationPermits` | `Accreditation_Permits` |
| `Model_AccreditationGates` | `EventGates` |
| `Model_AccreditationLoadingZones` | `Accreditation_Loading_Zones` |
| `Model_AccreditationLoadingServices` | `Accreditation_Load_Services_Types` |
| `Model_AccreditationQueing` | `AccriditationApplicationQueing` |

Views referenced from model SQL (not created in this repo):

- `Accreditations_view` — event profile by `aCode` or `AC`.
- `AccreditationRoles_view` — roles, including `photo` and `SupplierReg`.
- `AccreditationCategory_view` — public categories (`Teams = 0`, `hidden = 0`).
- `SquadAccredition_view`, `AccriditationNewConfirmedApprovedStatus_view` — queried by methods no controller calls.

`UserLogin` joins `AccreditationApplycation` to `Accreditation_List` on `AC`.

Inserts pass the request `data` array through. Columns that do not exist cause the catch block to `print_r` the exception and return false.

Queries concatenate request values into `where` strings (`email`, `ac`, `ref`, link SQL, updates by `AAID`). Provenance: **Implemented.**

`aZones` is JSON. The profile `json_decode`s it and loads zone rows by id.

## Auth

No Zend_Auth, no password hash, no RBAC.

Login: POST email and reference. Session namespace `user_session` stores `AAID` and `Code` (`aCode` from `Accreditation_List`). Profile reads `AAID`. Apply redirects if `AAID > 0`.

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

Cookie lifetime 10 days (`864000`). No secure/httponly flags are set in the ini file beyond Zend defaults. **Unknown** what the server's PHP defaults are.

## Mail

`ApplicationController::sendMail`:

- `Zend_Mail_Transport_Smtp` host `smtp.gmail.com`.
- Auth login, SSL `tls`, port 587, username `accreditation@magus.events`. Password in source, omitted here.
- From that address, display name `Accreditation - The Sun Met`.
- HTML body. Subject `The Sun Met Notification`.
- On failure, `print_r` the exception.

Commented block above it uses `mail.footprintapp.net` and `accreditation@footprintapp.net` and also contains a password.

`random_id` uses `mcrypt_create_iv`, which requires the `mcrypt` extension (removed from PHP core in 7.2).

## Photo integration

After insert, `sendImage` POSTs multipart fields `ac`, `ref`, `pic` to `https://www.footprintapp.net/accreditations/application/updateimage`. Timeout 86400. A commented URL points at `https://footprintapp.net/test/recievedata`. Response is discarded.

Profile: HEAD then, on HTTP 200, GET `https://www.footprintapp.net/accre/photos/{Ref}.jpg`, base64-inlined. Otherwise `/accre/profile-icon.png`.

`SavePhoto` would write `{REF}.jpg` under `AC_PATH/photos` and resize to 500×500. `AC_PATH` is never defined in this repo. `applyAction` does not call `SavePhoto` (the call is commented).

## Front end

Inspinia theme assets served from `/assets`. Scripts are linked from the layout, not bundled. jQuery Validate plus jQuery Steps run the wizard. Cropper produces the data-URI stored in a hidden textarea `data[User_pic]`.

No Node toolchain, no `package.json`, no Composer `composer.json`.

Browser support links in the layout point at Chrome, Firefox, and Safari download pages. They are images, not a detection script.

## Deployment and infrastructure

`bitbucket-pipelines.yml`:

- Image `python:3.5.1`.
- Branches `qa` and `prod` only. Each step exports `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_`.
- Installs `zip` and `boto3==1.3.0`.
- `zip -r /tmp/artifact.zip *` from the build directory (shell globs do not include dotfiles, so `.htaccess` and `.gitignore` may be omitted). **Inferred** from the script. **Unknown / Requires Confirmation** whether the live host depends on the root `.htaccess` being inside the artifact. `public/.htaccess` is not a dotfile at the zip root; it is under `public/` and is included.
- Runs `python codedeploy_deploy.py`.

`codedeploy_deploy.py` (header: Amazon Bitbucket Builds template, v1.0.0):

- Uploads the zip to `s3://$S3_BUCKET/$APPLICATION_NAME/$VERSION_LABEL-bitbucket_builds.zip`.
- `create_deployment` with `deploymentConfigName` from `DEPLOYMENT_CONFIG`. The pipeline file never exports `DEPLOYMENT_CONFIG`. If the variable is empty, the API call fails. **Unknown / Requires Confirmation** whether the Bitbucket project sets it outside this file.
- `ignoreApplicationStopFailures` true.
- Polls deployment status. The rest of the file sleeps and prints success or failure.

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

- Copies `/` to `/mnt/data/www/html/magus.events/sunmet2020.magus.events/`.
- Owner and group `apache`.
- BeforeInstall: `scripts/install_dependencies` (`yum install -y httpd`) as root, timeout 300.
- ApplicationStop: `scripts/remove_archives.sh` deletes CodeDeploy revision directories other than the last successful install. The script's `stop_server` is not this hook.
- ApplicationStart: `scripts/start_server` (`service httpd start`) as root.
- `scripts/stop_server` exists (`service httpd stop` when `httpd` is running) and is not referenced by `appspec.yml`. The stop script's `isExistApp = \`pgrep httpd\`` assignment has spaces around `=`, which is invalid in bash. It is unused by the appspec.

No Docker, no Terraform, no monitoring agent, no queue, no cron, no background worker.

## Security notes (observed, not a penetration test)

- Production config and the front controller display errors.
- `phpinfo.php` is in the web root and in `public/`.
- Database password and SMTP password are in git. An older SMTP password remains in a comment.
- `testAction` sends mail with no authentication.
- `updateAction` updates any `AAID` from POST with no session check.
- Login and email checks build SQL by concatenation.
- Reference number is the login secret and is returned in the apply response.
- `barcodeAction` and `imageAction` are unauthenticated and incomplete.
- Session is the only profile gate. Logout does not regenerate anything else; it unsets the namespace.

These are **Implemented** properties of the tree. Remediation is a product decision and is not specified here.

## Development and testing

- No PHPUnit, no CI test step, no lint config, no editorconfig.
- Authors in history: `root` (initial import, 2019-12-12), `heinrichoverdrive`, `Heinrich du Plessis`.
- Several commits have the message "no message". One ini edit was made in the Bitbucket UI (`2a47b00`).
- Local run instructions are not documented. A local run needs PHP with Zend include path, `mcrypt` if references are generated, `pdo_mysql`, a readable `application.ini`, and network access to the database and to Footprint if photos and mail are exercised.
- `ErrorController` and `displayExceptions = 1` are the debugging mechanism in production config.

## Background work, queues, storage

- No queue consumer. Table `AccriditationApplicationQueing` has a model and no caller in the controllers.
- No object storage SDK in the application. The pipeline uses S3 only as the CodeDeploy revision bucket.
- Photos are stored by Footprint, not on this host, on the live apply path.
- `public/archives` is referenced as `UPDATE_PATH` and is not in git.

## What this document does not establish

- Whether `sunmet2020.magus.events` still resolves, and which branch is on disk there.
- PHP, Apache, and MySQL versions.
- The live value of CodeDeploy application name, deployment group, S3 bucket, region, and deployment config.
- Schema ownership of `teamwizz_SARU` and which other applications share it.
- Who operates the Footprint accreditation admin that this portal posts photos to.
