diff --git a/plugins/wp-graphql-headless-webhooks/assets/js/admin.js b/plugins/wp-graphql-headless-webhooks/assets/js/admin.js index 0a9f1c0e..e43f51d4 100644 --- a/plugins/wp-graphql-headless-webhooks/assets/js/admin.js +++ b/plugins/wp-graphql-headless-webhooks/assets/js/admin.js @@ -11,7 +11,7 @@ 'click', function () { var headerRow = $( wpGraphQLWebhooks.headerTemplate || wpGraphQLWebhooks.headerRowTemplate ); - $( '#webhook-headers-container, #webhook-headers' ).append( headerRow ); + $( '#webhook-headers-container' ).append( headerRow ); } ); diff --git a/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksAdmin.php b/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksAdmin.php index 399ce8b5..77a0e162 100644 --- a/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksAdmin.php +++ b/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksAdmin.php @@ -41,38 +41,34 @@ class WebhooksAdmin { */ public function __construct( WebhookRepositoryInterface $repository ) { $this->repository = $repository; - - add_action( 'admin_menu', [ $this, 'add_admin_menu' ] ); - add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); - add_action( 'admin_post_graphql_webhook_save', [ $this, 'handle_webhook_save' ] ); - add_action( 'admin_post_graphql_webhook_delete', [ $this, 'handle_webhook_delete' ] ); - add_action( 'admin_init', [ $this, 'handle_admin_actions' ] ); - add_action( 'wp_ajax_test_webhook', [ $this, 'ajax_test_webhook' ] ); } /** - * Optionally initialize additional admin hooks. + * Initialize the admin functionality. * * @return void */ public function init(): void { + add_action( 'admin_menu', [ $this, 'add_admin_menu' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_action( 'admin_init', [ $this, 'handle_actions' ] ); + add_action( 'wp_ajax_test_webhook', [ $this, 'ajax_test_webhook' ] ); } /** - * Registers the top-level "Webhooks" admin menu. + * Registers the webhooks submenu under the GraphQL admin menu. * * @return void */ public function add_admin_menu(): void { - add_menu_page( - __( 'Webhooks', 'wp-graphql-headless-webhooks' ), + // Add submenu under GraphQL menu using the correct parent slug + add_submenu_page( + 'graphiql-ide', + __( 'GraphQL Webhooks', 'wp-graphql-headless-webhooks' ), __( 'Webhooks', 'wp-graphql-headless-webhooks' ), 'manage_options', self::ADMIN_PAGE_SLUG, - [ $this, 'render_admin_page' ], - 'dashicons-rss', - 25 + [ $this, 'render_admin_page' ] ); } @@ -97,6 +93,11 @@ public function get_admin_url( array $args = [] ): string { * @return void */ public function enqueue_assets( string $hook ): void { + // Only enqueue on our admin page + if ( false === strpos( $hook, self::ADMIN_PAGE_SLUG ) ) { + return; + } + wp_enqueue_style( 'graphql-webhooks-admin', WPGRAPHQL_HEADLESS_WEBHOOKS_PLUGIN_URL . 'assets/css/admin.css', @@ -136,24 +137,6 @@ private function get_header_row_template(): string { return ob_get_clean(); } - /** - * Handles admin actions from the webhooks page. - * - * @return void - */ - public function handle_actions(): void { - if ( ! isset( $_GET['page'] ) || self::ADMIN_PAGE_SLUG !== $_GET['page'] ) { - return; - } - - if ( isset( $_POST['action'] ) && 'save_webhook' === $_POST['action'] ) { - $this->handle_webhook_save(); - } - - if ( isset( $_GET['action'] ) && 'delete' === $_GET['action'] && isset( $_GET['webhook_id'] ) ) { - $this->handle_webhook_delete(); - } - } /** * Checks if the current user has permission to manage options. @@ -227,44 +210,55 @@ public function handle_webhook_save() { } /** - * Handles deleting a webhook. + * Handles admin actions from the webhooks page. * * @return void */ - public function handle_webhook_delete() { - // To be implemented: Individual deletes are handled through the list table's handle_row_actions. + public function handle_actions(): void { + if ( ! isset( $_REQUEST['page'] ) || self::ADMIN_PAGE_SLUG !== $_REQUEST['page'] ) { + return; + } + + // Handle save action + if ( isset( $_POST['action'] ) && 'save_webhook' === $_POST['action'] ) { + $this->handle_webhook_save(); + } + + // Handle delete action + if ( isset( $_GET['action'] ) && 'delete' === $_GET['action'] && isset( $_GET['webhook'] ) ) { + $this->handle_webhook_delete(); + } } /** - * Handles bulk admin actions (such as bulk delete). + * Handles single webhook deletion. * * @return void */ - public function handle_admin_actions() { - if ( - ( isset( $_REQUEST['action'] ) && 'delete' === $_REQUEST['action'] ) || - ( isset( $_REQUEST['action2'] ) && 'delete' === $_REQUEST['action2'] ) - ) { - if ( ! $this->verify_admin_permission() || ! $this->verify_nonce( 'bulk-webhooks', '_wpnonce' ) ) { - return; - } - - $webhook_ids = isset( $_REQUEST['webhook'] ) ? array_map( 'intval', (array) $_REQUEST['webhook'] ) : []; - $deleted = 0; - - foreach ( $webhook_ids as $webhook_id ) { - if ( $this->repository->delete( $webhook_id ) ) { - $deleted++; - } - } + private function handle_webhook_delete(): void { + // Verify permissions + if ( ! $this->verify_admin_permission() ) { + return; + } - if ( $deleted > 0 ) { - wp_redirect( add_query_arg( [ 'deleted' => $deleted ], $this->get_admin_url() ) ); - exit; - } + // Get webhook ID + $webhook_id = intval( $_GET['webhook'] ); + $nonce = isset( $_GET['_wpnonce'] ) ? $_GET['_wpnonce'] : ''; + + // Verify nonce + if ( ! wp_verify_nonce( $nonce, 'delete-webhook-' . $webhook_id ) ) { + wp_die( __( 'Security check failed.', 'wp-graphql-headless-webhooks' ) ); } + + // Delete webhook + $deleted = $this->repository->delete( $webhook_id ) ? 1 : 0; + + // Redirect with result + wp_redirect( add_query_arg( [ 'deleted' => $deleted ], remove_query_arg( [ 'action', 'webhook', '_wpnonce' ], $this->get_admin_url() ) ) ); + exit; } + /** * Renders the webhooks admin page. * diff --git a/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksListTable.php b/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksListTable.php index 1e1e9aea..ec92182e 100644 --- a/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksListTable.php +++ b/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksListTable.php @@ -77,45 +77,56 @@ public function get_sortable_columns() { */ public function get_bulk_actions() { return [ - 'bulk-delete' => __( 'Delete', 'wp-graphql-webhooks' ), + 'delete' => __( 'Delete', 'wp-graphql-webhooks' ), ]; } + /** * Process bulk actions */ public function process_bulk_action() { - // Handle bulk delete - if ( 'bulk-delete' === $this->current_action() ) { - $webhook_ids = isset( $_POST['webhook'] ) ? array_map( 'intval', $_POST['webhook'] ) : []; - - if ( ! empty( $webhook_ids ) && isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'bulk-' . $this->_args['plural'] ) ) { - foreach ( $webhook_ids as $id ) { - $this->repository->delete( $id ); - } - - wp_redirect( add_query_arg( 'deleted', count( $webhook_ids ), remove_query_arg( [ 'action', 'webhook', '_wpnonce' ] ) ) ); - exit; - } + // Only handle delete action + if ( 'delete' !== $this->current_action() ) { + return; } - - // Handle single delete - if ( 'delete' === $this->current_action() ) { - $webhook_id = isset( $_GET['webhook'] ) ? intval( $_GET['webhook'] ) : 0; - $nonce = isset( $_GET['_wpnonce'] ) ? $_GET['_wpnonce'] : ''; - - if ( $webhook_id && wp_verify_nonce( $nonce, 'delete-webhook-' . $webhook_id ) ) { - $this->repository->delete( $webhook_id ); - wp_redirect( add_query_arg( 'deleted', 1, remove_query_arg( [ 'action', 'webhook', '_wpnonce' ] ) ) ); - exit; + + // Verify nonce + if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk-' . $this->_args['plural'] ) ) { + wp_die( __( 'Security check failed.', 'wp-graphql-webhooks' ) ); + } + + // Check permissions + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( __( 'You do not have sufficient permissions to access this page.', 'wp-graphql-headless-webhooks' ) ); + } + + // Get selected webhooks + $webhook_ids = isset( $_REQUEST['webhook'] ) ? array_map( 'intval', (array) $_REQUEST['webhook'] ) : []; + if ( empty( $webhook_ids ) ) { + return; + } + + // Delete webhooks + $deleted = 0; + foreach ( $webhook_ids as $webhook_id ) { + if ( $this->repository->delete( $webhook_id ) ) { + $deleted++; } } + + // Redirect with success message + if ( $deleted > 0 ) { + wp_redirect( add_query_arg( [ 'deleted' => $deleted ], remove_query_arg( [ 'action', 'action2', 'webhook', '_wpnonce' ] ) ) ); + exit; + } } /** * Prepare items for display */ public function prepare_items() { + // Process bulk actions first $this->process_bulk_action(); $per_page = $this->get_items_per_page( 'webhooks_per_page', 20 ); diff --git a/plugins/wp-graphql-headless-webhooks/src/Admin/views/webhook-form.php b/plugins/wp-graphql-headless-webhooks/src/Admin/views/webhook-form.php index cc9e7abd..6f7d1ab3 100644 --- a/plugins/wp-graphql-headless-webhooks/src/Admin/views/webhook-form.php +++ b/plugins/wp-graphql-headless-webhooks/src/Admin/views/webhook-form.php @@ -28,9 +28,9 @@