# Technical documentation — dev.sabullion

Evidence date: 2026-10-17. Source: `git@bitbucket.org:overdrive/dev.sabullion.git`, `master` `c58febf` (2020-07-12, "Investment Calc adding index graph").

Credential values are omitted. They are present in the files named below.

## Repository

| Item | Value |
|---|---|
| Remote | `git@bitbucket.org:overdrive/dev.sabullion.git` |
| Default branch | `master` (`origin/HEAD`) |
| Tip | `c58febfcd9819ff62995196effb9c42ed3783065` |
| Commits | 79, 2020-05-12 through 2020-07-12 |
| Other branches | none |
| README | "SA Bullion. New SA Bullion WordPress website. Temp URL: sabullion.co.za.www18.jnb2.host-h.net" |

The scan-host checkout named on the onboarding issue was not used. This document was written from a clone of `c58febf`.

## Stack

| Layer | What the tree shows |
|---|---|
| Language | PHP. Theme Composer requires `>= 5.4.0`. Comments record a PHP 7 conversion and a PHP 7.2 QA host. The version that served this commit is **Unknown** |
| CMS | WordPress `5.1.5` (`wp-includes/version.php`) |
| Theme | Sage 8 ("Rocket Theme" 1.2). `composer.json` name `roots/sage`. Front-end notes in that file: HTML5 Boilerplate, gulp, Bower, Bootstrap. A built `dist` workflow was not re-run for this document |
| Custom plugin | `sab-api` 1.0 |
| Portal UI | Metronic-style markup (page-sidebar, Materialize, Morris, Highstock, SweetAlert) under `assets/` and `dashboard/assets/` |
| Mail | PHPMailer 6 (`vendor/phpmailer`, theme Composer constraint `^6.1`) |
| PDF / spreadsheets | mPDF, dompdf, phpToPDF, PHPExcel, PDFCrowd class files under `dashboard/plugins/` and `sab-api/api/plugins/` |
| Database | MySQL via PDO. Two databases per host (access and web) |
| CI | Bitbucket Pipelines, image `python:3.7.3`, `git-ftp` |
| Tests | No PHPUnit, Jest, or other test tree was found |

## Application structure

WordPress owns the document root. Directories that exist on disk are not rewritten.

```text
index.php                  WordPress front
wp-admin/ wp-includes/     WordPress 5.1.5
wp-content/themes/sabullion/   Sage theme, and a nested copy of config, helpers, templates, dashboard assets
wp-content/plugins/sab-api/    REST and calculator scripts
wp-content/plugins/        vendored CMS and shop plugins
config/ helpers/ content/ templates/    legacy public PHP
dashboard/                 client portal (own config, session, API)
krugerrand/ scheduler/     legacy buy, sell, drop-off entry points
assets/ download_files/    static files and the download library
vendor/                    Composer autoload and PHPMailer
```

`sab-api` includes files with `get_template_directory()`, so the copy under the theme is the one the REST routes execute. The repository-root `config/` and `helpers/` are what `krugerrand/`, `scheduler/`, and `dashboard/` include. The two trees can drift. Provenance: **Implemented**. Which tree operators treated as canonical is **Unknown / Requires Confirmation**.

`.gitignore` ignores `wp-config.php`, `wp-content/plugins/sab-api/api/pdf/*.pdf`, that plugin's `api/pdf/error_log`, and `.htaccess`. `.htaccess` is nevertheless tracked (WordPress rewrite block). `wp-config.php` is absent. WordPress DB name, table prefix, and salts are **Unknown**.

## Host switch

`config/site.php` and `dashboard/config/site.php` switch on `$_SERVER["SERVER_NAME"]`.

| Case | Public site URL | Dashboard URL |
|---|---|---|
| `localhost` | `http://localhost/dev.sabullion` | `http://localhost/sabullion/dashboard` |
| `lamp` | `http://lamp/sabullion` | `http://lamp/sabullion/dashboard` |
| `dev.overdrive.co.za` | `http://dev.overdrive.co.za/sabullion` | `http://dev.overdrive.co.za/sabullion/dashboard` after a string replace |
| `sabullion.overdriveqa.co.za` | `https://sabullion.overdriveqa.co.za` | `https://sabullion.overdriveqa.co.za/dashboard` |
| `sabullion.co.za` and default | `https://sabullion.co.za` | `https://sabullion.co.za/dashboard` |

Theme header and footer hardcode the QA origin for account links. Partnership templates hardcode `https://sabullion.co.za`. See the PRD.

Dashboard timezone: `Africa/Johannesburg`. `display_errors` is on. `error_reporting` is `E_ALL` without strict, notice, or warning.

## Databases

`config/dbconnect.php` (and the theme and dashboard copies) open two PDO connections. Names seen in the switch, without passwords:

| Host case | Access database | Web database | SQL host written in the file |
|---|---|---|---|
| `localhost` | `sabullio_accessdb` | `sabulli1_web_db` | `localhost` |
| `lamp` | `sabullio_accessDB` | `sabullio_sabulli1_web_db` | `lamp` |
| `dev.overdrive.co.za` | `sa_bullion` | `sabulli1_web_db` | `sql8.jnb1.host-h.net` and `sql38.jnb1.host-h.net` |
| `sabullion.overdriveqa.co.za` | `sab_accessDB_2018` | `sabulli1_web_db` | `sql26.cpt3.host-h.net` |
| `sabullion.co.za` | `sabullio_accessDB` | a second web database in the same file | `localhost` for the access connection. The rest of the live case was not copied here |

Passwords and usernames are in those files in plaintext, including commented-out older passwords. They are omitted here. Treat them as exposed.

Tables referenced by the code that was read:

| Table | Used for |
|---|---|
| `gold_price` | Columns `Date`, `ZAR`, `USD`, `EUR`, `GBP` |
| `sysVat` | `dblVat` |
| `applogin` | `email`, `password` (MD5) |
| `web_ifa_company_detail` | House profile |
| `web_ifa_advisor` | IFA profile |
| `web_client_detail` | Approved client, including `AurumNumber` |
| `web_client_registration` | Unapproved registration, `strRegistrationStatus` |
| `web_global_admin_detail` | Global admin |
| `sb_invst_minted_bar_calc` | Minted-bar illustration rows and `sbic_reference_number` |
| `tblDocuments` | Joined to registrations in the reminder script |

No migration files are in the repository. Schema beyond these column names is **Unknown**.

## HTTP API (`sab-api`)

Registered on `rest_api_init` in `wp-content/plugins/sab-api/sab-api.php`. Namespace `api/v1`. All routes use `permission_callback` returning true.

| Method | Route | Purpose |
|---|---|---|
| GET | `/gold/index/` | Latest prices, or a series when `currency` and `period` are set |
| GET | `/gold/alt/` | Alternate gold response |
| GET | `/calc/index/` | Investment series from historical `gold_price` |
| GET | `/altcalc/index/` | `investment_calc_index` |
| GET | `/calc/minted-bar-index/` | Minted-bar series |
| GET | `/email/index/` | Email an illustration |
| GET | `/email/minted-bar-email/` | Email a minted-bar illustration |
| GET | `/upload/krugerrand/` | Store a buy request |
| GET | `/upload/krugerrand/sell/` | Store a sell request |
| GET | `/upload/investment/` | Store an investment illustration |
| GET | `/upload/savings/` | Store a savings illustration |
| GET | `/upload/minted-bar-investment/` | Store a minted-bar illustration. Rejects a low amount |
| GET, POST | `/functions/` | `ajax_functions` |

Upload and email routes are registered as GET and read `$_REQUEST`. Provenance: **Implemented**.

Portal login is not a WordPress REST route. It is `dashboard/api/login/index.php`, JSON body `{ request: "login", email, password }`.

## Authentication and session

- Portal password check: `md5` of the raw password against `applogin.password`. Salt is commented out.
- Session save path: `dashboard/SESSION`. Lifetime 259200 seconds. Several `sess_*` files are committed. Do not treat their contents as documentation; they are leftover session payloads.
- Unapproved clients are a session flag, not a WordPress role.
- WordPress authentication is core `wp-login.php`. No project overlay was found.
- `dashboard/config/site.php` defines `AuthorizationKEY` and `SALT_KEY` to the same value. The login script that would have required the key is commented out. The value is omitted here.

## Mail

`dashboard/config/mailer.php` (comment "2020April Updated PHPMailer version"):

- `lamp`: `isSendMail()`, debug level 1.
- Every other host: SMTP `smtp.office365.com`, port 587, TLS, auth as `business@sabullion.co.za`, from "SA Bullion". `SMTPKeepAlive` true. `verify_peer`, `verify_peer_name` false, `allow_self_signed` true.
- The theme has its own `config/mailer.php`. Same class of secret. Not quoted.

## Theme page templates

WordPress template headers in `wp-content/themes/sabullion/template-*.php`:

Our Story, Seller Details, Services, Sell, Schedule, Invest SA, Partnership, Page, Minted Bar Illustration, Merged Investment Illustration, Investment Illustration, International Investment Illustration, Invest in Gold, Invest International, Gold Price, FAQs, Download, Documents, Deal SA, Wholesalers, Deal SA Selling, Deal SA - Sell, Custom Template, Contact, Capital Investment Illustration, Buy, Buy Request, About.

`sab-api` decides which calculator script to load with `is_page()` slugs: `the-gold-price`, `buy-gold-from-us`, `buy-request`, `seller-details`, `sell-gold-to-us`, `international-gold-solutions`, `south-african-gold-solutions`, `minted-bar-illustration`, `investment-illustration`, `capital-investment-illustration`, `merged-investment-illustration`.

Those slugs exist only if matching pages are stored in WordPress. **Unknown**.

## Plugins shipped beside sab-api

| Directory | Header version |
|---|---|
| `woocommerce` | 4.1.1 |
| `gravityforms` | 2.4.11 |
| `elementor` | 2.9.11 |
| `elementor-pro` | 2.5.9 |
| `advanced-custom-fields-pro` | 5.8.1 |
| `classic-editor` | 1.5 |
| `woocommerce-gateway-paygate-pw3` | PayGate PayWeb3 1.3.1 |
| `woopay-paygate` | WooPay - PayGate 1.1.4 |
| `woo-eftsecure-gateway` | Directory present. Plugin header not found by filename search |
| Also present | `acf-repeater-flexible-content-collapser`, `ajax-load-more`, `bulk-page-creator`, `custom-post-type-ui`, `duplicate-post`, `gravity-forms-css-ready-selector`, `side-cart-woocommerce`, `woocommerce-checkout-manager`, `wordpress-importer` |

No `mu-plugins` directory. No object cache drop-in.

## Deployment and environments

`bitbucket-pipelines.yml` runs only on `master`. It installs `zip`, `git`, and `git-ftp`, then `git ftp push` to `ftp://$FTP_HOST` with `$FTP_USER` and `$FTP_PWD`. `git ftp init` is commented out. The pipeline does not run tests, lint, or SonarQube.

`sonar-project.properties` and `sonarqube_scan.bat` are not in this commit. The onboarding scan listed them at the repository root. They were either untracked on the scan host or removed before `c58febf`. **Unknown**.

Local names in config (`localhost`, `lamp`, `dev.overdrive.co.za`, `sabullion.overdriveqa.co.za`) are the environments the code knows. No Docker Compose file is in the tree.

## Background work

| Script | Behaviour | Scheduler in git |
|---|---|---|
| `dashboard/helpers/cron.daily.php` | Deletes pdf, png, and xls via `active.dir.cleanup.cls.php` when the flag is true | None found |
| `dashboard/helpers/cron.documents.reminder.php` | Selects unapproved registrations with a null file name and dumps them | None found |
| `wp-cron.php` | WordPress core | Events are database state. **Unknown** |

`scheduler/index.php` is a public "Schedule Drop Off" page, not a job runner.

## Security notes (evidence, not a remediation plan)

- Plaintext DB passwords and an SMTP password are in git, including passwords inside comments.
- Portal passwords are unsalted MD5.
- Calculator and upload REST routes are world-readable and world-callable.
- SMTP TLS verification is disabled.
- `display_errors` is enabled in the dashboard bootstrap.
- `error_log` files and live session files are committed under `dashboard/` and elsewhere.
- Login queries concatenate the email with a quote helper (`$SA->qs`). That helper was not audited line by line.

## Development and testing process

No contributor guide beyond the three-line README. No test suite. No linter config at the root other than the absent Sonar file. Theme `composer.json` describes the upstream Sage gulp/Bower workflow; whether this commit's assets were rebuilt with it is **Unknown**.

Deploy is "push `master`, Pipelines FTPs the tree."

## Related repositories

Silicon Ark already has `sabullion/` (`overdrive/sabullion`, tip used there `a5cd347`, 2024-03-12) and `sabullion_current_live/` (`overdrive/sabullion_current_live`, tip `6cbf6777`, 2022-10-01). This document does not merge those trees. The shared README sentence "New SA Bullion WordPress website" is noted in the project plan. It is not proof that the commits are the same.

## Not established

- The PHP version, web server, and FTP host that ran this commit.
- WordPress `wp-config.php` values, active theme, active plugins, and published page slugs.
- The job that inserts `gold_price` rows.
- Whether the root PHP tree or the theme copy was edited last for any given file.
- Whether the committed secrets have been rotated.
