# PRD — wcu_wp

Evidence date: 2026-09-29. Source: `git@bitbucket.org:overdrive/wcu_wp.git` at `ed9b5b6` (`main`, 2024-12-18).

There is no prior PRD in the repository or in Silicon Ark. Requirements below were recovered from code, config, and git history. Provenance is marked on each item.

Content that editors enter in WordPress (pages, posts, teams, fixtures, forms, menus) is not in git. Requirements that depend on that content are marked **Unknown / Requires Confirmation**.

## Product purpose

The repository is a WordPress site for a sports organisation referred to in the theme as **WRU**. The repository name is **wcu_wp**.

The child theme is "Rookie Child Theme for WRU". Rookie is a ThemeBoy theme for organisations that use SportsPress. SportsPress Pro is included so the site can hold clubs, teams, players, staff, events, and the Pro modules shipped in version 2.7.25.

Provenance: **Implemented** (theme header and plugin header). The organisation's full name, sport, and public domain are **Unknown / Requires Confirmation**. No marketing copy is stored in the repository.

The core package is South African English (`$wp_local_package = 'en_ZA'`). Provenance: **Implemented**.

Overdrive is credited in the footer as the designer ("Silcon Overdrive", link `http://overdrive.co.za/`). Provenance: **Implemented**. That credit is not a statement of product ownership beyond what the string says.

## Users and roles

| Role | What they can do | Evidence | Provenance |
|---|---|---|---|
| Public visitor | Reads the public site. Can submit whatever Ninja Forms are published, if that plugin is active and a form is embedded | WordPress front end; Ninja Forms is vendored | Inferred. Actual pages and forms are not in git |
| WordPress administrator | Full `wp-admin` access for the site (or the network, if multisite) | WordPress core | Implemented by core. Who holds the account is Unknown |
| Editor / author / contributor / subscriber | Standard WordPress capabilities | WordPress core roles | Implemented by core. Which roles are in use is Unknown |
| SportsPress limited user | If the Team Access module is running, roles `sp_player`, `sp_staff`, `sp_team_manager`, and `sp_event_manager` are limited to SportsPress data for teams stored in user meta `sp_team` | `sportspress-team-access.php` `role_is_limited` | Vendor behaviour. Whether the module is enabled is Unknown |
| Network administrator | Would manage sites if WordPress is multisite | NS Cloner description; child theme blog ID check | Inferred that multisite was intended. Not confirmed, because `wp-config.php` is absent |

No custom role is registered by the child theme. No login form was added by this project. Sign-in is WordPress `wp-login.php`.

## Main workflows

### W1 — Read the public site

Trigger: a visitor opens the site.

Behaviour that is in code:

1. WordPress loads the active theme. The active theme is an option in the database. It is **Unknown**. The only theme with WRU-specific code is `rookie-child`.
2. The child theme enqueues the parent `style.css`, then `css/custom.css` (version string `1.0`).
3. If `get_current_blog_id()` is `2`, it also enqueues `css/subsite-2.css`. That file is not in the repository, so the enqueue on blog 2 requests a missing stylesheet.
4. Header layout is the Rookie header (widgets, branding, optional banner, primary menu, optional search), copied into the child and given an inline `background-color` from the `themeboy` option `primary`.
5. Footer credit is the child override: a link to `http://overdrive.co.za/` with the text "Designed by Silcon Overdrive". Copyright line remains the parent behaviour: current year and the site name from WordPress settings (`rookie_footer_copyright` was not overridden).

Provenance: **Implemented** in `wp-content/themes/rookie-child/`.

What the visitor actually reads (club name, fixtures, news) is **Unknown**.

### W2 — Publish sports data

Trigger: a staff user opens `wp-admin` and uses SportsPress, if the plugin is active.

SportsPress Pro registers the vendor content model (events, teams, players, staff, tables, calendars, and the modules under `wp-content/plugins/sportspress-pro/includes/`). The child theme CSS styles SportsPress markup (tables, countdowns, event blocks, brackets, player and staff titles, sponsor footer).

This repository does not seed teams, leagues, or events. Whether any of that data exists in the database is **Unknown**.

Rookie marks SportsPress as a required plugin and will show a TGMPA notice if the `SportsPress::instance` callable is missing. SportsPress Pro provides that class and, on `admin_init`, deactivates `sportspress/sportspress.php` if a separate free plugin is active.

Provenance: **Implemented** vendor code plus child CSS. Activation and data entry are **Unknown**.

### W3 — Limited staff edits only their team

Trigger: a user whose role is one of `sp_player`, `sp_staff`, `sp_team_manager`, or `sp_event_manager` opens admin, and the Team Access module is enabled.

The module adds a Team field on the user profile and filters `pre_get_posts` and `sportspress_user_can` so those roles only reach SportsPress post types tied to their `sp_team` user meta: `sp_event`, `sp_calendar`, `sp_team`, `sp_table`, `sp_player`, `sp_list`, `sp_staff`.

Provenance: **Implemented** in the vendored module. Enablement is a SportsPress setting in the database. **Unknown** whether it is on. No project code changes the role list (a `sportspress_team_access_roles` filter exists and is unused by this repo).

### W4 — Build a form or a slider

Trigger: an administrator uses Ninja Forms or Smart Slider 3 in `wp-admin`, if those plugins are active.

Both plugins are in the tree. Form records use Ninja Forms' own tables (the plugin checks for `{prefix}nf3_forms`). Slider data is stored by Smart Slider. Neither definitions nor submissions are in git.

Provenance: **Implemented** as vendored plugins. Whether a public form or slider exists is **Unknown**.

### W5 — Clone a site in the network

Trigger: an administrator runs NS Cloner, which only works on multisite.

The vendored plugin is the free NS Cloner 4.4.7.5. Its header says it copies a site including theme, plugins, and settings. The Pro add-on (`ns-cloner-pro`) is not in the repository.

Provenance: **Implemented** vendor plugin. Whether the install is multisite is **Unknown / Requires Confirmation**.

### W6 — Deploy

Trigger: a push to `main`, or to `qa` if that branch is created.

See functional requirements FR-010 and FR-011. There is no manual deploy script in git. `sonarqube_scan.bat` was listed when the task was created and is not in the commit.

## Functional requirements

### FR-001 — WordPress public site

Requirement: The system serves a WordPress site from this tree.

Who: public visitor; WordPress for rendering.

Trigger: HTTP request to the host.

Inputs: request path; database content; active theme and plugins.

Expected behaviour: WordPress 6.7.1 bootstraps from the standard root files (`index.php`, `wp-blog-header.php`, `wp-load.php`, `wp-settings.php`). Pretty permalinks depend on `.htaccess` or `web.config`, which are gitignored, so the permalink mode is **Unknown**.

Outputs: HTML produced by the active theme.

Validation: none added by this project.

Error conditions: missing `wp-config.php` stops WordPress with its usual setup screen. That file is intentionally not in git.

Dependencies: PHP >= 7.2.24, MySQL >= 5.5.5, a `wp-config.php` on the server.

Implementation: Implemented (core). Runtime config: Unknown.

Confidence: High for the version in git. Low for the live host.

### FR-002 — WRU child theme presentation

Requirement: When `rookie-child` is the active theme, the site uses the WRU presentation in the child stylesheet and `css/custom.css`.

Who: public visitor.

Trigger: front-end page load with this theme active.

Inputs: parent Rookie templates; child `style.css`; `css/custom.css`; `themeboy` options.

Expected behaviour:

- Parent stylesheet is enqueued, then `css/custom.css`.
- Child `style.css` is the theme stylesheet WordPress loads for a child theme. It sets Oswald for headings and Lato for body, teal accent `#00a69c`, header and menu `#2b353e`, page background `#e8e8e8`, and SportsPress, WooCommerce, and BuddyPress colour rules.
- `custom.css` centres the featured image in the Latest Posts block and paints `.header-inner` with a white linear gradient (solid to 30%, then transparent).
- Child `inc/template-tags.php` replaces Rookie header markup. `.header-inner` gets inline `background-color` from `$options['primary']` (the `themeboy` option array). The value is not escaped. If `primary` is absent, PHP will warn and the colour will be empty.
- Search in the header menu is shown unless `themeboy` option `nav_menu_search` is set and false. Provenance of that rule: copied from the parent into the child file.

Outputs: styled HTML.

Validation: none.

Error conditions: missing `primary` option (see above). WooCommerce and BuddyPress rules have no effect unless those plugins are installed. They are not in this repo.

Dependencies: parent theme directory `rookie` (Template: rookie).

Implementation: Implemented.

Confidence: High for the CSS and enqueue. The active theme is Unknown.

Evidence: `wp-content/themes/rookie-child/functions.php`, `style.css`, `css/custom.css`, `inc/template-tags.php`.

### FR-003 — Extra stylesheet for blog ID 2

Requirement: On the site whose blog ID is 2, the child theme loads `css/subsite-2.css` after `custom.css`.

Who: visitor of that site.

Trigger: `wp_enqueue_scripts` when `get_current_blog_id() == 2`.

Inputs: current blog ID.

Expected behaviour: enqueue handle `subsite-2-style`, version `1.0`.

Outputs: an extra stylesheet link.

Validation: the code does not check that the file exists.

Error conditions: the file is absent, so the browser requests a 404. No other blog ID gets this stylesheet. Blog ID 1 (the usual main site) does not.

Dependencies: multisite, or a single site that still reports blog ID 1 (in which case this branch never runs).

Implementation: Implemented enqueue. Stylesheet file: not in the repository.

Confidence: High.

Evidence: `rookie-child/functions.php`. Glob of `subsite*` returned no files.

### FR-004 — Footer credit

Requirement: The theme footer credit is an Overdrive link, not the ThemeBoy default.

Who: public visitor.

Trigger: footer render while the child theme is active.

Inputs: none.

Expected behaviour: `rookie_footer_credit()` in the child prints a link to `http://overdrive.co.za/` with the visible text "Designed by Silcon Overdrive". The child function is defined before the parent file is loaded, and the parent definition is wrapped in `function_exists`, so the child wins.

Outputs: HTML anchor, passed through the `rookie_footer_credit` filter.

Validation: none. The scheme is `http`, not `https`. The brand string is spelled "Silcon".

Error conditions: none in code.

Implementation: Implemented.

Confidence: High.

Evidence: `rookie-child/functions.php` and `rookie/functions.php` (`rookie_footer_credit`).

### FR-005 — SportsPress content on the public site

Requirement: Sports pages can show events, teams, players, staff, tables, calendars, and the Pro modules that ship with SportsPress Pro 2.7.25, using Rookie's SportsPress templates.

Who: administrator enters data; visitor reads it.

Trigger: SportsPress shortcodes, widgets, or singular templates, when the plugin and theme are active.

Inputs: SportsPress posts in the database.

Expected behaviour: vendor plugin and `rookie/sportspress/` templates. Pro loads every module directory under `includes/` by scanning that folder. Modules present:

- sportspress (bundled core)
- sportspress-branding
- sportspress-duplicator
- sportspress-facebook
- sportspress-google-maps
- sportspress-highlight-places
- sportspress-league-menu
- sportspress-match-stats
- sportspress-midseason-transfers
- sportspress-multisite
- sportspress-past-meetings
- sportspress-results-matrix
- sportspress-scoreboard
- sportspress-sponsors
- sportspress-staff-directories
- sportspress-team-access
- sportspress-team-colors
- sportspress-timelines
- sportspress-tournaments
- sportspress-twitter
- sportspress-updater
- sportspress-user-scores

Which modules are switched on is a database option. **Unknown**.

Google Maps, Facebook, and Twitter modules can call external services. API keys, page tokens, and handles are **Unknown** (not in git).

Outputs: SportsPress HTML, styled by the child theme.

Error conditions: if a separate SportsPress plugin is active, Pro deactivates it on `admin_init`. Updater module can update Pro from the dashboard when ThemeBoy update credentials are configured. Those credentials are **Unknown**.

Implementation: Implemented vendor code. Data and module toggles: Unknown.

Confidence: High that the code is present. Low that every module is in use.

### FR-006 — Ninja Forms

Requirement: Administrators can build forms with Ninja Forms 3.8.23, and visitors can submit published forms.

Who: administrator; visitor.

Trigger: form shortcode or block, if the plugin is active.

Inputs: form definition in `nf3_*` tables; visitor POST.

Expected behaviour: vendor plugin. `en_ZA` translations for Ninja Forms are in `wp-content/languages/plugins/`. The plugin includes a SendWP integration file. Whether SendWP is connected is **Unknown**.

Outputs: stored submission; vendor email behaviour.

Validation and errors: vendor rules. No project-specific validation was added.

Implementation: Implemented as a vendored plugin. Active flag and form list: Unknown.

Confidence: High for presence. Low for live use.

### FR-007 — Smart Slider 3

Requirement: Administrators can build sliders with Smart Slider 3 3.5.1.25.

Who: administrator; visitor sees a published slider.

Trigger: slider shortcode or widget, if the plugin is active.

Inputs: slider stored by the plugin.

Expected behaviour: plugin loads only when PHP >= 7.0, WordPress >= 5.0, and `NONCE_SALT` is defined. Otherwise it shows an admin notice and does not load the slider library.

Outputs: slider markup. The child theme styles `.mega-slider` (margin 0, active row background `#00a69c`). That class is in the child CSS. Whether Smart Slider emits `mega-slider` is **Unknown** — it may be a class from an older slider. No `mega-slider` markup was found in the child PHP.

Implementation: Implemented vendor plugin plus CSS hook.

Confidence: High for the version. Low for whether a slider is published.

### FR-008 — Site copy on multisite

Requirement: On a multisite install, an administrator can clone a site with NS Cloner 4.4.7.5.

Who: administrator (vendor plugin requires the capability it checks; this repo does not wrap it).

Trigger: NS Cloner admin screen.

Inputs: source site in the network.

Expected behaviour: vendor free cloner. Pro add-on is not included.

Error conditions: on a single-site install the plugin cannot clone. Confirmed multisite status is Unknown.

Implementation: Implemented vendor plugin.

Confidence: Medium. Intent is inferred from the plugin plus FR-003.

### FR-009 — South African English

Requirement: The shipped WordPress core is the `en_ZA` package.

Who: any user seeing translated core strings.

Trigger: WordPress loads translations.

Inputs: `$wp_local_package = 'en_ZA'` and the `en_ZA` files under `wp-content/languages/`.

Expected behaviour: core admin strings, including network admin files (`admin-network-en_ZA`), Ninja Forms, Smart Slider 3, and Twenty Twenty-Five have `en_ZA` catalogs in git. The site locale option `WPLANG` can still override this in the database. That option is **Unknown**.

Implementation: Implemented package. Live locale: Unknown, likely `en_ZA` if defaults were kept.

Confidence: High for the package. Medium for the live setting.

### FR-010 — Deploy main by FTP

Requirement: A push to `main` uploads the git tree to the production FTP host and then runs SonarQube.

Who: the pipeline, using repository variables.

Trigger: Bitbucket Pipelines on branch `main`.

Inputs: variables `FTP_USERNAME`, `FTP_PASSWORD`, `FTP_HOST`, `SONAR_HOST_URL`, `SONAR_TOKEN`.

Expected behaviour, in order:

1. Image `atlassian/default-image:latest`. Clone depth is 1 (pipeline `clone.depth`).
2. Step "Build and Test production" installs `zip`, `git`, and `git-ftp` with `apt-get`. It does not run tests and does not run `composer` or `npm`. `zip` is unused by the script. A composer cache is declared and attached.
3. `git ftp init` to `ftp://$FTP_HOST/` with the production credentials.
4. `git ftp push` to the same URL with `--resume`.
5. Step "SonarQube analysis" runs `sonarsource/sonarqube-scan:2.0.1`. `sonar-project.properties` sets `sonar.projectKey` only. No exclusions are configured in that file, so the scan scope is the default for that pipe (the whole clone, including WordPress core).

Outputs: files on the FTP host; a SonarQube analysis.

Validation: none in the script. If `git ftp init` fails, the push and the Sonar step do not run (Bitbucket stops the pipeline when a step fails). Re-running `git ftp init` on every deploy is what the file does. git-ftp uses init to start tracking; running it again on an already initialised remote may fail. Whether production has already been initialised is **Unknown / Requires Confirmation**.

Error conditions: missing variables; FTP login failure; init rejected because the remote is already initialised.

Implementation: Implemented in `bitbucket-pipelines.yml`.

Confidence: High for the script. Low for whether deploys succeed.

### FR-011 — Deploy qa by FTP

Requirement: A push to branch `qa` uploads the tree to the QA FTP host with `--resume`, then runs the same SonarQube step.

Who: the pipeline.

Trigger: push to `qa`.

Inputs: `FTP_USERNAME_QA`, `FTP_PASSWORD_QA`, `FTP_HOST_QA`, plus the Sonar variables.

Expected behaviour: same install commands as production. `git ftp init` for QA is commented out (the commented URL ends in `/public_html`; the active push URL does not). Push uses `--resume`. The step is still named "Build and Test production".

Outputs: files on the QA host, if the branch exists and variables are set.

Error conditions: branch `qa` is not on the remote. A push cannot be deployed by this pipeline until that branch exists. If the remote was never initialised, `git ftp push` without a successful init fails. That is vendor git-ftp behaviour, **Inferred** here from the commented init line.

Implementation: Pipeline text is implemented. Branch is absent.

Confidence: High.

### FR-012 — Team-scoped admin access

Requirement: When SportsPress Team Access is enabled, selected roles can only work with SportsPress records for their assigned team.

Who: users with role `sp_player`, `sp_staff`, `sp_team_manager`, or `sp_event_manager`.

Trigger: admin queries and capability checks while the module is loaded.

Inputs: user meta `sp_team` (multiple values allowed; `get_user_meta( ..., false )`).

Expected behaviour: as in workflow W3. Administrators and other roles are not limited by `role_is_limited`.

Outputs: filtered admin lists and blocked edits outside the user's teams.

Implementation: Vendor module is in the tree. Enabled state: Unknown.

Confidence: High for the code. Low for live use.

## Integrations

| System | Purpose | Direction | Auth in repo | Failure |
|---|---|---|---|---|
| MySQL | WordPress and plugin data | App to database | Credentials only in `wp-config.php`, not in git | WordPress cannot boot |
| FTP host (`$FTP_HOST`) | Production deploy | Pipeline pushes files | Bitbucket variables | Pipeline step fails |
| FTP host (`$FTP_HOST_QA`) | QA deploy | Pipeline pushes files | Bitbucket variables | Pipeline step fails; branch missing |
| SonarQube | Static analysis | Pipeline sends the clone | `SONAR_TOKEN`, `SONAR_HOST_URL` | Scan step fails; deploy already happened |
| ThemeBoy update service | SportsPress Updater module | Plugin may phone home | Not in git | Vendor behaviour. Unknown |
| Google Maps | SportsPress Google Maps module | Browser or server map | API key not in git | Maps fail if the module is on and the key is missing |
| Facebook / Twitter | SportsPress modules of those names | Vendor integration | Not in git | Unknown |
| SendWP | File included by Ninja Forms | Email delivery if connected | Not in git | Unknown |
| overdrive.co.za | Footer credit link only | Browser navigation | none | Link target is outside this app |

No payment provider, SMS gateway, or CRM integration appears in project-authored code.

## Constraints

- Secrets and environment-specific config stay out of git: `wp-config.php`, `.htaccess`, `web.config`. Provenance: **Implemented** (`.gitignore`).
- Uploads and SQL dumps stay out of git. Provenance: **Implemented**.
- The child theme must have parent `rookie` installed beside it. Provenance: **Implemented** (style header `Template: rookie`).
- Smart Slider 3 will not run below PHP 7.0. WordPress itself requires PHP 7.2.24.
- There is no project test suite. The pipeline name says "Build and Test" and does not test.
- Plugin and theme versions are pinned by whatever is committed. There is no Composer or npm lockfile at the repository root. WordPress and plugin updates are a manual tree replacement, except the SportsPress updater if it is enabled and licensed.
- Rookie 1.5.4 and the WRU Custom scaffold declare older "Tested up to" versions than WordPress 6.7.1. That is a compatibility risk, not a demonstrated defect.

## Assumptions (not requirements)

- That `rookie-child` is the theme visitors see. The code supports that reading. The database decides it.
- That the site is multisite because of NS Cloner, blog ID 2, and the SportsPress multisite module. `admin-network-en_ZA` language files also ship inside a normal `en_ZA` WordPress package, so those files alone do not prove multisite.
- That WCU and WRU are the same organisation. The repo does not say so.
