# Technical documentation — knowmore_current (Knowmore)

Evidence date: 2026-10-11. Source: `git@bitbucket.org:overdrive/knowmore_current.git`, `master` at `98e19240` (2025-04-04).

This document describes the tree that was inspected. It does not describe `knowmore_php84` or `knowmorescan`.

## Stack

| Layer | What the repository shows |
|---|---|
| Language | PHP. Commit subjects from 2022-12 through 2024-02 call the edits a PHP 8, PHP 8.1, and PHP 8.2 upgrade. `composer.json` has no `php` constraint. The interpreter on the server was not observed |
| Data | MySQL through `mysqli` in `webadmin/system.php` |
| Public pages | Static HTML, CSS, and images exported from WordPress with Elementor and GeneratePress. WordPress core is not in the tree |
| Application framework | Custom Nemo classes: `Nemo`, `NemoBasic`, `NemoDatabase` / `NemoDatabase2`, `NemoDetails`, `NemoList`, `NemoMenu`, `NemoEmail`, `NemoPassword`, `NemoFIM` |
| HTTP dependencies | Composer: `league/oauth2-client` 2.6.0, `vlucas/phpdotenv` 5.3.0, `guzzlehttp/guzzle` 7.3.0 |
| Front-end libraries in the export | GeneratePress, Elementor, Font Awesome, Contact Form 7 styles, dashicons. They are CSS/JS assets, not a running WordPress |
| PDF | `htmltopdf/` and copies under `tools/htmltopdf/` |
| Spreadsheets | PHPExcel under `tools/Excel/` |
| Deploy | Bitbucket Pipelines image `atlassian/default-image:latest`, then `git-ftp` |
| Scan | `sonar.projectKey=knowmore_sso` in `sonar-project.properties`. The Sonar pipe is not used by the `master` pipeline |

There is no Docker file, no `package.json`, and no PHPUnit (or other) test suite. Files such as `tools/test.php` and `tools/phpinfo.php` are manual scripts.

## Repository shape

```text
knowmore_current/
├── *.html, login.php, register*.php, contact.us.php, authorize.php, init_oauth.php
├── bootstrap.php, composer.json, bitbucket-pipelines.yml
├── training/          learner PHP
├── training7.4/       content only (dcontent, profilepictures, webadmin7.4)
├── webadmin/          Nemo admin, cron, system.php
├── tools/             practice tools, payments, nested webadmin
├── layouts/  modules/  css/  js/  images/  flowplayer/
├── phpazure/          Azure AD sample
├── fim_2.0/           file-integrity cron
├── htmltopdf/
└── vendor/            Composer install
```

PHP file counts at inspection, excluding nothing: `tools` 564, `vendor` 210, `webadmin` 165, `training` 40, root 38, `htmltopdf` 24, `layouts` 23, `modules` 11, `fim_2.0` 6, `phpazure` 4. `training7.4` has no PHP.

`.gitignore` ignores:

- `/webadmin/SESSION/*`
- `/PDF/*`
- `/tools/webadmin/SESSION/*`
- `/training/dcontent/*`

It does not ignore `training7.4/dcontent`. One file there has a name longer than the default Windows `MAX_PATH`. A normal `git checkout` on Windows fails on that file unless `core.longpaths` is enabled for the command. The object is in git.

## Request flow

Public HTML is served as files. PHP entry points include their Nemo bootstrap (`_nemo.basic.cls.php` or `webadmin/_nemo.cls.php`), which pulls in `system.php`.

`webadmin/system.php`:

1. Builds `$DATABASE_SETTINGS` for the current `SERVER_NAME`.
2. Connects with `mysqli_connect` to that host, user, password, and database.
3. Sets `session.save_path` from the same row.
4. Loads `sysSettings` into `$SystemSettings`.

There is a second array, `$DATABASE_SETTINGS2`, with its own host and database per key. Callers of that second connection were not listed.

Active keys and how they are labelled (passwords and usernames omitted):

| `SERVER_NAME` key | `environment` value | Database name |
|---|---|---|
| `lamp` | QA | `knowmore_2016_qa` on localhost |
| `debian` | dev | `knowmore_2016_qa` on `192.168.100.6` |
| `localhost` | localhost | `knowmore_2016` on `sql21.cpt1.host-h.net`. Session path ends in `knowmore_current/webadmin/SESSION/`. Protocol forced to HTTPS |
| `dev.overdrive.co.za` | QA | `knowmore_2016qa` on `sql23.cpt1.host-h.net` |
| `www.qa-celestis.co.za` | live | `knowmore_2016` on `sql21.cpt1.host-h.net` |
| `www.knowmore.co.za` | live | `knowmore_2016` on `sql21.cpt1.host-h.net` |

Commented blocks still contain an older live definition for `www.knowmore.co.za` and a QA definition for `www.qa-celestis.co.za` on `sql22.jnb1.host-h.net` / `knowmore_2016_qa`.

Credentials are hardcoded in `system.php`. They are intentionally not copied into Silicon Ark. Removing them from the source repository is a product/security decision, not something this document does.

## Authentication

### Password

`NemoPassword` (`webadmin/_nemo.password.cls.php`) stores `hash = sha256(salt + password)` with a 32-byte salt. Types passed from login are `"User"` and `"Learner"`. Policy keys are in the business-rules document.

`login.php` is the only password form target found (`login.html` `action='login.php'`).

### OAuth

`bootstrap.php` requires Composer, includes `webadmin/_system.php`, then `\Dotenv\Dotenv::createUnsafeImmutable(__DIR__)->load()`. `load()` throws if `.env` is absent. No `.env` is in git. The provider itself reads `PING_*` keys from `$SystemSettings`, not from `getenv`, except `process_auth_code.php`, which calls `getenv('RESOURCE_SERVER_URL')`.

The provider is `League\OAuth2\Client\Provider\GenericProvider` with PKCE S256.

`init_oauth.php` redirects the browser to the authorization URL and stores state and the PKCE code in the session.

`authorize.php` checks `state`, exchanges the code, GETs the resource-owner URL, echoes the body, and exits. It does not create `$_SESSION["USER"]` or `$_SESSION["LEARNER"]`.

`process_auth_code.php` prints the access token and refresh token in an HTML table.

`login.html` points at `oauth/init_oauth.php`. There is no `oauth/` directory.

### Azure AD sample

`phpazure/inc/auth.php` is the CoasterKaty `modAuth` class (header date 2021-11-17). `_config.inc` includes `_nemo.cls.php` and defines `_OAUTH_*` constants from `$SystemSettings`. The file header still says to rename it to `config.inc`. Nothing in `login.html` includes this directory.

### Sessions

Three session shapes appear:

- `$_SESSION["USER"]` — webadmin. Fields set at login: `ID`, `USERNAME`, `EMAIL`, `SECURITYGROUPID`.
- `$_SESSION["LEARNER"]` — learner. Fields set in the learner branch: `ID`, `USERTYPEID`, `USERCATEGORYID`, `USERNAME`, `DISPLAYNAME`, `EMAIL`, `ACTIVE`, `REGIONID`, `TYPOGRAPHY`, `PICTUREPATH`.
- `$_SESSION["ADVISOR"]` — required by `tools/index.php`. Where it is written was not found in the files read for login.
- `$_SESSION["ACCESS"]` — set only on the multi-portal branch.

Session files are configured per host. The gitignore excludes `webadmin/SESSION` and `tools/webadmin/SESSION`.

## AWS

`webadmin/aws.functions.inc.php`:

- `aws_url($uri)` strips an `s3://` prefix, treats the first path segment as the bucket, and returns `https://d1ogvodmq23438.cloudfront.net/` plus the remaining key. It does not sign the URL. The docblock says it creates a signed URL.
- `aws_put_file()` builds `Aws\S3\S3Client` with `aws_access_key`, `aws_secret_key`, and `aws_region` from system settings, and `putObject` with `ACL` `public-read`. `aws/aws-sdk-php` is not a Composer requirement and is not under `vendor/`. This function cannot run on a checkout that has only the committed Composer tree.
- The client is constructed with `'scheme' => 'http'`.

## Mail, SMS, and cron

`NemoEmail` loads a named template, substitutes tokens, and sends. Daily course-start mail uses `SMTP Send As` or, in later sections of the same file, `SMTP Send As OMW`, plus `SMTP BCC`.

`webadmin/system.functions.inc.php` builds an SMS payload from `smsUsername`, `smsPassword`, and `smsLive` on at least one security-warning path.

Cron entry points are PHP files, not queue workers. There is no queue library in `composer.json`. Invocation is outside the repository (HTTP, CLI, or panel cron). `webadmin/cron.hourly.bak.php` and `cron.daily.bak.php` are backups sitting beside the live scripts.

`webadmin/cronjoboutput.txt` is committed. It was not treated as a specification.

## Payments

`tools/purchase.netcash.php` includes the header and menu, then `print_rr('yes include'); die;`. The transaction and licence steps below that line are dead.

`tools/payGateResults.php` is the PayGate callback file. Its result handling was not transcribed.

Invoice PDF classes live beside those scripts (`invoicePDF.cls.php`, `unjinvoicePDF.cls.php`).

## File integrity

`fim_2.0/fim.cron.php` includes `fim.cls.php`, keeps a `sesFile.txt` list, and switches on `Action`, including `FIM: PULL MASTER DATA`. `webadmin/_nemo.fim.cls.php`, `webadmin/cron.fim.php`, and `webadmin/email.fim.php` are the admin-side pieces. A 2016 comment in `system.php` ties NemoFIM to the environment row.

## Web server

`.htaccess` enables the rewrite engine and 301-redirects HTTP to HTTPS. The WordPress block ends with `# END WordPress`. The `.html` extension rules are commented and would have prefixed `/knowmore_current/`.

`.user.ini` sets `auto_prepend_file` to `/usr/www/users/knowmvpnxm/wordfence-waf.php`. That path is not in the repository. Applying this ini on a host without that file will fail PHP startup for this site.

`google82a264ecb0e0454e.html` is a site-verification file.

## Pipeline and quality

`bitbucket-pipelines.yml`:

- `clone.depth: full`.
- Definitions include a SonarQube step (`sonarsource/sonarqube-scan:2.0.1`) using `SONAR_HOST_URL` and `SONAR_TOKEN`, and a `sonar` cache.
- The only pipeline is `branches.master`. Its script updates apt, installs `zip`, `git`, and `git-ftp`, leaves `git ftp init` commented, and runs `git ftp push -u "$FTP_USER" -p "$FTP_PWD" ftp://$FTP_HOST`.
- The Sonar step is not referenced, so a `master` push does not run the scan defined in the same file.

No test step runs.

## Security notes for reviewers

These are observations, not a penetration test. Secret values are omitted on purpose.

- Database passwords, a reCAPTCHA private key name, OAuth client secrets, AWS keys, and SMS passwords are stored in PHP or expected from the database settings table. The database passwords are in git.
- `authorize.php` and `process_auth_code.php` write access tokens and refresh tokens into the HTTP response.
- `aws_put_file()` requests `public-read` on uploaded objects.
- `localhost` and `www.qa-celestis.co.za` are configured onto the database name used for `www.knowmore.co.za`.
- `register.php` dumps `$_REQUEST` before it dies.
- `tools/phpinfo.php` is present.
- Login failures redirect to a path that this tree does not contain, so the user-facing error page is whatever the web server does with `comms/log-in/`.

## Development and testing

No README describes a local setup. A local host named `localhost` is wired to the remote SQL host above, not to a local `knowmore_qa` database (that alternative is commented out).

There is no automated test command to run. Verification of this documentation was reading the tree and the git history at `98e19240`.

## Related systems that are not this repository

| Name | Why it is mentioned |
|---|---|
| `knowmore_php84.git` | Earlier Knowmore tree with WordPress still present. Different remote, different tip |
| `knowmorescan.git` | 2022 scan snapshot. Different remote |
| WordPress on the historical site | Removed here in September 2024. The static export and the learner iframe `latestblogposts/` still assume pages that WordPress used to render |
| Wordfence WAF | Path in `.user.ini` only |
| FTP host | Pipeline variable |
| OAuth server | `PING_*` URLs in system settings |
| FPI / FSCA | Named in CPD copy. No integration client was found |
