@@ -45,6 +45,9 @@ public function init(): void {
4545
4646 // Register admin-post.php handlers
4747 add_action ( 'admin_post_graphql_webhook_save ' , array ( $ this , 'handle_webhook_save ' ) );
48+
49+ // Register AJAX handlers
50+ add_action ( 'wp_ajax_test_webhook ' , array ( $ this , 'handle_test_webhook ' ) );
4851 }
4952
5053 /**
@@ -79,7 +82,8 @@ public function get_admin_url( array $args = array() ): string {
7982 * @param string $hook_suffix Current admin page.
8083 */
8184 public function enqueue_assets ( string $ hook_suffix ): void {
82- if ( 'graphql_page_ ' . self ::ADMIN_PAGE_SLUG !== $ hook_suffix ) {
85+ // Only load on our admin page - check if we're on the webhooks page
86+ if ( ! isset ( $ _GET ['page ' ] ) || $ _GET ['page ' ] !== self ::ADMIN_PAGE_SLUG ) {
8387 return ;
8488 }
8589
@@ -106,6 +110,7 @@ public function enqueue_assets( string $hook_suffix ): void {
106110 array (
107111 'restUrl ' => rest_url ( 'graphql-webhooks/v1/ ' ),
108112 'nonce ' => wp_create_nonce ( 'wp_rest ' ),
113+ 'ajaxUrl ' => admin_url ( 'admin-ajax.php ' ),
109114 'headerTemplate ' => $ this ->get_header_row_template (),
110115 'confirmDelete ' => __ ( 'Are you sure you want to delete this webhook? ' , 'wp-graphql-headless-webhooks ' ),
111116 )
@@ -225,6 +230,155 @@ public function handle_webhook_delete(): void {
225230 exit ;
226231 }
227232
233+ /**
234+ * Handle webhook test via AJAX
235+ */
236+ public function handle_test_webhook (): void {
237+ // Verify nonce
238+ if ( ! isset ( $ _POST ['nonce ' ] ) || ! wp_verify_nonce ( $ _POST ['nonce ' ], 'wp_rest ' ) ) {
239+ wp_send_json_error ( __ ( 'Security check failed. ' , 'wp-graphql-headless-webhooks ' ) );
240+ }
241+
242+ // Verify permissions
243+ if ( ! current_user_can ( 'manage_options ' ) ) {
244+ wp_send_json_error ( __ ( 'Insufficient permissions. ' , 'wp-graphql-headless-webhooks ' ) );
245+ }
246+
247+ $ webhook_id = isset ( $ _POST ['webhook_id ' ] ) ? intval ( $ _POST ['webhook_id ' ] ) : 0 ;
248+ if ( ! $ webhook_id ) {
249+ wp_send_json_error ( __ ( 'Invalid webhook ID. ' , 'wp-graphql-headless-webhooks ' ) );
250+ }
251+
252+ // Get the webhook
253+ $ webhook = $ this ->repository ->get ( $ webhook_id );
254+ if ( ! $ webhook ) {
255+ wp_send_json_error ( __ ( 'Webhook not found. ' , 'wp-graphql-headless-webhooks ' ) );
256+ }
257+
258+ // Create test payload based on the event type
259+ $ test_payload = $ this ->get_test_payload_for_event ( $ webhook ->event );
260+
261+ // Send the webhook using a synchronous request for testing
262+ $ args = [
263+ 'headers ' => $ webhook ->headers ?: [ 'Content-Type ' => 'application/json ' ],
264+ 'timeout ' => 10 ,
265+ 'blocking ' => true , // We need blocking for test to get response
266+ ];
267+
268+ $ payload = apply_filters ( 'graphql_webhooks_payload ' , $ test_payload , $ webhook );
269+
270+ if ( strtoupper ( $ webhook ->method ) === 'GET ' ) {
271+ $ url = add_query_arg ( $ payload , $ webhook ->url );
272+ $ args ['method ' ] = 'GET ' ;
273+ } else {
274+ $ url = $ webhook ->url ;
275+ $ args ['method ' ] = strtoupper ( $ webhook ->method );
276+ $ args ['body ' ] = wp_json_encode ( $ payload );
277+ if ( empty ( $ args ['headers ' ]['Content-Type ' ] ) ) {
278+ $ args ['headers ' ]['Content-Type ' ] = 'application/json ' ;
279+ }
280+ }
281+
282+ $ response = wp_remote_request ( $ url , $ args );
283+
284+ if ( is_wp_error ( $ response ) ) {
285+ wp_send_json_error ( $ response ->get_error_message () );
286+ }
287+
288+ // Get response details
289+ $ response_code = wp_remote_retrieve_response_code ( $ response );
290+ $ response_body = wp_remote_retrieve_body ( $ response );
291+
292+ // Strip HTML tags from response body to remove any links
293+ $ response_body = wp_strip_all_tags ( $ response_body );
294+
295+ $ message = sprintf (
296+ __ ( 'Webhook sent successfully!\n\nResponse Code: %d\nResponse Body: %s ' , 'wp-graphql-headless-webhooks ' ),
297+ $ response_code ,
298+ substr ( $ response_body , 0 , 200 ) // Limit response body to 200 chars
299+ );
300+
301+ wp_send_json_success ( array ( 'message ' => $ message ) );
302+ }
303+
304+ /**
305+ * Get test payload for a specific event
306+ *
307+ * @param string $event Event type.
308+ * @return array
309+ */
310+ private function get_test_payload_for_event ( string $ event ): array {
311+ $ base_payload = array (
312+ 'event ' => $ event ,
313+ 'timestamp ' => current_time ( 'mysql ' ),
314+ 'test ' => true ,
315+ 'test_mode ' => true , // Additional flag to clearly indicate test mode
316+ 'message ' => 'This is a TEST webhook payload - no production data was affected ' ,
317+ );
318+
319+ // Add event-specific test data
320+ switch ( $ event ) {
321+ case 'smart_cache_created ' :
322+ case 'smart_cache_updated ' :
323+ case 'smart_cache_deleted ' :
324+ $ base_payload ['data ' ] = array (
325+ 'key ' => 'test:post:999999 ' ,
326+ 'action ' => str_replace ( 'smart_cache_ ' , '' , $ event ),
327+ 'purge_url ' => home_url ( '/test-graphql-endpoint ' ),
328+ 'test_note ' => 'This is test data - no actual cache was purged ' ,
329+ );
330+ break ;
331+
332+ case 'smart_cache_nodes_purged ' :
333+ $ base_payload ['data ' ] = array (
334+ 'key ' => 'test:list:post ' ,
335+ 'nodes ' => array (
336+ array ( 'id ' => 'test_node_1 ' , 'type ' => 'post ' ),
337+ array ( 'id ' => 'test_node_2 ' , 'type ' => 'term ' ),
338+ ),
339+ 'test_note ' => 'This is test data - no actual nodes were purged ' ,
340+ );
341+ break ;
342+
343+ case 'post.published ' :
344+ case 'post.updated ' :
345+ case 'post.deleted ' :
346+ $ base_payload ['data ' ] = array (
347+ 'id ' => 999999 ,
348+ 'title ' => 'Test Post (Not Real) ' ,
349+ 'status ' => 'test ' ,
350+ 'author ' => 0 ,
351+ 'test_note ' => 'This is test data - no actual post exists ' ,
352+ );
353+ break ;
354+
355+ case 'user.created ' :
356+ $ base_payload ['data ' ] = array (
357+ 'id ' => 999999 ,
358+ 'username ' => 'test_webhook_user ' ,
359+ 360+ 'role ' => 'test ' ,
361+ 'test_note ' => 'This is test data - no actual user exists ' ,
362+ );
363+ break ;
364+
365+ default :
366+ $ base_payload ['data ' ] = array (
367+ 'message ' => 'This is a test webhook payload ' ,
368+ 'test_note ' => 'This is test data for event: ' . $ event ,
369+ );
370+ break ;
371+ }
372+
373+ /**
374+ * Filter the test payload for webhook testing
375+ *
376+ * @param array $base_payload The test payload data
377+ * @param string $event The event type being tested
378+ */
379+ return apply_filters ( 'graphql_webhooks_test_payload ' , $ base_payload , $ event );
380+ }
381+
228382 /**
229383 * Render admin page
230384 */
0 commit comments