# Technical documentation — kb-online (Kohler Box)

Evidence date: 2026-10-11. Source: `git@bitbucket.org:overdrive/kb-online.git`, `main` at `5aa7141` (2026-09-17, rachelmogano).

The source tree has no README, architecture document, or operations runbook. This file records what the tree shows.

## Application shape

Custom PHP pages on the in-house Nemo framework. There is no router, no Composer autoload of application classes, and no `public/` directory. Each feature is a PHP file in the repository root. Shared behaviour is in `_nemo.*.cls.php` and `includes/`.

Request flow:

1. The page includes a Nemo class.
2. Nemo includes `system.php` (through `_nemo.basic.cls.php` / `_nemo.cls.php`).
3. `system.php` loads `.env`, connects with `mysqli`, and loads `sysSettings`.
4. `Nemo` starts from the session and `sysSecurity`.
5. The page switches on `$_REQUEST["Action"]` and echoes HTML from `layouts/`.

`NemoBasic` is used by `login.php` and `login2fa.php` so those pages do not require an existing session. Other pages use `Nemo`.

## Languages and libraries

| Item | Evidence |
|---|---|
| PHP | All application files. Comments and commit `fecf0b1` refer to a PHP 8 upgrade. `composer.json` does not constrain a PHP version |
| JavaScript | Inline in the PHP, plus `js/`. jQuery 1.8.2 from `ajax.googleapis.com`. jQuery UI 1.8.6 under `js/` |
| CSS | `css/`, including `css/nemo.{NemoPrimary}.css` where `NemoPrimary` is a setting |
| MySQL access | `mysqli_*` in `system.php`. Wrapper `NemoDatabase` |
| PDF | `mpdf/mpdf` ^8.2 via Composer. `Order` calls `vendor/autoload.php` |
| Spreadsheets | `phpoffice/phpspreadsheet` ^1.29. Production workbook uses `Writer\Xlsx` and saves a `.xls` path |
| Barcodes | `barcode.php`, GD `imagecreate`. Default symbology Code 128 |
| HTML mail | `NemoEmail` builds headers and calls PHP `mail()` |
| Old copies | `mpdf/` (legacy mPDF) and `PHPExcel/` remain in the tree. The live order class uses Composer |

`composer.json` requires only those two packages. `vendor/` is committed.

## Runtime configuration

`system.php`:

- If `SERVER_NAME` is `kb-online.co.za`, it is rewritten to `www.kb-online.co.za`.
- `loadEnv(__DIR__ . "/.env")` parses `KEY=VALUE` lines and skips lines whose trim starts with `#`.
- Connection fields are `getenv("DB_HOST")`, `DB_USER`, `DB_PASS`, `DB_NAME`.
- Session save path is `DOCUMENT_ROOT` plus `getenv("DB_SESSIONPATH")`.
- `SERVER_PROTOCOL` is overwritten from `getenv("DB_PROTOCOL")` when that setting object field is set.
- `session.gc_maxlifetime` is `720000`.
- `display_errors` is `1`. `error_reporting` is `E_ALL` without `E_NOTICE` and without `E_WARNING`.
- After connect, every `sysSettings` row becomes `$SystemSettings[strSetting] = strValue`.

`.env` is not in the git tree and is not listed in `.gitignore`. A 2016 comment says the live server moved to Hetzner. A 2017 comment says FTP and database details were reset after a Hetzner breach. Current host, PHP version, and database version are **Unknown / Requires Confirmation**.

Settings the code reads by name include: `Home Screen Message`, `Title`, `Brand`, `NemoPrimary`, `SessionExRedirect`, `LogoutRedirect`, `SMTP Send As`, `SMTP BCC`, `ORDER_STATUS`, `dir_quotes`, `dir_orders`, `dir_signed_estimates`, `dir_supporting_documents`, `smsUsername`, `smsPassword`, `smsLive`. There are more keys in the database than this list. **Unknown** beyond the names the PHP references.

`$HTTP` is hardcoded to `https://` for email substitution. The login redirect to `www.kb-online.co.za` uses `http://`.

## Authentication

`login.php` compares `md5($strPassword)` to `sysUser.strPasswordMD5`. The session object is a `stdClass` with `ID`, `USERNAME`, `EMAIL`, `SECURITYGROUPID`.

`database/passwordCleanup.sql` alters `sysUser.strPassword` to nullable `VARCHAR(20)` and sets `strPassword` null on `sysUser` and `sysLogin`. `tblLoginInsert` no longer writes the password. Whether the script has been executed is **Unknown / Requires Confirmation**.

`login2fa.php` and `includes/security.cls.php` implement a 6-digit OTP, stored on `tblEmailAuthCodes`, described as valid for 5 minutes. The seed template subject and body name Pernod Ricard SA, not Kohler Box. Commit `2bcf33f` (2026-08-07) says OTP changes were reverted. `login.php` does not redirect here.

`CheckSecurityLock` does not persist `blnLogin = 0` because `Save()` is commented out.

`barcode.php` and `info.php` do not include Nemo and do not check the session. `info.php` calls `phpinfo()`.

## Authorisation

`sysSecurity` joined to `sysMenuLevel2` for the session's security group. Missing `blnView` redirects to `message.php`.

Hardcoded exceptions, not stored as security rows:

- Factory spreadsheet reprint: groups `"1"` and `"2"`, compared as strings, plus six `@kohlerbox.com` addresses.
- Order-list defaults for groups `4`, `6`, and `7`, compared as integers.

Menu construction is in `_nemo.menu.cls.php`. Labels and URLs are selected from `sysMenuLevel1` and `sysMenuLevel2`.

## Data

There is no full schema dump. Tables and views are known because PHP names them.

| Name | Role |
|---|---|
| `tblOrder` | The job. Status, dates, PO number, quantity, unit price, amount, notes, instructions, archive flag, printed flag |
| `vieEnquiry`, `vieEstimation`, `vieQuote`, `vieOrder` | Unarchived slices of `tblOrder` by status prefix. Definitions are comments, not migration files |
| `tblCustomer`, `tblContact`, `tblProduct` | Master data. Foreign keys are described in class-header comments |
| `tblBoard`, `tblStyle`, `tblDescriptor`, `tblIndustry`, `tblSubCategory`, `tblSalesRep` | Reference data |
| `vieSalesPerson`, `vieEstimator` | User slices used as dropdowns |
| `tblProductPrice`, `tblQuoteProductPrice` | Prices and which prices are on the quote |
| `tblDelivery`, `vieDelivery` | Deliveries and a summary used by lists |
| `impSignedEstimates`, `impSupportingDocuments` | Uploaded files for an order |
| `impSheet` | Spreadsheet import queue |
| `tmpProductPrice` | Annual increase queue |
| `tblEmailTemplate` | Named templates |
| `tblEmailAuthCodes` | OTP rows. Created by `database/tblMailAuthCodes.sql` |
| `sysUser`, `sysSecurityGroup`, `sysSecurity`, `sysMenuLevel1`, `sysMenuLevel2` | Identity and menu |
| `sysSettings` | Key/value configuration |
| `sysLogin` | Login audit |
| `sysSpam` | Written by `tblSpamInsert`. No caller was found in the application pages read for this document |

Nemo details screens call `AssimulateTable`, which builds controls from the live table. Column nullability and labels therefore follow the database, not a schema file in git.

Order number assignment and status transitions are in `includes/order.cls.php`. See the PRD and business rules for the formulas.

Uploads use `move_uploaded_file` into the setting directories. `chmod` `0777` is applied to the temporary upload before the move.

## Files written by the application

| Artifact | Path in code |
|---|---|
| Estimate PDF used by email | `documents/Estimates/{filename}` |
| Quote PDF | `{dir_quotes}/{strOrderNumber}.quote.pdf` |
| Production workbook | `documents/Orders/{strOrderNumber}.order.xls` and setting `dir_orders` for the view link |
| Signed estimates | `{dir_signed_estimates}` |
| Supporting documents | `{dir_supporting_documents}` |

`user_files/` and `SESSION/` are in `.gitignore`.

## Email

`NemoEmail::Send` calls PHP `mail()`. Reply-To is emitted with the hyphenated header name. Templates are loaded by name from `tblEmailTemplate`.

Names the application requests:

| Template | Caller |
|---|---|
| New Enquiry | `Order::SendEnquiryToEstimator` |
| Estimation completed | `Order::SendEstimationToSalesPerson` |
| Quote | `Order::SendQuote` |
| KB Estimate | `Order::EmailEstimate` |
| Order Confirmation | `Order::ConfirmOrder` |
| Personalised Price Increase | `Product` and `customer.price.list.cls.php` |
| General Price Increase | `Product` |
| One-Time Password | `Security::sendOTP` only |

The OTP seed is the only template body in git, and it addresses a different product (PRSA). Other bodies are **Unknown** without the database.

## Background work

No queue worker, cron file, or daemon was found. The annual price increase tells the user to refresh after a timeout. `product.price.increase.php` raises `max_execution_time` to 1200 and `memory_limit` to `512M`.

`SendSMS` posts to MyMobileAPI. Nothing calls it.

## Security notes observed in the tree

These are facts about the code, not a test report.

- Passwords are stored and compared as MD5.
- Many queries concatenate request values. `NemoDatabase::qs` exists and is used in some places. Other statements interpolate `$_REQUEST` and `$_POST` directly, including `jsSaveNewCustomer`.
- `jsGetCalculatedProductInfo` runs `eval` on style-spec segments after substituting length, width, and height.
- `display_errors` is enabled in `system.php`.
- `info.php` exposes `phpinfo()` without a login check.
- `barcode.php` renders any `text` query without a login check.
- Database connection failure is printed to the response.
- A historical comment records a 2017 Hetzner credential reset. Those secrets are not in this snapshot. `.env` is absent from git.
- The OTP seed and `login2fa.php` are unused by `login.php`.

## Development and testing

- No PHPUnit configuration, no application tests, no `bitbucket-pipelines.yml`, no Dockerfile, no README.
- `test.php` and `zSandBox.php` are present. They were not treated as an automated suite.
- Sublime project files `kohlerbox.sublime-project` and `kohlerbox.sublime-workspace` are committed.
- `.htaccess` is empty.
- Comments name contributors over time (Stephen, neils, Rachel, maanie, Shorouq, HDP, Wessels, Uriel, pj, ca). The git history on this remote starts 2026-05-18 with the snapshot import. Author of the tip commit is rachelmogano.

## Repository layout

```text
kb-online/
  *.php                     Feature pages and Nemo framework classes
  includes/                 Domain classes and report scripts
  layouts/                  HTML shell
  css/ js/ images/ assets/  Front-end assets
  custom_controls/          Calendar control
  database/                 Three SQL scripts only
  documents/                Runtime output paths referenced by code
  vendor/                   Composer install, committed
  mpdf/ PHPExcel/           Older library copies
  composer.json
```

## Local inspection

The task's scan path was not on this machine. Inspection used a clone of `https://bitbucket.org/overdrive/kb-online.git` at `5aa7141`. The source repository was not modified.
