# Business rules — personal_laundry_website

Evidence date: 2026-10-06. Source: `git@bitbucket.org:overdrive/personal_laundry_website.git` at `90e6cd3` (`master`).

No business-rules document exists in the source repository. Rules below are taken from code, config, the sitemap, and committed logs. Anything inferred is labelled **Inferred**.

Database passwords, authentication salts, and the Wordfence API key are not restated here.

## Domain

**Inferred.** The site markets laundry services under the name Personal Laundry at `www.personallaundry.co.za`, with separate retail and commercial pages, an about page, and a contact page.

Evidence: `sitemap.xml`; Wordfence `siteURL` / `homeURL`; media filenames `Personal-Logo`, `Personal-Laundry-Factory`, `Retail-Laundry-Services-Cape-Town`, `Commercial-Laundry-Services-Cape-Town`.

There is no price, turnaround time, minimum order, service area polygon, or terms-and-conditions file in Git. Those rules, if they exist, are page copy in the database. **Unknown / Requires Confirmation.**

## Geography and premises

**Inferred** from filenames only:

- Cape Town appears in `Commercial-Laundry-Services-Cape-Town` and `Retail-Laundry-Services-Cape-Town`.
- A Sea Point store appears in `Personal-Cleaners-Sea-Point-Store` and `Personal-Cleaners-Store-Sea-Point`.
- A factory appears in `Personal-Laundry-Factory` and `At-Personal-Laundry-Factory`.

Whether those locations are current trading premises is **Unknown / Requires Confirmation**.

Image filenames that look like hotels or people (`DoubleTree`, `Ellerman-House`, `One-and-Only`, `Taj-Group`, `Bantry-Bay-International-Vacation-Resort`, `Richard-Ward`, `Grant-Gibor`, `Edith`) are not treated as a client register. **Unknown** what they represent.

## Services offered

**Inferred** from page slugs and photography, not from a catalogue:

- Retail laundry.
- Commercial laundry.
- Work depicted in assets includes washing, ironing shirts, folding towels and sheets, and steam-cleaning trousers.

No SKU, price list, garment type table, or express-service flag exists in code. **Unknown / Requires Confirmation** what is actually sold.

Guest-laundry imagery (`Guest-Laundry-Slide-Image`) suggests hospitality laundry was pictured. It is not a coded product line.

## What the site does not encode

- No online payment, basket, or WooCommerce plugin. Fusion Core contains WooCommerce shortcode classes. They do nothing without WooCommerce and without a page that uses them. **Inferred:** retail laundry is a content page, not a shop.
- No booking calendar, collection route, or ticket workflow in plugins or the child theme.
- No customer account plugin beyond WordPress users and a styled `.login-register` menu class. Whether customers can log in is **Unknown / Requires Confirmation**.

## Public URLs

**Implemented.** The committed sitemap lists exactly five HTTP URLs, each with `changefreq` yearly. It is a static file. It does not stay in sync with WordPress pages by itself.

**Inferred.** Pretty permalinks are required: `.htaccess` uses `RewriteBase /` and front-controller routing to `/index.php`.

## Access and permissions

| Rule | Provenance |
|---|---|
| WordPress roles and capabilities apply. No custom role is defined in the child theme or a must-use plugin | **Implemented** absence of custom role code. Actual assignments **Unknown** |
| Dashboard theme and plugin file editor is disabled (`DISALLOW_FILE_EDIT`) | **Implemented** in `wp-config.php` |
| `wp-config.php` must not be downloaded over HTTP (`.htaccess` `Files` deny) | **Implemented** |
| Directory listing is off (`Options -Indexes`) | **Implemented** |
| PHP is not executed from `wp-content/uploads/` (Wordfence `php_flag engine 0` and `Options -ExecCGI`) | **Implemented** in `wp-content/uploads/.htaccess` |
| Debug output is off (`WP_DEBUG` false) | **Implemented** |
| Wordfence may block requests when the WAF prepend file is loaded from the QA path | **Implemented** files. Enforcement on the real host **Unknown** |
| Wordfence snapshot is an unpaid licence (`isPaid` false) with advanced blocking enabled and an empty country-block list | **Implemented** in `config-synced.php`. Current licence **Unknown** |

There is no application-level rule that limits content by customer, branch, or staff role beyond WordPress defaults.

## Content and editorial rules

No editorial workflow (draft approval, required fields on pages, blocked words) was found outside WordPress core and the plugins' own admin.

**Inferred.** Menu item ID 30 was important enough to receive a hover colour in the child theme. Its label is **Unknown**.

**Inferred.** `#site_credit` is Avada's footer credit element. The hover colour is white. The credit text is a theme option in the database. **Unknown** what it says.

The child theme does not register a navigation menu, a widget area, or a page template.

## Contact and mail

Contact Form 7 is installed. No form export is in Git.

**Unknown / Requires Confirmation:**

- Required fields.
- Recipient address.
- Whether mail is sent as HTML.
- Any rule that stores enquiries in the database (CF7 can be configured either way; this repo does not show the setting).

## Language

**Implemented.** Admin and two plugins have British English packs (`en_GB`). Theme language packs for Twenty Fourteen and Twenty Fifteen are present even though those themes are not.

**Unknown** whether the public site is shown in English only. Avada includes a multilingual class (`Avada_Multilingual` in `wp-content/themes/Avada/functions.php`) and a WPML include. The WPML plugin is not in `wp-content/plugins/`. A second language is **Unknown / Requires Confirmation**.

The child theme asks WordPress to load translations from a missing `languages` folder. That call has no catalogues to load. **Implemented** in `Avada-Child-Theme/functions.php`.

## Validation rules that were patched

These are code guards, not laundry business policy. They are **Implemented** in commit `90e6cd3`:

| Rule | Where |
|---|---|
| If the in-request settings-error list is empty, return an empty array and do not count it | `wp-admin/includes/template.php` `get_settings_errors()` |
| Revolution Slider metabox list starts as an array. If a box has no `title` or no `location`, those values are empty strings | `revslider/includes/framework/base-admin.class.php` |
| Side-header breakpoint media query subtracts 32 inside the numeric expression | `Avada/includes/dynamic_css.php` |

The 2016 `error_log` also shows Revolution Slider calling `implode()` with invalid arguments (`slider.class.php` line 116 on that host) and `flat-responsive` creating a default object from an empty value. Those warnings are historical. They are not rules.

## Deployment identity rule introduced in 2021

**Implemented**, from commit messages and path edits, not from a runbook:

1. On 2021-03-29 the Wordfence prepend path was changed from hosting user `persotrccj` to `devovvbypb`. The message says this is the QA site username.
2. On 2021-03-31 the path was changed again to `/usr/www/users/devovvbypb/personal_laundry/`. The message says this is the QA site path.

**Inferred.** After those commits, this Git tree's firewall bootstrap expects the QA directory, not the `persotrccj` directory still stored inside `wp-content/wflogs/config-synced.php`.

`wp-config.php` points at database `personal_db` with a user in the `devovvbypb` account family and host `sql47.jnb1.host-h.net`. Whether that database is the QA copy or production is **Unknown / Requires Confirmation**.

## Security rules the repository violates by containing secrets

**Implemented** facts, recorded so they are not mistaken for acceptable practice:

- `wp-config.php` is in Git and contains the database password and WordPress salts.
- `wp-content/wflogs/config-synced.php` is in Git and contains a Wordfence API key and block data.
- `error_log` is in Git.

Rotation and removal from history are **Unknown / Requires Confirmation**. This document does not repeat the secret values.

## Data retention

No retention rule is coded. Wordfence's synced config includes a blocked-time value inside its block JSON. That is Wordfence configuration, not a laundry-business retention policy. Uploaded media from 2016 is retained in Git with no expiry.

## Rules that must not be invented from this repo

- Opening hours, pricing, turnaround, collection fees, and lost-item liability.
- Which staff may publish pages.
- That every filename is a current customer.
- That the login menu creates a customer portal.
- That the site is still online.
