# PRD — website (1 For The Road)

Evidence date: 2026-09-28. Source: `git@bitbucket.org:overdrive/website.git`.

There is no prior PRD in the repository or in Silicon Ark. Every requirement below was recovered from the code and the public HTML. Provenance is marked on each item.

**Branch rule used in this PRD.** Requirements of the membership product come from `origin/development_russel` at `a87b0c5` (2021-06-25). That branch is not merged into `master`. `master` at `581c5d9` (2023-05-05) is a Prime Cab HTML template plus Matomo. Template behaviour is listed separately and is not treated as the membership product. Whether either tree is what customers see in production is **Unknown / Requires Confirmation**.

## Product purpose

1 For The Road sells monthly memberships. A driver collects the member from a venue and drives the member's own car home. The member books by phone.

The About page states the service was founded by Joffy Senekal as an alternative to driving after drinking. Provenance: **Documented** (`aboutus.html` on `development_russel`).

## Users and roles

| Role | What they do | Evidence | Provenance |
|---|---|---|---|
| Prospect | Reads marketing pages, compares packages, submits a membership application or a contact message | Public HTML, `register.php`, `contact.php` | Implemented |
| Member | Phones the call centre to book, cancel, or request a pickup. Receives SMS confirmations described in the FAQ | `faq.html`, `packages.html` | Documented on the site. SMS sending is not implemented in this repo |
| Family member | Additional people in the same household on a discounted add-on | `packages.html`, `register.php` (`chkFamilyDiscount`, package flagged `blnFamilyDiscountPackage`) | Documented and implemented |
| Call-centre / office staff | Not a separate login. Staff use `webadmin` | `webadmin/login.php` | Inferred that call-centre staff are webadmin users |
| Webadmin user | Signs in with email and password. Sees menu items allowed by their security group | `webadmin/login.php`, `sysUser`, `sysSecurityGroup`, menu SQL | Implemented |
| Young-adult parent | Receives an approval email for a 14ya booking | `webadmin/includes/bookingYA.cls.php` | Implemented |
| Anonymous public | Can open marketing pages with no account | Static HTML | Implemented |

There is no member login on the custom site. `template/login.html` and `template/user-account.html` on `master` are Prime Cab theme pages and are not wired to `webadmin`.

## Main workflows

### W1 — Learn and choose a package

Trigger: visitor opens the site.

Steps shown in the HTML:

1. Navigation: Home, About, Packages, Why Join, Contact Us, and a Join Now button. FAQ and Testimonials are footer links.
2. Packages page explains that membership is: pick a package, complete the application including bank details for a debit order, then phone `0861 622 642` to book.
3. Visible package cards link to `register.html?PackageID=1` through `4`. The Function card tells the visitor to contact the business rather than self-register.

Provenance: **Documented** and **Implemented** as static pages. The Function card's contact path is a mailto-style contact, not the numbered package form.

### W2 — Apply for membership

Trigger: visitor submits `register.html` to `register.php` with `Action=Submit`.

Inputs (form):

- Package radio `radPackage` values 1–4, hidden `PackageID` filled by script from the query string or the radio.
- Optional family-discount checkbox and quantity (price shown as R40, hidden price field for package 7).
- Name, ID number, email, home phone, cellphone, work phone.
- Home address, postal address, postal code, province.
- How they heard about the service: Bangersandnash, Facebook, Friends, Twitter, Web, Other.
- Vehicle make and model, registration, colour.
- Bank name, branch code, account type (Current/Checking, Savings, Transmission), account holder, account number.
- Terms checkbox `chkTC`.
- reCAPTCHA v3 token.

Client validation (`jsValidate` in `register.html`) blocks submit until package, name, ID, a syntactically valid email, cellphone, home address, postal code, province, vehicle, registration, colour, all bank fields, and terms are present. Home phone, work phone, postal address, and "how did you hear" are not in that required list.

Server behaviour (`register.php`), after reCAPTCHA reports success:

1. Insert a row into `sysRegistration` with the client IP and a serialized copy of the whole POST body (this includes bank details).
2. If `radPackage` is missing, redirect to `register.html?MID=2`.
3. If the email contains `googlemail.com`, insert a spam record.
4. If `sysSpam` already has that email, redirect to `register.html?MID=3` and stop.
5. Allocate `strMembershipNumber` as one plus the current maximum number that is greater than 0 and less than 9999999.
6. Insert `tblClient` (active, `dtRegistered` today, user stamp `Registration`). Empty postal address is replaced with `txtAddressPysical` — the form field is `txtAddressPostal`, so this fallback does not match the form. **Inferred** defect.
7. If `PackageID` > 0, insert `tblVehicle` (default vehicle) and `tblClientPackage` using prices from `tblPackage` (`dblPriceInc`, `dblMinCharge`, `dblFreeKM`, `dblMinimumTrip`, `dblChargePerKM`). If the family checkbox is `on`, also attach the package whose `blnFamilyDiscountPackage` is 1, with the submitted quantity.
8. Send a welcome email via `NemoEmail` / `mail()`, subject "Welcome new 1 For the Road Subscriber", using `SMTP Send As` and `SMTP BCC` from system settings. Body says the service will be activated shortly and the applicant will be contacted.
9. Redirect to `register.html?MID=1` on save success, `MID=2` on save failure. reCAPTCHA failure is not given its own redirect in the portion read; a failed verify skips the save.

Provenance: **Implemented**. The welcome copy is **Documented** in the PHP string.

### W3 — Contact the business

Trigger: `contact.html` posts to `contact.php`.

Inputs: name, email, subject, message, reCAPTCHA token.

Server: if reCAPTCHA succeeds, build an email to `SMTP Send As` with BCC `SMTP BCC`, subject "New Message from Website: Contact Form", then call `Send()`. The next line calls `Send()` again and branches on that second result: `contact.html?MID=1` when error is 0, `MID=2` otherwise. reCAPTCHA failure redirects to `MID=3`.

Provenance: **Implemented**. Calling send twice is **Inferred** as a defect (the message may be delivered twice).

Public addresses printed on the contact page: `bookings@1fortheroad.co.za`, `accounts@1fortheroad.co.za`, `joffy@1fortheroad.co.za`, `info@1fortheroad.co.za`. Phones printed: `0861 622 642` (also written `086 162 2642`) and `081 809 8956`.

### W4 — Book a driver (call centre)

This workflow is **Documented** on `faq.html` and `packages.html`. It is not a web form on `development_russel`.

- Pre-book by phoning `0861 622 642` at least 2 hours before the driver is needed. Advance bookings are welcomed.
- If the pickup time is unknown: phone about an hour before leaving. An SMS confirms venue, time, drop-off address, and driver. A later SMS says the driver is waiting. The member has 15 minutes to meet the driver.
- Spontaneous bookings are also described: phone and request a pickup time. On arrival, an SMS gives the driver's location, and the member has 15 minutes.
- The driver drives the member's car. A partner follows in another vehicle. Both leave after the car is parked.
- The call centre closes at 02:00. A driver needed after 02:00 must be pre-booked at least 2 hours ahead.
- Expected arrival is normally 45 minutes to 1 hour. Delays are announced by SMS.
- The member is told to check the driver's 1 For The Road ID before handing over keys.
- The member can request extra drop-offs before the driver arrives. Extra mileage or another round trip is charged per extra drop-off.
- The service is only the member's own vehicle. A friend's car, or a trip with no vehicle, is stated as outside the contract.

SMS delivery, call-centre software, and the 15-minute rule are not implemented in this repository. Staff record the outcome in `webadmin` bookings. Provenance of the operational rules: **Documented** (FAQ). Provenance of system enforcement: **Unknown** except where `webadmin` status fields exist (see business rules).

### W5 — Staff sign-in and operations

Trigger: POST `action=Login` to `webadmin/login.php` with email and password.

Behaviour:

1. The script destroys any existing session, then starts one only inside the login attempt.
2. Load `sysUser` joined to `sysSecurityGroup` where the email matches and the security group is active.
3. Compare `strPasswordMD5` to `md5` of the submitted password.
4. If the user is inactive, show "User account is inactive." and log "Loggin failed: Inactive User" (spelling as in the source).
5. If the password does not match, show "Username or Password is incorrect."
6. If no row, show "Login Details are invalid."
7. On success, store user id, name, email, and security group id in `$_SESSION[USER]`, log success, redirect to `webadmin/index.php`.

Menu entries come from `tblMenuLevel1` / `tblMenuLevel2` filtered by `refSecurityGroupID`. A page is shown when that page's `Security->blnView` is 1 (seen on `webadmin/index.php` for bookings and the usage log).

Provenance: **Implemented**.

### W6 — Run a booking inside webadmin

`webadmin/includes/booking.cls.php` lists bookings with date, pickup time, and `refStatus`. Saving sets status from checkboxes, in this order:

| Condition | Status stored |
|---|---|
| Cancelled checked | `c: Cancelled` |
| Cancelled-billable checked | `cb: Cancelled Billable` |
| Otherwise if a completed flag is set | `6: Completed` |
| Client collecting | `5: Client Collecting` |
| Driver collecting | `4: Driver Collecting` |
| Client confirmed | `3: Client Confirmed` |
| Driver confirmed | `2: Driver Confirmed` |
| Else | `1: Initial` |

Delete refuses rows whose status is `cb: CancelledBillable` or `6: Completed`. The save path writes `cb: Cancelled Billable` (with a space). Those two strings do not match. Provenance: **Implemented**, and the mismatch is an **Inferred** defect.

Trip bookings (`bookingTrip.php`, `tblTripBooking`) group bookings under a trip and can assign a driver, date, trip description, and notes. Young-adult bookings (`bookingYA.php`) email a parent subject "1 For Young Adults Approval required" and notify `info@14ya.co.za` and `bookings@14ya.co.za` with subject "New 14YA Booking Made".

Invoices: `tblInvoice` has `blnSystemGenerated` and `strPeriod`. The booking code reads the max system-generated period. Exact invoice generation steps were not fully traced. Provenance: **Implemented** that invoices and periods exist; the generation schedule is **Unknown / Requires Confirmation**.

## Functional requirements

### FR-001 — Public marketing site

The public site presents Home, About, Packages, Why Join, FAQ, Contact, and Register, branded 1 For The Road, with a COVID-19 banner linking to `https://sacoronavirus.co.za/`.

Evidence: `index.html`, `aboutus.html`, `packages.html`, `whyjoin.html`, `faq.html`, `contact.html`, `register.html` on `development_russel`.

Implementation: Implemented on that branch only.

Confidence: High for the branch. Low for production, until deployment is confirmed.

### FR-002 — Package catalogue

The site offers member packages Basic, 20, 40, 80, and Function.

Visible card prices on `packages.html`:

| Package | Form id | Visible monthly fee | Visible allowance |
|---|---|---|---|
| Basic | 1 | R99.00 | No free kilometres; per-km charge |
| 20 | 2 | R275.00 | Card heading is "20"; the free-km sentence for this card was not as explicit as 40 and 80 |
| 40 | 3 | R418.00 | 40 km free, then a discounted per-km rate |
| 80 | 4 | R677.00 | 80 km free, then the lowest per-km rate described in the copy |
| Function | none | Not listed | Tailored; contact the business |
| Family add-on | package where `blnFamilyDiscountPackage = 1` (registration code also queries `PackageID = 7`) | R40 per person per month, shown for households on 20/40/80 | Quantity captured on the form |

An HTML comment in `packages.html` contains a comparison table that is not rendered: free km n/a, 20, 40, 80; minimum trip km 15, 5, 15, 15; per km R12.71, R13.92, R10.59, R8.47; monthly R99, R275, R418, R677. Another comment mentions older monthly fees R250, R380, and R615. Those commented figures are not the live page.

Registration does not charge the HTML price. It copies `tblPackage` columns onto `tblClientPackage`.

Implementation: Implemented (display) and Implemented (DB copy). Commercial equality of the two is **Requires Confirmation**.

Confidence: High that the visible monthly fees are what the HTML shows. Low that they match production billing.

### FR-003 — Membership application

The visitor can submit one application that creates a client, a default vehicle, and a client-package row, then receives a welcome email saying activation follows.

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

Validation: client-side required fields listed in W2. Server requires a successful reCAPTCHA and a selected `radPackage`. Server does not re-check ID format, bank-account format, or terms.

Error conditions:

- Missing package radio → `MID=2`.
- Email listed in `sysSpam`, including a new `googlemail.com` address → `MID=3`.
- Client save error → `MID=2`.
- reCAPTCHA failure → no success redirect (save is skipped).
- Database unavailable → connection failure message from `system.php` ("Database Connection has Failed!").

Implementation: Implemented on `development_russel`.

Confidence: High.

### FR-004 — Contact message

The visitor can send a name, email, subject, and message. Staff receive it by email when reCAPTCHA succeeds.

Evidence: `contact.html`, `contact.php`.

Implementation: Implemented.

Confidence: High for the code path. The live mailbox is whatever `SMTP Send As` is in system settings. **Requires Confirmation**.

### FR-005 — Phone booking rules

The product tells members to book and cancel by phone, with the timing, charging, area, vehicle, and identification rules in the business-rules document.

Evidence: `faq.html`, `packages.html`.

Implementation: Documented in content. Not enforced by the public website. Partial status support exists in webadmin.

Confidence: High for the copy. Medium that operations still follow it.

### FR-006 — Staff administration

Signed-in staff can maintain the operational records exposed as PHP pages:

`client.php`, `package.php`, `vehicle.php`, `driver.php`, `booking.php`, `bookingTrip.php`, `bookingYA.php`, `child.php`, `invoice.php`, `invoiceYA.php`, `pay.php`, `area.php`, `schedule.php`, `report.php`, `user.php`, `security.group.php`, `settings.php`, `sweeper.php`, `message.php`, `usage.log.php`.

Evidence: those files under `webadmin/` on `development_russel`.

Access: login plus security-group menu and per-page view flag.

Implementation: Implemented as an application. Individual field rules inside every class were not all transcribed. Confidence: High that the modules exist. Medium on every field-level rule.

### FR-007 — Analytics on the default branch

Root `index.html` on `master` loads Matomo from `//analytics.medmin.co.za/matomo.js`, site id 5, cookie domain `*.1fortheroad.co.za`, and sends a noscript pixel. Link tracking and `trackPageView` are enabled.

Evidence: `index.html` at `581c5d9`.

The same snippet is not on `development_russel` (that branch forked before the tracking commits).

Implementation: Implemented on `master` only.

Confidence: High.

### FR-008 — Prime Cab template (default branch)

`master` serves a Prime Cab HTML5 theme: three homepages, about, services, fleet grid/list/detail, blog list/detail, 404, register, login, FAQ, booking form, confirm booking, payment, payment confirmation, booking cancel, user account, and contact. Root `index.html` is a copy of a homepage whose links point at those HTML files as if they were beside `index.html`. They are stored under `template/`.

Theme forms post to PHP mailers (`template/contact/contact-form.php`, `home-form.php`, `ride-booking.php`) and `template/mailchimp/subscribe.php`. A Twitter helper lives in `js/api/`. Placeholder contact data is `primecab@booking.com`, `+1-234-000-2345`, "Primecab, United States".

Provenance: **Implemented** as vendored theme content. It is not evidence of 1 For The Road product behaviour.

Confidence: High.

## Integrations

| System | Direction | Trigger | Data | Auth | Failure |
|---|---|---|---|---|---|
| Google reCAPTCHA siteverify | Outbound HTTPS | Register or contact submit | Token, IP, secret | Secret hardcoded in the two PHP files | Register skips the save. Contact redirects `MID=3` |
| MySQL `1fortheroad` | Outbound | Page load of PHP that includes `system.php` | Clients, packages, bookings, users | Username/password in `system.php` per HTTP host | Message "Database Connection has Failed!" plus the driver error |
| PHP `mail()` | Outbound | Successful register or contact; some booking events | Email body | `SMTP Send As` / `SMTP BCC` from system settings. `NemoEmail` uses `mail()`, not an SMTP library, in the methods inspected | Contact sets `MID=2` only on the second `Send()` |
| Matomo `analytics.medmin.co.za` | Browser to Matomo | `master` homepage view | Page view, site id 5 | None in the snippet | Tracker failure is not handled |
| `sacoronavirus.co.za` | Browser link | COVID banner | None | None | Ordinary link |
| Font Awesome Pro CDN | Browser | Russel homepage stylesheet | CSS | Integrity hash on the link | CSS fails to load; local Font Awesome files also exist |
| Twitter OAuth (`js/api`) | Present in the theme | Not referenced by the 1 For The Road pages that were inspected | API helper | `js/api/config.php` exists on both lines | **Unknown** whether it is called |

No payment-gateway name (card processor, debit-order bureau) appears in `pay.php` from the search performed. Debit order is described to the customer. How the debit order is submitted to a bank is **Unknown / Requires Confirmation**.

## Constraints

- Service area stated on the FAQ: "Cape Town and the surrounds."
- Hours on the footer: 10:00–02:00, and a second line "CURRENTLY 10H00 - 00H00 Due to COVID19". FAQ says the call centre closes at 02:00. These statements disagree. Provenance: **Documented**, conflict **Requires Confirmation**.
- Contract length, cancellation fees, and insurance indemnity are FAQ statements, not code. See business rules.
- Production PHP is configured with `SERVER_PROTOCOL = HTTPS` for `www.1fortheroad.co.za`.
- Apex host `1fortheroad.co.za` is rewritten in `system.php` to `www.1fortheroad.co.za`. The same pattern exists for `14ya.co.za` → `www.14ya.co.za`.
- Passwords in `sysUser` are stored as MD5. Provenance: **Implemented**.
- Secrets (database password, reCAPTCHA secret) are in git on `development_russel`. They are intentionally omitted here.

## Non-functional observations

- No automated tests, linters, or CI configuration are in the repository.
- No README or environment sample.
- jQuery 1.12.5 and MD5 passwords are the implemented stack, not a target.
- Registration persists the full POST, including bank account numbers, in `sysRegistration`.

## Open product questions

1. Is the live site `master`, `development_russel`, or something that was deployed and never pushed?
2. Which package prices does `tblPackage` hold today?
3. Is 14ya still operated from this admin?
4. Is there a member app, and does "Download our App" still apply?
5. What sends the SMS messages the FAQ promises?
6. What system submits the debit order after bank details are stored?
