# Technical documentation — matchcentre.footprintapp.net

Evidence date: 2026-10-09. Source: `git@bitbucket.org:overdrive/matchcentre.footprintapp.net.git`, `master` at `3bd3534`. `origin/prod` is `160e70b` (2023-02-08).

Provenance: **Documented**, **Implemented**, **Inferred**, **Planned**, **Unknown**.

Credentials that appear in tracked config are not copied into this file.

## Architecture

**Implemented.** Classic Zend Framework 1 MVC.

1. Root `.htaccess` rewrites any URI that does not already start with `/public/` to `public/{path}`.
2. `public/.htaccess` sends non-file requests to `public/index.php`.
3. `public/index.php` sets the time zone to `Africa/Johannesburg`, defines `APPLICATION_PATH` as `../application`, defaults `APPLICATION_ENV` to `production`, defines `UPDATE_PATH` as `public/archives`, puts `library/` on the include path, and runs `Zend_Application`.
4. `application/configs/application.ini` sets the module directory, PDO MySQL, layout `default`, session cookie lifetime, locale `af_ZA`, and front-controller plugin `PG_Controller_Plugin_LayoutPicker`.
5. `LayoutPicker` sets the layout name to the current module name.
6. Controllers live in `application/modules/{module}/controllers`. Models live in `application/models` and extend `Zend_Db_Table`.
7. The root `index.php` is an empty file. It is not the front controller.

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

## Application structure

```text
public/index.php                 front controller
application/configs/application.ini
application/Bootstrap.php
application/layouts/             default.phtml, bluecard, menu, food, admin, repo
application/models/              Zend_Db_Table classes
application/modules/default/     match centre
application/modules/bluecard/
application/modules/menu/
application/modules/companiesold/
application/modules/companyonl/
library/Zend/                    Framework 1.11.10
library/PG/                      local plugins (layout, mail, upload, FPDF)
library/Other/                   Functions.php, Notifications.php
public/                          web root, theme assets, dompdf, archives (gitignored)
website/                         second tracked tree (application, library, public)
scripts/                         CodeDeploy hooks
appspec.yml
bitbucket-pipelines.yml
codedeploy_deploy.py
test/                            static PHP/FCGI/SSI sample, not app tests
```

Zend default routes apply. There is no custom routes file. A request `/tournaments/profile/tour/123` is module `default`, controller `tournaments`, action `profile`, param `tour=123`. Module-specific controllers are classes like `Bluecard_IndexController` and `Menu_SessionController`.

Layouts present under `application/layouts`: `admin`, `bluecard`, `default`, `food`, `menu`, `repo`, and `Copy of companies.phtml`. The ini also names layouts `union`, `referee`, `gir`, and `accreditations`, and those files are not in `application/layouts`. `LayoutPicker` replaces the configured layout with the module name, so `companiesold` and `companyonl` look for layout files that are not in this folder.

## Frameworks and languages

| Item | Evidence | Provenance |
|---|---|---|
| PHP | `.php` controllers, models, `public/index.php` | **Implemented**. Language version **Unknown**. `mcrypt_create_iv` is called from `CronController` and from company controllers, which implies a PHP build that still has `mcrypt` |
| Zend Framework 1.11.10 | `library/Zend/Version.php` constant `VERSION` | **Implemented** |
| JavaScript and CSS | jQuery 2.1.1, Bootstrap JS, Sportsline theme, Font Awesome 4.4.0, under `public/wp-content` and `public/wp-includes` | **Implemented** static assets |
| Python 3.5.7 | Bitbucket Pipelines image, used only to run `codedeploy_deploy.py` | **Implemented** for deploy |
| HTML views | `.phtml` | **Implemented** |

No Composer, npm, or webpack config was found at the root.

## Database

**Implemented** in `application/configs/application.ini`:

- Adapter `pdo_mysql`.
- Host `localhost`.
- Database name `teamwizz_SARU`.
- A username and password are stored in the same tracked file. They are omitted here.
- The ini section `production` holds those values. `staging`, `testing`, and `development` inherit `production` and do not replace the database.

The file is listed in `.gitignore` and is still tracked (`git ls-files application/configs/application.ini`).

`website/application/configs/application.ini` is also tracked. It was not transcribed.

Tables and views the PHP names are listed in `prd.md`. Schema DDL is not in the repository. Column types, indexes, and view definitions are **Unknown**.

Queries concatenate request values into SQL (`where("T.TID ='".$id."'")` and similar). That is **Implemented**. It is not parameter binding.

## Routing surface (default module)

| URL shape | Action | Notes |
|---|---|---|
| `/` | `IndexController::indexAction` | Home |
| `/tournaments/profile/tour/{id}` | `profileAction` | Tournament |
| `/tournaments/match/id/{id}` | `matchAction` | Match |
| `/tournaments/team/id/{id}` | `teamAction` | Squad |
| `/tournaments/commentary/id/{id}` | `commentaryAction` | No layout |
| `/tournaments/getmatchreport/fid/{id}` | `getmatchreportAction` | PDF stream |
| `/tournaments/fixtures/tour/{id}` | `fixturesAction` | Writes JSON, no layout |
| `/tournaments/result/tour/{id}` | `resultAction` | No layout |
| `/login` | `LoginController::indexAction` | |
| `/login/logout` | `logoutAction` | |
| `/login/reset/user/{key}` | `resetAction` | |
| `/ajax/getdata` | `AjaxController::getdataAction` | Cache rebuild, then `exit` |
| `/news` | `NewsController::indexAction` | Uses undefined `UPDATES_PATH` |
| `/cron/...` | `CronController` | See background section |
| `/bluecard`, `/bluecard/submit` | Blue Card module | |
| `/menu`, `/menu/session/dates/id/{id}` and sibling actions | Menu module | |

Test actions (`test`, `indextest`, `matchtest`, `profiletest`, `testupdate`, and others) use the same router. See `features.md`.

## External integrations

| Target | Call site | Provenance |
|---|---|---|
| `https://footprintapp.net/pdf/matchreport/id/{id}` | `TournamentsController::getmatchreportAction` via `file_get_contents`, then DOMPDF | **Implemented** |
| `https://fsrugby.appwizard.co.za/scripts/AppWiz.dll` | `CronController::senddata_to_Api`, POST multipart, timeout 86400 seconds. A commented URL points at `https://footprintapp.net/test/recievedata` | **Implemented** |
| `Zend_Mail` from `support@footprint.net` | Login reset. Blue Card uses its own `sendMail` | **Implemented**. SMTP host **Unknown** |
| Google Analytics `UA-125030318-1` | `application/layouts/default.phtml` | **Implemented** |
| `https://scan.appmonitor.co.za` | Sonar scanner config | **Implemented** as scan config |
| AWS S3 and CodeDeploy | `codedeploy_deploy.py` | **Implemented** |
| Google Fonts and `maxcdn.bootstrapcdn.com` Font Awesome | Default layout | **Implemented** |

`getmatchreportAction` trusts the remote HTML as DOMPDF input. Auth on that URL is not set in this repository.

## Infrastructure and deployment

**Implemented.**

`bitbucket-pipelines.yml` defines steps only for branches `qa` and `prod`. Each step exports `QA_*` or `PROD_*` pipeline variables into `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION`, `APPLICATION_NAME`, `DEPLOYMENT_GROUP_NAME`, and `S3_BUCKET`, installs `zip` and `boto3==1.3.0`, zips `*` to `/tmp/artifact.zip`, and runs `python codedeploy_deploy.py`.

`codedeploy_deploy.py` (Amazon Bitbucket Builds template, header dated 2016) uploads that zip to S3 at `{APPLICATION_NAME}/{timestamp}-bitbucket_builds.zip` and calls `create_deployment`. It passes `deploymentConfigName` from env `DEPLOYMENT_CONFIG`. The pipeline file does not set `DEPLOYMENT_CONFIG`. **Unknown** whether the variable exists in Bitbucket. The script sets `ignoreApplicationStopFailures` true.

`appspec.yml`:

- OS `linux`.
- Files from `/` to `/mnt/data/www/html/footprintapp.net/matchcenter.footprintapp.net/`.
- Owner and group `apache`.
- `BeforeInstall`: `scripts/install_dependencies` (`yum install -y httpd`), run as root, timeout 300.
- `ApplicationStop`: `scripts/stop_server` (stop httpd when `pgrep` finds it) and the appspec also lists `scripts/remove_archives.sh` on `ApplicationStop`. `remove_archives.sh` deletes CodeDeploy revision directories other than the last successful install. It does not delete `public/archives`.
- `ApplicationStart`: `scripts/start_server` (`service httpd start`).

The destination path spells the host `matchcenter` (no second "e"). The git slug spells it `matchcentre`. **Requires Confirmation** which name the vhost uses.

`stop_server` assigns `isExistApp = \`pgrep httpd\`` with spaces around `=`, which is invalid in bash. **Implemented** as written. Whether the hook succeeds is **Unknown**.

The zip command is `zip -r /tmp/artifact.zip *`. It does not use git archive. Untracked files present in the pipeline workspace are included. Gitignored `public/archives` is included only if the workspace has those files. Dotfiles that `*` does not match are **Inferred** to be omitted. **Requires Confirmation.**

## Environment configuration

| Setting | Where | Provenance |
|---|---|---|
| `APPLICATION_ENV` | Environment variable, default `production` in `public/index.php` | **Implemented** |
| Database, locale `af_ZA`, session `864000`, layout, plugin | `application/configs/application.ini` | **Implemented** |
| `UPDATE_PATH` | `public/archives` absolute path derived in `index.php` | **Implemented** |
| `UPDATES_PATH` | Used by `NewsController` and some tournament actions. Not defined in `index.php` | **Implemented** use of an undefined constant |
| `IMG_PATH` | Used by `CronController::addtozip`. Not defined in `index.php` | **Implemented** use. Definition **Unknown** |
| Pipeline and Sonar tokens | Environment only. Not committed as values | **Implemented** as variable names |

`development` and `testing` ini sections still set `display_errors` to 0 and inherit the production database.

## Authentication

**Implemented** only in `default` `LoginController` and a parallel login in `companiesold`:

- `Zend_Auth` with `Zend_Auth_Adapter_DbTable`.
- Identity column `Username`, credential column `Password`, table name `co_credetials`.
- Password compared as MD5, except the fixed-literal path described in `business-rules.md` BR-029.
- Successful identity stored is the `profile` row, not the credential row.
- `Model_Credentials` is constructed in the login action. There is no `application/models/Credentials.php`. Zend's model autoloader will not find that class from the files present. **Requires Confirmation** how `/login` runs in production.
- No `Zend_Acl` usage was found in the application modules that were read. The ACL package exists only because the whole Zend library is vendored.
- No captcha and no CSRF field were found on the login form action.

## Background work, queues, storage

There is no separate worker process, cron entry file, or queue daemon in the repository.

`CronController` is an HTTP controller:

| Action | Behaviour | Models it constructs |
|---|---|---|
| `autorunAction` | Disables layout. No further work | — |
| `queingAction` | Reads a cue list, builds an A6 PDF with DOMPDF from `{scheme}://{host}/pdf/a6/id/{AAID}`, writes it under `IMG_PATH/accre/pdfs/...`, sets `Cue` 0 and `Status` 3. When `AC == 14`, POSTs to AppWizard | `Model_AccreditationsApplications`, `Model_AccreditationZone`, `Model_AccreditationList` |
| `getaccreAction` | For each row, calls `ApplyAccreditationNow` and deletes the queue row | `Model_AccreditationQueing`. `ApplyAccreditationNow` was not found on this controller |
| `matchesAction`, `logsAction`, `getfixtuesAction`, `viewAction` | Fixture and log helpers | `Model_Fixture`, `Model_Tournament`, and in `init` also `Model_Profile` and `Model_Assessments` |

`Model_Assessments`, `Model_AccreditationsApplications`, `Model_AccreditationZone`, `Model_AccreditationQueing`, and `Model_Credentials` are not files under `application/models` (search excluded `library/Zend` and `website`). `init()` constructs `Model_Assessments`, so any `/cron` request fails at init if that class cannot autoload. **Requires Confirmation** whether `website/` or another include path supplies them. The front controller include path is only `library/`.

Storage:

- MySQL for system of record.
- `public/archives` for JSON snapshots and generated JPEG images (tournaments, squads, logs, results).
- Blue Card source PDFs expected at `public/archives/bluecard/`.
- Accreditation PDFs expected under `IMG_PATH` (undefined in the front controller).

No S3 application storage, Redis, or message queue was found in application code. S3 is used only as the CodeDeploy artifact bucket.

## Monitoring and quality

| Mechanism | Evidence | Provenance |
|---|---|---|
| Google Analytics | Layout tag | **Implemented** |
| SonarQube project `overdrive_matchcentre_footprintapp_net` | `sonar-project.properties`. Sources `.`. Host `https://scan.appmonitor.co.za` | **Implemented** |
| `sonarqube_scan.bat` | `git pull`, then `sonar-scanner.bat` from `C:\wamp64\www\sonar_scan\sonar-scanner\bin`, token from `SONAR_TOKEN` or `SONARQUBE_TOKEN` | **Implemented** local script |
| Bitbucket pipeline tests | None. The pipeline only deploys | **Implemented** |
| PHPUnit or application tests | Not present. `test/` is a hosting demo page | **Implemented** absence |
| Zend error controllers | Each module has `ErrorController` with `errorAction` | **Implemented**. Log destination **Unknown** |

## Security observations

These are facts about the tree, not a pentest. Secrets are not repeated.

| Observation | Evidence | Provenance |
|---|---|---|
| Database username and password are in a tracked ini file | `application/configs/application.ini` is in `git ls-files` | **Implemented**. Rotation is **Requires Confirmation** |
| Password storage is MD5 | `LoginController` | **Implemented** |
| A fixed password literal bypasses hashing and uses the stored hash | `LoginController::indexAction` | **Implemented**. Policy **Requires Confirmation** |
| Request values are concatenated into SQL | Models and controllers throughout | **Implemented** |
| `/ajax/getdata` and cron actions have no auth check in the methods read | Those controllers | **Implemented** |
| `public/index.php` enables `display_errors` and `E_ALL` before bootstrap | `public/index.php` | **Implemented** |
| Match report HTML is loaded from another host and rendered by DOMPDF | `getmatchreportAction` | **Implemented** |
| Blue Card `testAction` emails a hardcoded recipient | `Bluecard_IndexController` | **Implemented** |
| HTTPS redirect in the root `.htaccess` is commented out | `.htaccess` | **Implemented** as disabled |
| Pipeline image is Python 3.5.7 and pins `boto3==1.3.0` | `bitbucket-pipelines.yml` | **Implemented** |

## Development and testing process

**Implemented** as far as the repo shows:

- Branches that exist on the remote: `master`, `dev-19-10-2022`, `qa`, `prod`.
- Deploy is "merge into `qa` or `prod`". There is no pipeline on `master`.
- Authors on the short history: Heinrich du Plessis and `heinrichoverdrive`.
- No README, no contribution guide, no PHPUnit configuration, no migration tool.
- Local Sonar scan is a Windows batch file aimed at a WAMP scanner path.

How a developer runs the app locally (virtual host, PHP version, database dump) is **Unknown / Requires Confirmation**. The ini expects MySQL on `localhost` with database `teamwizz_SARU`.

## `website/` tree

**Implemented:** `git ls-files website` returns 15,545 paths, including `website/application`, `website/library`, and `website/public`.

**Unknown / Requires Confirmation:** whether any vhost document root points at `website/public`, whether it is an older snapshot committed beside the live tree, or whether it is unused. The root rewrite and `public/index.php` bootstrap the top-level `application/` directory, not `website/application`.

## Branch difference that affects runtime

`git diff master origin/prod -- application` is four files: `application.ini` (2 lines), `application/layouts/default.phtml`, `application/models/Tournament.php`, and `IndexController.php`.

Behavioural difference in `IndexController` on `origin/prod`: the home year is not forced to 2019, and tournament selection and year bucketing use `EndDate`. Details are in `prd.md` WF-1 and `business-rules.md` BR-006 and BR-007.

## Not in this repository

- Database schema and seed data.
- Web server vhost file.
- A system cron schedule calling `/cron` or `/ajax/getdata`.
- SMTP credentials.
- A README or runbook.
- Automated tests of match, login, or Blue Card behaviour.
