# Technical documentation — magus-core

Evidence date: 2026-10-09. Source: `git@bitbucket.org:overdrive/magus-core.git`, `master` at `f806a7e` (2019-08-19).

Only branch: `master`. Authors on the five commits: Mandus Momberg, Andre Ferraz, siliconoverdrive.

## Stack

| Item | Evidence |
|---|---|
| Language | PHP. Front controller enables `E_ALL\|E_STRICT`. Dump header records PHP 7.1.14 on the export machine. The app calls `mcrypt_create_iv`, which PHP 7.2 deprecates and later PHP removes |
| Framework | Zend Framework 1.11.10 (`library/Zend/Version.php`). MVC, `Zend_Db_Table`, `Zend_Auth`, `Zend_Paginator`, `Zend_Mail`, `Zend_Session` |
| Database | MySQL via `pdo_mysql`. Dump: MySQL 5.7.23-0ubuntu0.16.04.1, phpMyAdmin 4.8.3, generated 2019-03-15 11:04, host `localhost:3306`, database name `admin_magus` |
| Web server | Apache `mod_rewrite` (root `.htaccess` and `public/.htaccess`) |
| UI | Limitless admin theme. `default.phtml` contains an HTTrack comment: mirrored 2018-02-02 from `demo.interface.club/limitless` |
| PDF helpers | `library/PG/Controller/Plugin/fpdf.php` and `html2fpdf.php`. No controller in `application/` was seen constructing them |
| Tests | None |
| Package manager | No `composer.json`. `.gitignore` mentions Composer and Doctrine; the application does not use them |
| CI / containers | No `bitbucket-pipelines.yml`, Dockerfile, or deploy script |

## Request path

Root `.htaccess` rewrites any URI that does not already start with `/public/` to `public/{uri}`.

`public/.htaccess` serves the file when it exists and otherwise rewrites to `index.php` (standard Zend public rewrite, including a `BASE` environment variable for aliased vhosts).

`public/index.php`:

- Sets timezone `Africa/Johannesburg`.
- Defines `APPLICATION_PATH` as `../application`.
- Defines `APPLICATION_ENV` from the environment, default `production`.
- Defines `UPDATE_PATH` as `../public/photos/`.
- Puts `library/` on `include_path`.
- Runs `Zend_Application` with `application/configs/application.ini`.
- A full-page maintenance message is commented out above this boot.

`application/Bootstrap.php` defines `_initDbAdapter()` and the method body is empty. Database setup is the INI `resources.db` entry.

`application/modules/default/Bootstrap.php` is the module bootstrap class `Default_Bootstrap` (trimmed in commit `9b5b3e2`).

`PG_Controller_Plugin_LayoutPicker::preDispatch` sets the layout to `$request->getModuleName()`. The only module directory is `default`, so the layout file is `application/layouts/default.phtml`.

INI keys `admin.resources.layout.layout`, `union.resources.layout.layout`, `referee.resources.layout.layout`, `gir.resources.layout.layout`, and `accreditations.resources.layout.layout` do not correspond to files in `application/layouts/`. With this picker they are not selected.

## Configuration

`application/configs/application.ini` at `f806a7e`:

| Key | Value |
|---|---|
| `phpSettings.display_startup_errors` / `display_errors` | 1 in production and development; 0 in testing |
| `resources.frontController.params.displayExceptions` | 1 in production; 0 in development |
| `resources.db.adapter` | `pdo_mysql` |
| `resources.db.params.host` | `localhost` (production and development) |
| `resources.db.params.username` | `root` |
| `resources.db.params.password` | empty |
| `resources.db.params.dbname` | `magus` |
| `resources.locale.default` | `af_ZA` |
| `resources.session.cookie_lifetime` | `864000` |
| `resources.frontController.moduleDirectory` | `application/modules` |
| `autoloaderNamespaces.pg` | `PG_` |
| `resources.layout.layout` | `default` |
| `staging` | Inherits production with no overrides |

The configured database name `magus` is not the dump's database name `admin_magus`.

There is no `.env`. Secrets that exist are PHP string literals, not environment variables.

## Repository layout

```text
magus-core/
  .htaccess                          # send traffic into public/
  .gitignore                         # Composer/Doctrine template; does not ignore the SQL dump
  README.md                          # one line
  admin_magus.sql                    # full phpMyAdmin dump, about 70 MB
  application/
    Bootstrap.php
    configs/application.ini
    layouts/default.phtml            # active shell
    layouts/layout.phtml             # older shell, /assets_old
    models/                          # Zend_Db_Table classes
    modules/default/
      Bootstrap.php
      controllers/
      views/scripts/
      views/helpers/
  library/
    Zend/                            # framework 1.11.10
    PG/                              # project plugins, plus unused match-report files
    Other/Functions.php
    Other/Notifications.php
  public/
    index.php
    .htaccess
    assets/                          # CSS/JS referenced by default.phtml
    dist/
    html/
    limitless/                       # theme vendor tree
    photos/                          # UPDATE_PATH; image writes
```

`library/Zend` and `public/limitless` are the bulk of the 5446 tracked files. Application PHP outside those trees is the Zend module, the models, and `library/PG`.

## Controllers and routes

Zend default routing: `/{controller}/{action}/{param}/{value}`. Module is `default` and does not appear in the URL.

| Controller | Auth in `init` | Actions with behaviour |
|---|---|---|
| `LoginController` | No. Redirects away if already signed in | `index`, `forgot`, `reset`, `logout`, `companypic`, `uploadremotecompany` |
| `RegisterController` | No. Layout off | `index`, `newcompany`, `validateemail` |
| `IndexController` | Yes, else `/login/` | `index` empty; `test` dumps one user |
| `CompaniesController` | Yes | `index`, `add`, `edit`, `list`, `downaloadopt`, `deletetypes` |
| `UsersController` | Yes | `index`, `profile` empty, `list`, `add`, `edit`, `deleteusers`, `imageupload` |
| `VenuesController` | Yes | `index`, `add`, `edit`, `list` |
| `EventsController` | Yes | `index`, `accreditation`, `loadin`, `add`, `addpermit`, `edit`, `list`, `savepermit`, `getpermit`, `getpermite`, `getpermits` |
| `AccreditationController` | Yes | `index` empty, `add` empty, `edit`, `list` |
| `PermitsController` | Yes | `questions`, `profile`, `profiletest`, `create`, `addpermit`, `savepermit`, `emailtest` |
| `RolesController` | Yes | `index`, `add`, `edit`, `list`, `delete` |
| `TypesController` | Yes | `index`, `addtype`, `edittype`, `listtypes`, `deletetypes` |
| `LoadinController` | Yes, else `/companies/login/` | `index` empty |
| `GeoController` | No | geocode helpers |
| `WalkinsController` | No | empty |
| `MonstorController` | No | empty |
| `AuthonController` | No | empty / `test` exits |
| `ErrorController` | No | `error` |

List actions answer DataTables: they read `$_GET["start"]`, `length`, `search`, `order`, and `draw`, paginate with `Zend_Paginator`, and echo JSON.

## Models and tables

| Class | Table |
|---|---|
| `Model_Events` | `Accreditation_List` (not `Events`) |
| `Model_AccreditationPermits` | `Accreditation_Permits` |
| `Model_AccreditationLoadingZones` | `Accreditation_Loading_Zones` |
| `Model_AccreditationLoadingServices` | `Accreditation_Load_Services_Types` |
| `Model_AccreditationGates` | `EventGates` |
| `Model_AccreVehicleTypes` | `vehicletypes` |
| `Model_Companies` | `Companies` |
| `Model_CompanyTypes` | `companyType` |
| `Model_UserProfile` | `users` |
| `Model_UserRoles` | `UserRoles` |
| `Model_Venues` | `Venues` |
| `Model_BBEELevel` | `BBEE_Level` |
| `Model_Country` | `countries` |
| `Model_Province` | `Provinces` |
| `Model_Cities` | `Cities` |
| `Model_Suburbs` | `suburb` |

Most tables are MyISAM, `latin1` or `utf8`. `companyType`, `PermitType`, and `UserRoles` are InnoDB.

### Classes constructed in controllers that have no file

| Missing class | Called from |
|---|---|
| `Model_CompanyProfile` | `RegisterController`, `LoginController::forgotAction`, `LoginController::uploadremotecompanyAction` |
| `Model_AccreCompanyTypes` | `RegisterController::indexAction` |
| `Model_CompanyEvents` | `RegisterController::newcompanyAction`, `PermitsController`, `EventsController::addpermitAction` |
| `Model_AccreditationList` | `PermitsController::createAction`, `Model_AccreditationPermits::getRef` |

`Model_AccreditationServices` appears only in a comment in `EventsController`.

### Names used in SQL that are not tables in the dump

| Name | Where |
|---|---|
| `CompaniesLogin` | `Model_UserProfile::Active` |
| `Jobtitles` | `Model_UserProfile::searchAll` |
| `Company_Events` | `Model_Events::getEvents`, `getEvent` |
| `AccriditationNewConfirmedApprovedStatus_view` | `Model_Events::getStats` |

`Model_Events::getTabe` filters `Accreditation_List` with `FirstName`, `Surname`, `IDNumber`, `Mobile`, and `Email`. Those columns are not on `Accreditation_List`. That search path cannot match this schema. `getAll` is what `AccreditationController::listAction` actually calls.

## Authentication

`Zend_Auth_Adapter_DbTable` on table `users`, identity column `Email`, credential column `password`.

The identity written to the session is the whole result row (`getResultRowObject()`), so the session holds the password digest and the rest of the user columns.

`indexAction` MD5s the password unless it equals a hardcoded literal. The lookup that would swap in a stored administrator hash is commented out.

Password reset and user create also store MD5. There is no salt column.

## Mail

`Zend_Mail_Transport_Smtp` is constructed inside `LoginController`, `RegisterController`, `CompaniesController`, and `PermitsController` (the same block copied four times). Host `mail.sarugby.co.za`, port 25, auth `login`, username `accreditation@sarugby.co.za`. The password literal is in those methods and is intentionally not copied into Silicon Ark.

`send` failures are caught and discarded.

`PermitsController::postEmailToFp` POSTs `Email`, `FullName`, and `Content` to `https://footprintapp.net/email/send` with curl. `addpermitAction` comments out the call.

## Google Geocoding

`GeoController` holds a Geocoding API key as a private property and calls `https://maps.googleapis.com/maps/api/geocode/json` with `file_get_contents`. The key is committed. It is not copied here. The controller does not check `Zend_Auth`.

## Schema notes that affect behaviour

`Accreditation_List` and `Events` share the same column list, including binary artwork (`AccreBG`, banners, A5/A6 backgrounds, MyPass images) and load-in / load-out date text fields. Only `Accreditation_List` is mapped.

`Accreditation_Permits.Accupants` is JSON text (occupants and driver), not a child table.

`users.street`, `suburb`, `city`, `province`, and `country` are `NOT NULL` in the dump. Registration inserts the posted person array; whether the form always sends those keys was not verified field by field.

`countries.rugby_nation` and `countries.fp_client` are enum flags in the dump. Application code that was read does not filter on them. **Inferred** leftover from a rugby / Footprint dataset.

Images in the dump (`longblob`, and hex image payloads on service types) make single INSERT lines very large. The file is about 70 MB and includes operational rows (people, companies, permits). That is personal data in git.

## Security findings (no secret values)

These are facts about the snapshot, for people who will maintain it.

- SMTP password, Google API key, and a login password literal are in PHP source.
- Password reset email contains the new password.
- Passwords are MD5.
- Several queries concatenate request parameters (`ResetKey`, `Email`, `IDNumber`, `RegistrationNumber`, DataTables `search`).
- `GeoController`, `companypicAction`, and `uploadremotecompanyAction` do not require a session.
- Production config displays errors and exceptions.
- `IndexController::testAction` prints a user row for a hardcoded identity number and is reachable by any signed-in user.
- `admin_magus.sql` contains user and company rows.

Rotation and removal are a human decision. This document does not record the secret strings.

## Background work, queues, storage, monitoring

No queue, cron, worker, or scheduler is in the repository.

File storage is the local `public/photos` directory. Accreditation artwork in the dump is in SQL blobs, not in that directory.

No application log configuration beyond Zend's error controller. `ErrorController` follows the usual Zend error view. Monitoring hooks were not found.

`public/index.php` sets `display_errors` to 1 itself, in addition to the INI.

## Development and testing

No PHPUnit, no fixture runner, no seed script separate from the SQL dump. `IndexController::testAction` and `PermitsController::emailtestAction` are ad hoc actions inside the web app.

Local run, from the code rather than from a README (the README does not say how to run it):

1. Apache document root such that the root `.htaccess` can see `public/`, or point the vhost at `public/` and ignore the root rewrite.
2. PHP with `pdo_mysql` and `mcrypt`.
3. A MySQL database. The INI expects name `magus` on localhost as `root` with an empty password. The dump creates `admin_magus`.
4. `APPLICATION_ENV` optional; default `production`.

Whether that combination was ever how the August 2019 server was configured is **Unknown / Requires Confirmation**.

## Sonar files from the onboarding scan

`sonar-project.properties` and `sonarqube_scan.bat` were reported at the root of the scan-host checkout. They are not in `master`. Same situation as other Silicon Ark onboardings where the scan directory had untracked files.

## Discrepancies to keep in view

1. INI database `magus` versus dump database `admin_magus`.
2. `Model_Events` uses `Accreditation_List`; table `Events` is an unused twin in the dump.
3. Permit status labels in `Permit_Status` versus the integers used by `EventsController` (see business rules BR-037 and BR-038).
4. Registration role string `Administrator` versus numeric `UserRoles.RID`.
5. Email copy (48-hour admin activation) versus `validateemailAction` setting `AllowLogin` immediately.
6. Layout picker and INI select `default`; `layout.phtml` and the union/referee/gir keys are unused.
7. Menu `nevigation.phtml` is live. `main_menu.phtml` is not included and points Employees and Events at `#`, and company profile at `/organisation/profile` with no controller.
