# Business rules — knowmorescan (Knowmore)

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

Rules are marked **Documented** when the public pages or in-repo SQL/comments state them, and **Inferred** when they are only visible as code behaviour. Live `sysSettings` and security-group rows can override several of the code defaults. Those rows are not in Git.

## Who the product is for

1. **Documented.** Knowmore provides learning content for financial planners, support staff, and business owners in the financial profession (`about.html`).
2. **Documented.** The online platform is available to anyone working in a financial-planning business. Some content is exclusive to partners. Partnering is via contact or a Wealth Account Manager (`about.html`).
3. **Documented.** Products and services are described as supplied to financial planners and support staff in South Africa, and supply is subject to availability (`terms-and-conditions.html`).
4. **Documented.** The legal owner named on the privacy policy is Old Mutual Wealth (Proprietary) Limited, registration number 2011/134187/07 (`privacy-policy.html`).
5. **Inferred.** A learner row with `blnActive = 0` cannot sign in, except when the login is the registration-purchase redirect (`$blnRegPurcLink == 1`) (`login.php`).

## Learning and CPD

6. **Documented.** Learners keep track of CPD points on the post-login dashboard and download a CPD report from the CPD tab to submit as proof of CPD earned through Old Mutual Wealth (`about.html`, `cpd-points.html`).
7. **Documented.** The gap-analysis marketing copy states 1 verifiable CPD point, FPI type Ethics and Practice Standards (`gap-analysis.html`).
8. **Documented.** A seeded article states that one General CPD point can be claimed for a BrightTALK webinar by submitting the viewing certificate to the FPI with approval code FPI21030028 (`db_updates/update.2.sql`). That is content inside a sample article, not a rule enforced by PHP.
9. **Inferred.** Course-start, module-reminder, assignment-reminder, and course-complete emails are sent by cron, with different templates for AML General Awareness (including a Ghana variant), the Executive Assistant course, and an IWP course (`webadmin/cron.daily.php` comments and cases).
10. **Inferred.** The learner home hides the user-category section and the WPS block (`training/index.php`, comment 2017-03-08). The category is still loaded and stored on the session at login.

## Business tools

11. **Documented.** The Business Assessment and the Client Satisfaction Survey are exclusive to partner financial-planning businesses (`business-assesment-tool.html`, `client-satisfaction-survey-tool.html`).
12. **Inferred.** Assessment results shown on the learner home are the current calendar year's rows in `dwfBatResults_2020` for the learner's `refPracticeID` and questionnaire id 4 (`training/index.php`).
13. **Inferred.** If the learner's practice has no WPS id, the code uses WPS id `-10` (`training/index.php`). The home then hides that block (rule 10).
14. **Inferred.** BAT questions used for display are the active ones. A comment says specific questions can be excluded from the max score (`tools/bat.class.php`, 2017-10-30 and 2018-09-18).
15. **Inferred.** Gap questions are selected only when `blnActive = 1` (`tools/gap.SCP.class.php`, `tools/gap.PoPI.class.php`).
16. **Documented (historical comment).** Cover pages of assessment PDFs were changed to show name, surname, and ID, and the regulator label was changed from FSB to Financial Sector Conduct Authority (FSCA) (`tools/bat.class.php`, `tools/gap.TCF.class.php`, comment 2019-04-03).
17. **Inferred.** A client survey can be locked so a CSS end date does not override it (`blnLockEndDate`, comment in `tools/cat.class.php`, 2013-11-21).
18. **Inferred.** CSS reminder mail is sent only when `ccs.blnSendReminder` is set (comment in `webadmin/cron.daily.php`, 2015-09-01; the comment spells `ccs`).
19. **Documented.** The valuation offering on the public site is a coached service, not described there as a self-service form (`valuation-service.html`).

## Registration, login, and passwords

20. **Inferred.** Learner identity is the email on `tblLearner`. The login form label is "Email Address" (`login.html`, `login.php`).
21. **Inferred.** A failed learner login does not say whether the email or the password was wrong, except the inactive-account message which does reveal that the account exists and is inactive (`login.php`).
22. **Inferred.** Nemo password policy, on first creation of `sysPassword`, is: policy on, minimum 8 characters, at least one lower-case letter, one upper-case letter, and one digit, history of 8 passwords, restricted substrings `password` and `123`, reset link valid for 60 minutes, max age disabled (`webadmin/_nemo.password.cls.php`). After that, values come from `sysSettings`.
23. **Inferred.** Nemo password storage is SHA-256(salt + password) with a 32-byte salt (`createPassword` / `checkPassword`).
24. **Inferred.** CMS passwords must be at least 8 characters and match `(?=.*\d)(?=.*[a-z])(?=.*[A-Z])`. Confirmation must match. Storage is `password_hash` (`cms/app/Controllers/User.php`, `Login.php`). The CMS rule does not include the Nemo restricted-word list. Commit `2286f50` says the CMS policy was changed to match webadmin; the two checks are not identical.
25. **Inferred.** CMS login password must also be at least 8 characters before `password_verify` runs. A legacy password shorter than 8 characters would fail validation even if the hash matched (`Login.php`).
26. **Inferred.** CMS email must be unique on `cmsSysUser.strEmail` when a user is created (`User.php`). The model table is `cmsSysUser` and the article model table is `cmsArticles`, while the SQL scripts create `cmssysuser` and `cmsarticles`. Whether those names are the same table depends on the server's `lower_case_table_names`. **Requires Confirmation**.

## CMS articles and users

27. **Inferred.** A new article is Draft unless the request posts `status` (`Article::add`).
28. **Inferred.** Archive sets status to `Archived` (`Article::archive`). The column enum also allows `Pending` and `Published` (`db_updates/update.2.sql`).
29. **Inferred.** Article title and content are required. No other field is in the validator (`Article.php`).
30. **Inferred.** Only a session whose `role` is `cmsadmin` may use the article controller. Any other role gets the text "Access denied" and the script exits (`Article::__construct`).
31. **Inferred.** User and security-group constructors allow the request when `role == cmsadmin` OR `isLoggedIn == true`, because the condition is `role != "cmsadmin" && isLoggedIn != true`. A logged-in user who is not `cmsadmin` therefore passes that constructor. This does not match rule 30. **Requires Confirmation** — it may be a defect. Evidence: `cms/app/Controllers/User.php`, `SecurityGroup.php`.
32. **Inferred.** A CMS user cannot delete the account they are signed in as. The list endpoint used to pick a new author returns `result: false` for the current user (`User::delete`, `User::list`).
33. **Inferred.** Deleting another CMS user first moves that user's articles to the selected owner (`updateOwnership`) and then deletes the user.
34. **Inferred.** The `admin` route group requires `isLoggedIn`. Unauthenticated callers are redirected to the CMS base URL (`cms/app/Filters/Auth.php`).
35. **Documented in SQL seed.** Article categories were inserted as the eleven names listed in the PRD (Publications through Strategy Management). Subcategories link Legislation and Retirement Planning to category 2, Client Management and Strategy Management to category 5, Top Tips to category 4, and Investments to category 3 (`db_updates/update.4.sql`).

## Payments and licences

36. **Inferred.** Some purchases submit to SagePay Pay Now and some to Netcash. Both are implemented. There is no in-repo rule that says which products use which gateway (`training/purchase*.php`).
37. **Inferred.** A Pay Now service key is chosen from the HTTP host (`www.knowmore.co.za` versus `www.qa-celestis.co.za`) inside `_system.php`. QA and production do not share a key.
38. **Inferred.** Webadmin has licence, voucher, invoice, and SLA screens (`webadmin/licence.php`, `voucher.php`, `invoice.php`, `sla.quote.php`, `sla.service.php`). The commercial rules (price, term, what a voucher waives) are in the database or in those long scripts and were not extracted as confirmed policy.

## Mail, surveys, and opt-out

39. **Inferred.** Client-survey invites and reminders are cron tasks (`Tools CSS`, `Tools CSS Reminder` in `webadmin/cron.hourly.php`).
40. **Inferred.** A respondent can opt out via `cat.unregister.php`. The public sample letter tells the client the survey is confidential and that their name was not supplied to a third party. That letter is inside a comment in `cron.daily.php` and still names Celestis. Do not treat the commented letter as the current template.
41. **Inferred.** Opening the online copy or the tracking image hits `email.read.online.php` and `email.read.receipt.php`.

## Access control in webadmin

42. **Inferred.** A webadmin user's menu is the set of `sysMenuLevel2` rows for their `refSecurityGroupID` with `blnMenuItem = 1`. View, delete, save, and special are separate flags (`webadmin/_nemo.menu.cls.php`).
43. **Inferred.** Webadmin login requires a `sysUser` row joined to `sysSecurityGroup` (`webadmin/login.php`).

## Host and environment

44. **Inferred.** Host `knowmore.co.za` is rewritten to `www.knowmore.co.za`, and `qa-celestis.co.za` to `www.qa-celestis.co.za`. If `SERVER_NAME` is empty, the code assumes `www.knowmore.co.za` (`_system.php`).
45. **Inferred.** HTTP is redirected to HTTPS by `.htaccess`.

## Conflicts that need a human decision

- Public brand is Knowmore / Old Mutual Wealth. `contact.us.php`, `content.php`, and old cron comments still say Celestis.
- Contact phone and address differ between `about.html` / `contact-us.html` and `contact.us.php` (see PRD FR-023).
- CMS article role check is strict (`cmsadmin` only). CMS user role check is not (rule 31).
- Password policy is described as aligned (`2286f50`) but the CMS omits the restricted-word rule.
- Directory modes are 0750 in `_system.php` and 0755 in `fim_2.0/fim.cls.php`.
- `.env.default` variable names do not match the names `_system.php` reads.
- Partner-only tools are a documented marketing rule. The code path found is "has a practice id", not a column named partner.
