|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +This file provides guidance to Codex (Codex.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Plugin Overview |
| 6 | + |
| 7 | +FreemKit is a WordPress plugin (v1.0.0-beta1) that bridges Freemius software licensing with Kit (formerly ConvertKit) email marketing. It receives Freemius webhook events and subscribes customers to Kit forms/tags based on whether they are free or paid users. Namespace: `WebberZone\FreemKit`. Prefix: `freemkit_`. Requires WordPress 5.0+, PHP 7.4+. |
| 8 | + |
| 9 | +## Commands |
| 10 | + |
| 11 | +### PHP |
| 12 | +```bash |
| 13 | +composer phpcs # Lint PHP (WordPress coding standards) |
| 14 | +composer phpcbf # Auto-fix PHP code style |
| 15 | +composer phpstan # Static analysis (level configured in phpstan.neon.dist) |
| 16 | +composer phpcompat # Check PHP 7.4–8.5 compatibility |
| 17 | +composer test # Run all checks (phpcs + phpcompat + phpstan) |
| 18 | +composer build:vendor # Install production-only dependencies |
| 19 | +composer zip # Create distribution zip |
| 20 | +``` |
| 21 | + |
| 22 | +### JavaScript/CSS |
| 23 | +```bash |
| 24 | +npm run build # Build JS/CSS assets with wp-scripts |
| 25 | +npm run build:assets # Minify CSS/JS and generate RTL CSS (node build-assets.js) |
| 26 | +npm start # Watch mode |
| 27 | +npm run lint:js # ESLint |
| 28 | +npm run lint:css # Stylelint |
| 29 | +npm run format # Format with wp-scripts |
| 30 | +npm run zip # Create plugin zip via wp-scripts |
| 31 | +``` |
| 32 | + |
| 33 | +## Architecture |
| 34 | + |
| 35 | +### Entry Point & Bootstrap |
| 36 | +`freemkit.php` defines constants (`FREEMKIT_VERSION`, `FREEMKIT_PLUGIN_FILE`, `FREEMKIT_PLUGIN_DIR`, `FREEMKIT_PLUGIN_URL`), loads Kit shared library classes from `vendor/convertkit/convertkit-wordpress-libraries/`, registers the autoloader, and calls `\WebberZone\FreemKit\load()` on `plugins_loaded`. |
| 37 | + |
| 38 | +**Autoloader convention:** Namespace segments become path segments under `includes/`; underscores → hyphens, lowercase, last segment prefixed with `class-`. e.g. `WebberZone\FreemKit\Admin\Settings` → `includes/admin/class-settings.php`. |
| 39 | + |
| 40 | +### Core Components |
| 41 | +- **`Main`** (`includes/class-main.php`) — Singleton. Instantiates `Runtime`, `Kit_Credential_Hooks`, and `Language_Handler`; registers hooks via `Hook_Registry`. |
| 42 | +- **`Runtime`** (`includes/class-runtime.php`) — Initializes `Database`, `Kit_API`, and `Webhook_Handler` on `init`. Creates the `Admin` object when in admin context. |
| 43 | +- **`Webhook_Handler`** (`includes/class-webhook-handler.php`) — Core logic. Registers a REST endpoint at `freemkit/v1/webhook` (or a query-var fallback). Validates HMAC-SHA256 signatures (`x-signature` header) and webhook freshness (15-minute window). Queues events as WP transients and processes them asynchronously via WP-Cron (`freemkit_process_webhook_event`). Includes deduplication via `freemkit_webhook_seen_*` transients, and exponential-backoff retry (max 3 attempts). |
| 44 | +- **`Database`** (`includes/class-database.php`) — Manages the `{prefix}freemkit_subscribers` custom table, which persists subscriber records locally. |
| 45 | +- **`Options_API`** (`includes/class-options-api.php`) — All settings stored as a single `freemkit_settings` array in `wp_options`. Access via `Options_API::get_option($key)` / `Options_API::get_settings()`. Sensitive keys (access/refresh tokens) are encrypted at rest using AES-256-CBC (OpenSSL) or libsodium. |
| 46 | + |
| 47 | +### Kit Integration (`includes/kit/`) |
| 48 | +- **`Kit_API`** — Wraps `ConvertKit_API_V4` to subscribe users to forms and apply tags. |
| 49 | +- **`Kit_Settings`** — Manages OAuth tokens (`kit_access_token`, `kit_refresh_token`). Falls back to the official Kit WordPress plugin's stored credentials if available. |
| 50 | +- **`Kit_Credential_Hooks`** — Listens for `freemkit_api_get_access_token` / `freemkit_api_refresh_token` / `convertkit_api_access_token_invalid` actions to keep tokens in sync. |
| 51 | +- **`Kit_Audit_Log`** — Logs Kit API interactions. |
| 52 | + |
| 53 | +### Admin (`includes/admin/`) |
| 54 | +- **`Admin`** — Admin menu, settings page, subscriber list screen. |
| 55 | +- **`Settings`** / **`Settings_Wizard`** — Settings pages; wizard is shown on first activation. Settings include: global Kit form/tag defaults, per-plugin overrides (free and paid form IDs, tag IDs, event types), custom field mappings, webhook endpoint type (REST vs query-var). |
| 56 | +- **`Kit_OAuth`** — OAuth flow for connecting to Kit. |
| 57 | +- **`Subscribers_List`** / **`Subscribers_List_Table`** — Admin screen displaying the local `freemkit_subscribers` table. |
| 58 | + |
| 59 | +### Utilities (`includes/util/`) |
| 60 | +- **`Hook_Registry`** — Static registry for all registered actions/filters; prevents duplicates (same pattern as CRP). |
| 61 | + |
| 62 | +## Key Patterns |
| 63 | + |
| 64 | +- **Webhook event routing:** Freemius sends a `type` field (e.g. `install.installed`, `license.created`). Default free events: `['install.installed']`; default paid events: `['license.created']`. Both can be overridden per-plugin config or via `freemkit_default_free_event_types` / `freemkit_default_paid_event_types` filters. |
| 65 | +- **Per-plugin config:** The settings store a `plugins` array, each entry keyed by Freemius plugin ID, with separate free/paid form IDs, tag IDs, and event types. Falls back to global kit form/tag settings when per-plugin values are empty. |
| 66 | +- **Settings access:** Always use `Options_API::get_option($key)` rather than reading `freemkit_settings` directly. |
| 67 | +- **Hook registration:** Use `Hook_Registry::add_action()` / `Hook_Registry::add_filter()` rather than WordPress functions directly. |
| 68 | +- **Async processing:** Webhooks are never processed synchronously (except when WP-Cron is disabled or scheduling fails). Always go through `queue_webhook_event()` → `process_queued_webhook()`. |
0 commit comments