# PRD — knowmorescan (Knowmore)

Evidence date: 2026-10-10. Source: `git@bitbucket.org:overdrive/knowmorescan.git`, `master` at `71190cf` (2022-06-02).

There is no prior PRD in the repository or in Silicon Ark. Requirements below were recovered from static HTML, PHP, SQL increments, and Git history. Provenance is marked on each item.

Production deployment of this revision is **Unknown / Requires Confirmation**.

## Product purpose

Knowmore is the Old Mutual Wealth online learning platform for people who work in a financial-planning business. It publishes courses, workshops, CPD tracking, publications, and business tools (assessment, client survey, gap analysis, valuation). Some tools and content are described as exclusive to partner businesses.

Provenance: **Documented** on `about.html`, `courses.html`, `business-tools.html`, `cpd-points.html`, and `terms-and-conditions.html`.

The privacy policy states that Old Mutual Wealth (Proprietary) Limited, registration number 2011/134187/07, owns KnowMore. Provenance: **Documented** (`privacy-policy.html`).

## Users and roles

| Role | What they do | Evidence | Provenance |
|---|---|---|---|
| Anonymous visitor | Reads marketing and legal pages | Root `*.html` | Implemented (static export) |
| Prospect / new learner | Opens `register.html`, which iframes `register.ph3.php` | `register.html` line with iframe `https://knowmore.co.za/register.ph3.php` | Documented page, implemented script |
| Learner | Signs in with email and password, uses courses, workshops, tools, CPD, profile | `login.html` posts to `second.wp.php`; `login.php` reads `tblLearner` | Implemented |
| Inactive learner | Sign-in is refused with "Your account is currently inactive." unless the request is the registration-purchase redirect flag | `login.php` checks `blnActive` | Implemented |
| Practice user | Business tools are scoped by practice id (`PracticeID` on BAT, CAT, gap classes) | `tools/bat.class.php`, `tools/cat.class.php`, `tools/gap.TCF.class.php` | Implemented. How a learner is linked to a practice is the `refPracticeID` column used in `training/index.php` |
| Webadmin user | Signs in at `webadmin/login.php` against `sysUser` joined to `sysSecurityGroup`. Menu items come from `sysMenuLevel1` / `sysMenuLevel2` filtered by security group, with view / delete / save / special flags | `webadmin/login.php`, `webadmin/_nemo.menu.cls.php` | Implemented. The menu labels live in the database and are not in this repo |
| CMS administrator | Signs in to the CodeIgniter app. Session role `cmsadmin` is required by `Article` before any action. Users and security-group screens use a different condition (see FR-030) | `cms/app/Controllers/Login.php`, `Article.php`, `User.php` | Implemented |
| Client of a planner | Receives a client-satisfaction survey email and can complete or opt out | `tools/css.survey.php`, `cat.unregister.php`, cron case `Tools CSS` | Implemented. The sample letter in `webadmin/cron.daily.php` is old Celestis copy |
| Cron / server | Runs `webadmin/cron.daily.php` and `webadmin/cron.hourly.php` (and numbered hourly variants) with no interactive user | Those files | Implemented. The scheduler that calls them is not in the repo |

There is no member-role table in the CMS SQL. CMS users have a `role` string defaulting to `cmsadmin` (`db_updates/update.5.sql`).

## Main workflows

### W1 — Read the public site

Trigger: visitor opens the site.

Static pages at the repository root include home, about, courses, course audience pages (executive assistants, financial planners, business owners), business tools, business assessment, client satisfaction survey, gap analysis, valuation service, workshops, publications, CPD points, contact, register, login, and the legal pages (privacy, terms, copyright, disclaimer).

Navigation and calls to action in the export: Contact Us, Login, Register. Home meta description tells the reader that a new Advanced Executive Assistant course is available and that registration is: login, select courses, view available courses, complete the required information, or email `info@knowmore.co.za`.

Provenance: **Documented** and **Implemented** as exported HTML. The WordPress editor that produced the pages is not in this repository.

`.htaccess` redirects HTTP to HTTPS and rewrites extensionless URLs to a `.html` file when that file exists, with the substitution `/KnowmoreV2/$1.html`. Provenance: **Implemented**. Whether the live document root is `/KnowmoreV2` is **Unknown / Requires Confirmation**.

### W2 — Sign in as a learner

Trigger: `login.html` posts `Action=Login`, `strUsername`, `strPassword` to `second.wp.php`.

`second.wp.php` chooses a landing URL from `System`:

- `Courses` → `training/purchase.courses.php?CID=`
- `Workshops` → `training/purchase.workshops.php?CID=`
- `Tools` → `training/login.business_tools.php?Action=Redirect`
- `Wealth_Intelligence` → `training/home.wi.php`
- default → `training/index.php`

`login.php` (also used for a direct login action) loads `tblLearner` by email, left-joined to `tblUserType`.

Expected behaviour when the email matches:

1. If `blnActive` is 0 and this is not the registration-purchase redirect, refuse with "Your account is currently inactive." and write `tblLoginInsert("Login failed [Learner]: Inactive User")`.
2. Otherwise check the password with `NemoPassword::checkPassword("Learner", LearnerID, password)`.
3. On success, store learner id, user type, user category, display name, email, active flag, region, typography, and profile picture path in `$_SESSION[LEARNER]`, log "Login successful [Learner]", and redirect to the chosen location.
4. On bad password, log the failure and show "Your Username or Password is incorrect." plus a link to `forgot.password.php`.
5. If no row matches, log "User not found or SG not active" and show "Invalid Login Details."

Message ids: `MID=LD9DHR` means the session expired; `MID=B28JHA` means logout succeeded.

Provenance: **Implemented**.

### W3 — Register a learner

Trigger: `register.html` shows an iframe whose `src` is `https://knowmore.co.za/register.ph3.php`.

`register.ph3.php` is titled "Register", includes the Nemo password class and the webadmin email class, and comments record a phase-3 access-control registration (2016), a validation change (2019), and a registration change (2020). The reCAPTCHA include is commented out.

The field list and the exact success email are long and were not fully transcribed in this pass. Provenance of the entry point: **Implemented**. The full field-level contract of `register.ph3.php` is only partly reviewed; treat unpublished field rules as **Unknown / Requires Confirmation** until that script is read against a live form.

Other registration scripts exist and were not promoted to the public iframe: `register.changes.php`, `register.invite.php`, `register.rsvp.php`, `register.special.php`, `register.tools.php`, `register.wi.php`, `tools/register.php`, `tools/bpw.registration.php`. Which of these are still linked from the live site is **Unknown / Requires Confirmation**.

### W4 — Learn: courses, modules, workshops, CPD

Trigger: an authenticated learner opens `training/`.

Implemented screens include:

- `training/index.php` — learner home. Shows last login from `sysLogin`, user category text, and the current year's Business Assessment pillar results for the learner's practice (`dwfBatResults_2020`, questionnaire id 4). A 2017 comment says the user-category block and the WPS block are permanently hidden.
- `training/home.courses.php`, `course.php`, `module.php` — courses and modules.
- `training/home.workshops.php`, `workshop.php`, `workshop.assignments.php` — workshops.
- `training/my.assignments.php`, `assignments.php`, `article.assessments.php` — assignments and assessments.
- `training/my.cpd.points.php` — CPD points. The About page tells the learner to download the CPD report from the CPD tab after login.
- `training/forum.php`, `general.forum.php` — forums.
- `training/profile.php`, `learner.profile.php` — profile.
- `training/home.wi.php` and `avp.wp.*.php` — Wealth Intelligence pages (overview, business management, business overview, financial planning, wealth management) plus `avp.my.info.php`, `avp.my.team.php`, `avp.media.php`.

Webadmin maintains the catalogue: `webadmin/course.php`, `course.type.php`, `course.type.module.php`, `module.php`, and module-part editors `mod.video.php`, `mod.question.php`, `mod.practical.php`, `mod.assignment.php`, `mod.workbook.php`, `mod.note.php`, `mod.forum.php`, plus `workshop.php`, `cpd.points.php`.

Provenance: **Implemented**. Course names shown on the marketing site (Treating Clients Fairly; Executive Assistant; Advanced Executive Assistant) are **Documented** in the HTML. The catalogue itself is database content and is not in Git.

### W5 — Buy a course or workshop

Trigger: purchase screens under `training/`.

- `purchase.courses.php` and `purchase.workshops.php` post to SagePay Pay Now (`https://paynow.sagepay.co.za/site/paynow.aspx`). A Netcash form remains in comments on the workshop page.
- `purchase.php` and `purchase.omw.php` post to Netcash (`https://gateway.netcash.co.za/vvonline/ccnetcash.asp`).
- `training/transactions.php` labels one process `purchase: SagePay` and has a "netcash returns" section.
- `_system.php` stores a Pay Now service key per host for `www.qa-celestis.co.za` and `www.knowmore.co.za`. The key values are credentials and are not repeated here.

Provenance: **Implemented**. Which gateway is the approved one for which product is **Requires Confirmation** — both are present.

### W6 — Use business tools

Trigger: a signed-in practice user opens the tools area (`training/home.tools.php`, `training/login.business_tools.php`, or `tools/`).

Marketing page `business-tools.html` names:

- Business Assessment Tool — "assesses the overall management of your business against six critical" areas, exclusive to partner financial-planning businesses (`business-assesment-tool.html`; the filename spells "assesment").
- Client Satisfaction Survey — feedback from the planner's clients, exclusive to partner businesses (`client-satisfaction-survey-tool.html`).
- Gap Analysis (`gap-analysis.html`).
- Valuation Service — a business coach leads a data-collection process and the data is analysed with a valuation model (`valuation-service.html`). The page describes a coached service. An automated valuation engine was not identified in this pass. Provenance of the service description: **Documented**. Provenance of a self-service valuation module: **Unknown / Requires Confirmation**.

Implemented engines:

- `tools/bat.class.php`, `bat.engine.php`, `bat.engine2.php`, `bat.engine3.pwm.php`, `bat_2020.class.php`, `bat_2020.engine.php`. Comments say the BAT 1 / BAT 2 database switch was deprecated in 2016, and a 2020 results table is what `training/index.php` reads.
- `tools/cat.class.php` and `cat.engine.php` — client survey (CAT), with PDF report, invite send, opt-out (`cat.unregister.php`), and export.
- `tools/css.survey.php` — the respondent-facing survey.
- `tools/gap.TCF.*` — Treating Clients Fairly gap analysis.
- `tools/gap.RDR.*` — the gap-analysis marketing page describes a tool "gearing up for the Retail Distribution Review (RDR)".
- `tools/gap.PoPI.*` — breadcrumb text "PoPI Gap Analysis". The statute name is not spelled out in the class header.
- `tools/gap.SCP.*` — class `GapSCP` and tables `gapSCPQuestion` / `gapSCPCluster`. The expanded name is **Unknown / Requires Confirmation**.

Gap marketing copy also states verifiable CPD of 1 point, FPI CPD type Ethics and Practice Standards, for the TCF / RDR description block. Provenance: **Documented** in `gap-analysis.html`. The same paragraph is attached to more than one heading in the export; which heading owns which sentence should be confirmed against the live page.

### W7 — Client completes a satisfaction survey

Trigger: cron case `Tools CSS` sends an invite; the client opens `tools/css.survey.php`. Reminder case: `Tools CSS Reminder`. Opt-out: `cat.unregister.php` / `tools/cat.unregister.php`. Read-receipt and online-view endpoints: `email.read.receipt.php`, `email.read.online.php`.

The daily cron contains a 2015 sample letter that names Celestis and a deadline of 8 September 2015. That block is inside comments. Provenance: **Implemented** send path; the commented letter is historical.

### W8 — Webadmin operates the platform

Trigger: staff opens `webadmin/login.php`.

Password check uses `NemoPassword` (comment dated 2017: password security framework). Menus and permissions are rows in `sysSecurity` / `sysMenuLevel2` for the user's `SecurityGroupID` (`blnView`, `blnDelete`, `blnSave`, `blnSpecial`).

Screens present as PHP files include users, user types, user categories, learners, clients, practices, regions, countries, courses, modules, workshops, venues, products, vouchers, licences, invoices, SLA quote and service, adverts, articles, CPD points, email and email templates, reports, usage log, settings, security groups, messages, and "goto" shortcuts into business tools and another academy webadmin (`gotoBusinessTools.php`, `gotoAcademyWebadmin.php`).

Provenance: **Implemented** as files. Which menu entries a given security group sees is data, not code.

### W9 — CMS author manages publications

Trigger: CMS user opens the CodeIgniter app. Default route is `Login::index`.

Routes in `cms/app/Config/Routes.php`:

- Public: `/` and `login`, `register`, `logout`, `forgot-password`, `reset-password`, dropzone upload.
- `admin` group, filter `auth` (session `isLoggedIn`): article list/add/edit/update/delete/archive, user list/add/edit/update/delete/list, security-group list/edit/update/delete, profile, logout.
- Dashboard route is commented out.
- `admin/add` is wired to `SecurityGroup::add` (the path is `add`, not `security-group/add`).

Article create requires `txtContent` and `strArticleTitle`. Status defaults to `Draft` unless `status` is posted. Archive sets `strStatus` to `Archived`. The SQL enum is `Draft`, `Pending`, `Published`, `Archived` (`db_updates/update.2.sql`).

Deleting a CMS user: if the id is the signed-in user, the delete is skipped. Otherwise articles are reassigned to `idToOwn` and the user row is deleted. The UI disables the delete control when the row is the current user. Provenance: **Implemented** (`cms/app/Controllers/User.php`, `cms/app/Views/user/index.php`). The redirect after a skipped self-delete still flashes "User Deleted Succesfully!".

No route was found that lists published articles to the public site. Provenance: **Unknown / Requires Confirmation** whether publications on the marketing site are still the static export only.

### W10 — Scheduled mail and imports

`webadmin/cron.hourly.php` cases include: ImportClients, invoice and order emails, CPD learner import, certificate notification, course start, course complete, workshop launch, workshop RSVP, notices, apologies, CSS invite and reminder, CPD points notification, IBC registration email, AML registration email.

`webadmin/cron.daily.php` cases include notices, apologies, invite generation for PFA / IFA / IFA Private, course-start mail, and processing of AML learners and IBC learners. Comments special-case email templates for an AML "General Awareness" course (including a Ghana variant), an Executive Assistant course, and an IWP course.

Provenance: **Implemented** as switch cases. The expanded names of AML, IBC, IWP, PFA, and IFA are not defined in those case labels. **Unknown / Requires Confirmation**.

Numbered files `cron.hourly1.php` through `cron.hourly8.php` also exist. How they are divided from `cron.hourly.php` was not fully read. **Unknown / Requires Confirmation**.

## Functional requirements

### FR-001 — Public marketing pages

The site serves the static pages listed in W1 without a login.

Evidence: root HTML files, export comment, `.htaccess`.

Implementation: Implemented.

Confidence: High for the files in Git. Low for whether they match production WordPress today.

### FR-002 — HTTPS redirect

When the request is not HTTPS, Apache redirects to HTTPS on the same host.

Evidence: `.htaccess` rewrite.

Implementation: Implemented.

Confidence: High for the rule. Whether that file is the live vhost config is unknown.

### FR-003 — Learner authentication

The system authenticates a learner by email against `tblLearner`, rejects inactive learners, checks the password with the Nemo password class, and stores the session fields listed in W2.

Evidence: `login.php`, `second.wp.php`.

Implementation: Implemented.

Confidence: High.

### FR-004 — Login destination by system

After a successful login, `second.wp.php` sends the learner to courses, workshops, tools, Wealth Intelligence, or the training home according to `System`.

Evidence: `second.wp.php` switch.

Implementation: Implemented.

Confidence: High.

### FR-005 — Session expiry message

If the learner area sets `MID=LD9DHR`, the login page states that the session expired and the user must log in to access learner content.

Evidence: `login.php`.

Implementation: Implemented.

Confidence: High for the message. The timeout value is `session.gc_maxlifetime` 7200 seconds in `_system.php`, plus whatever the learner scripts check themselves.

### FR-006 — Learner registration entry

The public register page embeds `register.ph3.php`.

Evidence: `register.html`, `register.ph3.php`.

Implementation: Implemented.

Confidence: Medium. The iframe URL is absolute (`https://knowmore.co.za/...`), so a non-production host still loads production unless that file is changed.

### FR-007 — Course catalogue administration

Webadmin can maintain courses, course types, modules, and module parts (video, question, practical, assignment, workbook, note, forum).

Evidence: `webadmin/course.php`, `module.php`, `mod.*.php`.

Implementation: Implemented as screens. Permission to open each screen is a database security flag.

Confidence: Medium (files exist; behaviour of each form was not executed).

### FR-008 — Learner course and workshop access

A signed-in learner can open course, module, workshop, assignment, forum, and CPD screens under `training/`.

Evidence: file list in `training/`.

Implementation: Implemented.

Confidence: Medium.

### FR-009 — CPD tracking

The learner can open `training/my.cpd.points.php`. Marketing copy says the learner downloads a CPD report from the CPD tab and submits it as proof of CPD earned through Old Mutual Wealth.

Evidence: `training/my.cpd.points.php`, `about.html`, `cpd-points.html`, `webadmin/cpd.points.php`.

Implementation: Implemented screen. The exact report file layout was not read in this pass.

Confidence: Medium.

### FR-010 — Partner-only business assessment

The Business Assessment is described as exclusive to partner financial-planning businesses and scores the practice against pillars and clusters. The 2020 engine and `dwfBatResults_2020` are what the learner home reads for the current calendar year.

Evidence: `business-assesment-tool.html`, `tools/bat_2020.class.php`, `training/index.php`.

Implementation: Implemented.

Confidence: Medium. "Partner" is marketing language; the code gate is a practice id and product mode, not a flag named partner.

### FR-011 — Client satisfaction survey

A practice can run a client survey. The system can email invites and reminders, record responses, generate a PDF, and accept an opt-out.

Evidence: `tools/cat.class.php`, `tools/css.survey.php`, `cat.unregister.php`, cron cases `Tools CSS` and `Tools CSS Reminder`.

Implementation: Implemented.

Confidence: Medium.

### FR-012 — Gap analysis tools

The system provides gap-analysis questionnaires for TCF, RDR, PoPI, and SCP, each with its own class, engine, and `gap*` tables (table names appear in the SQL inside the classes).

Evidence: `tools/gap.TCF.class.php`, `gap.RDR.*`, `gap.PoPI.class.php`, `gap.SCP.class.php`, `gap-analysis.html`.

Implementation: Implemented.

Confidence: Medium. SCP's full name is unknown.

### FR-013 — Valuation service page

The public site describes a coached valuation service.

Evidence: `valuation-service.html`.

Implementation: Documented on the page. A matching self-service engine was not confirmed.

Confidence: Low for the software behaviour. High for the page copy.

### FR-014 — Card payments

Course and workshop purchases can post to SagePay Pay Now. Other purchase screens post to Netcash. Return handling exists in `training/transactions.php`.

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

Implementation: Implemented.

Confidence: Medium. Both gateways are live in code; product policy for which one to use is not written down.

### FR-015 — Webadmin security menu

After webadmin login, the menu is built from `sysMenuLevel1` and `sysMenuLevel2` for the user's security group. A level-2 item without `blnView` is rendered inactive.

Evidence: `webadmin/_nemo.menu.cls.php`.

Implementation: Implemented.

Confidence: High for the mechanism. The menu contents are data.

### FR-016 — Password policy for Nemo users

When `sysPassword` does not exist, the password class creates it and inserts settings: policy enabled, minimum length 8, strength check on, history 8, restricted words `password` and `123`, reset-link interval `60 MINUTE`, max age `false`. The browser check requires minimum length, one lowercase letter, one uppercase letter, and one digit. Passwords are stored as SHA-256 of a random salt plus the password.

Evidence: `webadmin/_nemo.password.cls.php`.

Implementation: Implemented. Live values can differ because they are read back from `sysSettings` after the first run.

Confidence: High for the initializer. Medium for production settings.

### FR-017 — CMS sign-in

CMS login requires an email of at least 4 characters that passes `valid_email`, and a password of at least 8 characters, checked with `validateUser` against the stored hash. Failure message: "Email or password dont match".

Evidence: `cms/app/Controllers/Login.php`, `cms/app/Validation/UserRules.php` (`password_verify`).

Implementation: Implemented.

Confidence: High.

### FR-018 — CMS password complexity

CMS registration, user create, and password change require minimum length 8 and the regex `(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}`. Confirm must match. Stored with `password_hash(..., PASSWORD_DEFAULT)`.

Evidence: `cms/app/Controllers/Login.php`, `User.php`, `cms/app/Models/UserModel.php`.

Implementation: Implemented. Commit `2286f50` says this was changed to match webadmin. The Nemo rule also rejects the substrings `password` and `123`. That restricted-word check was not found in the CMS regex.

Confidence: High.

### FR-019 — CMS article lifecycle

An article has title, content, author, post date, thumbnail, order, categories, and status. Create defaults status to Draft. Archive sets Archived. Category seed data includes Publications, Advice Matters, Investment Matters, Business Tips, Business Matters, Legislation, Retirement Planning, Investments, Top Tips, Client Management, Strategy Management.

Evidence: `cms/app/Controllers/Article.php`, `db_updates/update.1.sql`, `update.2.sql`, `update.3.sql`.

Implementation: Implemented for admin CRUD. Public display was not found.

Confidence: High for admin. Low for public rendering.

### FR-020 — CMS user delete protection

The signed-in CMS user cannot delete their own row. Deleting someone else reassigns that person's articles to a chosen user first.

Evidence: `User::delete`, `User::list`, the delete control in `cms/app/Views/user/index.php`. Commit `4a10866`.

Implementation: Implemented.

Confidence: High. The success flash on a skipped self-delete is also implemented and is misleading.

### FR-021 — Scheduled communications

The cron scripts send or queue the mail types listed in W10, including course start and completion, certificates, workshop RSVP, CSS invites, and CPD notifications.

Evidence: `webadmin/cron.hourly.php`, `webadmin/cron.daily.php`.

Implementation: Implemented as code paths. The operating-system schedule is not in the repo.

Confidence: Medium.

### FR-022 — File permission enforcement

`_system.php` runs `find` to chmod directories under `training/dcontent/` to 0750 and files to 0644, and chmods `training/dcontent/`, `webadmin/SESSION/`, and `webadmin/documents/SLA/` toward 0750 when they are not mode 0755. `fim_2.0/fim.cls.php` walks a tree and sets directories to 0755 and files to 0644 when they differ.

Evidence: `_system.php`, `fim_2.0/fim.cls.php`.

Implementation: Implemented. The two mode targets disagree (0750 in `_system.php`, 0755 in FIM).

Confidence: High that both exist. Which one runs in production is unknown.

### FR-023 — Contact

The static contact page gives the address Old Mutual Wealth, Jan Smuts Drive, Mutualpark, Pinelands, 7495, Cape Town. The About footer gives phone `+21 524 4669` and `info@knowmore.co.za`. `contact.us.php` gives telephone `+27 21 5244 668`, Skype `CelestisBB`, and postal address PO Box 856, Howard Place, 7450, and Celestis marketing copy.

Evidence: `contact-us.html`, `about.html`, `contact.us.php`.

Implementation: Both exist. They disagree.

Confidence: High that the disagreement is in the repo. Which contact path is live is **Requires Confirmation**.

## Integrations

| System | Purpose | Direction | Evidence | Failure behaviour |
|---|---|---|---|---|
| MySQL | Learners, admin, tools, CMS tables | App → database | `_system.php` `mysqli_connect`; on failure prints a critical `SystemMessage` | Connection errors are printed with `mysqli_connect_error` |
| SagePay Pay Now | Card payment for some purchases | Browser → SagePay; return into `transactions.php` | Purchase form actions; service key in `_system.php` | Return handling exists; full failure matrix was not read |
| Netcash | Card payment for other purchases | Browser → Netcash | `purchase.php`, `purchase.omw.php` | "netcash returns" section in `transactions.php` |
| SMTP / PHP mail | Course, survey, certificate, CMS reset mail | App → mail server | `webadmin/_nemo.email.cls.php` | **Unknown / Requires Confirmation** |
| WordPress (external) | Original source of the static HTML; leftover AJAX and REST URLs | Static HTML → `www.knowmore.co.za` | Export comment, `wp-json` links | Those URLs 404 if WordPress is gone. Not testable from this repo |
| Celestis site | `content.php` iframe | Browser → `http://www.celestis.co.za/` | `content.php` | Cleartext iframe. Whether that host still serves content is unknown |
| Wordfence | WAF auto-prepend | PHP startup | `.user.ini` | Path is a specific host user directory |
| reCAPTCHA | Library present; registration include commented out | — | `modules/recaptchalib.php`, `register.ph3.php` | Not active on that include |
| AWS SDK | Composer requirement | — | Root `composer.json` | No call site found outside `vendor/`. Purpose unknown |
| SonarQube | Scanner properties only | Local scan | `sonar-scanner.properties` | Not a runtime integration |
| BrightTALK | Linked from a seeded CMS article (Budget Speech webinar) | Outbound link in article HTML | `db_updates/update.2.sql` | Not an API |

Authentication to SagePay is a hosted Pay Now service key. The key is hardcoded. It is not copied into this PRD.

## Important constraints

- Learner and webadmin passwords are salted SHA-256 in `sysPassword`. CMS passwords are PHP `password_hash`. The two stores are separate (`tblLearner` / `sysUser` versus `cmssysuser`).
- Webadmin authorisation is data-driven. A code deploy does not by itself change who can see a menu.
- Business-tool scores and survey answers are per practice and per questionnaire in MySQL. Losing the database loses the product data; Git does not contain it.
- Static HTML and PHP have diverged in brand (Knowmore / Old Mutual Wealth versus Celestis) and in contact details.
- `_system.php` loads Dotenv from `$_SERVER["DOCUMENT_ROOT"] . "/Knowmore"`. The variable names it reads (`PROJ_ENV`, `DATABASE_USERNAME`, `DATABASE_NAME`, `PROJ_SESSION_PATH`, `PROJ_ENV_ROOT`) do not match `.env.default` or `.env.txt` (`APP_ENV`, `DATABASE_DNS`, `DATABASE_USER`, `DATABASE_PASSWORD`). Provenance: **Implemented** mismatch.
- `cms/app/Config/Database.php` has empty credentials and `hostname` localhost. How the CMS reaches the same database in production is **Unknown / Requires Confirmation**.
- Session files are configured under `PROJ_SESSION_PATH`. `.gitignore` ignores `webadmin/SESSION/*`, `PDF/*`, `tools/webadmin/SESSION/*`, `training/dcontent/*`, and `webadmin/import/*`.

## Non-requirements (not claimed)

- A public REST API for learners was not found.
- A mobile app was not found. `615aef8` says "Updated App redirect url"; the app itself is not in this repo.
- Automated tests for Nemo behaviour were not found.
- A queue worker other than the cron PHP scripts was not found.
