# Technical documentation — convergence

Evidence date: 2026-10-18. Source: `master` at `6b66438` (2021-03-15, "PHP 7.4 Changes").

Credentials appear in `system.php` and `system2.php`. This document names hosts and database names only.

## Architecture

Convergence is a server-rendered PHP application on the Nemo framework. Each screen is a root `*.php` controller plus a class in `includes/`. There is no router, no Composer autoload, and no front controller. Apache or equivalent maps the URL to the PHP file. The web server is not configured in this repository.

```text
Request
  → system2.php          (included by NemoBasic)
       mysqli connect, session start, load sysSettings
  → NemoBasic            login, ticket.ops, cron, password reset
  → Nemo                 every other screen
       menu, security flags, filters, sort, paging
  → includes/*.cls.php   queries and HTML
  → layouts/*.incl.php   Gentelella-style shell
```

`_framework/_nemo.basic.cls.php` line 7 includes `system2.php`. `system.php` is a second config file and is not the include used by `NemoBasic`. A commented include of `system.php` remains in `_framework/_nemo.email.cls.php`.

## Application structure

| Path | Contents |
|---|---|
| `*.php` at root | Page controllers. 59 PHP files at the root, including tests and backups |
| `includes/` | Entity classes, mPDF, and a few reports |
| `_framework/` | Nemo: database, list, details, menu, email, audit, password, widgets |
| `layouts/` | Back-office shell, basic (login) layout, email layout, calendar layout |
| `css/`, `js/`, `images/` | Application assets. jQuery 1.10.2 is under `js/` |
| `vendors/` | Third-party front-end (Bootstrap, Font Awesome, FullCalendar, Morris, Select2, PNotify, and others) |
| `build/css`, `build/js` | Built theme assets |
| `PHPExcel/`, `PHPExcel20210315/` | Spreadsheet libraries. The dated copy was added on 2021-03-15 |
| `edulab/training_certificates/` | Certificate files |
| `uploads/`, `profilepictures/` | Uploaded files and profile images |
| `SESSION/` | PHP session save path for some environments. One session file is committed |

Backup and experiment files kept beside the live ones: `proposal.bak.php`, `sla.ticket.backup.php`, `sla.taskX.php`, `*_clsOLD.php`, `pydg.report.cls.alt.dont.delete.php`, `clientTest.php`, `email.test.php`, `test.php`, `xtestNestedSortx.php`, `controls.pj.php`.

## Languages and frameworks

| Item | Evidence |
|---|---|
| PHP | All application code. Commit message on `6b66438` says PHP 7.4. `project.functions.inc.php` replaces `$string{$i}` with `$string[$i]` under a 2021-02-25 comment |
| MySQL through `mysqli` | `_framework/_nemo.database2.cls.php`. An older `mysql_*` class remains in `_nemo.database.cls.php` |
| Nemo | In-repo framework. Not a public package |
| Bootstrap / Gentelella-style admin theme | `layouts/header.incl.php`, `vendors/bootstrap` |
| jQuery | `js/jquery-1.10.2.js` and vendor copies |
| mPDF | `includes/mpdf/`, called from `includes/proposal.cls.php` |
| PHPExcel | `new PHPExcel()` in `_framework/_nemo.list.cls.php` |
| No Composer, npm, or PHPUnit project files at the root | Absence at `6b66438` |

`error_reporting` is `E_ALL` without `E_STRICT`, `E_NOTICE`, and `E_WARNING`. `display_errors` is `1`. Both are set in `system2.php`.

## Databases

Connection settings are a PHP map keyed by `$_SERVER[SERVER_NAME]`.

| Key | Role in comments | Database name | Host name as stored |
|---|---|---|---|
| `debian` | DEV REPO | `convergence_2017` | `192.168.100.6` |
| `lamp` | DEV QA | `convergence_2017` | `localhost` |
| `localhost` | DEV Localhost in `system2.php` | `convergence_2017` | `sql45.jnb1.host-h.net` |
| `www.liveurl` | LIVE QA | placeholder `dbname` | placeholder `hostname` |
| `convergence.overdrive.co.za` | LIVE PROD | `convergence` | `sql20.jnb1.host-h.net` |

`www.convergence.overdrive.co.za` and a null server name are rewritten to `convergence.overdrive.co.za`. Live and localhost keys set `SERVER_PROTOCOL` to `HTTPS`.

`system.php` differs: its `localhost` key points at local `root` and database `convergence_2017`, and it has no `DATABASE_SETTINGS_AUX` block. Because `NemoBasic` includes `system2.php`, the `system2.php` map is the one that runs.

`DATABASE_SETTINGS_AUX['localhost']` names database `convergence_2017_aux` on `sql45.jnb1.host-h.net`. No other file reads `DATABASE_SETTINGS_AUX`. Email code uses the primary `$xdb` connection against `tblEmail`.

Session path examples:

| Key | Path |
|---|---|
| `debian` | `SESSION/` |
| `lamp` | `{DOCUMENT_ROOT}/Convergence_2017/dev/SESSION/` |
| `localhost` (`system2.php`) | `{DOCUMENT_ROOT}/convergence/SESSION/` |
| live host | `{DOCUMENT_ROOT}/webadmin/SESSION/` |

There is no schema dump. Tables the application names (false positives from English words in SQL strings removed):

System: `sysUser`, `sysSecurityGroup`, `sysSecurity`, `sysMenuLevel1`, `sysMenuLevel2`, `sysMenuSidebar`, `sysPassword`, `sysSettings`, `sysLogin`, `sysSpam`, `sysAudit`, `sysAuditLog`, `sysAuditCriticalFields`, `sysFAQ`, `sysCompany`, `sysWidget`, `sysCommLog`.

CRM: `crmClient`, `crmContact`, `crmDepartment`, `crmDepartmentStatus`, `crmTicket`, `crmTask`, `crmTime`, `crmProduct`, `crmProductBundle`, `crmProductBundleItem`, `crmProductCategory`, `crmProposal`, `crmProposalGroup`, `crmProposalItem`, `crmProposalBundle`, `crmPaymentTerms`, `crmCodeFault`, `crmCodeFix`, `crmTaskFault`, `crmTaskFix`, `crmSalesPerson`, `crmSupplier`, `crmTicketOwner`.

SLA: `slaSchedule`, `slaTicket`, `slaTask`.

Training: `eduTrainingCourse`, `eduTrainingMaterial`, `eduTrainingMaterialBooking`, `eduTrainingCertificate`.

Mail: `tblEmail`, `tblEmailTemplate`.

Other names in queries: `vieClient`, `vieDepartmentStatus`, `vieEmailContacts`, `vieFAQ`, `vieProductBundle`, `vieBillingRate`, `vieCalendar`, `dwfProposal`, `dwfProposalGroup`, `dwfProposalItem`, `dwfTask`, `dwfTemplate`, `auditlog`, `faq`.

Older names still mentioned: `tblClient`, `tblClientOLD`, `tblProduct`, `tblSalesPerson`, `tblOrders`, `tblMember`, `tblMenuLevel1`, `tblMenuLevel2`, `tblGLAccountName`.

Views and data-warehouse tables (`vie*`, `dwf*`) are not defined in the repository. Provenance: **Unknown** beyond the name.

Detail forms introspect `information_schema.COLUMNS`. `getQuoteStatusArray` evals the column type of `crmProposal.strStatus` after rewriting `enum` to `array`.

The password bootstrap creates `sysPassword` (`Type` enum `User` or `Supplier`, `salt`, `hash`, `dtPassword`) if it is missing, then stops the request.

## APIs

There is no REST API. `ajaxFunctions.php` is a POST switch on `type`. Cases include:

- `SendResetEmail`
- Uploads: `UploadProfilePicture`, `UploadProduct`, `UploadDocument`, `UploadTrainningCertificate`
- Lookups: `getClientDetails`, `getContactDetails`, `getClientContacts`, `getClientJSON`, `getContactsJSON`, `getProductJSON`, `getDepartmentStatusJSON`, `jsGetTicketJSON`, `getTicketTasks`, `getTasksByTicket`, `getTaskDetails`, `getCodeFaultDetail`, `getCodeFixDetail`
- Proposal builder: `ajaxAddProposalItem`, `ajaxDeleteProposalItem`, `ajaxMoveProposalItem`, `ajaxAddProposalGroup`, `ajaxDeleteProposalGroup`, `ajaxMoveProposalGroup`, `getProposalEmailHTML`
- Bundles: `ajaxAddProductBundleItem`, `ajaxDeleteProductBundleItem`, `ajaxMoveProductBundleItem`
- Schedule: `ajaxAddScheduleTimeTask`, `ajaxUpdateScheduleTimeTask`, `ajaxChangeScheduleTimeTask`, `ajaxDeleteScheduleTimeTask`, `ajaxGetScheduleTimes`, `saveScheduleTimeRecord`, `getScheduleTimePopupJSON`
- Other: `jsReloadfrStatus`, `getTicketDueDate`, `checkAccountCode`, `saveTempTicket`, `jsApplyMarkup`, `setApprovalText`

The file includes `system2.php` directly. It does not construct `Nemo`, so it does not run the session redirect in `Nemo::__construct`. Provenance: **Implemented**. What each case does when called without a session is **Requires Confirmation** case by case.

## External integrations

| Integration | How | Evidence |
|---|---|---|
| PHP `mail()` | `NemoEmail` builds headers and calls `mail()` | `_framework/_nemo.email.cls.php` |
| MyMobileAPI HTTP | `SendSMS` POSTs to `http://www.mymobileapi.com/api5/http5.aspx` | `system.functions.inc.php`. No caller |
| Lucidchart | iframe on the home page | `Dashboard::RenderProjectWBS` |
| xneelo MySQL | host names `sql20.jnb1.host-h.net` and `sql45.jnb1.host-h.net` | `system2.php` |

No AWS SDK, queue, or webhook receiver is in the application code.

## Infrastructure and deployment

Not in the repository: Dockerfile, CI config, web-server config, crontab, or deploy script.

Comments and paths imply:

- Dev on a host named `lamp` under `/Convergence_2017/dev/`.
- A machine named `debian` at `192.168.100.6`.
- Public site `convergence.overdrive.co.za` with the app under `/webadmin/` for sessions.
- README: "Always production on master".

A comment dated 2017-11-02 in both system files says "Hetzner Breach Reset All FTP/DB details". Provenance: **Documented** as a comment. What was reset, and whether the values now in git are the post-reset ones, is **Unknown**.

`6b66438` is the PHP 7.4 change. It adds `system2.php` and `PHPExcel20210315/`, patches string offsets, and commits a file under `SESSION/`.

## Environment configuration

Settings that change behaviour without a code edit live in `sysSettings` (`strSetting`, `strValue`), loaded into `$SystemSettings` at connect time.

Keys referenced in code:

| Key | Used for |
|---|---|
| `Brand` | Page title (comment 2017-03-24) |
| `PASSWORD_Enable_Policy`, `PASSWORD_Strength_Check`, `PASSWORD_Strength_Check_MinLength`, `PASSWORD_MaxAge`, `PASSWORD_MaxHistory`, `PASSWORD_Restricted_Words`, `PASSWORD_Reset_Interval` | Password policy |
| `SMTP Send As`, `SMTP BCC` | Mail From and BCC |
| `smsUsername`, `smsPassword`, `smsLive` | SMS helper |
| `Title` | Lockout mail subject prefix |
| `LogoutRedirect`, `SessionExRedirect` | Referenced by the shell |

The full settings catalogue is the live table. **Unknown** from git.

## Authentication

1. Email lookup plus active group.
2. `sha256(hex salt + password)` compared to `sysPassword.hash`. Salt is 32 bytes from `openssl_random_pseudo_bytes`, stored hex-encoded.
3. Session keys: `ID`, `USERNAME`, `EMAIL`, `SECURITYGROUPID`, `DEPARTMENTID` on `$_SESSION[USER]`.
4. Lockout after five bad passwords deactivates the user.
5. Reset email via ajax, completed on `resetPassword.php`.

`Supplier` is a legal `sysPassword.Type`. No supplier login page was found. Provenance: column enum **Implemented**; supplier login **Unknown**.

Menu security is loaded per URL. The view flag is then overwritten to 1 for the current script (see business rule BR-008).

`ticket.ops.php` accepts `Action`, `TicketID`, and `ContactID` on a page that does not pass through `Nemo`'s login redirect.

## Background work

| Entry | Schedule in repo | Work |
|---|---|---|
| `cron.daily.createticket.php` | None. It is a web page | Creates today's SLA tickets and tasks |

No queue, worker, or cron daemon config is in the tree. The page uses `$_SESSION['USER']->USERNAME` when building `strFirstUser`, which is empty when nobody is logged in.

## Storage

| Store | Path | Notes |
|---|---|---|
| Profile pictures | `profilepictures/` | Header falls back to `blank.jpg` |
| Uploads | `uploads/` | Ajax upload types write here. Exact subpaths are in `ajaxFunctions.php` |
| Training certificates | `edulab/training_certificates/` | |
| Sessions | `SESSION/` or the host-specific path | One session file is in git. Contents were not read |
| Proposal PDFs | path built in `Proposal` PDF output | Directory is the code's `$path` at generation time |

## Monitoring and security notes

Observed in the source. This is not a pentest.

- Database passwords are committed in `system.php` and `system2.php`. A 2017 comment records a hosting-account reset. Whether the committed values are still valid is **Requires Confirmation**. They are omitted here on purpose.
- `display_errors` is on, and failed queries `die` with the SQL text (`NemoDatabase2::doQuery`).
- `Nemo::iniSecurity` disables the view-permission denial.
- Authorisation links are a fixed dev URL with ticket and contact ids.
- `ajaxFunctions.php` is included without the `Nemo` session gate.
- `cron.daily.createticket.php` is included without that gate and uses `MAX(TicketID)`.
- A session file is committed.
- `email.test.php` contains a destination address in source.
- SQL is often concatenated. Some values pass through `NemoDatabase::qs`. Many do not.
- No CAPTCHA, rate limit, or security-header configuration was found.
- `includes/mpdf/mpdf.php` can `curl` a URL when `allow_url_fopen` is off. That is library code.

## Development and testing

Git authors on `master`: Jaco Brits and jacobrits (2019-04-04), rachelmogano (2021-03-15). File comments also name maanie, CL / christiaan, Rachel, pj, Gael, neils, Stephen.

There is no automated test runner. Pages named test are manual screens. `README.md` tells the reader to commit on the Bitbucket website and says production is `master`.

PHP 7.4 work that is visible:

- String offsets in `cleanColumnName` and matching one-line edits in framework classes.
- `system2.php` introduced as the included config.
- PHPExcel replaced or duplicated so the library runs on PHP 7.4.

How to run it locally is **Unknown / Requires Confirmation**. A working setup needs PHP with `mysqli` and `openssl`, a database that already contains the Nemo tables, and an HTTP host name that matches a key in `system2.php`. Empty databases will fail when `sysSettings` is read, unless the password class is the first code to create its own table.

## Relationship to convergence_production

`silicon_ark/convergence_production/` describes `convergence_production` at `a91a555` (2021-06-15). Both trees are Nemo applications branded as Silicon Overdrive / Convergence and name the host `convergence.overdrive.co.za`. This repository's tip is three months earlier and does not contain that tree's HR, email-scry, hourly cron, or under-construction files. Whether one replaced the other in production is **Unknown / Requires Confirmation**.
