# Technical documentation — sawis_api

Evidence date: 2026-10-04.

Source: `git@bitbucket.org:silicon_bit_bucket/sawis_api.git`.

`master` at `162260a` (2025-06-20) was read file by file. `prod_api_2020` at `b0d4147` (2026-09-01) was compared for files that change how the process starts, where it deploys, and which API methods exist. Passwords and the hardcoded bypass token are not copied.

## Stack

| Item | Evidence |
|---|---|
| Language | PHP. Files use `mysqli_*`, `simplexml_load_string`, `get_magic_quotes_gpc` (removed from PHP), and unquoted array keys such as `$row[Page]` on `master`. |
| Database | MySQL-compatible, database name `sawis_api`, charset `utf8`, InnoDB named only in the `sysPassword` CREATE statement. Spatial function `ST_GeomFromText` is used on `prod_api_2020` only. |
| HTTP style | One script, `api.php`. Method name is the `call` parameter. Not a router framework. |
| Libraries | No Composer file. NuSOAP is vendored under `wsdl/test/lib/` and is not loaded by `api.php`. jQuery 2.2.0 is loaded only by `test/index.php`. |
| PHP version | **Unknown.** Branch `feature/development/20240719-RS-PHP-8.2-upgrade` (tip 2024-07-22, subject "still busy") is an ancestor of `prod_api_2020` and is not an ancestor of `master`. |

`get_magic_quotes_gpc` in `Rest.inc.php` and `system.functions.inc.php` fatals on PHP 8. **Requires Confirmation** which PHP version `master` was written for, and which version the host of `prod_api_2020` runs. Prod's `system.functions.inc.php` differs by 154 lines and was not re-read.

## Repository layout (`master`)

```text
api.php                         HTTP entry
api.user.php                    Fixed vendor/call/token entry
system.php                      Boot, DB connect, settings load
system.server.PROD.php          Host map for production names
project.functions.inc.php       Unused farm/block helpers
Rest.inc.php                    REST base class
cron.fim.php                    File-integrity entry
deploy.php                      git reset script
email.read.online.php
email.read.receipt.php
emailTest.php
phpinfo.php
_framework/                     Nemo classes (API, DB, email, password, FIM, UI)
_nemo.basic.cls.php             Duplicate of the framework basic class
test/index.php                  Manual call form
wsdl/                           WSDL emitters and NuSOAP
css/ colourlessNEW.css
images/                         Logos and browser icons
403.html 404.html 500.html
bitbucket-pipelines.yml
sonar-project.properties        sonar.projectKey=sawis_api
deleted.htaccess
deletedagain.htaccess           Same rewrite rules as each other
```

106 files including images. Four commits on `master`.

`prod_api_2020` adds `system.server.QA.php`, `system.server.DEV.php`, `index.html`, `images/test.png`, `test.hmtl`, `test_deploy.txt`, and renames `deleted.htaccess` to `.htaccess`. It deletes `bitbucket-pipelines.yml` and `sonar-project.properties`.

## Boot sequence

`master` `system.php`:

1. `display_errors = 1`. `error_reporting` is `E_ALL` without strict, notice, or warning.
2. `include_once` PROD, QA, then DEV settings. QA and DEV files are absent, so those includes warn and continue.
3. Copy server name, force protocol string `https`, build `FULL_URL`, `FULL_PATH`, `BASE_URL`.
4. `session.gc_maxlifetime` 7200. `session.save_path` from the settings object. `session_start()`.
5. Include framework functions (this copies request fields into globals), translations, menu classes, and `project.functions.inc.php`.
6. `mysqli_connect` using hostname, username, password, and database for `$_SERVER[SERVER_NAME]`.
7. `mysqli_set_charset` `utf8`.
8. `NemoDatabase("sysSettings")` loads every `strSetting` / `strValue` into `$SystemSettings`.

`prod_api_2020` `system.php` includes exactly one settings file:

| `SERVER_NAME` | File |
|---|---|
| `api.sawisonline.co.za`, `10.0.0.173`, `10.0.0.62`, `sawisonline.co.za`, `www.sawisonline.co.za`, `10.0.0.60` | `system.server.PROD.php` |
| `10.0.0.108`, `196.22.249.180`, `196.22.249.180:11443`, `10.0.0.149`, `qa.sawisonline.co.za` | `system.server.QA.php` |
| `10.0.0.145`, `localhost`, `lamp`, `192.168.100.6` | `system.server.DEV.php` |

Protocol stays the scheme from `SERVER_PROTOCOL` unless `$_SERVER["HTTPS"]` is set. Menu, translation, and `project.functions.inc.php` includes are commented out.

Quoted array keys (`$_SERVER["SERVER_NAME"]`) appear on the prod copy. `master` uses unquoted keys.

## Database connection map

Credentials are hardcoded in the settings PHP files. Usernames and passwords are omitted here.

### `master` — `system.server.PROD.php` only

| Server name key | Environment | DB host | Database | Session path | `env_root` |
|---|---|---|---|---|---|
| `api.sawisonline.co.za` | `api` | `127.0.0.1` | `sawis_api` | `{DOCUMENT_ROOT}/sawis_api_v1/SESSION/` | `DOCUMENT_ROOT` |
| `10.0.0.173` | `api` | `127.0.0.1` | `sawis_api` | same session path | `{DOCUMENT_ROOT}/sawis_api_v1/` |
| `10.0.0.62` | `api` | not set | not set | not set | not set |

Comment in the file: 2018-02-02, new database connection details.

### `prod_api_2020` — PROD file

Same three keys and the same host, database, session path, and `env_root` as the table above. The file now constructs `stdClass` objects before assignment. The database username string differs from `master`. Password still sits in the file.

`10.0.0.62` is still only given `environment`. Hosts listed in the `system.php` switch (`sawisonline.co.za`, `www.sawisonline.co.za`, `10.0.0.60`) have no `$DATABASE_SETTINGS` entry in the PROD file.

### `prod_api_2020` — QA file

| Server name key | Environment | DB host | Database | Session path |
|---|---|---|---|---|
| `10.0.0.108` | `QA` | `10.0.0.108` | `sawis_api` | `{DOCUMENT_ROOT}/sawis_api/SESSION/` |

`env_root` is `DOCUMENT_ROOT`. The QA switch also names `196.22.249.180`, port `11443`, `10.0.0.149`, and `qa.sawisonline.co.za`. Those keys are not in this file.

### `prod_api_2020` — DEV file

Several assignments overwrite `localhost`. The last assignment in the file wins.

| Key | Environment | DB host | Database | Notes |
|---|---|---|---|---|
| `10.0.0.145` | not set on this key | `10.0.0.145` | `sawis_api` | Comment: DEV sawis_online |
| `localhost` | `dev` | first `10.0.0.145`, later overwritten to `10.0.0.108` | `sawis_api` | Session path `sawis_api_v1/SESSION/` |
| `lamp` | `dev` | `localhost` | `sawis_api` | Comment: DEV QA |
| `192.168.100.6` | `dev` | `localhost` | `sawis_api` | Comment: LAMP access through VPN |

`localhost:8888` is commented out.

## Request path

1. Optional rewrite. On `master` the rules live in `deleted.htaccess` and `deletedagain.htaccess`, so Apache will not apply them under the usual filename. On `prod_api_2020` the file is `.htaccess`: if the request is not an existing file, rewrite to `api.php` and keep the query string.
2. `api.php` sets CORS headers for the origins listed in the PRD, then builds `NemoAPI`.
3. `REST::__construct` reads GET, POST, or PUT into `$this->_request` and `strip_tags` each scalar. `NemoAPI` does not use `$this->_request` for dispatch. It uses globals and `$_GET`.
4. `processApi` checks the token, calls the method, whitelists fields, encodes.

`api.php` on `prod_api_2020` copies `VendorKey`, `call`, `Page`, `xmlData`, and `Token` from `$_REQUEST`. `master` relies on `localize()` having created those globals.

PUT is parsed by `REST` via `php://input`. The API class still expects `xmlData` as a field. **Unknown** whether any caller uses PUT.

## Classes that participate

| Class | File | Used by the API |
|---|---|---|
| `REST` | `Rest.inc.php` | Yes. Parent of `NemoAPI`. |
| `NemoAPI` | `_framework/_nemo.api.cls.php` | Yes. About 2,400 lines on `master`, larger on `prod_api_2020` (809 lines of diff). |
| `NemoDatabase` | `_framework/_nemo.database2.cls.php` | Yes. Generic table load/save. |
| `NemoPassword` | `_framework/_nemo.password.cls.php` | Yes. |
| `NemoEmail` | `_framework/_nemo.email.cls.php` | Yes, from reset and from `emailTest.php`. |
| `NemoFIM` | `_framework/_nemo.fim.cls.php` | Yes, from `cron.fim.php`. |
| `NemoBasic` and UI classes | `_framework/_nemo.*.cls.php`, root `_nemo.basic.cls.php` | Included from email and FIM paths. No admin screens are routed here. |

`NemoAPI` include of `Rest.inc.php` uses a path relative to the framework directory (`include_once("Rest.inc.php")`). `Rest.inc.php` itself is in the repository root. **Requires Confirmation** that the PHP include path on the server contains the root. A request that only has the framework directory on the include path would not find it. This was not executed.

## Tables and views named in SQL

Not a schema. These identifiers are queried or written. Columns exist only where a statement names them.

`apiVendor`, `apiVendorField`, `apiToken`, `apiLog`, `sysSettings`, `sysUser`, `sysLogin`, `sysSecurityGroup`, `sysPassword`, `sysYear` (prod spatial call), `sysAudit_{environment}`.

`tblCultivar`, `tblWineType`, `tblIrrigation`, `tblTrellis`, `tblMember`, `tblMemberType`, `tblMemberRelationship`, `tblMember_Address`, `tblMember_Contact`, `tblFarm`, `tblBlock`, `tblBlockMovement` (unused helper), `tblPackingMaterial`, `tblIncoterm`, `tblCountryCode`, `tblBG911Type`, `tblCertificateTransationCode`, `tblCertificateRejectionCode`, `tblCertificatePackingCode`, `tblCertificateStatus`, `tblCertificateResult`, `tblPackVolumeColour`, `tblPackVolumeClosureType`, `tblBottleDescription`, `tblBottleWeight` (prod), `tblManufacturer`, `tblCertification`, `tblLabAnalysis`, `tblTitleDeed`, `tblMagisterialDistrict`, `tblWietaCertification`, `tblBlendMaster`, `tblHarvest`, `tblHarvestItem`, `tblExportTransaction`, `tblEmail`, `tmpVinPro`, `tmpBlock`, `tmpFarm` (unused helper).

`wmsBlock`, `wmsBlockActivity`, `wmsBlockGUID`, `stg_wmsBlock`.

Views: `dwdLocation`, `dwdSawisLocation`, `vieWMS`, `vieLocation`, `vieLocGeo_*`, `vieLocRegion_*`, `vieLocDistrict_*`, `vieLocWard_*`, `vieFAQ_{language}` (tooltip helper, no caller).

## External integration

| System | Direction | Trigger | Data | Auth | Failure |
|---|---|---|---|---|---|
| Partner HTTP client | Inbound | Request to `api.php` | `VendorKey`, `call`, `Token`, `Page`, `type`, filters, XML | Vendor key plus token | Text error, often HTTP 202 |
| MySQL `sawis_api` | Outbound from PHP | Every request after boot | SQL above | Username and password in the settings file | Connect failure prints a critical message. Queries are not wrapped in a retry. |
| SMTP | Outbound | `resetPassword`, `emailTest.php` | Template "Reset Password", link, logo | `sysSettings` SMTP keys | `emailTest.php` echoes "SENDING FAILED". `resetPassword` does not check the return value before returning true. |
| SAWIS web `resetPassword.php` | Link only | Email body | `strEmail`, `ResetKey` | None in this repo | Page is not in this repo. |
| Browsers on the CORS list | Inbound browser | `HTTP_ORIGIN` match | Same API | Same vendor token. `Access-Control-Allow-Credentials: true` | Other origins get no CORS header. |
| Bitbucket Pipelines / SonarQube | CI, `master` only | Push to `master` or any pull request | Source scan | `SONAR_TOKEN` variable | Pipeline file does not deploy. |
| `deploy.php` | Shell on the web server | HTTP hit, no login | `git fetch` and `git reset --hard` | None in the script | Output is HTML of the command text. |

`test/index.php` calls `http://{HTTP_HOST}/sawis_api_v1/api.php` with curl and does not send a token. Against current `processApi` that request fails the token check unless the ignore parameter is also sent. The form does not send it.

## Deployment

No Dockerfile, no CodeDeploy, no Ansible.

`deploy.php` is a web page that `shell_exec`s git. Title "SAWIS Upload QA".

| Branch of the file | Commands |
|---|---|
| `master` | `echo $PWD`, `whoami`, `git branch`, fetch and hard reset `origin/dev_api_2020` |
| `prod_api_2020` and `qa_api_2020` | `cd /var/www/html/sawis_api/`, then the same probes, fetch and hard reset `origin/prod_api_2020` |

**Inferred** runtime directory `/var/www/html/sawis_api/` on the machine where that script is hosted. **Unknown** whether the script is exposed on a public vhost.

The Bitbucket pipeline on `master` does not deploy. It runs SonarQube scan 3.0.2 with 3072 MB Docker memory and a full clone.

## Security observations (evidence, not a pentest)

These are visible in the tree. They need a human decision. They are not written as approved product behaviour.

- Database password is committed in `system.server.PROD.php` on `master`, and in the PROD, QA, and DEV files on `prod_api_2020`.
- `phpinfo.php` has no authentication.
- `deploy.php` and `cron.fim.php` have no authentication. Deploy runs git reset. FIM chmods the tree.
- `IGNORE_TOKENZ` equal to the vendor password skips the session token.
- `api.user.php` embeds a vendor, a call, and a token.
- `display_errors` is on. Connect errors print the driver message.
- `REST` input cleaning is `strip_tags` and an optional strip-slashes. Queries use `qs()` in many places and string interpolation in others (`apiVendor` lookup, token SQL).
- `emailTest.php` contains a personal mailbox address and sends mail when requested.
- `setVinProMembers` prints the XML with `print_rr` before writing.

## Development and test process

No PHPUnit, no CI test step, no seed data.

`test/index.php` is a manual form for eight read calls. `wsdl/test/` is the NuSOAP sample server. Comments in the API class say particular calls were "DEV: Tested" on dates in 2017. Those notes are not repeatable tests.

Pipeline on `master`: SonarQube only.

## Monitoring

No application log file, metrics exporter, or health route was found.

`apiLog` is the call audit, retained 3 months. `sysLogin` rows are written by `tblLoginInsert` during user login. FIM writes `sysAudit_{environment}`.

`phpinfo.php` is not a monitor.

## Configuration not in git

All of these change behaviour and are **Unknown** as values:

- `sysSettings`, including `PageSize`, `CURENT_BLOCK_YEAR`, SMTP keys, password-policy keys, `FIM_EXCLUDE_PATH_LIKE`.
- Rows in `apiVendor`, `apiVendorField`, `sysSecurityGroup`, `sysUser`.
- The cron schedule for `cron.fim.php`.
- Apache vhost, TLS certificate, and whether `.htaccess` is allowed.
- PHP version and include path.
