# Spin Wheel plugin — codebase map

Version **2.3.0** (see `SPIN_WHEEL_VERSION` in [`spin-wheel.php`](spin-wheel.php)). This document summarizes how the plugin is structured for developers.

## Bootstrap and constants

Entry point: [`spin-wheel.php`](spin-wheel.php). Defines path/URL constants and a singleton `Spin_Wheel` that:

- Loads the text domain on `plugins_loaded` → `init()`.
- `load_files()` requires everything under `includes/`, conditionally loads `admin/*.php`, and always loads `public/class-swp-public.php`.
- `init_components()` instantiates core singletons. `SWP_Public` is only constructed when `!is_admin()`.

**Activation**

- Creates DB tables via `SWP_Database::create_tables()`.
- Registers the custom post type via `SWP_Post_Type::register()`.
- Schedules daily `swp_daily_cleanup` if not already scheduled.
- Flushes rewrite rules.

**Cron**

- `run_auto_cleanup()` reads the global option `swp_settings` (`auto_cleanup`, `data_retention_days`) and prunes old entries through `SWP_Database::cleanup_old_entries()`.

**Helpers** (same file)

- `swp_get_wheel_data_with_defaults()` merges per-wheel post meta with global `swp_settings` defaults (for `_swp_settings` and `_swp_styles`).
- `swp_normalize_form_settings()` merges defaults for `_swp_form_settings` (field visibility, required flags, terms checkbox, etc.).
- `swp_get_anonymous_guest_email()` builds a deterministic placeholder address when email is hidden or optional and empty (used for DB + duplicate checks).
- `spin_wheel_is_pro_activated()` wraps `apply_filters('swp_is_pro_active', false)`.

## Data model

**Custom post type:** `swp_wheel` — see [`includes/class-swp-post-type.php`](includes/class-swp-post-type.php). Admin UI only (`public` false), `show_in_rest` true for the block/editor.

**Post meta** (used heavily by the shortcode): `_swp_wheel_type`, `_swp_prizes`, `_swp_settings`, `_swp_styles`, `_swp_form_settings`, and related keys as saved in the metabox UI.

**Tables** — see [`includes/class-swp-database.php`](includes/class-swp-database.php):

- `{prefix}swp_entries` — spin / lead records.
- `{prefix}swp_winners` — event-style winner selection linkage.

## Frontend request flow

```mermaid
flowchart LR
  shortcode[SWP_Shortcode spin_wheel]
  block[SWP_Blocks spin-wheel/wheel]
  tpl[public/templates/wheel-container.php]
  js[public/js/public.js]
  ajax[SWP_Ajax handle_spin etc]
  db[SWP_Database]
  shortcode --> tpl
  block --> shortcode
  tpl --> js
  js --> ajax
  ajax --> db
```

- **[`includes/class-swp-shortcode.php`](includes/class-swp-shortcode.php)** registers `[spin_wheel id="…"]`, validates the wheel post, resolves display rules, includes [`public/templates/wheel-container.php`](public/templates/wheel-container.php), and hooks `wp_head` / `wp_footer` for optional global custom CSS/JS from `swp_settings`.
- **Assets:** `enqueue_assets()` registers `public/css/public.css`, `public/js/lucky-canvas.umd.min.js`, and `public/js/public.js` (with jQuery + lucky-canvas). It localizes `swpData` on `swp-public-script` (AJAX URL, `swp_spin_nonce`, strings, Pro sound flag, etc.).
- **[`includes/class-swp-blocks.php`](includes/class-swp-blocks.php)** registers block `spin-wheel/wheel` with attribute `wheelId` and `render_callback` that outputs `do_shortcode("[spin_wheel id=\"{$wheel_id}\"]")`.
- **[`public/class-swp-public.php`](public/class-swp-public.php)** currently only defines an empty constructor; most frontend behavior lives in the shortcode layer.

## AJAX surface

[`includes/class-swp-ajax.php`](includes/class-swp-ajax.php) registers:

- **Public:** `swp_spin_wheel` (`wp_ajax` + `wp_ajax_nopriv`), OTP `swp_send_otp` / `swp_verify_otp` (see [`includes/class-swp-otp.php`](includes/class-swp-otp.php)).
- **Admin:** `swp_select_winner`, `swp_revert_winner`, `swp_bulk_select_winners`, `swp_export_entries`, `swp_get_pages`.

The spin handler verifies nonce `swp_spin_nonce`, loads normalized `_swp_form_settings` (show/require per field, optional terms acceptance via `swp_accept_terms`), applies guest name / placeholder email when fields are hidden or optional, may invoke [`includes/class-swp-vpn-detector.php`](includes/class-swp-vpn-detector.php), persists via `SWP_Database`, and returns JSON.

`send_otp` is implemented as `SWP_Ajax::send_otp()` (same nonce as spin). `verify_otp` uses `swp_spin_nonce` as well.

## Admin surface

- **[`admin/class-swp-admin.php`](admin/class-swp-admin.php)** — submenu pages, list columns for `swp_wheel`, script/style enqueue, AJAX `swp_save_settings` and `swp_run_cleanup_now`.
- **[`admin/class-swp-metaboxes.php`](admin/class-swp-metaboxes.php)** plus templates under [`admin/templates/`](admin/templates/) (tabs: general, form, prizes, design, display; wheel editor, entries, settings screens).
- **[`admin/class-swp-admin-entries.php`](admin/class-swp-admin-entries.php)** — entries management.

**Loaded on require (admin only):**

- [`admin/class-swp-admin-feeds.php`](admin/class-swp-admin-feeds.php) — instantiates `SpinWheel\Admin_Feeds` at file end (dashboard RSS / product feed UI).

## Supporting includes

- [`includes/class-swp-email.php`](includes/class-swp-email.php) — winner / notification emails.
- [`includes/class-swp-pro-helper.php`](includes/class-swp-pro-helper.php) — Pro feature gates.
- [`uninstall.php`](uninstall.php) — if `swp_settings['delete_data_on_uninstall']` is enabled, removes `swp_wheel` posts, drops custom tables, and deletes plugin options.

## Repo extras

- i18n template: [`languages/spin-wheel.pot`](languages/spin-wheel.pot).
- GitHub Actions: [`.github/workflows/`](.github/workflows/).
- [`package.json`](package.json) — tooling for admin/front assets where applicable.
