diff --git a/plugins/wpgraphql-logging/README.md b/plugins/wpgraphql-logging/README.md index f1d6ed0b..3a8de27c 100644 --- a/plugins/wpgraphql-logging/README.md +++ b/plugins/wpgraphql-logging/README.md @@ -89,7 +89,6 @@ wpgraphql-logging/ - **Monolog-powered logging pipeline** - Default handler: stores logs in a WordPress table (`{$wpdb->prefix}wpgraphql_logging`). - - Default processors: Memory usage, memory peak, web request, process ID, and `WPGraphQLQueryProcessor` (adds `wpgraphql_query`, `wpgraphql_operation_name`, `wpgraphql_variables`). - **Simple developer API** - `Plugin::on()` to subscribe, `Plugin::emit()` to publish, `Plugin::transform()` to modify payloads. diff --git a/plugins/wpgraphql-logging/docs/Logging.md b/plugins/wpgraphql-logging/docs/Logging.md index f703c660..6022859d 100644 --- a/plugins/wpgraphql-logging/docs/Logging.md +++ b/plugins/wpgraphql-logging/docs/Logging.md @@ -41,7 +41,7 @@ Processors add extra data to each log record. The plugin includes several defaul - **MemoryPeakUsageProcessor** - Adds peak memory usage - **WebProcessor** - Adds web request data (IP, method, URI, etc.) - **ProcessIdProcessor** - Adds the process ID -- **WPGraphQLQueryProcessor** - Adds GraphQL query, variables, and operation name +- **RequestHeadersProcessor** - Adds requests headers ## Default Components diff --git a/plugins/wpgraphql-logging/src/Admin/View/List/List_Table.php b/plugins/wpgraphql-logging/src/Admin/View/List/List_Table.php index 664206c4..321de011 100644 --- a/plugins/wpgraphql-logging/src/Admin/View/List/List_Table.php +++ b/plugins/wpgraphql-logging/src/Admin/View/List/List_Table.php @@ -128,7 +128,7 @@ public function process_bulk_action(): void { $nonce_action = 'bulk-' . $this->_args['plural']; $nonce_value = $_REQUEST['_wpnonce'] ?? ''; - + $nonce = is_string( $nonce_value ) ? $nonce_value : ''; $nonce_result = wp_verify_nonce( $nonce, $nonce_action ); @@ -156,42 +156,44 @@ public function process_bulk_action(): void { $deleted_count = $count_before_delete; } - if ( $deleted_count > 0 ) { - $preserved_filters = []; - $filter_keys = [ 'level_filter', 'start_date', 'end_date' ]; + if ( $deleted_count <= 0 ) { + return; + } - foreach ( $filter_keys as $key ) { - $value = $_REQUEST[ $key ] ?? null; - if ( ! empty( $value ) && is_string( $value ) ) { - $preserved_filters[ $key ] = sanitize_text_field( wp_unslash( $value ) ); - } - } + $preserved_filters = []; + $filter_keys = [ 'level_filter', 'start_date', 'end_date' ]; - $redirect_url = remove_query_arg( [ 'action', 'action2', 'log', '_wpnonce' ] ); - $redirect_url = add_query_arg( - array_merge( - [ 'deleted_count' => $deleted_count ], - $preserved_filters - ), - $redirect_url - ); - - if ( ! headers_sent() ) { - wp_safe_redirect( esc_url_raw( $redirect_url ) ); - exit; - } else { - echo ''; - printf( - '

%s %s

', - esc_html__( 'Logs deleted successfully.', 'wpgraphql-logging' ), - esc_url( $redirect_url ), - esc_html__( 'Return to Logs', 'wpgraphql-logging' ) - ); - exit; + foreach ( $filter_keys as $key ) { + $value = $_REQUEST[ $key ] ?? null; + if ( ! empty( $value ) && is_string( $value ) ) { + $preserved_filters[ $key ] = sanitize_text_field( wp_unslash( $value ) ); } } + + $redirect_url = remove_query_arg( [ 'action', 'action2', 'log', '_wpnonce' ] ); + $redirect_url = add_query_arg( + array_merge( + [ 'deleted_count' => $deleted_count ], + $preserved_filters + ), + $redirect_url + ); + + if ( ! headers_sent() ) { + wp_safe_redirect( esc_url_raw( $redirect_url ) ); + exit; + } + + echo ''; + printf( + '

%s %s

', + esc_html__( 'Logs deleted successfully.', 'wpgraphql-logging' ), + esc_url( $redirect_url ), + esc_html__( 'Return to Logs', 'wpgraphql-logging' ) + ); + exit; } - + /** * Get the columns for the logs table. * @@ -475,7 +477,7 @@ protected function display_tablenav( $which ): void {
bulk_actions( $which_position ); ?>
- + extra_tablenav( $which ); $this->pagination( $which_position ); diff --git a/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php b/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php index e56e9744..b35a13a7 100644 --- a/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php +++ b/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php @@ -55,11 +55,11 @@ public function __construct( LoggerService $logger, array $config ) { * * This method hooks into the `do_graphql_request` action. * - * @param string $query + * @param string|null $query * @param string|null $operation_name * @param array|null $variables */ - public function log_pre_request( string $query, ?string $operation_name, ?array $variables ): void { + public function log_pre_request( ?string $query, ?string $operation_name, ?array $variables ): void { try { if ( ! $this->is_logging_enabled( $this->config, $query ) ) { return; @@ -114,7 +114,7 @@ public function log_graphql_before_execute( Request $request ): void { if ( ! in_array( Events::BEFORE_GRAPHQL_EXECUTION, $selected_events, true ) ) { return; } - + $payload = EventManager::transform( Events::BEFORE_GRAPHQL_EXECUTION, [ 'context' => $context, 'level' => Level::Info, @@ -135,7 +135,7 @@ public function log_graphql_before_execute( Request $request ): void { * @param array|\GraphQL\Executor\ExecutionResult $response * @param \WPGraphQL\WPSchema $schema * @param string|null $operation - * @param string $query + * @param string|null $query * @param array|null $variables * @param \WPGraphQL\Request $request * @param string|null $query_id @@ -145,7 +145,7 @@ public function log_before_response_returned( array|ExecutionResult $response, WPSchema $schema, ?string $operation, - string $query, + ?string $query, ?array $variables, Request $request, ?string $query_id diff --git a/plugins/wpgraphql-logging/src/Logger/Database/LogsRepository.php b/plugins/wpgraphql-logging/src/Logger/Database/LogsRepository.php index 83132411..d7f14398 100644 --- a/plugins/wpgraphql-logging/src/Logger/Database/LogsRepository.php +++ b/plugins/wpgraphql-logging/src/Logger/Database/LogsRepository.php @@ -115,6 +115,6 @@ public function delete(int $id): bool { public function delete_all(): void { global $wpdb; $table_name = DatabaseEntity::get_table_name(); - $wpdb->query( $wpdb->prepare( "DELETE FROM %i", $table_name ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery + $wpdb->query( $wpdb->prepare( 'DELETE FROM %i', $table_name ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } } diff --git a/plugins/wpgraphql-logging/src/Logger/LoggerService.php b/plugins/wpgraphql-logging/src/Logger/LoggerService.php index bdc7d35d..30851de3 100644 --- a/plugins/wpgraphql-logging/src/Logger/LoggerService.php +++ b/plugins/wpgraphql-logging/src/Logger/LoggerService.php @@ -13,7 +13,6 @@ use Monolog\Processor\WebProcessor; use WPGraphQL\Logging\Logger\Handlers\WordPressDatabaseHandler; use WPGraphQL\Logging\Logger\Processors\RequestHeadersProcessor; -use WPGraphQL\Logging\Logger\Processors\WPGraphQLQueryProcessor; /** * LoggerService class for managing the Monolog logger instance. @@ -225,7 +224,6 @@ public static function get_default_processors(): array { new MemoryPeakUsageProcessor(), // Logs memory peak data. new WebProcessor(), // Logs web request data. e.g. IP address, request method, URI, etc. new ProcessIdProcessor(), // Logs the process ID. - new WPGraphQLQueryProcessor(), // Custom processor to capture GraphQL request data. new RequestHeadersProcessor(), // Custom processor to capture request headers. ]; diff --git a/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php b/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php index eaaa2eee..83d77685 100644 --- a/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php +++ b/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php @@ -18,12 +18,21 @@ trait LoggingHelper { * phpcs:disable Generic.Metrics.CyclomaticComplexity, SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh */ protected function is_logging_enabled( array $config, ?string $query_string = null ): bool { + if ( null === $query_string ) { + return false; + } + $is_enabled = true; // Check the main "Enabled" checkbox. if ( ! (bool) ( $config[ Basic_Configuration_Tab::ENABLED ] ?? false ) ) { $is_enabled = false; } + // Do not log the seedQuery for Faust.js + if ( $is_enabled && ( 'query GetSeedNode' === trim( $query_string ) ) ) { + $is_enabled = false; + } + // Check if the current user is an admin if that option is enabled. if ( $is_enabled && ( (bool) ( $config[ Basic_Configuration_Tab::ADMIN_USER_LOGGING ] ?? false ) ) ) { if ( ! current_user_can( 'manage_options' ) ) { diff --git a/plugins/wpgraphql-logging/src/Logger/Processors/WPGraphQLQueryProcessor.php b/plugins/wpgraphql-logging/src/Logger/Processors/WPGraphQLQueryProcessor.php deleted file mode 100644 index 9fdfec6a..00000000 --- a/plugins/wpgraphql-logging/src/Logger/Processors/WPGraphQLQueryProcessor.php +++ /dev/null @@ -1,102 +0,0 @@ -|null - */ - protected static ?array $variables = null; - - /** - * The operation name for the current GraphQL request. - * - * @var string|null - */ - protected static ?string $operation_name = null; - - /** - * Constructor for the WPGraphQLQueryProcessor. - * - * This constructor sets up the necessary hooks to capture GraphQL request data and clear it after the request is completed. - */ - public function __construct() { - /** - * @psalm-suppress HookNotFound - */ - add_action( 'graphql_request_data', [ self::class, 'capture_request_data' ], 10, 1 ); // @phpstan-ignore-line - - /** - * @psalm-suppress HookNotFound - */ - add_action( 'graphql_process_http_request_response', [ self::class, 'clear_request_data' ], 999, 0 ); - } - - /** - * Captures the GraphQL request data. - * - * @param array $request_data The raw request data from WPGraphQL. - * - * @return array The raw request data from WPGraphQL. - */ - public static function capture_request_data(array $request_data): array { - self::$query = $request_data['query'] ?? null; - self::$variables = $request_data['variables'] ?? null; - self::$operation_name = $request_data['operationName'] ?? null; - - return $request_data; - } - - /** - * Clears the stored GraphQL request data to ensure it does not persist across requests. - */ - public static function clear_request_data(): void { - self::$query = null; - self::$variables = null; - self::$operation_name = null; - } - - /** - * This method is called for each log record. It adds the captured - * GraphQL data to the record's 'extra' array. - * - * @param \Monolog\LogRecord $record The log record to process. - * - * @return \Monolog\LogRecord The processed log record. - */ - public function __invoke(LogRecord $record): LogRecord { - if ( null !== self::$query ) { - $record->extra['wpgraphql_query'] = self::$query; - } - if ( null !== self::$operation_name ) { - $record->extra['wpgraphql_operation_name'] = self::$operation_name; - } - if ( null !== self::$variables ) { - $record->extra['wpgraphql_variables'] = self::$variables; - } - - return $record; - } -} diff --git a/plugins/wpgraphql-logging/tests/wpunit/Logger/Handlers/WordPressDatabaseHandlerTest.php b/plugins/wpgraphql-logging/tests/wpunit/Logger/Handlers/WordPressDatabaseHandlerTest.php index 7b908d53..568681c3 100644 --- a/plugins/wpgraphql-logging/tests/wpunit/Logger/Handlers/WordPressDatabaseHandlerTest.php +++ b/plugins/wpgraphql-logging/tests/wpunit/Logger/Handlers/WordPressDatabaseHandlerTest.php @@ -12,7 +12,7 @@ use WPGraphQL\Logging\Logger\Handlers\WordPressDatabaseHandler; /** - * Class WPGraphQLQueryProcessorTest + * Class WordPressDatabaseHandlerTest * * Tests for the WordPressDatabaseHandler class. * diff --git a/plugins/wpgraphql-logging/tests/wpunit/Logger/Processors/WPGraphQLQueryProcessorTest.php b/plugins/wpgraphql-logging/tests/wpunit/Logger/Processors/WPGraphQLQueryProcessorTest.php deleted file mode 100644 index 6e56b651..00000000 --- a/plugins/wpgraphql-logging/tests/wpunit/Logger/Processors/WPGraphQLQueryProcessorTest.php +++ /dev/null @@ -1,135 +0,0 @@ -get_test_record(); - - $processed_record = $processor($record); - - $this->assertEmpty($processed_record->extra, 'Extra array should be empty when no data is captured.'); - } - - public function test_invoke_adds_captured_data_to_log_record(): void - { - $request_data = [ - 'query' => 'query TestQuery { posts { nodes { id } } }', - 'variables' => ['first' => 10], - 'operationName' => 'TestQuery', - ]; - - // Manually capture the data to simulate a GraphQL request starting. - WPGraphQLQueryProcessor::capture_request_data($request_data); - - $processor = new WPGraphQLQueryProcessor(); - $record = $this->get_test_record(); - - $processed_record = $processor($record); - - // Assert that the extra array contains the correct GraphQL data. - $this->assertArrayHasKey('wpgraphql_query', $processed_record->extra); - $this->assertEquals($request_data['query'], $processed_record->extra['wpgraphql_query']); - - $this->assertArrayHasKey('wpgraphql_operation_name', $processed_record->extra); - $this->assertEquals($request_data['operationName'], $processed_record->extra['wpgraphql_operation_name']); - - $this->assertArrayHasKey('wpgraphql_variables', $processed_record->extra); - $this->assertEquals($request_data['variables'], $processed_record->extra['wpgraphql_variables']); - } - - public function test_clear_request_data_resets_static_properties(): void - { - // 1. Capture some data. - WPGraphQLQueryProcessor::capture_request_data([ - 'query' => 'query Test { posts { nodes { id } } }' - ]); - - // 2. Call the clear method. - WPGraphQLQueryProcessor::clear_request_data(); - - // 3. Use reflection to access the private static properties and check their values. - $query_prop = new ReflectionProperty(WPGraphQLQueryProcessor::class, 'query'); - $query_prop->setAccessible(true); - $this->assertNull($query_prop->getValue(), 'The static query property should be null after clearing.'); - - $variables_prop = new ReflectionProperty(WPGraphQLQueryProcessor::class, 'variables'); - $variables_prop->setAccessible(true); - $this->assertNull($variables_prop->getValue(), 'The static variables property should be null after clearing.'); - - $operation_name_prop = new ReflectionProperty(WPGraphQLQueryProcessor::class, 'operation_name'); - $operation_name_prop->setAccessible(true); - $this->assertNull($operation_name_prop->getValue(), 'The static operation_name property should be null after clearing.'); - } - - /** - * @test - * It should hook into WordPress actions upon instantiation. - */ - public function test_constructor_hooks_into_wordpress_actions(): void - { - // Instantiate the processor to trigger its constructor. - $processor = new WPGraphQLQueryProcessor(); - - // Check if the hooks have been added correctly. - $this->assertNotFalse( - has_action('graphql_request_data', [WPGraphQLQueryProcessor::class, 'capture_request_data']), - 'The capture_request_data method should be hooked to graphql_request_data.' - ); - - $this->assertNotFalse( - has_action('graphql_process_http_request_response', [WPGraphQLQueryProcessor::class, 'clear_request_data']), - 'The clear_request_data method should be hooked to graphql_process_http_request_response.' - ); - - // Clean up the hooks after the test. - remove_action('graphql_request_data', [WPGraphQLQueryProcessor::class, 'capture_request_data'], 10); - remove_action('graphql_process_http_request_response', [WPGraphQLQueryProcessor::class, 'clear_request_data'], 999); - } - - /** - * Helper method to get a basic LogRecord for testing. - */ - private function get_test_record(): LogRecord - { - return new LogRecord( - new DateTimeImmutable(), - 'test-channel', - Level::Debug, - 'This is a test message.' - ); - } -}