# Technical documentation — thegsa

Evidence date: 2026-10-01.

Source: `git@bitbucket.org:overdrive/thegsa.git`, `master` at `522705535dc8a80d3676a76d4b3a189dd7a924b4` (2025-11-10).

Passwords are identified by file. Their values are not copied here. They are committed in git and should be treated as exposed.

## Architecture

Server-rendered PHP. Each screen is a root script that includes `include/system.inc.php`, checks the login cookie, switches on `act`, and prints HTML.

```text
include/header.inc.php
  title, CSS, jquery.js, meta refresh
include/menu.inc.php
include/footer.inc.php
      ^
page script (advertiser.php, contract.php, ...)
  include/*.cls.php
      ^
include/system.inc.php
  copy request into globals
  mysqli connection
  checkLogin / getAccess
      ^
include/database/mysql.cls.php
include/database/sql.inc.php
```

There is no router and no front controller. `index.php` is the post-login home, not a dispatcher.

`composer.json` requires only `dompdf/dompdf` version `^2.0`. The lockfile vendors Dompdf and its libraries under `vendor/`. Most pages do not load the autoloader. `include/reports/salespersonreport.cls.php` calls `vendor/autoload.php`.

## Application structure

| Path | Role |
|---|---|
| `*.php` at root | Page controllers |
| `include/*.cls.php` | Screen classes |
| `include/reports/*.cls.php` | Report SQL and HTML |
| `include/database/sql.inc.php` | SQL string builders |
| `include/database/mysql.cls.php` | `cMySQL` mysqli wrapper |
| `include/system.inc.php` | Bootstrap used by pages |
| `include/systemLive.inc.php` | Second bootstrap. Not included by the root pages that were traced. Same database selection as `system.inc.php` |
| `_nemo.email.gsa.cls.php` | `NemoEmail` |
| `_nemo.database.cls.php` | Included by the email class |
| `include/mailer/` | MIME, SMTP, POP3 library. Not used by `NemoEmail::Send` |
| `include/fpdf.php`, `include/reports/fpdf.php`, `htmltopdf2/` | FPDF |
| `mysqldumper/` | Bundled MySQLDumper |
| `uploads/` | Committed contract files. Not opened |
| `data.thegsa.co.za.zip` | Committed archive, about 30 MB. Not opened |

Dated copies (`*20210414`, `zz-*20200915*`, `zcontract.cls.php`, `zzcontract.cls.php`, `contract.back.php`) are not what the root controllers include. `PreviewMail` and `MailContacts` exist on `zzcontract.cls.php` and are called from `contract.php` without that file being included. See the PRD, WF-12.

## Languages and frameworks

- PHP. Comments dated 2021-04-14 say "PHP 7.4 Upgrade". A comment dated 2022-09-26 says "PHP 8 Upgrade". No platform constraint is in `composer.json`. The host version is **Unknown**.
- MySQL through `mysqli` in `cMySQL`.
- HTML 4.01 Transitional pages, CSS under `include/style/`, and a root `jquery.js`.
- JavaScript menus in `include/javascript/menu.js`.
- Dompdf 2.x for at least the commissions PDF. Renewal PDFs use FPDF directly.
- No Node, no frontend build, no test runner.

## Request handling

`include/system.inc.php` copies every key of `$_GET`, `$_POST`, and `$_COOKIE` onto `$GLOBALS` (`gpc_extract`). Page scripts then read bare variables such as `$act` and `$CompanyID`.

`error_reporting` is `E_ALL` without notices or warnings. `display_errors` is `1`.

`$arrSys["intRowsPerPage"]` is 30. `$intCookieTime` is 3600000. The header meta refresh uses that value plus 1, in seconds.

`cMySQL::doQuery` runs `mysqli_query` and dies with the SQL text and `mysqli_error` on failure. `quote_smart` exists for string literals. Many ids are interpolated without it.

## Authentication

`login.php` clears `ckyGSA`, loads `tblUser` by `strUser`, compares `md5` of the posted password to `strPassword`, and requires `blnActive = 1`.

The cookie payload is `UserID|strUser`. `checkLogin` redirects to `login.php` when it is missing. `getAccess` takes the script name after the last slash and reads `tblSecurity.intLevel` for that URL and `$UID`.

User administration stores `md5` on insert and on update when the password box is non-empty. The password input type on the user form is `text`. The login form's client-side MD5 handler is not bound to `onclick`.

## Database

There is no `CREATE TABLE` script in the repository. Table and column names below are the ones the SQL builders use. A full column list is **Unknown** where `SELECT *` is used.

Connection, from the uncommented lines in `include/system.inc.php` and `include/systemLive.inc.php`:

- Host: `localhost`
- Database: `gsa_qa`
- Credentials: hardcoded in those two files. Not reproduced here.

Commented lines name database `gsa`, database `thegsegszw_db6` on `dedi483.jnb2.host-h.net`, and host `cmysql5-1.wadns.net`. Which of these is production is **Unknown / Requires Confirmation**. The committed passwords should be rotated if those hosts are still reachable.

### Tables referenced by the SQL builders

| Table | Used for |
|---|---|
| `tblUser` | Login account, MD5 password, `blnActive` |
| `tblUserSalesPerson` | Which sales people a user may edit |
| `tblSecurity` | Per-user, per-menu level 0, 1, or 2 |
| `tblSecurityItem` | Per-user item permission |
| `tblmenulevel0`, `tblmenulevel1`, `tblmenulevel1Item` | Menu labels and URLs |
| `tblCompany` | Advertiser |
| `tblAdvertiserPosition` | Positions chosen on the advertiser |
| `tblContact` | Contact, `blnDefault`, `refSubscribeStatusID` |
| `tblContactLog` | Call log |
| `tblContactLogMagazine` | Magazines discussed on a log |
| `tblContract` | Contract header |
| `tblContractItem` | One month of a contract |
| `tblSalesPerson` | Sales person, branch, active |
| `tblBranch` | Branch |
| `tblMagazine` | Publication, `blnSTAR`, `blnDefault` |
| `tblPosition`, `tblSize`, `tblColour`, `tblShape` | Booking attributes |
| `tblA` | Joined from size (`refAID`) in the STAR query. Meaning of the name is **Unknown** |
| `tblPaymentType` | Name and `intInterval` |
| `tblStatus` | Company status lookup. The advertiser list filter does not use it for Active/Prospective |
| `tblStatusContract` | Contract status |
| `tblSubscribeStatus` | Contact subscribe status |
| `tblContactLogType`, `tblContactLogStatus` | Log lookups. Type has `blnActive` |
| `tblTitle` | Contact title |
| `tblUpdateLabel` | Item update label |
| `tblEmailTemplate` | Named mail templates |
| `tblFeeStructure` | Referenced only by `sql_user_check_delete`, which user delete does not call |

`include/database/sql_object.cls.php` is included from `system.inc.php`. The screen classes that were read call `sql_*` functions and `cMySQL` rather than that object.

## APIs

No JSON REST API.

`contract.php`, when `arefCompanyID` is set and not 0, returns `Content-type: text/xml` with `itemContact` and `itemContract` elements and does not require the cookie. It includes contracts where the company is `refClientID` or `refAgencyID` and `refContractID = 0`.

`ajax.php` is a second contact XML attempt. It includes a path that does not exist (`includes/database/sql.inc.php`) and calls `mysql_query`.

## External integrations

| Integration | Direction | Trigger | Evidence |
|---|---|---|---|
| PHP `mail()` | Outbound | `NemoEmail::Send` | `_nemo.email.gsa.cls.php` |
| STAR newsletter | Outbound, not reached | `cron.daily.php` dies first | From address `info@thegsa.co.za` is in the script, not in a sent message |
| SonarQube | Outbound from CI | Pipeline on branch pattern `{main}` and on pull requests | `bitbucket-pipelines.yml` |

No payment gateway, no SMS, no object storage client, and no message queue were found.

`include/mailer/SMTP5.php` and related files are present. `NemoEmail::Send` calls `mail()` with the HTML body.

## Background work

`cron.daily.php` is the only scheduled-style script. It is not a queue worker. Invocation (cron, Task Scheduler, or a URL) is **Unknown**. Current behaviour is to exit immediately.

`ximport.php` and `zimport.php` are request-driven loaders, not workers. Login is commented out.

## Files and PDF

- Renewal PDF: FPDF, A4, points, portrait (`include/renewalpdf.cls.php`). `renewalpdf.php` does not call `checkLogin`.
- Commissions PDF: Dompdf, after `vendor/autoload.php`. Memory limit 250M and execution time 7200 seconds are set in that report class.
- `htmltopdf2/` is an older HTML-to-FPDF tree. The require in the commissions report is commented out.
- `uploads/` contains files whose names start with `Contract`. They were not opened.
- `include/images/GSA rates 2011.pdf` and root `Polyclip SCD_VK.pdf` are static files.

## Configuration and environments

There is no `.env`. Database host, user, password, and schema are PHP literals.

Both `include/system.inc.php` and `include/systemLive.inc.php` select `gsa_qa`. The Live file is not a separate production config in this revision.

`bitbucket-pipelines.yml`:

- Docker service memory 3072 MB
- Step size `4x`
- Pipe `sonarsource/sonarqube-scan:3.0.2`
- Variables `SONAR_HOST_URL` and `SONAR_TOKEN` (repository or workspace variables)
- Extra args force embedded Java and set scanner and JavaScript memory
- Clone depth `full`
- Branches: pattern `{main}`
- Pull requests: `**`

`sonar-project.properties` sets project key `overdrive_thegsa_60e72df9-4390-4206-b7ee-81e537e4c590`.

The inspected branch is `master`. A pipeline trigger named `{main}` does not match `master` unless Bitbucket is also building a branch with that name. **Unknown** whether `main` exists.

There is no deploy step, no CodeDeploy hook, and no Docker Compose file for the application.

## Security notes visible in the tree

These are observations, not a penetration test.

- MD5 passwords.
- Unsigned cookie that is the user id and username.
- Request keys become PHP variables, including cookie keys.
- SQL concatenation of request values.
- `display_errors` on, and failed queries print the SQL.
- Database password committed in `include/system.inc.php` and `include/systemLive.inc.php`.
- `renewalpdf.php`, `ximport.php`, `zimport.php`, and the contract XML branch do not require the cookie.
- `mysqldumper/` is a database administration UI inside the web tree. Its own configuration was not reviewed for live credentials.

## Development and testing

No PHPUnit, no `tests/` directory, and no README with a runbook.

Local shape implied by the code: PHP with mysqli, a MySQL database named `gsa_qa`, and a document root at the repository root so that `login.php` and `include/` resolve. That layout was not executed for this document.

`info.php` exists at the root. Confirm before exposing it; files of that name are often `phpinfo`. It was not executed here.

## Monitoring

The only monitoring artefact is the SonarQube pipeline. No application log writer, health endpoint, or metrics client was found. PHP `mail()` failures return from `mail()` into `NemoEmail` and are not wired to an external monitor in the portion that was read.

## Deployment

**Unknown / Requires Confirmation.** The repository does not contain a deploy script. Historical comments point at hosts `dedi483.jnb2.host-h.net` and `cmysql5-1.wadns.net`, and at domains `thegsa.co.za` and `the-gsa.co.za`. The current connection string points at `localhost` and database `gsa_qa`.
