Skip to content

Commit ac2b266

Browse files
mrabbaniclaude
andcommitted
Replace all AJAX with REST API and refactor controllers
- Remove DismissalHandler (AJAX dismissal) and merge dismiss endpoint into NoticeRESTController as POST /notices/dismiss - Extract migration REST endpoints from MigrationHooks into dedicated MigrationRESTController with GET /migration/status and POST /migration/upgrade - Rename NotificationHelper::ajax_action() to rest_action() with REST-oriented signature (endpoint, method, nonce) - Add WP REST class stubs to test bootstrap for Brain\Monkey compat - Update tests and developer guide to reflect all changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2060efe commit ac2b266

11 files changed

Lines changed: 512 additions & 289 deletions

docs/developer-guide.md

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ use WeDevs\WPKit\DataLayer\DataLayerFactory;
8080
use WeDevs\WPKit\Migration\MigrationRegistry;
8181
use WeDevs\WPKit\Migration\MigrationManager;
8282
use WeDevs\WPKit\Migration\MigrationHooks;
83+
use WeDevs\WPKit\Migration\MigrationRESTController;
8384
use WeDevs\WPKit\AdminNotification\NoticeManager;
84-
use WeDevs\WPKit\AdminNotification\DismissalHandler;
8585
use WeDevs\WPKit\AdminNotification\NoticeRESTController;
8686

8787
// 1. DataLayer
@@ -100,9 +100,10 @@ $manager = new MigrationManager( $registry, 'myplugin' );
100100

101101
// 3. Admin Notifications
102102
$notices = new NoticeManager( 'myplugin' );
103-
( new DismissalHandler( 'myplugin' ) )->register();
104103

105-
add_action( 'rest_api_init', function () use ( $notices ) {
104+
// 4. REST API (migration + notifications)
105+
add_action( 'rest_api_init', function () use ( $manager, $notices ) {
106+
( new MigrationRESTController( $manager, 'myplugin/v1' ) )->register_routes();
106107
( new NoticeRESTController( $notices, 'myplugin/v1' ) )->register_routes();
107108
} );
108109
```
@@ -983,9 +984,13 @@ $registry->get_pending_migrations(); // versions > installed, sorted by versio
983984
$manager = new MigrationManager( $registry, 'myplugin' );
984985
$manager->do_upgrade(); // Runs all pending, fires {prefix}_upgrade_finished
985986

986-
// MigrationHooks: adds WordPress AJAX handler and filter hooks
987+
// MigrationHooks: adds WordPress filter hooks
987988
$hooks = new MigrationHooks( $manager, 'myplugin' );
988989
$hooks->register();
990+
991+
// MigrationRESTController: REST API for status and upgrade
992+
$rest = new MigrationRESTController( $manager, 'myplugin/v1' );
993+
add_action( 'rest_api_init', [ $rest, 'register_routes' ] );
989994
```
990995

991996
#### WordPress Hooks Registered by MigrationHooks
@@ -994,11 +999,15 @@ $hooks->register();
994999
|------|------|-------------|
9951000
| `{prefix}_upgrade_is_upgrade_required` | filter | Returns whether upgrade is needed |
9961001
| `{prefix}_upgrade_upgrades` | filter | Returns pending upgrade list |
997-
| `wp_ajax_{prefix}_do_upgrade` | action | AJAX endpoint for admin-triggered upgrades |
9981002
| `{prefix}_upgrade_finished` | action | Fires after all migrations complete |
9991003
| `{prefix}_upgrade_is_not_required` | action | Fires when no upgrade is needed |
10001004

1001-
The AJAX handler verifies the nonce `{prefix}_admin` and requires the `update_plugins` capability.
1005+
#### Migration REST Endpoints
1006+
1007+
| Endpoint | Method | Description |
1008+
|----------|--------|-------------|
1009+
| `{prefix}/v1/migration/status` | GET | Returns migration status, log, and summary |
1010+
| `{prefix}/v1/migration/upgrade` | POST | Triggers admin upgrade (requires `update_plugins` capability) |
10021011

10031012
#### Migration Status
10041013

@@ -1045,7 +1054,7 @@ if ( $entry && $entry['status'] === 'failed' ) {
10451054

10461055
#### REST API for Migration Status
10471056

1048-
`MigrationHooks` registers a REST endpoint for polling migration status from the admin UI:
1057+
`MigrationRESTController` provides REST endpoints for polling migration status and triggering upgrades from the admin UI:
10491058

10501059
```
10511060
GET /wp-json/{prefix}/v1/migration/status
@@ -1222,7 +1231,7 @@ if ( $progress['is_processing'] ) {
12221231

12231232
## Admin Notifications
12241233

1225-
A system for collecting, filtering, and serving admin notices via REST API with AJAX dismissal support.
1234+
A system for collecting, filtering, and serving admin notices via REST API.
12261235

12271236
### Notice Providers
12281237

@@ -1317,7 +1326,7 @@ $notice = NotificationHelper::warning( 'Update Available', 'Version 2.0 is out.'
13171326
'is_dismissible' => true,
13181327
'actions' => [
13191328
NotificationHelper::link_action( 'Update Now', admin_url( 'update-core.php' ) ),
1320-
NotificationHelper::ajax_action( 'Remind Later', 'myplugin_snooze', 'myplugin_admin' ),
1329+
NotificationHelper::rest_action( 'Remind Later', '/wp-json/myplugin/v1/snooze' ),
13211330
],
13221331
] );
13231332
```
@@ -1330,26 +1339,9 @@ $notice = NotificationHelper::warning( 'Update Available', 'Version 2.0 is out.'
13301339
| info | 10 | local |
13311340
| success | 10 | local |
13321341

1333-
### DismissalHandler
1334-
1335-
Handles AJAX notice dismissal and persists dismissed notice keys:
1336-
1337-
```php
1338-
$handler = new DismissalHandler( 'myplugin' );
1339-
$handler->register(); // Registers wp_ajax_{prefix}_dismiss_notice
1340-
```
1341-
1342-
Dismissed notices are stored in the `{prefix}_dismissed_notices` option as an array of keys.
1343-
1344-
**AJAX endpoint details:**
1345-
- Action: `wp_ajax_myplugin_dismiss_notice`
1346-
- Nonce action: `myplugin_admin`
1347-
- Required capability: `manage_options`
1348-
- POST parameter: `key` (the notice key to dismiss)
1349-
13501342
### REST Controller
13511343

1352-
Exposes notices via the WordPress REST API:
1344+
Exposes notices via the WordPress REST API and handles notice dismissal:
13531345

13541346
```php
13551347
$controller = new NoticeRESTController( $manager, 'myplugin/v1' );
@@ -1359,13 +1351,16 @@ add_action( 'rest_api_init', function () use ( $controller ) {
13591351
} );
13601352
```
13611353

1362-
**Endpoint:** `GET /wp-json/myplugin/v1/notices/admin`
1354+
**Endpoints:**
13631355

1364-
| Parameter | Type | Required | Description |
1365-
|-----------|------|----------|-------------|
1366-
| `scope` | string | No | `'local'` or `'global'`. Omit for all. |
1356+
| Endpoint | Method | Description |
1357+
|----------|--------|-------------|
1358+
| `/wp-json/myplugin/v1/notices/admin` | GET | Get all notices (optional `scope` param: `local` or `global`) |
1359+
| `/wp-json/myplugin/v1/notices/dismiss` | POST | Dismiss a notice (required `key` param) |
1360+
1361+
Dismissed notices are stored in the `{prefix}_dismissed_notices` option as an array of keys.
13671362

1368-
**Permission:** Requires `manage_options` capability.
1363+
**Permission:** All endpoints require `manage_options` capability.
13691364

13701365
---
13711366

@@ -1697,7 +1692,6 @@ The `{prefix}` is the plugin-level prefix set via `set_filter_prefix()` (e.g., `
16971692
|------|------|------------|-------------|
16981693
| `{prefix}_upgrade_is_upgrade_required` | filter | `$required` | Check if upgrade is needed |
16991694
| `{prefix}_upgrade_upgrades` | filter | `$upgrades` | Get pending upgrades list |
1700-
| `wp_ajax_{prefix}_do_upgrade` | action || AJAX endpoint for admin-triggered upgrades |
17011695
| `{prefix}_upgrade_finished` | action || All migrations completed |
17021696
| `{prefix}_upgrade_is_not_required` | action || No upgrade needed |
17031697

@@ -1706,13 +1700,20 @@ The `{prefix}` is the plugin-level prefix set via `set_filter_prefix()` (e.g., `
17061700
| Route | Method | Permission | Description |
17071701
|-------|--------|------------|-------------|
17081702
| `{prefix}/v1/migration/status` | GET | `update_plugins` | Returns migration log, summary, and status |
1703+
| `{prefix}/v1/migration/upgrade` | POST | `update_plugins` | Triggers admin upgrade |
17091704

17101705
### Notification Hooks
17111706

17121707
| Hook | Type | Parameters | Description |
17131708
|------|------|------------|-------------|
17141709
| `{prefix}_admin_notices` | filter | `$notices` | Collect/modify admin notices |
1715-
| `wp_ajax_{prefix}_dismiss_notice` | action || AJAX dismissal endpoint |
1710+
1711+
### Notification REST Endpoints
1712+
1713+
| Route | Method | Permission | Description |
1714+
|-------|--------|------------|-------------|
1715+
| `{namespace}/notices/admin` | GET | `manage_options` | Get admin notices (optional `scope` param) |
1716+
| `{namespace}/notices/dismiss` | POST | `manage_options` | Dismiss a notice (required `key` param) |
17161717

17171718
### WordPress Options Used
17181719

@@ -1723,4 +1724,4 @@ The `{prefix}` is the plugin-level prefix set via `set_filter_prefix()` (e.g., `
17231724
| `{prefix}_migration_log` | MigrationManager | Per-migration execution log (version, status, timestamps, errors) |
17241725
| `{prefix}_bg_{action}` | BackgroundProcess | Queue storage (array of items) |
17251726
| `{prefix}_bg_{action}_total` | BackgroundProcess | Total items pushed (for progress percentage) |
1726-
| `{prefix}_dismissed_notices` | DismissalHandler | Array of dismissed notice keys |
1727+
| `{prefix}_dismissed_notices` | NoticeRESTController | Array of dismissed notice keys |

src/AdminNotification/DismissalHandler.php

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/AdminNotification/NoticeRESTController.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,25 @@ public function register_routes(): void {
6464
],
6565
]
6666
);
67+
68+
register_rest_route(
69+
$this->namespace,
70+
'/' . $this->base . '/dismiss',
71+
[
72+
[
73+
'methods' => WP_REST_Server::CREATABLE,
74+
'callback' => [ $this, 'dismiss_notice' ],
75+
'permission_callback' => [ $this, 'check_permission' ],
76+
'args' => [
77+
'key' => [
78+
'required' => true,
79+
'type' => 'string',
80+
'sanitize_callback' => 'sanitize_text_field',
81+
],
82+
],
83+
],
84+
]
85+
);
6786
}
6887

6988
/**
@@ -80,6 +99,27 @@ public function get_admin_notices( WP_REST_Request $request ): WP_REST_Response
8099
return rest_ensure_response( $notices );
81100
}
82101

102+
/**
103+
* Dismiss a notice by key.
104+
*
105+
* @param WP_REST_Request $request REST request.
106+
*
107+
* @return WP_REST_Response
108+
*/
109+
public function dismiss_notice( WP_REST_Request $request ): WP_REST_Response {
110+
$key = $request->get_param( 'key' );
111+
$prefix = $this->manager->get_prefix();
112+
113+
$dismissed = get_option( $prefix . '_dismissed_notices', [] );
114+
115+
if ( ! in_array( $key, $dismissed, true ) ) {
116+
$dismissed[] = $key;
117+
update_option( $prefix . '_dismissed_notices', $dismissed );
118+
}
119+
120+
return new WP_REST_Response( [ 'success' => true ], 200 );
121+
}
122+
83123
/**
84124
* Check if the current user has permission.
85125
*

src/AdminNotification/NotificationHelper.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,24 @@ public static function success( string $title, string $description, array $extra
9696
}
9797

9898
/**
99-
* Build an AJAX action button config.
99+
* Build a REST API action button config.
100100
*
101-
* @param string $text Button text.
102-
* @param string $ajax_action The AJAX action name.
103-
* @param string $nonce_action The nonce action.
104-
* @param array $extra Additional action properties.
101+
* @param string $text Button text.
102+
* @param string $endpoint REST API endpoint URL.
103+
* @param string $method HTTP method (default: POST).
104+
* @param array $extra Additional action properties.
105105
*
106106
* @return array
107107
*/
108-
public static function ajax_action( string $text, string $ajax_action, string $nonce_action, array $extra = [] ): array {
108+
public static function rest_action( string $text, string $endpoint, string $method = 'POST', array $extra = [] ): array {
109109
return array_merge(
110110
[
111111
'type' => 'primary',
112112
'text' => $text,
113-
'ajax_data' => [
114-
'action' => $ajax_action,
115-
'_wpnonce' => wp_create_nonce( $nonce_action ),
113+
'rest_data' => [
114+
'endpoint' => $endpoint,
115+
'method' => $method,
116+
'nonce' => wp_create_nonce( 'wp_rest' ),
116117
],
117118
],
118119
$extra

0 commit comments

Comments
 (0)