# WordPress Plugin (wp-plugin/rankwiz-ai/)

Separate PHP 8.1+ codebase. Do NOT mix with the main Laravel application.

## Structure

```
wp-plugin/rankwiz-ai/
├── rankwiz-ai.php              # Main plugin file (bootstrap, hooks, activation/deactivation)
├── uninstall.php               # Cleanup on plugin uninstall (removes wp_options)
├── includes/
│   ├── class-plugin.php        # Core plugin orchestrator
│   ├── class-rest-api.php      # REST API route registration
│   ├── class-hmac-auth.php     # HMAC-SHA256 request authentication
│   ├── class-handshake-endpoint.php  # POST /rankwiz/v1/handshake
│   ├── class-status-endpoint.php     # GET /rankwiz/v1/status
│   ├── class-inventory-endpoint.php  # GET /rankwiz/v1/inventory
│   ├── class-content-endpoint.php    # GET /rankwiz/v1/content/{id}
│   ├── class-publish-endpoint.php    # POST /rankwiz/v1/publish (receive + publish AI draft)
│   ├── class-disconnect-endpoint.php # DELETE /rankwiz/v1/disconnect
│   ├── class-post-hooks.php          # Hooks into WP post lifecycle (publish, update, delete)
│   ├── class-webhook-sender.php      # Sends webhooks back to Laravel on post events
│   ├── class-yoast-integration.php   # Extracts Yoast SEO metadata
│   └── class-admin-page.php         # WP admin settings page
├── tests/                      # PHPUnit tests
├── assets/                     # CSS/JS for admin UI
├── vendor/                     # Composer dependencies
├── composer.json
└── phpunit.xml
```

## Authentication

All REST endpoints (except handshake initiation) require HMAC-SHA256 signature verification:

1. Laravel sends request with `X-RankWiz-Signature` header
2. Plugin computes HMAC of request body using stored shared secret
3. Timing-safe comparison validates signature
4. Shared secret stored encrypted in `wp_options`

## REST API Endpoints

| Method | Endpoint | Purpose |
|--------|---------|---------|
| POST | `/rankwiz/v1/handshake` | Establish connection, exchange HMAC secret |
| GET | `/rankwiz/v1/status` | Connection health check |
| GET | `/rankwiz/v1/inventory` | Paginated list of published posts |
| GET | `/rankwiz/v1/content/{id}` | Single post content + Yoast metadata |
| POST | `/rankwiz/v1/publish` | Receive and publish AI-generated draft content |
| DELETE | `/rankwiz/v1/disconnect` | Revoke connection, delete stored secret |

## Bidirectional Publishing

Laravel → WordPress: `PublishDraftToWpJob` sends AI drafts via `POST /rankwiz/v1/publish`. The plugin creates/updates the WordPress post and stores the HMAC-verified content.

WordPress → Laravel: `class-post-hooks.php` hooks into WordPress post lifecycle events. When a post is published/updated, `class-webhook-sender.php` sends an HMAC-signed webhook back to Laravel's `WpWebhookController`, which dispatches `ProcessWpWebhookJob` to update sync state.

## Yoast Integration

When Yoast SEO is installed, `content` endpoint enriches response with:
- `yoast_title` — SEO title
- `yoast_description` — Meta description
- `yoast_focus_keyword` — Focus keyphrase
- `yoast_robots` — Robots meta directives (noindex, etc.)

## Development Setup

```bash
cd wp-plugin/rankwiz-ai
composer install
```

Requires a local WordPress installation. Symlink or copy `rankwiz-ai/` into `wp-content/plugins/`, then activate via WP Admin > Plugins.

## Commands

```bash
cd wp-plugin/rankwiz-ai
./vendor/bin/phpunit                          # Run all tests
./vendor/bin/phpunit --testsuite unit         # Unit suite only
./vendor/bin/phpunit tests/RestApiTest.php    # Single test file
```

## Testing

- PHPUnit 10 with `yoast/phpunit-polyfills` for WordPress test compatibility
- Tests bootstrap via `tests/bootstrap.php` and mock WP functions — no real WP installation required
- Coverage areas: HMAC auth, handshake, inventory, content, publish, disconnect, post hooks, webhook sender, secret encryption, admin page

## Coding Conventions

- **WordPress coding standards** (NOT Laravel PSR-12) — snake_case methods, underscore-prefixed private vars
- **Classmap autoloading** — Composer `classmap` on `includes/`, NOT PSR-4 namespaces. **After adding a new class file:** Always run `composer dump-autoload` to update the classmap, or new classes won't be found.
- **No namespaces** — all classes use `RankWiz_` prefix (e.g., `RankWiz_HMAC_Auth`, `RankWiz_Plugin`)
- **WordPress hooks** — register actions/filters in `class-plugin.php`, not in individual classes
- **Options API** — settings stored in `wp_options` under `rankwiz_options` key
- **Transients** — temporary data (last request timestamp) stored as WP transients

## Constants

| Constant | Value | Defined In |
|----------|-------|-----------|
| `RANKWIZ_REST_NAMESPACE` | `rankwiz/v1` | `rankwiz-ai.php` |
| `RANKWIZ_OPTIONS_KEY` | `rankwiz_options` | `rankwiz-ai.php` |

## Key Notes

- PHP 8.1+ required (separate from Laravel's 8.4+ requirement)
- Activation preserves settings on reactivation (`add_option` not `update_option`)
- Deactivation only clears `rankwiz_last_request_at` transient — does NOT delete options
- The Laravel app communicates with this plugin via `WpApiClient` service
- `WpApiClient.getContent()` returns an array, NOT a string — extract `$response['content']`

## Gotchas

1. **Return type of content endpoint**: `getContent()` returns `array` with keys `content`, `title`, `yoast_*` — NOT a raw string. Always extract: `$response['content']`.
2. **HMAC secret storage**: Secret is encrypted in `wp_options` — direct DB reads return ciphertext. Use the plugin's accessor methods.
3. **Webhook timing**: Post-publish webhooks fire asynchronously via `wp_remote_post`. Laravel must handle out-of-order delivery.
4. **Classmap autoload**: After adding a new class file to `includes/`, run `composer dump-autoload` to update the classmap.
5. **No PSR-4**: Do NOT add namespace declarations. WordPress ecosystem expects global classes with prefix convention.
6. **Nonce replay protection**: HMAC auth includes nonce replay protection via WordPress transients. `MAX_NONCE_LENGTH=64`. Future timestamp tolerance is 15 seconds.
7. **Publish content size limit**: `MAX_CONTENT_SIZE=5MB` enforced in publish endpoint.
8. **Test webhook mode**: Set `RANKWIZ_WEBHOOK_MODE=direct` in test bootstrap for synchronous webhook firing (no cron).
9. **Test suite command**: Use `vendor/bin/phpunit --testsuite unit` for the unit test suite specifically.
