# Technical documentation — sawis_api_old

Evidence date: 2026-10-04.

Source: `git@bitbucket.org:overdrive/sawis_api_old.git`, `master` at `112491927a9ced74304ff504f86c60424d3cc75d` (2020-10-14). Parent: `6b26d3de7bf9b1d52b4642696ef8e8a8c7040d5c` (2020-08-24, "ini commit", 76 files).

Secrets are not copied into this document. `system.server.PROD.php` contains a database password. `api.user.php` contains a token. Both are omitted.

## Repository

| Item | Value |
|---|---|
| Origin | `git@bitbucket.org:overdrive/sawis_api_old.git` |
| Default branch | `master` |
| Other branches | None on the remote that was cloned |
| Commits | Two. The second commit adds the comment `//test` in `api.php` |
| Language | PHP. No `composer.json`, no `package.json` |
| Tests | No PHPUnit, no CI config. `test/index.php` is a manual HTML form |
| Docs in the source repo | None. `wsdl/wsdl.php` embeds a short version history |

The onboarding issue listed `sonar-project.properties` and `sonarqube_scan.bat`. They are not in this commit. **Unknown / Requires Confirmation.**

## Boot sequence

`api.php`, `api.user.php`, `email.read.receipt.php`, `email.read.online.php`, and `emailTest.php` include `system.php`.

`system.php`:

1. `display_errors = 1`. `error_reporting` is `E_ALL` without `E_STRICT`, `E_NOTICE`, and `E_WARNING`.
2. Includes `system.server.PROD.php`, then `system.server.QA.php`, then `system.server.DEV.php`. Only the PROD file is in git. The other two includes stop the request.
3. Reads `SERVER_NAME`, protocol, script name, and request URI onto `$SystemSettings`. Then sets `SERVER_PROTOCOL` to the literal `https`.
4. Builds `FULL_URL`, `FULL_PATH`, and `BASE_URL` from that.
5. Sets `session.gc_maxlifetime` to 7200 and `session.save_path` from the connection profile. Calls `session_start()`.
6. Includes framework functions, translations, menu classes, and `project.functions.inc.php`.
7. `mysqli_connect` using the profile for `$_SERVER['SERVER_NAME']`. On failure it prints a `SystemMessage` and the server name. On success it sets charset `utf8` and copies every `sysSettings` row onto `$SystemSettings`.

`cron.fim.php` does not include `system.php`. It includes the Nemo, list, email, and FIM classes and then uses `$xdb` and `$DATABASE_SETTINGS`.

## Connection profile

`system.server.PROD.php` (comment 2018-02-02, "new database connection details").

| `SERVER_NAME` key | What is assigned |
|---|---|
| `api.sawisonline.co.za` | `environment` `api`. `hostname` `127.0.0.1`. `username` `root`. Password present, omitted here. `database` `sawis_api`. `sessionpath` `{DOCUMENT_ROOT}/sawis_api_v1/SESSION/`. `env_root` `DOCUMENT_ROOT` |
| `10.0.0.173` | Same environment, hostname, username, password, and database as the row above. `sessionpath` is the same expression. `env_root` is `{DOCUMENT_ROOT}/sawis_api_v1/` |
| `10.0.0.62` | `environment` `api` only. Hostname, user, password, database, session path, and `env_root` are not set for this key |

A commented line would force `SERVER_NAME` to `10.0.0.62`. It is not active.

Any other `SERVER_NAME` has no profile. `mysqli_connect` then receives empty fields. **Implemented.**

`system.php` comments mention S3 hosts `10.0.0.149` (QA) and `10.0.0.60` (prod) and a LAMP path `/sawis/webadmin`. Those assignments are commented out.

## Application structure

```text
api.php                          HTTP entry
api.user.php                     fixed resetPassword entry
system.php                       boot
system.server.PROD.php           connection map
project.functions.inc.php        farm/block helpers, included always
Rest.inc.php                     REST base class
phpinfo.php
email.read.receipt.php
email.read.online.php
emailTest.php
cron.fim.php
test/index.php                   manual caller
wsdl/wsdl.php                    WSDL emitter
wsdl/test/                       NuSOAP tester and library
_framework/_nemo.api.cls.php     NemoAPI
_framework/_nemo.database2.cls.php
_framework/_nemo.password.cls.php
_framework/_nemo.email.cls.php
_framework/_nemo.fim.cls.php
_framework/system.functions.inc.php
_framework/_nemo.*.cls.php       menu, list, report, wizard, details
_nemo.basic.cls.php              second copy at the repository root
403.html 404.html 500.html
css/  images/
deleted.htaccess  deletedagain.htaccess
```

`NemoAPI` extends `REST`. The constructor calls `parent::__construct()`, which parses the HTTP method and copies inputs through `strip_tags`. The API's own arguments were already copied to globals by `localize` before that.

Class files use the prefix `_nemo`. There is no namespace and no autoloader. Includes are `include_once` with filesystem paths.

## HTTP contract

Entry: `api.php`.

| Input | Where it is read | Notes |
|---|---|---|
| `VendorKey` | Global `$VendorKey` | Required for `apiVendor` |
| `call` | Global `$call` | Method name |
| `Page` | Global `$Page` | Default 1 |
| `xmlData` | Global `$xmlData` | XML for write calls |
| `Token` | Global `$Token` | `apiToken.strToken` |
| `type` | `$_GET['type']` | `json`, `text`, or default `xml`. Lost when POST clears `$_GET` |
| `Password` | `$_REQUEST["Password"]` | Vendor authenticate only |
| `IGNORE_TOKENZ` | `$_GET` | Compared to the vendor password |
| Other filters | `$this->args`, which is `$_GET` | Examples: `FarmID`, `UserID`, `Username`, `MemberID`, `List`, `refCellarID`, `LoginToken` |

Success body (XML), content type `application/xml`:

```text
<?xml version='1.0' encoding='UTF-8'?>
<data><rows><row><Column>value</Column></row></rows></data>
```

Success body (JSON), content type `application/json`: `{"data":{"rows":[{...}]}}`.

Authenticate success is `text/html` and the raw token, HTTP 200.

Error texts returned as `text/html`: `INVALID VENDOR KEY`, `INVALID TOKEN`, `INVALID CALL`, `INVALID DATA STRUCTURE`, `AUTHENTICATION FAILED`. The first three use HTTP 202. `AUTHENTICATION FAILED` uses HTTP 200. `REST` knows reason phrases for 200, 201, 204, 404, and 406 only.

`api.user.php` ignores the caller's vendor, call, and token and invokes `resetPassword` for `WOLINE`.

## Data

There is no schema file. Names below are taken from SQL strings. Columns are only those the PHP sets or filters on.

### API tables

| Table | Use |
|---|---|
| `apiVendor` | `VendorKey`, `blnActive`, `strPassword`, `strSite` |
| `apiVendorField` | `VendorKey`, `strCall`, `strField`, `strLastUser` (`INCLUDE` or the column is dropped) |
| `apiToken` | `strToken`, `VendorKey`, `data` (serialized args), `dtToken`, `ID` |
| `apiLog` | `VendorKey`, `SessionToken`, `strLogType`, `strLog`, `strRequest`, `Page`, `ip`, `encArgs`, `dtLog` |

### Settings and users

| Table | Use |
|---|---|
| `sysSettings` | `strSetting`, `strValue`. Loaded entirely into `$SystemSettings` |
| `sysUser` | User API. `UserID`, `strEmail`, `blnActive`, `refSecurityGroupID`, `strSetting:Language`, site flags `blnSite_{strSite}`, `strUser` |
| `sysLogin` | `strToken`, `strUsername`, `dtLastEdit`, `dtTokenExpiration` |
| `sysSecurityGroup` | Names `Member Superuser [Unapproved]` and `External Site User` |
| `sysAudit_{environment}` | FIM. Environment value in the profile is `api`, so the table name the default action deletes is `sysAudit_api` |

### Industry tables and views named by read or write calls

`tblCultivar`, `tblWineType`, `tblIrrigation`, `tblTrellis`, `dwdLocation`, `dwdSawisLocation`, `tblMemberType`, `tblMember`, `tblMemberRelationship`, `tblMember_Address`, `tblMember_Contact`, `tblFarm`, `tblBlock`, `tblPackingMaterial`, `tblIncoterm`, `tblCountryCode`, `tblBG911Type`, `tblCertificateTransationCode`, `tblCertificateRejectionCode`, `tblCertificatePackingCode`, `tblCertificateStatus`, `tblCertificateResult`, `tblPackVolumeColour`, `tblPackVolumeClosureType`, `tblBottleDescription`, `tblManufacturer`, `tblCertification`, `tblLabAnalysis`, `tblTitleDeed`, `tblMagisterialDistrict`, `tblWietaCertification`, `tblBlendMaster`, `tblHarvest`, `tblHarvestItem`, `tblEmail`, `vieWMS`, `stg_wmsBlock`, `stg_wmsBlockActivity`, `wmsBlockGUID`, `tblExportTransaction`, `tmpVinPro`.

`project.functions.inc.php` also names `tblBlockMovement`, `tmpFarm`, `tmpBlock`, `vieFAQ_{language}`, `vieLocation`, `vieLocGeo_{language}`, `vieLocRegion_{language}`, `vieLocDistrict_{language}`, `vieLocWard_{language}`. The API class does not query those.

Password storage is through `NemoPassword` (table created by that class if missing: the constructor SQL creates a password table and default `sysSettings` rows). The exact `CREATE TABLE` text is in `_framework/_nemo.password.cls.php`. Hash comment in that file: `hash(protocol, salt + password)`.

## Auth

1. Vendor row must be active (BR-001 in the business-rules document).
2. `call=authenticate` checks the vendor password and inserts `apiToken`.
3. Other calls check `apiToken` unless `IGNORE_TOKENZ` matches that password.
4. `loginUser` / `loginToken` are separate from the vendor token. They use `sysUser` and `sysLogin` and still require the vendor token first, because they go through `processApi`.
5. PHP session is started for every `system.php` request. The API token is not stored in `$_SESSION` by `NemoAPI`. The session path is under `sawis_api_v1/SESSION/`.

`NemoPassword` settings inserted when absent: minimum length 8, history 8, restricted words `password|123`, interval 12, reset interval `60 MINUTE`. `setPassword` does not use the restricted-word list (the loop is commented out).

## Integrations

| System | Direction | Trigger | Notes |
|---|---|---|---|
| MySQL `sawis_api` | Read and write | Every booted request | Localhost for the two fully configured server names |
| SMTP via `NemoEmail` | Outbound | `resetPassword`, `emailTest.php` | From and BCC keys `SMTP Send As Sawis3` and `SMTP BCC` in `sysSettings`. Host **Unknown** |
| SAWIS Online reset page | Link only | Password reset email | Hosts in `resetPassword` for `SAW3QA` and `S3PROD` |
| Browser GIS / SAWIS origins | Inbound CORS | `api.php` | Five origins. See the PRD |
| Stellenbosch host `sungis10.stb.sun.ac.za` | CORS only | Same file | No other reference |
| NuSOAP | None at runtime of `api.php` | `wsdl/test/index.php` includes the library | |
| jQuery 2.2.0 CDN | Browser load | `test/index.php` | |

`sawis_staging` in Silicon Ark writes that its API database profile uses the name `sawis_api`. This repository connects to a database of that name. Shared use is **Inferred**. It is not proven by a foreign key or a URL in this tree.

## File integrity monitor

`_framework/_nemo.fim.cls.php`, entered from `cron.fim.php`.

- `Record` walks `env_root`.
- `Compare` is static.
- `iniSetPermissions` chmods directories to `0755` and files to `0644`.
- Exclude list is `explode(";", $SystemSettings["FIM_EXCLUDE_PATH_LIKE"])`, matched with `in_array_wildcard`.
- The script allows 1200 seconds and 256M.
- It `print_rr`s `$_REQUEST` before the switch.

No crontab file is in the repository. Whether anything calls `cron.fim.php` is **Unknown / Requires Confirmation**.

## Deployment and environment

No Dockerfile, no pipeline, no deploy script, no `.env`.

Paths that appear in code:

| Path | Where |
|---|---|
| `/sawis_api_v1/` | Session directory, `env_root` for `10.0.0.173`, curl URL in `test/index.php` |
| `/sawis_api/api.php` | Namespace and location in `wsdl/wsdl.php` |
| `/sawis` or `/sawis/webadmin` | Commented LAMP base URL. Reset-password fallback `{SERVER_NAME}/sawis` |
| `http://lamp/sawis/webadmin/` | Favicon on the HTML error pages |

`display_errors` is on. `phpinfo.php` is in the web root of the tree.

`deleted.htaccess` and `deletedagain.htaccess` contain the same `mod_rewrite` rules: if the request is not a real directory or file, rewrite to `api.php`. They are not named `.htaccess`, so Apache will not read them under those names.

## Security notes from the tree

These are observations about what the code does. They are not a pentest.

- Database user in the committed profile is `root` on `127.0.0.1`, with the password in the same file.
- `phpinfo.php` and `display_errors = 1` expose configuration if this tree is the document root.
- `IGNORE_TOKENZ` skips the session token when it matches the vendor password.
- `api.user.php` embeds a vendor key and a token.
- `setVinProMembers` and `cron.fim.php` print the request or the XML.
- `email.read.online.php` will render `tblEmail.txtBody` for a `UID` after marking it read. The comment says the page is not used on the API.
- `Rest::cleanInputs` uses `strip_tags`. SQL is concatenated in many methods. Some values pass through `$xdb->qs`. Vendor key and token are interpolated inside double quotes in `checkVendor` and `checkToken`.
- `get_magic_quotes_gpc` is called. That function was removed in PHP 8. Combined with the missing QA/DEV includes, this tree does not start on a current PHP without files and changes that are not in git. The PHP version that last ran it is **Unknown / Requires Confirmation**.

## Development and testing process

No documented process. The practical artefacts are:

- `test/index.php` posts eight read calls to `/sawis_api_v1/api.php`.
- `wsdl/test/index.php` builds a NuSOAP client. Most of the file is commented.
- `emailTest.php` sends one template. `Send()` is not commented out. `$args` and `$np` are undefined in that file.
- Comments in the API class record manual test notes ("v1.3 Dev: Tested getCultivarList") dated 2017-03-13 and 2017-03-14. There is no stored result.

## Framework classes not on the API path

Included from `system.php` even for `api.php`: translations, menu, menu translator, `project.functions.inc.php`.

Present and not included by `api.php`: control, css, details, list, report, tabs, wizard, and the FIM class (included by `cron.fim.php` only). `_nemo.css.php` is about 1 000 lines. `_nemo.email.cls.php` is included by password reset and by the FIM class file.

`_nemo.basic.cls.php` exists both at the repository root and under `_framework/`. The root file is what `email.read.receipt.php` includes (`_nemo.basic.cls.php` relative to the root). `email.read.online.php` includes the framework copy.
