# Technical documentation — SuperHero2020

Evidence date: 2026-10-02. Source: `git@bitbucket.org:overdrive/superhero2020.git`, `master` at `ab4effc` (2020-02-07, "Merge branch 'prod'", heinrichoverdrive).

The scan-host checkout was not on this machine. This note is from a read-only shallow clone. History before that commit was not retrieved. Secrets are described, not copied.

## Stack

| Item | Value | Evidence |
|---|---|---|
| Language | PHP | `public/index.php` |
| Framework | Zend Framework 1.11.10 | `library/Zend/Version.php` |
| UI | Inspinia theme, Bootstrap, jQuery 3.1.1, jQuery Steps, jQuery Validate, Cropper | `application/layouts/default.phtml`, `public/assets` |
| Database | MySQL through `pdo_mysql` | `application/configs/application.ini.bak` |
| Web server | Apache `httpd` | `scripts/install_dependencies`, `scripts/start_server` |
| Deploy | Bitbucket Pipelines, Python 3.5.1 image, boto3 1.3.0, AWS CodeDeploy | `bitbucket-pipelines.yml`, `codedeploy_deploy.py` |
| Time zone | `Africa/Johannesburg` | `public/index.php`, `ApplicationController` |
| Default locale | `af_ZA` | ini backup. Application views inspected are English |

PHP version is not pinned. `random_id` calls `mcrypt_create_iv`. The `mcrypt` extension is not part of PHP 7.2 or later. Server PHP version is **Unknown / Requires Confirmation**.

There is no `composer.json`, no `README`, and no application test suite. `library/Zend` is the framework tree, including Zend's own tests and tool stubs. Those are not this product's tests.

## Repository layout

```text
public/index.php                 front controller
public/.htaccess                 rewrite to index.php when the file does not exist
public/assets                    Inspinia / Bootstrap / jQuery
public/assets_old                earlier asset copy
public/images
public/accre/profile-icon.png    default portrait
application/Bootstrap.php        empty _initDbAdapter
application/configs/application.ini.bak
application/layouts/default.phtml
application/modules/default/     only module
application/models/              20 Zend_Db_Table classes
library/Zend                     Zend Framework 1.11.10
library/PG/Controller/Plugin     project helpers (most unused by the portal)
library/Other/Functions.php
library/Other/Notifications.php
appspec.yml
bitbucket-pipelines.yml
codedeploy_deploy.py
scripts/                         httpd install, start, stop; CodeDeploy cleanup
```

`application/configs/application.ini` is gitignored (`.gitignore`). Only the `.bak` file is in git. Runtime config is whatever the server has at `application/configs/application.ini`. Whether it matches the bak file is **Unknown / Requires Confirmation**.

`sonar-project.properties` and `sonarqube_scan.bat` were listed when the onboarding task was created. They are not in `master`.

`public/archives/` is referenced as `UPDATE_PATH` in `index.php` and is not in the tree that was cloned. CodeDeploy's `remove_archives.sh` cleans CodeDeploy revision directories, not that public folder.

## Request lifecycle

`public/index.php`:

1. A commented maintenance page can `exit` before bootstrap. It is commented out.
2. `display_errors` is on. `error_reporting` is `E_ALL|E_STRICT`.
3. `APPLICATION_ENV` comes from the environment, or `production`.
4. Include path adds `library/`.
5. `Zend_Application` boots `application/configs/application.ini` and runs.

`.htaccess` serves real files and rewrites every other request to `index.php`.

Zend default routing is `/:module/:controller/:action/*` with module `default`. Module directory is `application/modules` (`resources.frontController.moduleDirectory`).

| URL | Handler |
|---|---|
| `/` | `IndexController::indexAction` |
| `/application/apply/code/{code}` | `ApplicationController::applyAction` |
| `/application/login` | `loginAction` |
| `/application/loginnow/ac/{AC}` | `loginnowAction` |
| `/application/profile/code/{code}` | `profileAction` |
| `/application/logout` | `logoutAction` |
| `/application/update/aaid/{id}` | `updateAction` |
| `/application/updateimage` | `updateimageAction` |
| `/application/checkemail/email/{email}/ac/{ac}` | `checkemailAction` |
| `/index/checkemail/email/{email}/ac/{ac}` | `IndexController::checkemailAction` |
| `/application/attendance/key/{key}/att/{1\|2}` | `attendanceAction` |
| `/application/applymultiple/code/{code}` | `applymultipleAction` |
| `/application/applytest/code/{code}` | `applytestAction` |
| `/application/testattendance/...` | `testattendanceAction` |
| `/application/test` | `testAction` |
| `/application/barcode` | `barcodeAction` |
| `/application/image` | `imageAction` |
| `/application/partners` | empty `partnersAction` |

There is no `accreditations` module. Views that post or GET `/accreditations/application/...` do not match a controller here.

`ErrorController` maps missing route, controller, or action to HTTP 404, and other exceptions to HTTP 500. A log resource is used if the bootstrap has one. The application bootstrap does not register a log. `displayExceptions` follows the front-controller flag (on in the production section of the bak file).

## Bootstrap and layout

`application/Bootstrap.php` defines `_initDbAdapter` and leaves the body empty. The database adapter is the `resources.db` entry in the ini file, not this method.

`application/modules/default/Bootstrap.php` is an empty subclass.

`resources.layout.layout = default`. The bak file also names layouts `admin`, `union`, `referee`, `gir`, and `accreditations`. Those phtml files are not in `application/layouts/`. Only `default.phtml` exists.

`PG_Controller_Plugin_LayoutPicker` is registered as a front-controller plugin (`autoloaderNamespaces.pg = "PG_"`).

Session resource: `resources.session.cookie_lifetime = 864000`.

Applicant session namespace: `user_session`, keys `AAID` and `Code`.

## Configuration backup

`application/configs/application.ini.bak` production section:

- `resources.db.adapter = pdo_mysql`
- host `db.footprintapp.net`
- database `teamwizz_SARU`
- a username and a plaintext password (not reproduced here)
- `resources.db.isDefaultTableAdapter = true` (set twice)
- `display_startup_errors`, `display_errors`, and `displayExceptions` are 1

`[staging : production]` adds nothing. `[testing : production]` and `[development : production]` turn display of errors and exceptions off.

The bak file is committed. Treating it as production config is **Inferred** from the filename. The live file is gitignored.

## Controllers

`ApplicationController` (`application/modules/default/controllers/ApplicationController.php`) is the portal. Memory limit `512M`. It raises the memory limit, turns display errors on, and sets the time zone again.

`IndexController` repeats a large part of the apply flow for GET `/`, with the event code fixed. Its POST block calls methods that exist only on `ApplicationController`. The homepage form does not post to `IndexController`.

`imageAction` contains a very large base64 image literal and prints it as `image/gif`. That is why the controller file is much larger than its logic.

## Models and tables

Each model is a `Zend_Db_Table`. Names below are the `$_name` values. Several queries ignore that name and read a view instead.

| Class | Table `_name` | Also reads |
|---|---|---|
| `Model_Accreditation` | `Accreditation` | |
| `Model_AccreditationList` | `Accreditation_List` | `Accreditations_view`, `SquadAccredition_view`, `AccriditationNewConfirmedApprovedStatus_view`, `ListAccreditations_view`, `AccreditationsZones`, `AccreditationTypes` |
| `Model_AccreditationsApplications` | `AccreditationApplycation` | `AccreditationApplycation_view`, `Accreditation_List`, `AccreditationRoles_view`, `Images`, `AccriditationWithImage_view`, `Accreditaion_Applicants_Services_view` |
| `Model_AccreditationApplicationCategory` | `Accreditation_Application_Category` | `AccreditationCategory_view`, `Accreditation_Administrators` |
| `Model_AccreditationApplicationRole` | `Accreditation_Application_Roles` | |
| `Model_AccreditationCategory` | `Accreditation_Category` | |
| `Model_AccreditationRoles` | `AccreditationRoles` | `AccreditationRoles_view` |
| `Model_AccreditationLinks` | `LinkedAccreditations` | `LinkedAccreditations_view` |
| `Model_AccreditationZone` | `AccreditationsZones` | |
| `Model_AccreditationTypes` | `AccreditationTypes` | |
| `Model_AccreditationServices` | `accreditation_Services` | |
| `Model_AccreditationApplicantsServices` | `Accreditation_Applicants_Services` | |
| `Model_AccreditationEventServices` | `accreditation_Event_Services` | |
| `Model_AccreditationAdministrators` | `Accreditation_Administrators` | |
| `Model_AccreditationHistory` | `Accreditation_History` | |
| `Model_AccreditationGates` | `EventGates` | |
| `Model_AccreditationPermits` | `Accreditation_Permits` | |
| `Model_AccreditationLoadingServices` | `Accreditation_Load_Services_Types` | |
| `Model_AccreditationLoadingZones` | `Accreditation_Loading_Zones` | |
| `Model_AccreditationQueing` | `AccriditationApplicationQueing` | |

Joins in `AccreditationList::getProfileByTourn` use `tournament`, `Fixture`, and `squad`. Those tables are not defined in this repository. They belong to the shared Footprint / SA Rugby schema. Provenance: **Implemented** SQL. Ownership of the schema is **Inferred**.

`getProfile` (the call used by the form) reads `Accreditations_view` by `aCode`. Columns the portal uses from that row include `AC`, `TID`, `FID`, `CloseDate`, `AccreditationName`, `DateFrom`, `DateTo`, `Location`, `FixtureLocation`, `FixtureDate`, `SquadName1`, `SquadName2`, `EmailNotes`, `EmailRejected`, `AccreBanner`, `ContactName`, `ContactEmail`, `ContactTelephone`, `MediaOnly`, `DaysDuration`, `AccreditationNote`, `TermsAndConditions`. The view definition is not in the repo, so the full column list is **Unknown**.

Application row fields written or read by the portal include `AAID`, `AC`, `TID`, `FID`, `Ref`, `email`, `FirstName`, `Surname`, `Mobile`, `IDnumber`, `organisation`, `Category`, `Role`, `Reason_for_pplication`, `DaysAttending`, `Producer_Fullname`, `Producer_Email`, `User_pic`, `acceptnotes`, `acceptTerms`, `DateApply`, `History`, `LastUpdate`, `Status`, `Pending`, `AttendanceKey`, `AttendanceRequest`, `AttendanceResponseDate`, `DeclinedDate`, `aZones`, `Barcode`. Column types and defaults are **Unknown** (no `CREATE TABLE` in the repo).

SQL is built by concatenating request values into `where()` strings in many methods. `Zend_Db` quoting is not used on those paths.

`insertData` on failure prints the exception. Several update methods do the same and `exit`.

## External calls

### Footprint photos

`sendImage` POSTs multipart fields `ac`, `ref`, `pic` to `https://www.footprintapp.net/accreditations/application/updateimage`. A test URL under `footprintapp.net/test/recievedata` is commented out. Timeout `86400`. The HTTP status is not checked.

Profile load HEADs `https://www.footprintapp.net/accre/photos/{Ref}.jpg` and, on HTTP 200, downloads it and puts a data-URI on the view.

### Mail

`Zend_Mail_Transport_Smtp` to `smtp.gmail.com`, port 587, SSL `tls`, auth login, username `accreditation@magus.events`. The password is a literal in `sendMail` and is not recorded here.

Commented predecessor: `mail.footprintapp.net`, username `accreditation@footprintapp.net`.

From: `accreditation@magus.events`, "Accreditation - Vodacom Super Hero Sunday 2020". Subject is fixed. Body is HTML. `addTo` uses the applicant email and `FirstName Surname`.

### Supplier site

Browser link only: `http://supplier.sarugby.online`.

## Auth

No Zend_Auth, no ACL, no API key. The applicant session is set only in `loginnowAction` after the email and reference match.

`isloggedIN` runs at the start of `loginAction`, `applyAction`, and `applytestAction`. If `AAID > 0` it redirects to the profile. It does not protect update or attendance.

`updateAction` and `updateimageAction` take the id and the payload from the request and write them.

## Background work, queues, storage

No cron, queue worker, or daemon is in the repository. `Model_AccreditationQueing` writes `AccriditationApplicationQueing`. Nothing in the controllers calls it.

`getCue` selects up to five approved rows (`Status = 3`, `Cue = 1`, `AC > 26`). No caller in this repo.

Local photo path `AC_PATH . '/photos/'` is used only by `SavePhoto`. `AC_PATH` is undefined here. `UPDATE_PATH` points at `public/archives`, which is not in git.

The live portrait store used by `applyAction` is the Footprint HTTP endpoint, not local disk.

## Deployment

`appspec.yml` (version 0.0, Linux):

- Files: `/` to `/mnt/data/www/html/magus.events/superhero2020.magus.events/`
- Owner and group `apache` on that directory
- `BeforeInstall`: `scripts/install_dependencies` (`yum install -y httpd`), run as root, timeout 300
- `ApplicationStop`: `scripts/remove_archives.sh` deletes CodeDeploy revision directories other than the last successful install
- `ApplicationStart`: `scripts/start_server` (`service httpd start`)

`scripts/stop_server` is not referenced by `appspec.yml`. It stops `httpd` when `pgrep` finds it. The script assigns `isExistApp` with spaces around `=`, which is not valid bash for that assignment.

Pipeline (`image: python:3.5.1`), branches `qa` and `prod` only:

1. Export AWS and CodeDeploy names from `QA_*` or `PROD_*` Bitbucket variables.
2. `apt-get install zip`, `pip install boto3==1.3.0`.
3. `zip -r /tmp/artifact.zip *` from the repo root.
4. `python codedeploy_deploy.py`.

`codedeploy_deploy.py` (Amazon Bitbucket Builds template, labelled "PROD Code Deploy" in a comment) uploads the zip to `s3://$S3_BUCKET/$APPLICATION_NAME/{timestamp}-bitbucket_builds.zip` and calls `create_deployment`. It passes `deploymentConfigName` from `DEPLOYMENT_CONFIG`. The pipeline file does not export `DEPLOYMENT_CONFIG`. It also sets `ignoreApplicationStopFailures=True`. The rest of the script polls the deployment. Bundle type `zip`.

`master` is not a pipeline branch. The inspected commit merges `prod` into `master`. Which branch is deployed to the Super Hero host is **Unknown / Requires Confirmation**.

The zip includes `application.ini.bak` and excludes `application.ini` only if that ignore is respected by `zip *`. Gitignore does not by itself exclude a file from `zip *` if the file is present on disk. The bak file is tracked, so it is in the artifact.

## Security notes (from what is in the tree)

These are observations for review, not a pentest procedure.

- Database password and SMTP password are plaintext in git (`application.ini.bak`, `ApplicationController::sendMail`).
- Production config as committed displays errors and exception details.
- `index.php` forces `display_errors` on before the ini file is read.
- Update and image-update actions do not check the session.
- Many queries concatenate request parameters into SQL.
- `testAction` will send mail to a hard-coded address if the URL is requested.
- `barcodeAction` includes `Barcode39.php` next to the controller include path. That file was not in the controller directory listing. A request to the action would fail the include unless the file is supplied on the server. **Unknown / Requires Confirmation**.

## Development and testing

No PHPUnit configuration for this application, no fixture SQL, no seed, and no documented local setup beyond copying the tree onto Apache with `application.ini` present.

`public/index.php` is the entry. Document root must be `public/` for the `.htaccess` rules and `/assets` URLs to match the layout. The CodeDeploy file mapping copies the whole repository, including `public/`, into the destination. It does not by itself set the Apache document root to `public/`. How the vhost points at `public/` is **Unknown / Requires Confirmation**.

Diagnostic actions left on the default module: `applytest`, `testattendance`, `test`, `barcode`, `image`.

## Monitoring

No application log resource, metrics agent, or health check is configured in the repository. `ErrorController` will log only if a bootstrap `Log` resource exists. It does not.
