# PRD — dev.knowmore (Knowmore)

Evidence date: 2026-10-17.

Source: `git@bitbucket.org:overdrive/dev.knowmore.git`, `master` at `43f9c23` (2021-02-10).

There is no prior PRD in the source repository or in Silicon Ark. Requirements below are recovered from the implementation. Provenance is labelled. Secrets and personal data are not reproduced.

## Product purpose

**Implemented.** Knowmore is the logged-in site for a learner and for a financial-planning practice.

A learner who already has a `LEARNER` session can open an overview, courses, workshops, business tools, publications, and a CPD record. Business tools (assessment, gap analysis, client surveys, licences, invoices) run under an `ADVISOR` session tied to a practice. Staff run a separate admin under a `USER` session.

The pages that create the learner session are not in this repository. When the session is missing, `training/_nemo.basic.cls.php` sends the browser to `../second.wp.php?MID=LD9DHR`.

**Inferred** from CPD copy, SLA partner types, and invoice branding: the audience is Old Mutual Wealth practices and their planners, including strategic partners, corporate partners, and an Acsis-legacy partner type. That commercial relationship is not stated in a requirements document.

**Unknown / Requires Confirmation:** whether this snapshot was still the production application on 2021-02-10, and whether it is still in use.

## Users and roles

| Actor | Evidence | Status |
|---|---|---|
| Learner | `$_SESSION[LEARNER]` required by `NemoBasic`. Profile name and picture in the header. Email is the login name looked up in `sysLogin` | **Implemented** session. The login page is **not in git** |
| Advisor / practice user | `$_SESSION[ADVISOR]` with practice id, practice name, email, password, and `blnSuperUser`. Built by `Login2()` and by `training/home.tools.php` | **Implemented** |
| Practice contact who has no practice yet | `home.tools.php` redirects to `login.business_tools.php` when `tblUser.refPracticeID` is 0 | **Implemented** |
| Tools admin (`sysUser`) | `tools/webadmin/login.php` sets `$_SESSION[USER]` with `SECURITYGROUPID` | **Implemented**, and the password check is defective (BR-004) |
| Security group 1 and 2 | SLA quote filter: groups other than 1 and 2 cannot change the WPS filter. Comment calls 1 and 2 admin and admin-dev | **Implemented** behaviour. The comment's names are **Documented** only inside that comment |
| WPS user | `vieWPS`. Non-admin users are limited to their own WPS id on the quote list | **Implemented** |
| Super user | `blnSuperUser` copied onto the advisor session. Phase-2 comment says new learners default `blnSuperUser` and `blnPracticeContact` to 1 | **Implemented** flag. The meaning of the flag beyond being stored is **Unknown** |
| Deploy / cron operator | `cron.daily.php`, `cron.hourly.php`, `cron.weekly.php`. When `SERVER_NAME` is unset, config forces `www.knowmore.co.za` | **Inferred** operator. No UI |

No role matrix is documented outside the `sysSecurity` / `sysSecurityGroup` / `sysMenuLevel2` tables, which are not in git. The menu rights loaded for a staff user are whatever those tables return.

## Systems the product talks to

1. The visitor's browser.
2. MySQL, chosen by HTTP host. Live host `www.knowmore.co.za` uses database `knowmore_2016` on `sql21.cpt1.host-h.net`. Settings rows in `sysSettings` are loaded into `$SystemSettings` after connect.
3. A WordPress front (logout URLs `wp-login.php` and `login.wp.php`; logo URL under `/wp-content/themes/knowmore/`). Those files are not in this repo.
4. CMS fragments via `getTypoIframe` (`latestblogposts`, `hidden/wealth-intelligence`, `hidden/avp-wp-overview`, `hidden/business-tools`). The fragment function and the CMS are not in this repo. **Unknown** which of TYPO3 or WordPress served those paths in 2021. Both are referenced.
5. PayGate PayXML, as the POST target of `tools/payGateResults.php`.
6. Netcash, as the purchase return in `tools/purchase.netcash.php` and as a SOAP validation client in `tools/soap.php`. The purchase return file stops before the business steps (see FR-014).
7. Sage Pay Pay Now. Service keys are assigned per host in `tools/webadmin/system.php`. The call site that charges a card was not isolated in this pass. **Unknown** which purchase page still posts to Sage Pay.
8. MyMobileAPI SMS, POST from `SendSMS()` using `sysSettings` keys `smsUsername`, `smsPassword`, `smsLive`.
9. SMTP, via the Nemo mailer. From address in tools mail is `KnowMore <info@knowmore.co.za>`. BCC and the visible From used by cron come from `sysSettings` (`SMTP Send Email As`, `SMTP BCC`).
10. Google Analytics (`NemoBasic::GoogleAnalytics`) and Google Visualization charts.
11. File-integrity mail. `webadmin/_framework/_nemo.fim.cls.php` emails a KnowMore report. `fim_2.0/fim.config.php` emails an American Shutters report and connects to database `american_shutters`. Those two FIM setups disagree.

## Major workflows

### WF-1 — Enter the learner portal

Trigger: a request to any training page that constructs `NemoBasic`.

If `$_SESSION[LEARNER]` is missing, the browser is sent to `../second.wp.php?MID=LD9DHR` and the script exits. That file is not in git.

If the session exists, `System` from the query string is stored on the learner session. An empty system becomes `Overview`. The header shows the learner display name, profile image, and a logout link. Logout goes to `second.wp.php?blnLogout=21AGF3L` because the host check is written as `== "lamp" || TRUE`.

Provenance: **Implemented**.

### WF-2 — Use the learner menu

The menu in `training/_nemo.basic.cls.php` is:

| Label | Target |
|---|---|
| Overview | `training/index.php?System=Overview` |
| Courses | `training/home.courses.php?System=Courses` |
| Workshops | `training/home.workshops.php?System=Workshops` |
| Business Tools | `training/home.tools.php?System=Business_Tools` |
| Publications | `training/home.wi.php?System=Wealth_Intelligence` |
| My CPD Points | `training/my.cpd.points.php?System=CPD_Points` |

The tools copy of the menu (`tools/menu.php`) still uses the key `Wealth_Intelligence` and displays it as "Publications" (comment 2019-04-23).

Footer links (terms, copyright, privacy, disclaimer, contact) point at `$SystemSettings["LiveURL"]/about/...`, so the legal pages live on the public site, not in this repo.

### WF-3 — Overview dashboard

`training/index.php` loads the latest `sysLogin` row for the learner email, excluding today, and shows "You last logged in on …" when a row exists.

User category and WPS blocks are forced to `display: none` (comment 2017-03-08, permanently hidden). The WPS id still defaults to `-10` when the practice has none.

If the learner's practice has Business Assessment Tool results for questionnaire id 4 in the current calendar year or the previous calendar year, the page draws red/amber/green totals from `dwfBatResults_2020`.

Latest articles are loaded by `getTypoIframe2("latestblogposts/?limit=2")`.

Provenance: **Implemented**.

### WF-4 — Open business tools from the learner portal

`training/home.tools.php` looks up `tblUser` by the learner email.

- No matching user: behaviour after the `if($rowCheckUser)` block was not fully traced. **Unknown** what that learner sees.
- Matching user with `refPracticeID == 0`: redirect to `login.business_tools.php` and stop.
- Matching user with a practice: copy id, name, email, password, practice, and super-user flag onto `$_SESSION[ADVISOR]`, set `celusername` and `celpassword` cookies for 3600 seconds, then continue into the tools UI.

`tools/session.php` refuses to run unless `$_SESSION[ADVISOR]` is set, and otherwise sends the browser to `../index.php` (also not in this repo). It sets the same cookies for 7200 seconds.

Provenance: **Implemented**.

### WF-5 — Staff login to tools admin

`tools/webadmin/login.php` loads `system.php` and `../../webadmin/_nemo.password.cls.php`. The password class file is **not in git**, so this page cannot run from the snapshot alone.

The intended check, from the surrounding code, is: find an active `sysUser` joined to an active security group, then call `NemoPassword::checkPassword("User", ...)`. On success, store `USER` on the session and go to `index.php`. Inactive users and unknown emails are written to `tblLogin`.

The password check is OR-ed with `isset($btnLogin)`, and the outer `if` assigns `$btnLogin = "Login"`. Posting the form therefore skips the password test. See business rule BR-004.

`CheckSecurityLockout` is present in `tools/webadmin/include/system_functions.inc.php` and is commented out at this login. See business rule BR-005.

Provenance: **Implemented**.

### WF-6 — Advisor login on the tools front

`tools/login.php` selects `tblUser` joined to `tblPractice` by email. The user must have `blnActive == 1`. The password matches when `md5` of the posted password equals `strPasswordMD5`. Success calls `Login2()` and redirects to `$SystemSettings['Landing Page']`. Failure and inactive cases insert `sysLogin`, and the inserted row includes the posted password in clear text.

Provenance: **Implemented**.

### WF-7 — Buy or renew a licence

Purchase scripts under `tools/` (`purchase.php`, `purchase.renewal.php`, and dated backups) and under `training/` (`purchase.courses.php`, `purchase.workshops.php`, `purchase.omw.php`) create transactions and invoices and activate licences. `payGateResults.php` reads the PayGate POST, checks an MD5 checksum, and on success continues the licence activation. The checksum secret in that file is the literal value `test`.

`purchase.netcash.php` documents six steps (update transaction, insert invoice, generate licences, read licence, activate, show a message) and then executes `print_rr('yes include'); die;` before those steps. The steps below that line do not run.

VAT comments say amounts use `$SystemSettings[VAT]`, with 14% before 2018-04-01 and 15% from that date.

Provenance: **Implemented**. Which gateway a given product uses in production is **Unknown / Requires Confirmation**.

### WF-8 — Complete an assessment or gap tool

BAT (`tools/bat.engine.php`) defaults to update mode, questionnaire version 1, and checks that the practice holds a licence before showing the product. Gap engines (TCF, PoPI, RDR, SCP) save the current cluster and, when navigation is "Finish", save and can grant CPD points (`grantCPDPoints`, comment 2015-05-04).

Results feed `dwfBatResults_2020` (dashboard) and the PDF report classes.

Provenance: **Implemented**. The licence status values that count as "has access" live in SQL inside `bat.engine.php` and were renamed in 2016 from `refLicenceStatusID` to `strStatus`. The status strings themselves are data.

### WF-9 — Licence renewal and survey reminders

`tools/cron.daily.php` states three jobs: email practices whose licences expire in 6 weeks, 2 weeks, or 3 days, or expired yesterday; remind CAT surveys that are not complete and not opted out; import `ConsultingHours.csv` into consultation-hour rows, with marketing subjects stored as zero hours.

`tools/cron.hourly.php` releases `tblCronJobToDo` rows stuck in status 1 for 90 minutes, then processes up to 400 status-0 rows. Job types in the switch are `CSS`, `CSS Reminder`, `Renewal`, `Invoice`, and `Seminar`.

`tools/cron.weekly.php` emails a list of licences expiring inside the window `+8 weeks`.

Provenance: **Implemented** in those files. Whether a scheduler still invokes them is **Unknown**.

### WF-10 — CPD record

`training/my.cpd.points.php` builds a CPD list and a PDF. Comments record these rules: ask for an ID number when it is blank; add business-tool points; offer a previous-year PDF; populate categories from data; count Wealth Intelligence as 0.5 on every third item and 0 otherwise; show class of business; print a disclaimer; show ID number and CPD cycle. Cycle dates named in comments: 1 Jan 2018–31 May 2019 for the 2018 cycle, and 1 Jun 2019–31 May 2020 for the 2019 cycle.

`training/my.assignments.php` also contains a hard-coded HTML catalogue of Knowmore courses and tools with FPI reference numbers and end dates in 2021. That table is **Implemented** markup. It can disagree with the database-driven list.

Import CSVs under `webadmin/import/` use the header `ID`, `Email`, `CPDCategory`, `Event Date`, `PointsFPI`, `PointsFSB`, `FPIRef`, `Business Item`. File names run from 2016-11 to 2020-07 and include "OMW Knowmore CPD".

Provenance: **Implemented**.

### WF-11 — SLA proposal

`webadmin/includes/sla.quote.cls.php` is a multi-step quote for partner types Strategic Partner, Corporate Partner, and Strategic Partner (Acsis Legacy only). Package comments name Knowmore Standard, Knowmore Plus, Knowmore BCH M, Knowmore BCH H, and Knowmore Consulting Services. A proposal that contains inactive services cannot be edited. Status is always "proposal" after a 2016 change that removed a separate finalize step. VAT follows the 2018-04-01 rule. The last-page footer and personalised corporate-partner prices are later comments (2019, 2020).

The controller that instantiates `Quote` is **not in git**. The class file is **Implemented**.

## Functional requirements

### FR-001 — Learner session gate

The training pages that construct `NemoBasic` require `$_SESSION[LEARNER]`. Without it they redirect to `second.wp.php` and exit.

Evidence: `training/_nemo.basic.cls.php` constructor.

Implementation: **Implemented**. Confidence: **High** for this snapshot. The login that creates the session is outside the snapshot.

### FR-002 — Learner navigation

The learner can move among Overview, Courses, Workshops, Business Tools, Publications, and My CPD Points. The active item follows `$_SESSION[LEARNER]->SYSTEM`.

Evidence: `Menu()` in `training/_nemo.basic.cls.php`.

Implementation: **Implemented**. Confidence: **High**.

### FR-003 — Publications are Wealth Intelligence content

The menu label is Publications. The system key and the iframe path remain Wealth Intelligence (`home.wi.php`, `hidden/wealth-intelligence/`).

Evidence: `training/_nemo.basic.cls.php`, `tools/menu.php`, `training/__home.wi.php`.

Implementation: **Implemented**. Confidence: **High**.

### FR-004 — Overview shows last login and BAT charts

Last login text comes from `sysLogin` for the learner email, excluding the current date. BAT charts render only when the practice has current-year or previous-year rows for questionnaire 4 in `dwfBatResults_2020`.

Evidence: `training/index.php`.

Implementation: **Implemented**. Confidence: **High**.

### FR-005 — User category and WPS are not shown

The overview hides both blocks with inline `display: none`, including when a category or WPS id exists.

Evidence: `training/index.php` comments dated 2017-02-03 and 2017-03-08.

Implementation: **Implemented**. Confidence: **High**.

### FR-006 — Business tools require a practice

A learner whose `tblUser.refPracticeID` is 0 is sent to `login.business_tools.php`. A learner with a practice receives an advisor session and the celestis cookies.

Evidence: `training/home.tools.php`.

Implementation: **Implemented**. Confidence: **High**.

### FR-007 — Advisor password check is MD5

Tools front login compares `md5` of the posted password to `tblUser.strPasswordMD5`, and only for `blnActive == 1`.

Evidence: `tools/login.php`.

Implementation: **Implemented**. Confidence: **High**.

### FR-008 — Staff password check depends on a missing class

Tools admin login instantiates `NemoPassword` from a file that is not in the repository, then calls `checkPassword`.

Evidence: `tools/webadmin/login.php`.

Implementation: **Partial** in this snapshot. Confidence: **High** that the include is missing.

### FR-009 — Staff menu rights come from the database

`LoadSecurity()` loads `sysSecurity` rows for the logged-in `sysUser` into `$_SESSION[Security]->Entity`, keyed by menu URL. `getAccessLevels()` returns that row.

Evidence: `tools/webadmin/project.functions.inc.php`.

Implementation: **Implemented**. The allowed URLs are data. Confidence: **High** for the mechanism.

### FR-010 — Courses remain listed for one week after the start date

Comment dated 2012-10-16 in the training purchase pages: show the course again for one week after the start date, citing KnowMore course TEA/6.

Evidence: `training/purchase.php`, `training/purchase.courses.php`, `training/purchase.workshops.php`, `training/purchase.omw.php`.

Implementation: **Implemented** as a comment next to the query. Confidence: **Medium** until the SQL predicate is re-read on a change. The same comment is copied across four files.

### FR-011 — BAT is licence-gated

`bat.engine.php` checks access rights before the questionnaire. BAT version 1 is the questionnaire id used by that engine. A 2016 comment says the separate BAT1/BAT2 database switch is deprecated.

Evidence: `tools/bat.engine.php`.

Implementation: **Implemented**. Confidence: **High** that a check exists. The status values are data, not constants in the files summarised here.

### FR-012 — Gap tools can award CPD

The TCF gap engine comment says `grantCPDPoints` runs from project functions when the tool is finished. Sibling engines exist for PoPI, RDR, and SCP.

Evidence: `tools/gap.TCF.engine.php` and `tools/gap.*.engine.php`.

Implementation: **Implemented** entry points. Confidence: **Medium** on the exact point values, which are not constants in the engine header.

### FR-013 — CPD Wealth Intelligence rule

After 2017-08-19 the code awards 0.5 CPD on every third Wealth Intelligence item and 0 on the others. An earlier same-month comment had used 0.1667 for each item. The later comment is the one that describes the intended replacement.

Evidence: `training/my.cpd.points.php` header comments.

Implementation: **Implemented** per those comments. Confidence: **Medium** (the arithmetic was not line-traced in this pass).

### FR-014 — PayGate return verifies a checksum; Netcash return does not finish

PayGate posts are accepted only when the MD5 of the concatenated fields plus the configured secret matches `CHECKSUM`. The secret in source is `test`.

The Netcash return file dies on a debug print before invoice and licence steps.

Evidence: `tools/payGateResults.php`, `tools/purchase.netcash.php`.

Implementation: **Implemented**. Confidence: **High**.

### FR-015 — Renewal mail is queued, then sent in batches

Daily and weekly jobs insert `tblCronJobToDo` work. The hourly job sends up to 400 pending rows and resets rows left in progress for 90 minutes. A 2012 comment caps the host at one run every two hours and 500 emails.

Evidence: `tools/cron.daily.php`, `tools/cron.hourly.php`, `tools/cron.weekly.php`.

Implementation: **Implemented**. Confidence: **High** for the code. Scheduler installation is **Unknown**.

### FR-016 — SLA quotes are proposals for named partner types

The quote class supports the three partner types and the package names listed in WF-11. Non-admin security groups cannot retarget the WPS filter. Inactive services block further edits.

Evidence: `webadmin/includes/sla.quote.cls.php`.

Implementation: **Implemented** class. The screen that hosts it is **not in git**. Confidence: **High** for the class, **Low** for how staff open it.

### FR-017 — Legal and contact pages are external

Terms, copyright, privacy, disclaimer, and contact are links on `LiveURL`, not pages in this repository. The phone and mailbox in the footer are the values in the footer copy.

Evidence: `training/_nemo.basic.cls.php` `Footer()`.

Implementation: **Implemented** links. Confidence: **High**.

### FR-018 — File integrity report

The framework class writes `FIM_KnowMore_Report_<date>` and emails it. The `fim_2.0` config writes an American Shutters report to a different database. Table-creation SQL in that config is commented out, so this snapshot does not create the audit tables.

Evidence: `webadmin/_framework/_nemo.fim.cls.php`, `fim_2.0/fim.config.php`, `fim_2.0/fim.cron.php`.

Implementation: **Implemented** as two conflicting copies. Confidence: **High**.

## Constraints

- Runtime settings (landing page, logout URL, VAT, SMTP, SMS, live URL) come from `sysSettings` and are not in git.
- Learner authentication is outside this repository.
- Training pages do not run without the missing `webadmin/_system.php` and Nemo classes.
- Host names `knowmore.co.za` and `qa-celestis.co.za` are rewritten to the `www` forms before the database map is read.
- A cron process with no `SERVER_NAME` is treated as live `www.knowmore.co.za`.
- Session lifetime is set to 7200 seconds. Advisor cookies from `Login2()` last 3600 seconds. `tools/session.php` sets them for 7200 seconds.
- Display errors: `tools/webadmin/system.php` turns `display_errors` on, then the condition `SERVER_NAME != "lamp" || SERVER_NAME != "debian"` turns them off. That condition is true for every host, including `lamp` and `debian`.

## Unknown / requires confirmation

- The product owner and whether Knowmore, Celestis, and Old Mutual Wealth names were all still current in 2021.
- Where the WordPress theme, `second.wp.php`, and `webadmin/_system.php` are maintained.
- Whether PayGate, Netcash, or Sage Pay is the live card gateway.
- Whether `CheckSecurityLockout` is called from a file this pass did not open.
- Whether questionnaire id 4 is still "the" BAT on the overview, or only the 2020 dashboard query.
- Live contents of `sysSettings`, security groups, and licence statuses.
