# Technical documentation — wow_turf (WowTurf)

Evidence date: 2026-09-28. Revision: `master` `00137305` (2019-06-12).

Secrets that are committed in the source tree (README password, commented database password, WordPress salts) are not copied here.

## Architecture

WowTurf is a WordPress 4.9.6 application. Core, theme, and custom plugins are vendored in git under `public_html/`. Product behaviour sits in PHP classes in custom plugins and in theme templates. WordPress custom post types hold campaigns and notifications. Custom MySQL tables prefixed `nfh_` hold serving, spend, payments, and competition data. `geo_records` holds geography.

```text
public_html/                 WordPress root (wp-admin, wp-includes, wp-content)
  wp-content/themes/wowturf/ Theme, dashboards, ad iframes, Gulp sources in dev/
  wp-content/plugins/        Custom Ninja plugins plus cloudflare
  db/wowturf.sql             Database dump named by the README
  locationapi/               Standalone country/region/city scripts and ezSQL
  new_staging/wp-config.php  A second config file
html/                        Static prototype and its own Gulp project
docs/                        Author technical spec (PHP + Material Design Lite)
function_flows.txt           Two sequence notes: WowWin listing and threshold payment
```

There is no `composer.json` and no application framework besides WordPress. Provenance: **Implemented**.

## Languages and frameworks

| Layer | Version or library | Evidence |
|---|---|---|
| CMS | WordPress 4.9.6 | `public_html/wp-includes/version.php` |
| Server language | PHP | Plugins and theme |
| Database | MySQL, charset `utf8mb4` in the active config | `wp-config.php` |
| Theme CSS | Sass, compiled by Gulp | `dev/scss/`, `html/package.json` |
| Theme JS | jQuery, Gulp concat and uglify | `dev/js/`, `single-campaign.php` loads jQuery 3.3.1 from Google's CDN |
| UI docs | Material Design Lite | `docs/components/` |
| Custom fields | Advanced Custom Fields API (`get_field`, `update_field`, `have_rows`) | Used throughout plugins. Plugin source is **not** in the repo |
| Forms | Formidable Forms | Documented. Plugin source is **not** in the repo |
| Payments | Stripe, plugin Ninja Payments STRIPE | `public_html/wp-content/plugins/ninja-payments-STRIPE/` |
| HTTP edge | Cloudflare plugin directory is present | `wp-content/plugins/cloudflare` |

PHP runtime version required by this WordPress release is **Unknown / Requires Confirmation**. The repo does not pin it.

## Application structure

### Theme

Theme header in `style.css`: name WowTurf, author Ninjas For Hire.

Page templates are listed in `docs/pagetemplates.php`. They live in the theme root (`home.php`, `dashboard-*.php`, `single-campaign.php`, `preview-*.php`, and others). Ad interaction bodies are `templates/adtypes/view-only.php`, `click-through.php`, `take-action.php`, and `premium.php`.

Gulp sources: `public_html/wp-content/themes/wowturf/dev/`. Built assets: `dist/`. The README says to run `npm install` and `gulp` from the theme folder.

`html/` is a separate prototype. `html/index.php` is a list of static screens. `html/package.json` name is `starter-theme` version `0.0.1`, with Gulp 3.9.1. Its test script exits 1.

### Custom plugins

| Directory | Role | Main types named in docs |
|---|---|---|
| `ninja-frontend` | Front-end hooks, search, listing endpoints, contribute points | `$nff`, `$nffActions` |
| `ninjas-users` | Profiles, invites, bookmarks, registration REST | `$nwu` |
| `ninjas-campaigns` | Campaign CPT, filters, spend | `$nwc`, `$nwcActions`, `$nwcFilters` |
| `ninja-payments-STRIPE` | Stripe customers, charges, outstanding balances | `$nwps` (docs also say `nfps`) |
| `ninja-payments-ZOTAPAY` | Legacy payments. README says unused | still registers `handlepayments` |
| `ninjas-pointstokens` | Points, tokens, competition | `$nwpt` |
| `ninjas-notifications` | Notification CPT | `$nwn` |
| `ninja-geo-manager` | Country, region, city admin against `geo_records` | Documented as built by Gerhard |
| `cloudflare` | Vendor plugin | Not described in `docs/` |

Method indexes in `README.md` and `docs/plugins.php` match these classes. They are the best map of entry points. This file does not repeat every method.

### Custom post types and taxonomies

| Name | Registered by | Notes |
|---|---|---|
| `campaign` | `campaignRegister` | Targeted ad |
| `campaigntype` | `campaigntype_taxonomies` | Slugs used in `single-campaign.php`: `premium`, `take-action`, `click-through`, `view-only`, `default-campaigns` |
| `notification` | `notificationRegister` | Dashboard notices |
| `userpreferences` | `userpreferences_taxonomies` | Category preferences |

### Database

WordPress tables use the default prefix. `runCompetition` queries `wp_usermeta` by that literal name. If a site uses another prefix, that query does not follow it. Provenance: **Implemented**.

Custom tables documented in `docs/database.php`:

| Table | Documented purpose |
|---|---|
| `geo_records` | Country, region, city, currency, timezone |
| `nfh_advertiser_coupons` | Advertiser coupon usage |
| `nfh_campaign_category` | Categories of live campaigns, for filtering |
| `nfh_campaign_interactions` | Interaction log |
| `nfh_campaign_region` | Location data for live campaigns |
| `nfh_campaign_spending` | Spend log |
| `nfh_campaigns` | Basic info for active campaigns |
| `nfh_campaigns_debug` | Debugger dumps |
| `nfh_competitions` | Draw results |
| `nfh_deleted_users` | Deleted users and their campaigns |
| `nfh_entries` | Draw entries. Cleared at the start of each competition |
| `nfh_payments_stripe_cards` | Stripe card ids |
| `nfh_payments_stripe_charges` | Stripe charges |
| `nfh_payments_stripe_errors` | Stripe errors |
| `nfh_payouts` | Bursary payouts marked complete |
| `nfh_periodic_invoicing_logs` | When periodic invoicing last ran |
| `nfh_personal_coupons` | Invite coupons that have been used |
| `nfh_postviews` | Post views |
| `nfh_referrals` | Invites sent from the dashboard |

Code inserts invites into `nfh_refferals`, not `nfh_referrals`. Provenance: **Discrepancy**.

Install dump: `public_html/db/wowturf.sql` (about 9.8 MB). A smaller ZotaPay backup dump from 2018-10-08 is beside it. The README says to import `wowturf.sql` and find-and-replace it for a local install.

Active `wp-config.php` defines database `testdb`, user `root`, empty password, host `localhost`. A commented block above it names an older hosted database. Those values are not repeated here.

`public_html/new_staging/wp-config.php` is a second config. It was not treated as the running config.

## HTTP API

All custom routes use namespace `nff/v1`. WordPress 4.9 does not require `permission_callback`. Routes that omit it are callable under that core's default REST rules.

Routes that return `is_user_logged_in()`:

| Method | Path | Callback |
|---|---|---|
| as registered | `/bookmark/` | bookmark processing |
| as registered | `/updatecats/` | category preferences |
| as registered | `/updatenotifications/` | notification preferences |
| GET | `/sendinvite/` | `sendInvite` |
| GET | `/getprofiledata/` | `getProfileDetail` |
| GET | `/adjustuserpoints/` | `adjustUserPoints` |
| GET | `/gettokensamount/` | `getTokensAmount` |
| as registered | `/getcampaigninfo/` | campaign info |
| as registered | `/sendadminmail/` | admin mail |

Routes registered without `permission_callback` in the files read:

| Method | Path | Notes |
|---|---|---|
| GET | `/percentage/` | Profile percentage |
| POST | `/fbloginreg/` | Facebook login or registration |
| POST | `/registeruser/` | Creates a user and generates a password |
| POST | `/loginuser/` | Login by email |
| GET | `/viewedposts/` | Viewed posts |
| as registered | `/getnotification/` | Single notification |
| as registered | `/seennotification/` | Mark seen |
| as registered | `/categoryposts/` | Category listing |
| as registered | `/getcampaigns/` | Campaign listing used by WowWin |
| as registered | `/periodicinvoicing/` | Periodic invoices |
| as registered | `/handlepayments/` | ZotaPay legacy |

`function_flows.txt` records the WowWin call as `WOWTURF.ajax.get_more_campaigns(...)` to `/wp-json/nff/v1/getcampaigns/`.

Several listing and payment routes build SQL by interpolating request or profile values (`filter_by_age`, `filter_by_preferences`, `runCompetition`). Provenance: **Implemented**. This document does not describe how to call them.

## Background work

There is no queue worker and no `wp_schedule_event` usage was found in the custom plugins that were searched.

Two jobs are hooked to `init`, so they run on ordinary page loads:

| Hook | Function | Behaviour |
|---|---|---|
| `init` | `flushCompletedCampaigns` | Ends campaigns past their end date |
| `init` | `check_invite_reminders` | Sends invite reminders older than 7 days |

`wp-cron.php` is the stock WordPress file. Whether the server hits it on a schedule is **Unknown / Requires Confirmation**.

Periodic invoicing is an HTTP endpoint (`periodicinvoicing`), not a confirmed cron entry. What calls it is **Unknown / Requires Confirmation**.

## Integrations

| System | How the repo shows it |
|---|---|
| Stripe | `process_stripe_payment`, customer and card methods, tables `nfh_payments_stripe_*` |
| Mandrill | Documented as the transactional provider, via Post SMTP. Plugin not vendored. `wp_mail` is also called directly in debugger and competition error paths |
| Mailchimp | Documented. Method `subcribeControllerMailchimp` |
| Facebook | Route `fbloginreg` only |
| Cloudflare | Plugin directory present |
| OpenVPN | Documented for staging operators. Host and login steps are in `README.md` and `docs/database.php`. Credentials are "a supplied text file", not in git |
| Google CDN | jQuery 3.3.1 on the campaign iframe |

Direct `wp_mail` calls in the code that was read use the address `etienne@ninjasforhire.co.za` for debugger and competition-error mail. Several are commented. One phone-debug mail in `handleProfileForm` is not commented.

## Auth

- WordPress users and roles: `subscriber`, `customer`, plus administrator and super admin for wp-admin and the competition page.
- Logged-in checks on some REST routes, as listed above.
- Ad completion uses nonce action `user_interaction`.
- Password reset customises the mail through `wpse_retrieve_password_message` and sets HTML mail with `email_set_content_type`.
- The README documents a local super-admin username and password in plain text. Treat that password as exposed. It is not repeated here.
- `registerUser` generates an 8-character password from a mixed alphabet using `rand`.
- Facebook and Formidable authentication details are **Unknown** because those plugins and app settings are not in git.

## Storage

- Media uploads use the WordPress library (`uploadMedia`, `uploadMedias`).
- Avatars are an ACF field `custom_avatar`. A 2019-04-04 commit says upload was changed to AJAX and worked in mobile browsers but not in "the app".
- No S3 or other object-store client was identified in the custom plugins during this pass.
- Database dumps are committed under `public_html/db/`.

## Environments and deployment

| Environment | What is written down |
|---|---|
| Local | WAMP or similar. Import `wowturf.sql`. Active config points at `localhost` / `testdb` |
| Staging | `https://staging.wowturf.org`. AWS, managed by overdrive.co.za. OpenVPN, MySQL Workbench, FTP. `docs/hosting.php` names a contact at overdrive.co.za |
| Production | A commented mail template uses `https://www.wowturf.org`. No deploy script, Dockerfile, or pipeline file was found |

`origin/staging` is a git branch, not proof of what is on the staging server. Its tip is eight months newer than `master`.

No CI configuration, Sonar project file, or `sonarqube_scan.bat` is in `master`. The onboarding scan listed the Sonar files at the repo root. They are **Unknown** relative to git.

## Security notes (observed, not a pentest)

- Commented historical database credentials and live WordPress salts are in `wp-config.php`.
- A super-admin password is in `README.md`.
- Custom SQL concatenates values in campaign filters and the competition query.
- REST routes listed above omit `permission_callback`, including registration, login, campaign listing, and periodic invoicing.
- Campaign debugger writes filter dumps to `nfh_campaigns_debug` on `get_campaign_id`. The author comment says to turn that off in production.
- Phone validation's `preg_replace` pattern has no delimiters, so the digit strip may not run.

## Development and testing

- Frontend build: `npm install` then `gulp` in the theme directory (README).
- In-repo docs: open the PHP files under `docs/` on a PHP server. They include a shared header and nav.
- Debugging: push arrays into `$debugger` and `wp_mail` them, or use wp-admin "Campaigns Debugger Tool" (`cdbt_settings_page`), which shows the latest calls.
- Automated tests: none for the product. `html/package.json` test script exits 1. ezSQL's own generated PHPDoc under `locationapi/` is third-party documentation, not WowTurf tests.
- `country_code_list.txt` at the repo root is a text list. It was not compared to `geo_records`.

## Monitoring

No application monitoring, error tracker, or metrics client was found in the custom code. Operator visibility that does exist:

- Stripe error table `nfh_payments_stripe_errors`
- Campaign debugger table
- Mandrill logs, documented as living in Mandrill, not in this repo
- Competition failure mail

## File map for the next reader

| Question | Start here |
|---|---|
| What the authors said the product is | `README.md`, `docs/index.php` |
| Campaign rules | `docs/campaigns.php`, `public_html/wp-content/plugins/ninjas-campaigns/inc/nwc.php` |
| Points and draws | `docs/pointstokens.php`, `.../ninjas-pointstokens/inc/nwpt.php` |
| Users and invites | `docs/users.php`, `.../ninjas-users/inc/nwu.php`, `nwu-actions.php` |
| Payments | `docs/payments.php`, `.../ninja-payments-STRIPE/` |
| Ad iframe | `public_html/wp-content/themes/wowturf/single-campaign.php` |
| Listing sequence | `function_flows.txt` |
