diff --git a/.changeset/gorgeous-planes-prove.md b/.changeset/gorgeous-planes-prove.md new file mode 100644 index 00000000..3b588494 --- /dev/null +++ b/.changeset/gorgeous-planes-prove.md @@ -0,0 +1,5 @@ +--- +"@wpengine/wpgraphql-logging-wordpress-plugin": patch +--- + +chore: Various snags. diff --git a/plugins/wpgraphql-logging/README.md b/plugins/wpgraphql-logging/README.md index 3a8de27c..bc21377f 100644 --- a/plugins/wpgraphql-logging/README.md +++ b/plugins/wpgraphql-logging/README.md @@ -48,6 +48,35 @@ Once you have the composer repository setup, please run `composer req wpengine/w Plugin should start logging data, once activated. +--- + +## Uninstallation and Data Cleanup + +By default, WPGraphQL Logging preserves all logged data when the plugin is deactivated to prevent accidental data loss. If you want to completely remove all plugin data (including database tables) when deactivating the plugin, you must explicitly enable this behavior. + +### Enabling Database Cleanup on Deactivation + +To enable automatic database cleanup when the plugin is deactivated, add the following constant to your `wp-config.php` file or in a must-use plugin: + +```php +define( 'WP_GRAPHQL_LOGGING_UNINSTALL_PLUGIN', true ); +``` + +> [!WARNING] +> **Data Loss Warning**: When `WP_GRAPHQL_LOGGING_UNINSTALL_PLUGIN` is defined as `true`, deactivating the plugin will permanently delete all logged data and drop the plugin's database tables. This action is irreversible. + +### Manual Data Cleanup + +If you prefer to manually clean up data without defining the constant, you can: + +1. Use the plugin's admin interface to clear logs (when available) +2. Manually drop the database table: `{$wpdb->prefix}wpgraphql_logging` +3. Remove plugin options from the WordPress options table + +--- + +@TODO add more info once we have configuration setup. + @TODO add more info once we have configuration setup. diff --git a/plugins/wpgraphql-logging/composer.json b/plugins/wpgraphql-logging/composer.json index 6dfc5d5c..6137264d 100644 --- a/plugins/wpgraphql-logging/composer.json +++ b/plugins/wpgraphql-logging/composer.json @@ -53,9 +53,7 @@ "phpstan/phpstan-strict-rules": "^2.0", "slevomat/coding-standard": "^8.0", "szepeviktor/phpstan-wordpress": "^2.0", - "wp-cli/wp-cli-bundle": "^2.8.1", - "wp-graphql/wp-graphql": "^2.3", - "wp-graphql/wp-graphql-testcase": "^3.0.1" + "wp-cli/wp-cli-bundle": "^2.8.1" }, "config": { "allow-plugins": { diff --git a/plugins/wpgraphql-logging/composer.lock b/plugins/wpgraphql-logging/composer.lock index 72fc3786..4dcc5a52 100644 --- a/plugins/wpgraphql-logging/composer.lock +++ b/plugins/wpgraphql-logging/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "39c90e7e58d79f6b447c2e916f498bdc", + "content-hash": "80763354fb1a58eacf78bd70d5e8cfbc", "packages": [ { "name": "league/csv", @@ -407,60 +407,6 @@ ], "time": "2024-04-13T18:00:56+00:00" }, - { - "name": "appsero/client", - "version": "v2.0.4", - "source": { - "type": "git", - "url": "https://github.com/Appsero/client.git", - "reference": "12ff65b9770286d21edf314e7acfcd26fdde3315" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Appsero/client/zipball/12ff65b9770286d21edf314e7acfcd26fdde3315", - "reference": "12ff65b9770286d21edf314e7acfcd26fdde3315", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", - "phpcompatibility/phpcompatibility-wp": "dev-master", - "phpunit/phpunit": "^8.5.31", - "squizlabs/php_codesniffer": "^3.7", - "tareq1988/wp-php-cs-fixer": "dev-master", - "wp-coding-standards/wpcs": "dev-develop" - }, - "type": "library", - "autoload": { - "psr-4": { - "Appsero\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tareq Hasan", - "email": "tareq@appsero.com" - } - ], - "description": "Appsero Client", - "keywords": [ - "analytics", - "plugin", - "theme", - "wordpress" - ], - "support": { - "issues": "https://github.com/Appsero/client/issues", - "source": "https://github.com/Appsero/client/tree/v2.0.4" - }, - "time": "2024-11-25T05:58:23+00:00" - }, { "name": "automattic/vipwpcs", "version": "3.0.1", @@ -3144,51 +3090,6 @@ }, "time": "2024-04-01T10:36:11+00:00" }, - { - "name": "ivome/graphql-relay-php", - "version": "v0.7.0", - "source": { - "type": "git", - "url": "https://github.com/ivome/graphql-relay-php.git", - "reference": "06bd176103618d896197d85d04a3a17c91e39698" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ivome/graphql-relay-php/zipball/06bd176103618d896197d85d04a3a17c91e39698", - "reference": "06bd176103618d896197d85d04a3a17c91e39698", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "webonyx/graphql-php": "^14.0 || ^15.0" - }, - "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "satooshi/php-coveralls": "~1.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "A PHP port of GraphQL Relay reference implementation", - "homepage": "https://github.com/ivome/graphql-relay-php", - "keywords": [ - "Relay", - "api", - "graphql" - ], - "support": { - "issues": "https://github.com/ivome/graphql-relay-php/issues", - "source": "https://github.com/ivome/graphql-relay-php/tree/v0.7.0" - }, - "time": "2023-10-20T15:43:03+00:00" - }, { "name": "johnpbloch/wordpress-core", "version": "6.8.2", @@ -4067,64 +3968,6 @@ }, "time": "2022-02-21T01:04:05+00:00" }, - { - "name": "php-extended/polyfill-php80-str-utils", - "version": "1.3.7", - "source": { - "type": "git", - "url": "https://gitlab.com/php-extended/polyfill-php80-str-utils.git", - "reference": "0749426252e3e27c526fda939e8d3ff050bf907b" - }, - "dist": { - "type": "zip", - "url": "https://gitlab.com/api/v4/projects/php-extended%2Fpolyfill-php80-str-utils/repository/archive.zip?sha=0749426252e3e27c526fda939e8d3ff050bf907b", - "reference": "0749426252e3e27c526fda939e8d3ff050bf907b", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "php-extended/placeholder-phpunit": "^9" - }, - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "PhpExtended\\Polyfill\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Anastaszor", - "homepage": "https://gitlab.com/Anastaszor", - "role": "developer" - } - ], - "description": "A php implementation of string functions introduced in php8 and above", - "homepage": "https://gitlab.com/php-extended/polyfill-str-utils", - "keywords": [ - "implementation", - "php", - "polyfill", - "str", - "string_ends_with", - "string_starts_with", - "utils" - ], - "support": { - "issues": "https://gitlab.com/php-extended/polyfill-str-utils/issues", - "source": "https://gitlab.com/php-extended/polyfill-str-utils" - }, - "abandoned": "php >= 8.0", - "time": "2024-03-31T13:28:10+00:00" - }, { "name": "php-stubs/wordpress-globals", "version": "v0.2.0", @@ -9446,80 +9289,6 @@ }, "time": "2022-06-03T18:03:27+00:00" }, - { - "name": "webonyx/graphql-php", - "version": "v15.20.0", - "source": { - "type": "git", - "url": "https://github.com/webonyx/graphql-php.git", - "reference": "60feb7ad5023c0ef411efbdf9792d3df5812e28f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/60feb7ad5023c0ef411efbdf9792d3df5812e28f", - "reference": "60feb7ad5023c0ef411efbdf9792d3df5812e28f", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "php": "^7.4 || ^8" - }, - "require-dev": { - "amphp/amp": "^2.6", - "amphp/http-server": "^2.1", - "dms/phpunit-arraysubset-asserts": "dev-master", - "ergebnis/composer-normalize": "^2.28", - "friendsofphp/php-cs-fixer": "3.73.1", - "mll-lab/php-cs-fixer-config": "5.11.0", - "nyholm/psr7": "^1.5", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "2.1.8", - "phpstan/phpstan-phpunit": "2.0.4", - "phpstan/phpstan-strict-rules": "2.0.4", - "phpunit/phpunit": "^9.5 || ^10.5.21 || ^11", - "psr/http-message": "^1 || ^2", - "react/http": "^1.6", - "react/promise": "^2.0 || ^3.0", - "rector/rector": "^2.0", - "symfony/polyfill-php81": "^1.23", - "symfony/var-exporter": "^5 || ^6 || ^7", - "thecodingmachine/safe": "^1.3 || ^2 || ^3" - }, - "suggest": { - "amphp/http-server": "To leverage async resolving with webserver on AMPHP platform", - "psr/http-message": "To use standard GraphQL server", - "react/promise": "To leverage async resolving on React PHP platform" - }, - "type": "library", - "autoload": { - "psr-4": { - "GraphQL\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP port of GraphQL reference implementation", - "homepage": "https://github.com/webonyx/graphql-php", - "keywords": [ - "api", - "graphql" - ], - "support": { - "issues": "https://github.com/webonyx/graphql-php/issues", - "source": "https://github.com/webonyx/graphql-php/tree/v15.20.0" - }, - "funding": [ - { - "url": "https://opencollective.com/webonyx-graphql-php", - "type": "open_collective" - } - ], - "time": "2025-03-21T08:45:04+00:00" - }, { "name": "wp-cli/cache-command", "version": "v2.2.0", @@ -11787,158 +11556,6 @@ ], "time": "2025-07-24T20:08:31+00:00" }, - { - "name": "wp-graphql/wp-graphql", - "version": "v2.3.3", - "source": { - "type": "git", - "url": "https://github.com/wp-graphql/wp-graphql.git", - "reference": "0b1512b746818bb3a0b0347f0a6841c811426f0f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wp-graphql/wp-graphql/zipball/0b1512b746818bb3a0b0347f0a6841c811426f0f", - "reference": "0b1512b746818bb3a0b0347f0a6841c811426f0f", - "shasum": "" - }, - "require": { - "appsero/client": "2.0.4", - "ivome/graphql-relay-php": "0.7.0", - "php": "^7.4 || ^8.0", - "webonyx/graphql-php": "15.20.0" - }, - "require-dev": { - "automattic/vipwpcs": "^3.0", - "codeception/module-asserts": "^1.0", - "codeception/module-cli": "^1.0", - "codeception/module-db": "^1.0", - "codeception/module-filesystem": "^1.0", - "codeception/module-phpbrowser": "^1.0", - "codeception/module-rest": "^1.2", - "codeception/module-webdriver": "^1.0", - "codeception/util-universalframework": "^1.0", - "composer/semver": "^3.0", - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "lucatume/wp-browser": "<3.5", - "phpcompatibility/php-compatibility": "dev-develop as 9.9.9", - "phpcompatibility/phpcompatibility-wp": "^2.1", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "~2.1.2", - "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpunit/phpunit": "^9.5", - "slevomat/coding-standard": "^8.9", - "szepeviktor/phpstan-wordpress": "~2.0.1", - "wp-cli/wp-cli-bundle": "^2.8", - "wp-graphql/wp-graphql-testcase": "^3.0" - }, - "type": "wordpress-plugin", - "autoload": { - "files": [], - "psr-4": { - "WPGraphQL\\": "src/" - }, - "classmap": [ - "src/WPGraphQL.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0-or-later" - ], - "authors": [ - { - "name": "Jason Bahl", - "email": "jasonbahl@mac.com" - }, - { - "name": "Edwin Cromley" - }, - { - "name": "Ryan Kanner" - }, - { - "name": "Hughie Devore" - }, - { - "name": "Chris Zarate" - } - ], - "description": "GraphQL API for WordPress", - "support": { - "issues": "https://github.com/wp-graphql/wp-graphql/issues", - "source": "https://github.com/wp-graphql/wp-graphql/tree/v2.3.3" - }, - "time": "2025-06-17T14:27:16+00:00" - }, - { - "name": "wp-graphql/wp-graphql-testcase", - "version": "v3.4.0", - "source": { - "type": "git", - "url": "https://github.com/wp-graphql/wp-graphql-testcase.git", - "reference": "572d4c51e9a0a33ec1b99970155fe005f468f4ec" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wp-graphql/wp-graphql-testcase/zipball/572d4c51e9a0a33ec1b99970155fe005f468f4ec", - "reference": "572d4c51e9a0a33ec1b99970155fe005f468f4ec", - "shasum": "" - }, - "require": { - "php-extended/polyfill-php80-str-utils": "^1.3" - }, - "require-dev": { - "automattic/vipwpcs": "^2.3", - "composer/installers": "^1.9", - "johnpbloch/wordpress": "^6.1", - "php-coveralls/php-coveralls": "2.4.3", - "squizlabs/php_codesniffer": "^3.5", - "wp-coding-standards/wpcs": "^2.3", - "wpackagist-plugin/wp-graphql": "^1.26" - }, - "suggest": { - "codeception/module-asserts": "Needed for \\Tests\\WPGraphQL\\TestCase\\WPGraphQLTestcase to work.", - "codeception/util-universalframework": "Needed for \\Tests\\WPGraphQL\\TestCase\\WPGraphQLTestcase to work.", - "guzzlehttp/guzzle": "Needed for \\Tests\\WPGraphQL\\Codeception\\Module\\WPGraphQL to work.", - "lucatume/wp-browser": "Needed for \\Tests\\WPGraphQL\\TestCase\\WPGraphQLTestcase to work.", - "phpunit/phpunit": "Needed for \\Tests\\WPGraphQL\\TestCase\\WPGraphQLUnitTestcase to work.", - "wp-phpunit/wp-phpunit": "Needed for \\Tests\\WPGraphQL\\TestCase\\WPGraphQLUnitTestcase to work.", - "yoast/phpunit-polyfills": "Needed for \\Tests\\WPGraphQL\\TestCase\\WPGraphQLUnitTestcase to work." - }, - "type": "library", - "extra": { - "installer-paths": { - "local/public/wp-content/plugins/{$name}/": [ - "type:wordpress-plugin" - ] - }, - "wordpress-install-dir": "local/public" - }, - "autoload": { - "psr-4": { - "Tests\\WPGraphQL\\": "src/" - }, - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Geoff Taylor", - "email": "geoffrey.taylor@outlook.com" - } - ], - "description": "Codeception module for WPGraphQL API testing", - "support": { - "issues": "https://github.com/wp-graphql/wp-graphql-testcase/issues", - "source": "https://github.com/wp-graphql/wp-graphql-testcase/tree/v3.4.0" - }, - "time": "2024-08-08T18:48:14+00:00" - }, { "name": "wp-hooks/wordpress-core", "version": "1.10.0", @@ -12039,7 +11656,7 @@ "platform": { "php": ">=8.1.2" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.1.2" }, diff --git a/plugins/wpgraphql-logging/phpstan.neon.dist b/plugins/wpgraphql-logging/phpstan.neon.dist index 58fb14d1..52477abc 100644 --- a/plugins/wpgraphql-logging/phpstan.neon.dist +++ b/plugins/wpgraphql-logging/phpstan.neon.dist @@ -34,3 +34,5 @@ parameters: - identifier: empty.notAllowed - message: '#Constant WPGRAPHQL_LOGGING.* not found\.#' + - + message: '#GraphQL\\.*#' diff --git a/plugins/wpgraphql-logging/psalm.xml b/plugins/wpgraphql-logging/psalm.xml index 9bf8ed5a..eca593fe 100644 --- a/plugins/wpgraphql-logging/psalm.xml +++ b/plugins/wpgraphql-logging/psalm.xml @@ -18,7 +18,6 @@ - @@ -48,5 +47,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/Basic_Configuration_Tab.php b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/Basic_Configuration_Tab.php index ac69e1f5..92d49ed0 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/Basic_Configuration_Tab.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/Basic_Configuration_Tab.php @@ -52,6 +52,20 @@ class Basic_Configuration_Tab implements Settings_Tab_Interface { */ public const EVENT_LOG_SELECTION = 'event_log_selection'; + /** + * The field ID for the exclude query text input. + * + * @var string + */ + public const EXCLUDE_QUERY = 'exclude_query'; + + /** + * The field ID for whether to log the response from the WPGGraphQL query into the context object. + * + * @var string + */ + public const LOG_RESPONSE = 'log_response'; + /** * Get the name/identifier of the tab. */ @@ -93,10 +107,19 @@ public function get_fields(): array { __( 'e.g., 192.168.1.1, 10.0.0.1', 'wpgraphql-logging' ) ); + $fields[ self::EXCLUDE_QUERY ] = new Text_Input_Field( + self::EXCLUDE_QUERY, + $this->get_name(), + __( 'Exclude Queries', 'wpgraphql-logging' ), + '', + __( 'Comma-separated list of GraphQL query names to exclude from logging.', 'wpgraphql-logging' ), + __( 'e.g., __schema,SeedNode,__typename', 'wpgraphql-logging' ) + ); + $fields[ self::ADMIN_USER_LOGGING ] = new Checkbox_Field( self::ADMIN_USER_LOGGING, $this->get_name(), - __( 'Log only for admin users', 'wpgraphql-logging' ), + __( 'Admin User Logging', 'wpgraphql-logging' ), '', __( 'Log only for admin users.', 'wpgraphql-logging' ) ); @@ -134,6 +157,14 @@ public function get_fields(): array { true ); + $fields[ self::LOG_RESPONSE ] = new Checkbox_Field( + self::LOG_RESPONSE, + $this->get_name(), + __( 'Log Response', 'wpgraphql-logging' ), + '', + __( 'Whether or not to log the response from the WPGraphQL query into the context object.', 'wpgraphql-logging' ), + ); + return apply_filters( 'wpgraphql_logging_basic_configuration_fields', $fields ); } } 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 321de011..670d8e12 100644 --- a/plugins/wpgraphql-logging/src/Admin/View/List/List_Table.php +++ b/plugins/wpgraphql-logging/src/Admin/View/List/List_Table.php @@ -118,6 +118,10 @@ public function get_bulk_actions(): array { * * @phpcs:disable WordPress.Security.NonceVerification.Missing * @phpcs:disable WordPress.Security.NonceVerification.Recommended + * @phpcs:disable Generic.Metrics.NestingLevel.TooHigh + * @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh + * @phpcs:disable Generic.Metrics.CyclomaticComplexity.MaxExceeded + * @phpcs:disable SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh */ public function process_bulk_action(): void { $action = $this->current_action(); @@ -126,8 +130,8 @@ public function process_bulk_action(): void { return; } - $nonce_action = 'bulk-' . $this->_args['plural']; - $nonce_value = $_REQUEST['_wpnonce'] ?? ''; + $nonce_action = 'bulk-' . esc_attr( $this->_args['plural'] ); + $nonce_value = isset( $_REQUEST['_wpnonce'] ) && is_string( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : ''; $nonce = is_string( $nonce_value ) ? $nonce_value : ''; @@ -141,9 +145,9 @@ public function process_bulk_action(): void { // WordPress sometimes sends 'delete' for selected items. if ( in_array( $action, [ 'delete', 'bulk_delete' ], true ) && ! empty( $_REQUEST['log'] ) ) { $ids = array_map( 'absint', (array) $_REQUEST['log'] ); - // Remove redundant empty check since array_map always returns array + // Remove redundant empty check since array_map always returns array. foreach ( $ids as $id ) { - if ( $id > 0 ) { // Only process valid IDs + if ( $id > 0 ) { // Only process valid IDs. $this->repository->delete( $id ); } } @@ -164,9 +168,9 @@ public function process_bulk_action(): void { $filter_keys = [ 'level_filter', 'start_date', 'end_date' ]; foreach ( $filter_keys as $key ) { - $value = $_REQUEST[ $key ] ?? null; - if ( ! empty( $value ) && is_string( $value ) ) { - $preserved_filters[ $key ] = sanitize_text_field( wp_unslash( $value ) ); + $value = isset( $_REQUEST[ $key ] ) && is_string( $_REQUEST[ $key ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) ) : null; + if ( ! empty( $value ) ) { + $preserved_filters[ $key ] = $value; } } diff --git a/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-filters.php b/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-filters.php index 91dc67f1..527f659f 100644 --- a/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-filters.php +++ b/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-filters.php @@ -6,37 +6,44 @@ exit; } -// Current filter values. -$current_level = $_GET['level_filter'] ?? ''; -$current_start_date = $_GET['start_date'] ?? ''; -$current_end_date = $_GET['end_date'] ?? ''; +$wpgraphql_logging_current_level = isset( $_POST['level_filter'] ) ? sanitize_text_field( wp_unslash( $_POST['level_filter'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing +$wpgraphql_logging_current_start_date = isset( $_POST['start_date'] ) ? sanitize_text_field( wp_unslash( $_POST['start_date'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing +$wpgraphql_logging_current_end_date = isset( $_POST['end_date'] ) ? sanitize_text_field( wp_unslash( $_POST['end_date'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -$log_levels = [ 'debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency' ]; +// Currently only use info and error. +$wpgraphql_logging_log_levels = [ 'info', 'error' ]; +$wpgraphql_logging_log_levels = apply_filters( 'wpgraphql_logging_log_levels', $wpgraphql_logging_log_levels ); ?>
- - - 'margin: 0;' ] ); ?> + + + +
diff --git a/plugins/wpgraphql-logging/src/Events/Events.php b/plugins/wpgraphql-logging/src/Events/Events.php index fcc15ad5..231e4903 100644 --- a/plugins/wpgraphql-logging/src/Events/Events.php +++ b/plugins/wpgraphql-logging/src/Events/Events.php @@ -60,23 +60,4 @@ final class Events { * @var string */ public const REQUEST_RESULTS = 'graphql_request_results'; - - /** - * WPGraphQL filter: graphql_debug_enabled. - * - * Determines if GraphQL Debug is enabled. Useful for toggling logging. - * - * @var string - */ - public const DEBUG_ENABLED = 'graphql_debug_enabled'; - - /** - * WPGraphQL filter: graphql_app_context_config. - * - * Filters the config for the AppContext. Useful for storing temporary - * data for the duration of a request. - * - * @var string - */ - public const APP_CONTEXT_CONFIG = 'graphql_app_context_config'; } diff --git a/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php b/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php index b35a13a7..015358d4 100644 --- a/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php +++ b/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php @@ -139,6 +139,9 @@ public function log_graphql_before_execute( Request $request ): void { * @param array|null $variables * @param \WPGraphQL\Request $request * @param string|null $query_id + * + * @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh + * @phpcs:disable SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh */ public function log_before_response_returned( array|ExecutionResult $filtered_response, @@ -170,6 +173,9 @@ public function log_before_response_returned( 'request' => $request, 'query_id' => $query_id, ]; + if ( ! $this->should_log_response( $this->config ) ) { + unset( $context['response'] ); + } $level = Level::Info; $message = 'WPGraphQL Response'; $errors = $this->get_response_errors( $response ); diff --git a/plugins/wpgraphql-logging/src/Events/QueryFilterLogger.php b/plugins/wpgraphql-logging/src/Events/QueryFilterLogger.php index 72c70cb8..c6a5ca06 100644 --- a/plugins/wpgraphql-logging/src/Events/QueryFilterLogger.php +++ b/plugins/wpgraphql-logging/src/Events/QueryFilterLogger.php @@ -9,6 +9,7 @@ use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab; use WPGraphQL\Logging\Logger\LoggerService; use WPGraphQL\Logging\Logger\LoggingHelper; +use WPGraphQL\Logging\Logger\Rules\EnabledRule; use WPGraphQL\Request; /** @@ -62,10 +63,6 @@ public function __construct( LoggerService $logger, array $config ) { */ public function log_graphql_request_data( array $query_data ): array { try { - if ( ! $this->is_logging_enabled( $this->config ) ) { - return $query_data; - } - $selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? []; if ( ! is_array( $selected_events ) || empty( $selected_events ) ) { return $query_data; @@ -74,8 +71,13 @@ public function log_graphql_request_data( array $query_data ): array { return $query_data; } + $query_string = $query_data['query'] ?? null; + if ( ! $this->is_logging_enabled( $this->config, $query_string ) ) { + return $query_data; + } + $context = [ - 'query' => $query_data['query'] ?? null, + 'query' => $query_string, 'variables' => $query_data['variables'] ?? null, 'operation_name' => $query_data['operationName'] ?? null, ]; @@ -115,7 +117,7 @@ public function log_graphql_request_results( ?string $query_id ): array|ExecutionResult { try { - if ( ! $this->is_logging_enabled( $this->config ) ) { + if ( ! $this->is_logging_enabled( $this->config, $query ) ) { return $response; } @@ -137,7 +139,9 @@ public function log_graphql_request_results( 'request' => $request, 'query_id' => $query_id, ]; - + if ( ! $this->should_log_response( $this->config ) ) { + unset( $context['response'] ); + } $level = Level::Info; $message = 'WPGraphQL Response'; if ( is_array( $response ) && isset( $response['errors'] ) && ! empty( $response['errors'] ) ) { @@ -169,7 +173,9 @@ public function log_graphql_request_results( * @return array The filtered array of headers. */ public function add_logging_headers( array $headers ): array { - if ( ! $this->is_logging_enabled( $this->config ) ) { + + $rule = new EnabledRule(); + if ( ! $rule->passes( $this->config ) ) { return $headers; } diff --git a/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php b/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php index 83d77685..43734b43 100644 --- a/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php +++ b/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php @@ -4,64 +4,68 @@ namespace WPGraphQL\Logging\Logger; -use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab; +use WPGraphQL\Logging\Logger\Rules\AdminUserRule; +use WPGraphQL\Logging\Logger\Rules\EnabledRule; +use WPGraphQL\Logging\Logger\Rules\ExcludeQueryRule; +use WPGraphQL\Logging\Logger\Rules\IpRestrictionsRule; +use WPGraphQL\Logging\Logger\Rules\LogResponseRule; +use WPGraphQL\Logging\Logger\Rules\QueryNullRule; +use WPGraphQL\Logging\Logger\Rules\RuleManager; +use WPGraphQL\Logging\Logger\Rules\SamplingRateRule; /** * Trait for shared logging helper methods. + * + * @package WPGraphQL\Logging + * + * @since 0.0.1 */ trait LoggingHelper { /** - * Checks if logging is enabled based on user settings. + * The rule manager instance. + * + * @var \WPGraphQL\Logging\Logger\Rules\RuleManager|null + */ + protected ?RuleManager $rule_manager = null; + + /** + * Determines if the response should be logged based on the configuration. * * @param array $config The logging configuration. * - * phpcs:disable Generic.Metrics.CyclomaticComplexity, SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh + * @return bool True if the response should be logged, false otherwise. */ - 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' ) ) { - $is_enabled = false; - } - } + public function should_log_response(array $config): bool { + $rule = new LogResponseRule(); + return $rule->passes( $config ); + } - // Check for IP restrictions. - $ip_restrictions = $config[ Basic_Configuration_Tab::IP_RESTRICTIONS ] ?? ''; - if ( $is_enabled && ! empty( $ip_restrictions ) ) { - $allowed_ips = array_map( 'trim', explode( ',', $ip_restrictions ) ); - $remote_addr = $_SERVER['REMOTE_ADDR'] ?? ''; // @phpcs:ignore - if ( ! in_array( $remote_addr, $allowed_ips, true ) ) { - $is_enabled = false; - } + /** + * Get the rule manager, initializing it if necessary. + */ + protected function get_rule_manager(): RuleManager { + if ( null !== $this->rule_manager ) { + return $this->rule_manager; } + $this->rule_manager = new RuleManager(); + $this->rule_manager->add_rule( new QueryNullRule() ); + $this->rule_manager->add_rule( new SamplingRateRule() ); + $this->rule_manager->add_rule( new EnabledRule() ); + $this->rule_manager->add_rule( new AdminUserRule() ); + $this->rule_manager->add_rule( new IpRestrictionsRule() ); + $this->rule_manager->add_rule( new ExcludeQueryRule() ); + apply_filters( 'wpgraphql_logging_rule_manager', $this->rule_manager ); + return $this->rule_manager; + } - // Check the data sampling rate. - if ( $is_enabled ) { - $sampling_rate = (int) ( $config[ Basic_Configuration_Tab::DATA_SAMPLING ] ?? 100 ); - if ( wp_rand( 0, 100 ) >= $sampling_rate ) { - $is_enabled = false; - } - } + /** + * Checks if logging is enabled based on user settings. + * + * @param array $config The logging configuration. + */ + protected function is_logging_enabled( array $config, ?string $query_string = null ): bool { - // Check if the query is an introspection query and skip logging if it is. - if ( $is_enabled && $this->is_introspection_query( $query_string ) ) { - $is_enabled = false; - } + $is_enabled = $this->get_rule_manager()->all_rules_pass( $config, $query_string ); /** * Filter the final decision on whether to log a request. @@ -71,17 +75,4 @@ protected function is_logging_enabled( array $config, ?string $query_string = nu */ return apply_filters( 'wpgraphql_logging_is_enabled', $is_enabled, $config ); } - - /** - * Checks if a query is an introspection query. - * - * @param string|null $query_string The GraphQL query string. - */ - protected function is_introspection_query( ?string $query_string ): bool { - if ( null === $query_string ) { - return false; - } - - return strpos( $query_string, '__schema' ) !== false; - } } diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/AdminUserRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/AdminUserRule.php new file mode 100644 index 00000000..e4d1e0e2 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Logger/Rules/AdminUserRule.php @@ -0,0 +1,41 @@ + $config The logging configuration. + * @param string|null $query_string The GraphQL query string. + * + * @return bool True if the rule passes (logging should continue). + */ + public function passes(array $config, ?string $query_string = null): bool { + + $is_admin_user = (bool) ( $config[ Basic_Configuration_Tab::ADMIN_USER_LOGGING ] ?? false ); + if ( ! $is_admin_user ) { + return true; + } + + return current_user_can( 'manage_options' ); + } + + /** + * Get the rule name for debugging. + */ + public function get_name(): string { + return 'admin_user_rule'; + } +} diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/EnabledRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/EnabledRule.php new file mode 100644 index 00000000..d8f98415 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Logger/Rules/EnabledRule.php @@ -0,0 +1,35 @@ + $config The logging configuration. + * @param string|null $query_string The GraphQL query string. + * + * @return bool True if the rule passes (logging should continue). + */ + public function passes(array $config, ?string $query_string = null): bool { + return (bool) ( $config[ Basic_Configuration_Tab::ENABLED ] ?? false ); + } + + /** + * Get the rule name for debugging. + */ + public function get_name(): string { + return 'enabled_rule'; + } +} diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/ExcludeQueryRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/ExcludeQueryRule.php new file mode 100644 index 00000000..2b57fc96 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Logger/Rules/ExcludeQueryRule.php @@ -0,0 +1,47 @@ + $config The logging configuration. + * @param string|null $query_string The GraphQL query string. + * + * @return bool True if the rule passes (logging should continue). + */ + public function passes(array $config, ?string $query_string = null): bool { + $queries = $config[ Basic_Configuration_Tab::EXCLUDE_QUERY ] ?? ''; + if ( null === $query_string ) { + return true; + } + + $excluded_queries = array_map( 'trim', explode( ',', $queries ) ); + foreach ( $excluded_queries as $excluded_query ) { + if ( stripos( $query_string, $excluded_query ) !== false ) { + return false; + } + } + + return true; + } + + /** + * Get the rule name for debugging. + */ + public function get_name(): string { + return 'exclude_query_rule'; + } +} diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/IpRestrictionsRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/IpRestrictionsRule.php new file mode 100644 index 00000000..da291574 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Logger/Rules/IpRestrictionsRule.php @@ -0,0 +1,49 @@ + $config The logging configuration. + * @param string|null $query_string The GraphQL query string. + * + * @return bool True if the rule passes (logging should continue). + */ + public function passes(array $config, ?string $query_string = null): bool { + + $ip_restrictions = $config[ Basic_Configuration_Tab::IP_RESTRICTIONS ] ?? ''; + if ( empty( $ip_restrictions ) ) { + return true; + } + $allowed_ips = array_map( 'trim', explode( ',', $ip_restrictions ) ); + if ( ! isset( $_SERVER['REMOTE_ADDR'] ) ) { // @phpcs:ignore WordPressVIPMinimum.Variables.ServerVariables.UserControlledHeaders + return false; + } + + $remote_addr = filter_var( $_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP ); // @phpcs:ignore WordPressVIPMinimum.Variables.ServerVariables.UserControlledHeaders, WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__REMOTE_ADDR__ + if ( false === $remote_addr ) { + return false; + } + return in_array( $remote_addr, $allowed_ips, true ); + } + + /** + * Get the rule name for debugging. + */ + public function get_name(): string { + return 'ip_restrictions'; + } +} diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/LogResponseRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/LogResponseRule.php new file mode 100644 index 00000000..6c734b30 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Logger/Rules/LogResponseRule.php @@ -0,0 +1,35 @@ + $config The logging configuration. + * @param string|null $query_string The GraphQL query string. + * + * @return bool True if the rule passes (logging should continue). + */ + public function passes(array $config, ?string $query_string = null): bool { + return (bool) ( $config[ Basic_Configuration_Tab::LOG_RESPONSE ] ?? false ); + } + + /** + * Get the rule name for debugging. + */ + public function get_name(): string { + return 'log_response_rule'; + } +} diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/LoggingRuleInterface.php b/plugins/wpgraphql-logging/src/Logger/Rules/LoggingRuleInterface.php new file mode 100644 index 00000000..cab5abd0 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Logger/Rules/LoggingRuleInterface.php @@ -0,0 +1,29 @@ + $config The logging configuration. + * @param string|null $query_string The GraphQL query string. + * + * @return bool True if the rule passes (logging should continue). + */ + public function passes(array $config, ?string $query_string = null): bool; + + /** + * Get the rule name for debugging. + */ + public function get_name(): string; +} diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/QueryNullRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/QueryNullRule.php new file mode 100644 index 00000000..b455ef5d --- /dev/null +++ b/plugins/wpgraphql-logging/src/Logger/Rules/QueryNullRule.php @@ -0,0 +1,33 @@ + $config The logging configuration. + * @param string|null $query_string The GraphQL query string. + * + * @return bool True if the rule passes (logging should continue). + */ + public function passes(array $config, ?string $query_string = null): bool { + return is_string( $query_string ) && '' !== trim( $query_string ); + } + + /** + * Get the rule name for debugging. + */ + public function get_name(): string { + return 'query_null_rule'; + } +} diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/RuleManager.php b/plugins/wpgraphql-logging/src/Logger/Rules/RuleManager.php new file mode 100644 index 00000000..4849f245 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Logger/Rules/RuleManager.php @@ -0,0 +1,39 @@ + */ + private array $rules = []; + + /** + * Add a rule to the manager. + */ + public function add_rule(LoggingRuleInterface $rule): void { + $this->rules[ $rule->get_name() ] = $rule; + } + + /** + * Check if all rules pass. + * + * @param array $config + * @param string|null $query_string + */ + public function all_rules_pass(array $config, ?string $query_string = null): bool { + foreach ( $this->rules as $rule ) { + if ( ! $rule->passes( $config, $query_string ) ) { + return false; + } + } + return true; + } +} diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/SamplingRateRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/SamplingRateRule.php new file mode 100644 index 00000000..450a8f65 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Logger/Rules/SamplingRateRule.php @@ -0,0 +1,37 @@ + $config The logging configuration. + * @param string|null $query_string The GraphQL query string. + * + * @return bool True if the rule passes (logging should continue). + */ + public function passes(array $config, ?string $query_string = null): bool { + $sampling_rate = (int) ( $config[ Basic_Configuration_Tab::DATA_SAMPLING ] ?? 100 ); + $rand = wp_rand( 0, 100 ); + return $rand <= $sampling_rate; + } + + /** + * Get the rule name for debugging. + */ + public function get_name(): string { + return 'enabled_rule'; + } +} diff --git a/plugins/wpgraphql-logging/src/Plugin.php b/plugins/wpgraphql-logging/src/Plugin.php index 61621f8b..0c590a96 100644 --- a/plugins/wpgraphql-logging/src/Plugin.php +++ b/plugins/wpgraphql-logging/src/Plugin.php @@ -101,9 +101,13 @@ public static function activate(): void { /** * Deactivation callback for the plugin. + * + * @since 0.0.1 */ public static function deactivate(): void { - // @TODO: Add configuration to determine if the table should be dropped on deactivation. + if ( ! defined( 'WP_GRAPHQL_LOGGING_UNINSTALL_PLUGIN' ) ) { + return; + } DatabaseEntity::drop_table(); }