# Business rules — knowmore_php84 (Knowmore)

Evidence date: 2026-10-11. Source: `git@bitbucket.org:overdrive/knowmore_php84.git`, `master` at `2e5a13d0` unless a rule says it is only on an unmerged branch.

Rules are taken from code, comments, and commit messages. They are not a signed business policy. Where the code implies a rule that nobody wrote down, the rule is marked **Inferred**.

Credential values are omitted on purpose.

## Access

1. **Implemented.** A learner login matches `tblLearner.strEmail` to the posted username (`login.php`).
2. **Implemented.** An inactive learner (`blnActive == 0`) cannot sign in, unless the request is the registration-purchase redirect (`blnRegPurcLink`). The message is "Your account is currently inactive."
3. **Implemented.** A successful learner login requires `NemoPassword::checkPassword("Learner", ...)`. The older MD5 comparison on this path is commented out.
4. **Implemented.** The registration-purchase redirect still finds the learner with `strPasswordMD5` and then signs them in toward `training/purchase.omw.php`.
5. **Implemented.** Training pages require `$_SESSION[LEARNER]`. Otherwise the browser is sent to `second.wp.php?MID=LD9DHR`.
6. **Implemented.** Web-admin pages that construct `Nemo` require `$_SESSION[USER]`. Otherwise the browser is sent to `SessionExRedirect` from settings.
7. **Implemented.** Admin login requires an active security group (`sysSecurityGroup.blnActive = 1`) and an active user (`blnActive == 1`).
8. **Implemented.** Admin menu items and page rights come from `sysSecurity` for the user's security group: view, delete, save, and special. A level-1 item is shown when the group is active, `blnView = 1`, and `MenuLevel1ID > 0`.
9. **Implemented.** Detail screens show Save only when `blnSave` is set for the page. Close and reload are always shown (`NemoDetails`).
10. **Implemented.** The tools shell requires `$_SESSION[ADVISOR]`. Otherwise it redirects to `../index.php`.
11. **Inferred.** Learner, admin user, and advisor are different sessions. Nothing in the files read shows one login that creates all three.

## Registration

12. **Implemented.** `register.php` rejects the form when reCAPTCHA returns `incorrect-captcha-sol`.
13. **Implemented.** A learner inserted by `register.php` is stored with `blnActive = 1`.
14. **Implemented.** The registration form asks whether the person has a financial-planner code, whether they have a staff code, and whether they will give an ID number or mark it refused (`register.php`).
15. **Implemented.** Country and region lists on that form include only `blnActive = 1` rows. Country `1` is excluded from the country query in the portion read.
16. **Documented in a comment.** `register.special.php` uses `tblCourse.strSpecialCode` as a generic voucher that stays open. RSVP registration is the base behaviour.
17. **Documented in a comment.** New RSVP learners store a null practice when the practice id would have been 0 (2016-10-25, `register.rsvp.php`).
18. **Unknown.** Which register script the public site links to. Several generations are in the tree together.

## Passwords

19. **Implemented.** When policy is enabled, passwords are stored as a salt plus a hash, not as the comment's "clear text" field alone. The class comment still says `strPassword` is clear text. Both statements are in `_nemo.password.cls.php`. How often clear text is still written is **Unknown / Requires Confirmation**.
20. **Implemented.** First-run defaults, if `sysPassword` is missing: policy on, minimum length 8, history 8, restricted words `password` and `123`, max age `false`, reset link `60 MINUTE`. Production values live in `sysSettings` and can differ.
21. **Implemented.** The class messages distinguish invalid login, inactive user, locked account, expired password, and a password that was already used.
22. **Inferred conflict.** The table created by that first run allows types `User` and `Supplier` only. Learner login passes type `Learner`. If production was created from this statement and never altered, learner checks cannot match a row. **Requires confirmation.**
23. **Documented in a comment.** Reset links expire using `PASSWORD_Reset_Interval` (2016-10-27).
24. **Documented in a comment.** `webadmin/system.php` records a 2017-11-02 password reset on live and QA after a Hetzner breach. The admin login page tells users to use Forgot Password because passwords were reset.

## Learner catalogue

25. **Documented in a comment.** The learner home permanently hides the user-category block and the WPS block (2017-03-08).
26. **Implemented.** If the learner's practice has no WPS id, the page uses WPS id `-10`.
27. **Implemented.** If the learner has no user category on the session, category id `-1` is used.
28. **Implemented.** Assignment upload stores at most three numbered files for a module assignment and learner. The counter is the numeric prefix of `modAssessment.strFilename`. A prefix that is not numeric still allows an upload and starts the counter at 1. A prefix of 3 or more blocks the upload (`training/module.php`).
29. **Documented in a commit, narrower than the code.** Commit `313c4dd2` calls rule 28 an IWP rule. The submit case does not check course type. **Requires confirmation.**
30. **Implemented.** A successful upload sets `modMilestone.strStatus` to `Submitted`.
31. **Documented in a comment.** My Assignments shows work from the past two years (2019-03-29, `training/my.assignments.php`).
32. **Implemented.** IWP module pages can show the line that the learner should scroll to the online module, assignments, and case studies. A separate line says some items are not applicable for CPD (`training/module.php`).
33. **Implemented.** Verifiable CPD on the module page uses `dblCPDPointsFPI` and `refCPDCategory`.
34. **Inferred.** CPD categories in page copy and in the unmerged succession SQL (Ethics and Practice Standards, Technical, General, online programmes, and others) are the allowed FPI-style categories. The live enum for courses on `master` is in the database, not in a migration on `master`.

## Business tools

35. **Documented in a comment.** BAT 1 and BAT 2 no longer switch databases (2016-05-06). BAT 2020 uses the `*_2020` tables.
36. **Documented in a comment.** Only active BAT questions are used (2018-09-18, `bat_2020.class.php`).
37. **Documented in a comment.** Report covers include name, surname, and ID, and say Financial Sector Conduct Authority (FSCA) rather than FSB (2019-04-03).
38. **Documented in a comment.** CAT custom multi questions are limited to 5 options (2010-06-28). Demographics questions do not get a comment box (2010-07-02).
39. **Documented in a comment.** A survey completion check runs in `CheckSecurity()` (2010-07-08).
40. **Documented in a comment.** `blnLockEndDate` stops the client survey from overriding the end date (2013-11-21).
41. **Documented in a comment.** CSS invites take their body from the survey email template table rather than from the posted form (2013-12-09).
42. **Documented in a comment.** The survey is not sent when it has no clients (2015-08-17).
43. **Documented in a comment.** Succession-planning gap analysis was copied from the PoPI gap tool (2017-02-14). The action-plan section and the cluster-level score total were hidden (2017-04-03).
44. **Documented in a comment.** Gap scores use the previous cluster id, not the current one (2017-04-24).
45. **Implemented.** Saving industry on the tools home writes `strIndustry`, `refSubCategoryID`, last user, and last edit onto `tblPractice`. Last user is the coach name when `$_SESSION['CoachName']` is set, otherwise the admin username.
46. **Inferred.** Practice id `643` is hardcoded in the learner-home BAT pillar query. That is not written as a business rule. **Requires confirmation** before anyone treats it as "show this practice's results to every learner".

## Email and cron

47. **Implemented.** Hourly processing includes distinct email kinds: client import, invoices and orders (encrypt and email), CPD import, certificates, course start, course complete, ad hoc, workshop launch, workshop RSVP, private IFA workshop, notice, apology, CSS invite, CSS reminder, CPD notification, IBC registration, AML registration (`webadmin/cron.hourly.php`).
48. **Documented in a comment.** AML "General Awareness" uses a different email from other AML courses (2019-03-19).
49. **Documented in a comment.** Module reminder mail uses an alternate template when the module is a feedback module (2016-03-28).
50. **Documented in a comment.** CSS reminders must link to the tools area (2017-04-11).
51. **Implemented.** A missing HTTP host during cron is forced to `www.knowmore.co.za` so the live database settings are selected. Comments attribute this to cron and Hetzner (2017-01-30, 2017-02-27).
52. **Inferred.** Because rule 51 defaults to production, a cron started with an empty host uses the live settings block. That is a behaviour of the code, and it is risky if those settings are still real.

## Payments and commercial flags

53. **Implemented.** Pay Now service keys are chosen by host. QA (`www.qa-celestis.co.za`, and the `lamp` key) and live (`www.knowmore.co.za`) are separate entries (2017-08-25).
54. **Implemented.** PayGate results update `tblTransaction` by the returned reference.
55. **Documented in template text.** A debit-order mandate in `tools/dtPDF.cls.php` says the authority is ceded to Netcash (Pty) Ltd.
56. **Implemented on the unmerged branch only.** A succession service has a cost (`dblCost`, default 0), a pass rate (default 60), a release mode of Time Based, Module Based, or Free (default Time Based), and flags for individual modules, workshop, WI, RSVP-only, and special-code-only. Workshop is commented "HIDDEN!". RSVP-only says to leave it unticked for public workshops. Special-code-only refers to a code in the RSVP email template.

## Admin and privacy of logs

57. **Documented in comments.** The admin home usage log must not select or group by password (2017-11-03, 2018-07-27).
58. **Implemented.** That log shows the last two days of `sysLogin` rows: date, result, username, system user, active flag, security group.
59. **Implemented.** `sysLogin` is also written for learner success and failure, including the text "Incorrect password" and "Inactive User". The password itself is not what those insert strings contain.

## Files and sessions

60. **Implemented.** PHP session garbage lifetime is 7200 seconds (`webadmin/system.php`).
61. **Implemented.** Session files are stored on a path that depends on the host key (for example under `webadmin/SESSION` on the QA and live keys).
62. **Implemented.** Directories `training/dcontent`, `webadmin/SESSION`, and `webadmin/documents/SLA` are forced to mode `0755` when they exist. FIM sets directories to `0755` and files to `0644`.
63. **Implemented.** `.gitignore` ignores `webadmin/SESSION`, `PDF`, `tools/webadmin/SESSION`, generated line-bar images, `training/dcontent`, `webadmin/import`, and `webadmin/documents/SLA`. Those paths are runtime data, not source.

## Host and environment

64. **Implemented.** `knowmore.co.za` is rewritten to `www.knowmore.co.za`. `qa-celestis.co.za` is rewritten to `www.qa-celestis.co.za`.
65. **Implemented.** Known host keys in `system.php` include `localhost` (environment `dev`, database name `knowmore_2016`), `lamp` (environment `QA`, database name `knowmore_2016_qa`), `dev.overdrive.co.za` (environment `QA`, database name `knowmore_2016qa`), `www.qa-celestis.co.za` (environment `QA`, database name `knowmore_2016_qa`), and `www.knowmore.co.za` (environment `live`, database name `knowmore_2016`).
66. **Inferred.** `lamp` is labelled QA in the environment field and uses a QA database name. It is not a separate product.
67. **Implemented.** HTTP is redirected to HTTPS for the same host (`.htaccess`). The QA and live setting blocks also set protocol `HTTPS`.

## What is not a confirmed rule

- A written definition of who may buy a course, who is a planner versus support staff, or which content is exclusive to a partner. Registration collects planner and staff codes. The business meaning of those codes is **Unknown / Requires Confirmation**.
- Pricing, VAT, and refund rules. Cost fields exist on the unmerged succession table and in invoice code. A price list was not found.
- The legal owner of Knowmore. Not stated in a policy page that was identified in this tree.
- PHP 8.4 as a required runtime. Not stated on `master`.
