diff --git a/.changeset/afraid-lobsters-hang.md b/.changeset/afraid-lobsters-hang.md new file mode 100644 index 00000000..6c314c87 --- /dev/null +++ b/.changeset/afraid-lobsters-hang.md @@ -0,0 +1,5 @@ +--- +"@wpengine/wpgraphql-logging-wordpress-plugin": patch +--- + +chore: Added data management for the logging plugin. diff --git a/plugins/wpgraphql-logging/README.md b/plugins/wpgraphql-logging/README.md index bc21377f..4060c0c5 100644 --- a/plugins/wpgraphql-logging/README.md +++ b/plugins/wpgraphql-logging/README.md @@ -91,7 +91,9 @@ wpgraphql-logging/ │ ├── Admin/ # Admin settings, menu, and settings page logic │ ├── Settings/ # Admin settings functionality for displaying and saving data. │ ├── Events/ # Event logging, pub/sub event manager for extending the logging. -│ ├── Logging/ # Logging logic, logger service, Monolog handlers & processors +│ ├── Logger/ # Logging logic, logger service, Monolog handlers & processors +│ ├── Rules/ # Rule Management on whether we log a query +│ ├── Scheduler/ # Automated data cleanup and maintenance tasks │ ├── Plugin.php # Main plugin class (entry point) │ └── Autoload.php # PSR-4 autoloader ├── tests/ # All test suites @@ -119,6 +121,12 @@ wpgraphql-logging/ - **Monolog-powered logging pipeline** - Default handler: stores logs in a WordPress table (`{$wpdb->prefix}wpgraphql_logging`). +- **Automated data management** + - **Daily cleanup scheduler**: Automatically removes old logs based on retention settings. + - **Configurable retention period**: Set how many days to keep log data (default: 30 days). + - **Manual cleanup**: Admin interface to trigger immediate cleanup of old logs. + - **Data sanitization**: Remove sensitive fields from logged data for privacy compliance. + - **Simple developer API** - `Plugin::on()` to subscribe, `Plugin::emit()` to publish, `Plugin::transform()` to modify payloads. @@ -127,6 +135,126 @@ wpgraphql-logging/ --- +## Data Sanitization + +WPGraphQL Logging includes robust data sanitization capabilities to help you protect sensitive information while maintaining useful logs for debugging and monitoring. The sanitization system allows you to automatically clean, anonymize, or remove sensitive fields from log records before they are stored. + +### Why Data Sanitization Matters + +When logging GraphQL requests, context data often contains sensitive information such as: +- User authentication tokens +- Personal identification information (PII) +- Password fields +- Session data +- Internal system information + +Data sanitization ensures compliance with privacy regulations (GDPR, CCPA) and security best practices while preserving the debugging value of your logs. + +### Sanitization Methods + +The plugin offers two sanitization approaches: + +#### 1. Recommended Rules (Default) +Pre-configured rules that automatically sanitize common WordPress and WPGraphQL sensitive fields: +- `request.app_context.viewer.data` - User data object +- `request.app_context.viewer.allcaps` - User capabilities +- `request.app_context.viewer.cap_key` - Capability keys +- `request.app_context.viewer.caps` - User capability array + +#### 2. Custom Rules +Define your own sanitization rules using dot notation to target specific fields: + +**Field Path Examples:** +``` +variables.password +request.headers.authorization +user.email +variables.input.creditCard +``` + +### Sanitization Actions + +For each field, you can choose from three sanitization actions: + +| Action | Description | Example | +|--------|-------------|---------| +| **Remove** | Completely removes the field from logs | `password: "secret123"` → *field removed* | +| **Anonymize** | Replaces value with `***` | `email: "user@example.com"` → `email: "***"` | +| **Truncate** | Limits string length to 47 characters + `...` | `longText: "Very long text..."` → `longText: "Very long text that gets cut off here and mo..."` | + +### Configuration + +Enable and configure data sanitization through the WordPress admin: + +1. Navigate to **GraphQL Logging → Settings** +2. Click the **Data Management** tab +3. Enable **Data Sanitization** +4. Choose your sanitization method: + - **Recommended**: Uses pre-configured rules for common sensitive fields + - **Custom**: Define your own field-specific rules + +#### Custom Configuration Fields + +When using custom rules, configure the following fields: + +- **Fields to Remove**: Comma-separated list of field paths to completely remove +- **Fields to Anonymize**: Comma-separated list of field paths to replace with `***` +- **Fields to Truncate**: Comma-separated list of field paths to limit length + +**Example Configuration:** +``` +Remove: variables.password, request.headers.authorization +Anonymize: user.email, variables.input.personalInfo +Truncate: query, variables.input.description +``` + +### Developer Hooks + +Customize sanitization behavior using WordPress filters: + +```php +// Enable/disable sanitization programmatically +add_filter( 'wpgraphql_logging_data_sanitization_enabled', function( $enabled ) { + return current_user_can( 'manage_options' ) ? false : $enabled; +}); + +// Modify recommended rules +add_filter( 'wpgraphql_logging_data_sanitization_recommended_rules', function( $rules ) { + $rules['custom.sensitive.field'] = 'remove'; + return $rules; +}); + +// Modify all sanitization rules +add_filter( 'wpgraphql_logging_data_sanitization_rules', function( $rules ) { + // Add additional rules or modify existing ones + $rules['request.custom_header'] = 'anonymize'; + return $rules; +}); + +// Modify the final log record after sanitization +add_filter( 'wpgraphql_logging_data_sanitization_record', function( $record ) { + // Additional processing after sanitization + return $record; +}); +``` + +### Performance Considerations + +- Sanitization runs on every log record when enabled +- Complex nested field paths may impact performance on high-traffic sites +- Consider using recommended rules for optimal performance +- Test custom rules thoroughly to ensure they target the intended fields + +### Security Best Practices + +1. **Review logs regularly** to ensure sanitization is working as expected +2. **Test field paths** in a development environment before applying to production +3. **Use remove over anonymize** for highly sensitive data +4. **Monitor performance impact** when implementing extensive custom rules +5. **Keep rules updated** as your GraphQL schema evolves + +--- + ## Usage WPGraphQL Logging Plugin is highly configurable and extendable and built with developers in mind to allow them to modify, change or add data, loggers etc to this plugin. Please read the docs below: diff --git a/plugins/wpgraphql-logging/assets/css/settings/wp-graphql-logging-settings.css b/plugins/wpgraphql-logging/assets/css/settings/wp-graphql-logging-settings.css index 556fc2e6..432d6e61 100644 --- a/plugins/wpgraphql-logging/assets/css/settings/wp-graphql-logging-settings.css +++ b/plugins/wpgraphql-logging/assets/css/settings/wp-graphql-logging-settings.css @@ -1,14 +1,15 @@ -settings_page_wpgraphql-logging #poststuff .postbox .inside h2 { +.settings_page_wpgraphql-logging #poststuff .postbox .inside h2 { font-size: 1.3em; font-weight: 600; padding-left: 0; } - +.form-table td input[type="number"], .form-table td input[type="text"] { width: calc(99% - 24px); display: inline-block; } + .form-table td select { width: calc(99% - 24px); display: inline-block; @@ -47,12 +48,11 @@ settings_page_wpgraphql-logging #poststuff .postbox .inside h2 { .wpgraphql-logging-tooltip .tooltip-text::after { content: ""; position: absolute; - top: 0; + top: 50%; left: -10px; border-width: 6px; border-style: solid; border-color: transparent #1d2327 transparent transparent; - top: 50%; transform: translateY(-50%); } @@ -71,16 +71,19 @@ settings_page_wpgraphql-logging #poststuff .postbox .inside h2 { .wpgraphql-logging-docs ul li:before { content: url(../../icons/doc.svg); height: 1em; + width: 0.5em; margin-left: -29px; margin-top: -2px; position: absolute; - width: 0.5em; } - .wpgraphql-logging-feature-list { list-style-type: disc; font-size: 1.1em; margin-left: 30px; padding-bottom: 16px; } + +.wpgraphql-logging-custom:not(.block) { + display: none; +} diff --git a/plugins/wpgraphql-logging/assets/js/settings/wp-graphql-logging-settings.js b/plugins/wpgraphql-logging/assets/js/settings/wp-graphql-logging-settings.js new file mode 100644 index 00000000..72c11d9f --- /dev/null +++ b/plugins/wpgraphql-logging/assets/js/settings/wp-graphql-logging-settings.js @@ -0,0 +1,22 @@ +document.addEventListener('DOMContentLoaded', function() { + const sanitizationMethodSelect = document.querySelector("#data_sanitization_method"); + if (!sanitizationMethodSelect) { + return; + } + + function toggleCustomFields() { + const isCustom = sanitizationMethodSelect.value === 'custom'; + const customElements = document.querySelectorAll('.wpgraphql-logging-custom'); + + customElements.forEach((el) => { + if (isCustom) { + el.classList.add('block'); + } else { + el.classList.remove('block'); + } + }); + } + + toggleCustomFields(); + sanitizationMethodSelect.addEventListener('change', toggleCustomFields); +}); diff --git a/plugins/wpgraphql-logging/composer.json b/plugins/wpgraphql-logging/composer.json index b06909b4..44d746f7 100644 --- a/plugins/wpgraphql-logging/composer.json +++ b/plugins/wpgraphql-logging/composer.json @@ -48,6 +48,7 @@ "johnpbloch/wordpress-core": "^6.8", "lucatume/wp-browser": "^4.0", "mockery/mockery": "^1.5", + "php-stubs/wordpress-stubs": "^6.8", "phpcompatibility/php-compatibility": "dev-develop as 9.99.99", "phpcompatibility/phpcompatibility-wp": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", diff --git a/plugins/wpgraphql-logging/composer.lock b/plugins/wpgraphql-logging/composer.lock index 4dcc5a52..188e898c 100644 --- a/plugins/wpgraphql-logging/composer.lock +++ b/plugins/wpgraphql-logging/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "80763354fb1a58eacf78bd70d5e8cfbc", + "content-hash": "be7c7a0aff5b08c432f2b70dc142b6f5", "packages": [ { "name": "league/csv", - "version": "9.24.1", + "version": "9.25.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "e0221a3f16aa2a823047d59fab5809d552e29bc8" + "reference": "f856f532866369fb1debe4e7c5a1db185f40ef86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/e0221a3f16aa2a823047d59fab5809d552e29bc8", - "reference": "e0221a3f16aa2a823047d59fab5809d552e29bc8", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/f856f532866369fb1debe4e7c5a1db185f40ef86", + "reference": "f856f532866369fb1debe4e7c5a1db185f40ef86", "shasum": "" }, "require": { @@ -33,7 +33,7 @@ "phpstan/phpstan-deprecation-rules": "^1.2.1", "phpstan/phpstan-phpunit": "^1.4.2", "phpstan/phpstan-strict-rules": "^1.6.2", - "phpunit/phpunit": "^10.5.16 || ^11.5.22", + "phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.3.6", "symfony/var-dumper": "^6.4.8 || ^7.3.0" }, "suggest": { @@ -95,7 +95,7 @@ "type": "github" } ], - "time": "2025-06-25T14:53:51+00:00" + "time": "2025-09-11T08:29:08+00:00" }, { "name": "monolog/monolog", @@ -1491,16 +1491,16 @@ }, { "name": "composer/composer", - "version": "2.8.11", + "version": "2.8.12", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "00e1a3396eea67033775c4a49c772376f45acd73" + "reference": "3e38919bc9a2c3c026f2151b5e56d04084ce8f0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/00e1a3396eea67033775c4a49c772376f45acd73", - "reference": "00e1a3396eea67033775c4a49c772376f45acd73", + "url": "https://api.github.com/repos/composer/composer/zipball/3e38919bc9a2c3c026f2151b5e56d04084ce8f0b", + "reference": "3e38919bc9a2c3c026f2151b5e56d04084ce8f0b", "shasum": "" }, "require": { @@ -1511,20 +1511,20 @@ "composer/semver": "^3.3", "composer/spdx-licenses": "^1.5.7", "composer/xdebug-handler": "^2.0.2 || ^3.0.3", - "justinrainbow/json-schema": "^6.3.1", + "justinrainbow/json-schema": "^6.5.1", "php": "^7.2.5 || ^8.0", "psr/log": "^1.0 || ^2.0 || ^3.0", - "react/promise": "^2.11 || ^3.3", + "react/promise": "^3.3", "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.2", "seld/signal-handler": "^2.0", - "symfony/console": "^5.4.35 || ^6.3.12 || ^7.0.3", - "symfony/filesystem": "^5.4.35 || ^6.3.12 || ^7.0.3", - "symfony/finder": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/console": "^5.4.47 || ^6.4.25 || ^7.1.10", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.1.10", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.1.10", "symfony/polyfill-php73": "^1.24", "symfony/polyfill-php80": "^1.24", "symfony/polyfill-php81": "^1.24", - "symfony/process": "^5.4.35 || ^6.3.12 || ^7.0.3" + "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10" }, "require-dev": { "phpstan/phpstan": "^1.11.8", @@ -1532,7 +1532,7 @@ "phpstan/phpstan-phpunit": "^1.4.0", "phpstan/phpstan-strict-rules": "^1.6.0", "phpstan/phpstan-symfony": "^1.4.0", - "symfony/phpunit-bridge": "^6.4.3 || ^7.0.1" + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3" }, "suggest": { "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", @@ -1585,7 +1585,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.8.11" + "source": "https://github.com/composer/composer/tree/2.8.12" }, "funding": [ { @@ -1597,7 +1597,7 @@ "type": "github" } ], - "time": "2025-08-21T09:29:39+00:00" + "time": "2025-09-19T11:41:59+00:00" }, { "name": "composer/metadata-minifier", @@ -3140,16 +3140,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.5.1", + "version": "6.5.2", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "b5ab21e431594897e5bb86343c01f140ba862c26" + "reference": "ac0d369c09653cf7af561f6d91a705bc617a87b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/b5ab21e431594897e5bb86343c01f140ba862c26", - "reference": "b5ab21e431594897e5bb86343c01f140ba862c26", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ac0d369c09653cf7af561f6d91a705bc617a87b8", + "reference": "ac0d369c09653cf7af561f6d91a705bc617a87b8", "shasum": "" }, "require": { @@ -3209,22 +3209,22 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.5.1" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.5.2" }, - "time": "2025-08-29T10:58:11+00:00" + "time": "2025-09-09T09:42:27+00:00" }, { "name": "lucatume/wp-browser", - "version": "4.5.5", + "version": "4.5.7", "source": { "type": "git", "url": "https://github.com/lucatume/wp-browser.git", - "reference": "5cf7588f6c23166e3ee53e2d21257bf25338323f" + "reference": "9c8bb63c26fe76ee5cfd06fac26d9b400a8216ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lucatume/wp-browser/zipball/5cf7588f6c23166e3ee53e2d21257bf25338323f", - "reference": "5cf7588f6c23166e3ee53e2d21257bf25338323f", + "url": "https://api.github.com/repos/lucatume/wp-browser/zipball/9c8bb63c26fe76ee5cfd06fac26d9b400a8216ef", + "reference": "9c8bb63c26fe76ee5cfd06fac26d9b400a8216ef", "shasum": "" }, "require": { @@ -3303,7 +3303,7 @@ ], "support": { "issues": "https://github.com/lucatume/wp-browser/issues", - "source": "https://github.com/lucatume/wp-browser/tree/4.5.5" + "source": "https://github.com/lucatume/wp-browser/tree/4.5.7" }, "funding": [ { @@ -3311,20 +3311,20 @@ "type": "github" } ], - "time": "2025-08-15T15:15:45+00:00" + "time": "2025-09-09T12:20:53+00:00" }, { "name": "marc-mabe/php-enum", - "version": "v4.7.1", + "version": "v4.7.2", "source": { "type": "git", "url": "https://github.com/marc-mabe/php-enum.git", - "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed" + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/7159809e5cfa041dca28e61f7f7ae58063aae8ed", - "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", "shasum": "" }, "require": { @@ -3382,9 +3382,9 @@ ], "support": { "issues": "https://github.com/marc-mabe/php-enum/issues", - "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.1" + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" }, - "time": "2024-11-28T04:54:44+00:00" + "time": "2025-09-14T11:18:39+00:00" }, { "name": "masterminds/html5", @@ -4176,18 +4176,18 @@ "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", - "reference": "ec975efc6752a2a41c2d0823e9c5e464c3c2dbb6" + "reference": "771535284869cd4f3ff178c292c032b804738771" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/ec975efc6752a2a41c2d0823e9c5e464c3c2dbb6", - "reference": "ec975efc6752a2a41c2d0823e9c5e464c3c2dbb6", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/771535284869cd4f3ff178c292c032b804738771", + "reference": "771535284869cd4f3ff178c292c032b804738771", "shasum": "" }, "require": { "php": ">=5.4", "phpcsstandards/phpcsutils": "^1.1.2", - "squizlabs/php_codesniffer": "^3.13.3" + "squizlabs/php_codesniffer": "^3.13.3 || ^4.0" }, "replace": { "wimg/php-compatibility": "*" @@ -4262,7 +4262,7 @@ "type": "thanks_dev" } ], - "time": "2025-09-05T15:02:16+00:00" + "time": "2025-09-09T21:00:45+00:00" }, { "name": "phpcompatibility/phpcompatibility-paragonie", @@ -4885,16 +4885,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.22", + "version": "2.1.28", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4" + "reference": "578fa296a166605d97b94091f724f1257185d278" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/41600c8379eb5aee63e9413fe9e97273e25d57e4", - "reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/578fa296a166605d97b94091f724f1257185d278", + "reference": "578fa296a166605d97b94091f724f1257185d278", "shasum": "" }, "require": { @@ -4939,7 +4939,7 @@ "type": "github" } ], - "time": "2025-08-04T19:17:37+00:00" + "time": "2025-09-19T08:58:49+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -5312,16 +5312,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.53", + "version": "10.5.55", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "32768472ebfb6969e6c7399f1c7b09009723f653" + "reference": "4b2d546b336876bd9562f24641b08a25335b06b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/32768472ebfb6969e6c7399f1c7b09009723f653", - "reference": "32768472ebfb6969e6c7399f1c7b09009723f653", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4b2d546b336876bd9562f24641b08a25335b06b6", + "reference": "4b2d546b336876bd9562f24641b08a25335b06b6", "shasum": "" }, "require": { @@ -5342,7 +5342,7 @@ "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.3", + "sebastian/comparator": "^5.0.4", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", "sebastian/exporter": "^5.1.2", @@ -5393,7 +5393,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.53" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.55" }, "funding": [ { @@ -5417,7 +5417,7 @@ "type": "tidelift" } ], - "time": "2025-08-20T14:40:06+00:00" + "time": "2025-09-14T06:19:20+00:00" }, { "name": "psr/container", @@ -7050,16 +7050,16 @@ }, { "name": "slevomat/coding-standard", - "version": "8.22.0", + "version": "8.22.1", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "a4cef983bad2e70125612d22b2f6e2bd1333d5c2" + "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/a4cef983bad2e70125612d22b2f6e2bd1333d5c2", - "reference": "a4cef983bad2e70125612d22b2f6e2bd1333d5c2", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/1dd80bf3b93692bedb21a6623c496887fad05fec", + "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec", "shasum": "" }, "require": { @@ -7071,11 +7071,11 @@ "require-dev": { "phing/phing": "3.0.1|3.1.0", "php-parallel-lint/php-parallel-lint": "1.4.0", - "phpstan/phpstan": "2.1.22", + "phpstan/phpstan": "2.1.24", "phpstan/phpstan-deprecation-rules": "2.0.3", "phpstan/phpstan-phpunit": "2.0.7", "phpstan/phpstan-strict-rules": "2.0.6", - "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.36|12.3.8" + "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.36|12.3.10" }, "type": "phpcodesniffer-standard", "extra": { @@ -7099,7 +7099,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.22.0" + "source": "https://github.com/slevomat/coding-standard/tree/8.22.1" }, "funding": [ { @@ -7111,7 +7111,7 @@ "type": "tidelift" } ], - "time": "2025-09-06T09:14:48+00:00" + "time": "2025-09-13T08:53:30+00:00" }, { "name": "softcreatr/jsonpath", @@ -9643,24 +9643,24 @@ }, { "name": "wp-cli/db-command", - "version": "v2.1.3", + "version": "v2.1.4", "source": { "type": "git", "url": "https://github.com/wp-cli/db-command.git", - "reference": "f857c91454d7092fa672bc388512a51752d9264a" + "reference": "c5277fe0335ea00c77b2790f2a892a692dfdf73c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/db-command/zipball/f857c91454d7092fa672bc388512a51752d9264a", - "reference": "f857c91454d7092fa672bc388512a51752d9264a", + "url": "https://api.github.com/repos/wp-cli/db-command/zipball/c5277fe0335ea00c77b2790f2a892a692dfdf73c", + "reference": "c5277fe0335ea00c77b2790f2a892a692dfdf73c", "shasum": "" }, "require": { - "wp-cli/wp-cli": "^2.12" + "wp-cli/wp-cli": "^2.13" }, "require-dev": { "wp-cli/entity-command": "^1.3 || ^2", - "wp-cli/wp-cli-tests": "^4" + "wp-cli/wp-cli-tests": "^5" }, "type": "wp-cli-package", "extra": { @@ -9711,9 +9711,9 @@ "homepage": "https://github.com/wp-cli/db-command", "support": { "issues": "https://github.com/wp-cli/db-command/issues", - "source": "https://github.com/wp-cli/db-command/tree/v2.1.3" + "source": "https://github.com/wp-cli/db-command/tree/v2.1.4" }, - "time": "2025-04-10T11:02:04+00:00" + "time": "2025-09-08T19:28:47+00:00" }, { "name": "wp-cli/embed-command", @@ -9784,20 +9784,20 @@ }, { "name": "wp-cli/entity-command", - "version": "v2.8.4", + "version": "v2.8.5", "source": { "type": "git", "url": "https://github.com/wp-cli/entity-command.git", - "reference": "213611f8ab619ca137d983e9b987f7fbf1ac21d4" + "reference": "896b7fb5ed51fe556017b2c71126947db5cd2b68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/entity-command/zipball/213611f8ab619ca137d983e9b987f7fbf1ac21d4", - "reference": "213611f8ab619ca137d983e9b987f7fbf1ac21d4", + "url": "https://api.github.com/repos/wp-cli/entity-command/zipball/896b7fb5ed51fe556017b2c71126947db5cd2b68", + "reference": "896b7fb5ed51fe556017b2c71126947db5cd2b68", "shasum": "" }, "require": { - "wp-cli/wp-cli": "^2.12" + "wp-cli/wp-cli": "^2.13" }, "require-dev": { "wp-cli/cache-command": "^1 || ^2", @@ -9805,7 +9805,7 @@ "wp-cli/extension-command": "^1.2 || ^2", "wp-cli/media-command": "^1.1 || ^2", "wp-cli/super-admin-command": "^1 || ^2", - "wp-cli/wp-cli-tests": "^4" + "wp-cli/wp-cli-tests": "^5" }, "type": "wp-cli-package", "extra": { @@ -10015,9 +10015,9 @@ "homepage": "https://github.com/wp-cli/entity-command", "support": { "issues": "https://github.com/wp-cli/entity-command/issues", - "source": "https://github.com/wp-cli/entity-command/tree/v2.8.4" + "source": "https://github.com/wp-cli/entity-command/tree/v2.8.5" }, - "time": "2025-05-06T16:12:49+00:00" + "time": "2025-09-12T10:52:53+00:00" }, { "name": "wp-cli/eval-command", @@ -10688,29 +10688,29 @@ }, { "name": "wp-cli/php-cli-tools", - "version": "v0.12.5", + "version": "v0.12.6", "source": { "type": "git", "url": "https://github.com/wp-cli/php-cli-tools.git", - "reference": "34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da" + "reference": "f12b650d3738e471baed6dd47982d53c5c0ab1c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da", - "reference": "34b83b4f700df8a4ec3fd17bf7e7e7d8ca5f28da", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/f12b650d3738e471baed6dd47982d53c5c0ab1c3", + "reference": "f12b650d3738e471baed6dd47982d53c5c0ab1c3", "shasum": "" }, "require": { - "php": ">= 5.6.0" + "php": ">= 7.2.24" }, "require-dev": { "roave/security-advisories": "dev-latest", - "wp-cli/wp-cli-tests": "^4" + "wp-cli/wp-cli-tests": "^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.11.x-dev" + "dev-master": "0.12.x-dev" } }, "autoload": { @@ -10745,9 +10745,9 @@ ], "support": { "issues": "https://github.com/wp-cli/php-cli-tools/issues", - "source": "https://github.com/wp-cli/php-cli-tools/tree/v0.12.5" + "source": "https://github.com/wp-cli/php-cli-tools/tree/v0.12.6" }, - "time": "2025-03-26T16:13:46+00:00" + "time": "2025-09-11T12:43:04+00:00" }, { "name": "wp-cli/process", diff --git a/plugins/wpgraphql-logging/docs/ConfigurationHelper.md b/plugins/wpgraphql-logging/docs/ConfigurationHelper.md new file mode 100644 index 00000000..706f68f9 --- /dev/null +++ b/plugins/wpgraphql-logging/docs/ConfigurationHelper.md @@ -0,0 +1,105 @@ +# Configuration Helper Usage Examples + +The `ConfigurationHelper` class provides a centralized and cached way to access WPGraphQL Logging configuration. This class implements a singleton pattern to ensure configuration is only loaded once per request and provides convenient methods for accessing different configuration sections. + +## Basic Usage + +### Getting the Configuration Helper Instance + +```php +use WPGraphQL\Logging\Admin\Settings\ConfigurationHelper; + +$config_helper = ConfigurationHelper::get_instance(); +``` + +### Getting Full Configuration + +```php +$config_helper = ConfigurationHelper::get_instance(); +$full_config = $config_helper->get_config(); +``` + +### Getting Configuration Sections + +```php +$config_helper = ConfigurationHelper::get_instance(); + +// Get basic configuration +$basic_config = $config_helper->get_basic_config(); + +// Get data management configuration +$data_management_config = $config_helper->get_data_management_config(); + +// Get any custom section +$custom_section = $config_helper->get_section_config('custom_section', []); +``` + +### Getting Specific Settings + +```php +$config_helper = ConfigurationHelper::get_instance(); + +// Get a specific setting from a section +$log_level = $config_helper->get_setting('basic_configuration', 'log_level', 'info'); + +// Check if a feature is enabled +$is_sanitization_enabled = $config_helper->is_enabled('data_management', 'data_sanitization_enabled'); +``` + +## Migration from Direct get_option() Usage + +### Before (old approach): +```php +$full_config = get_option( WPGRAPHQL_LOGGING_SETTINGS_KEY, [] ); +$basic_config = $full_config['basic_configuration'] ?? []; +$log_level = $basic_config['log_level'] ?? 'info'; +``` + +### After (using ConfigurationHelper): +```php +$config_helper = ConfigurationHelper::get_instance(); +$log_level = $config_helper->get_setting('basic_configuration', 'log_level', 'info'); +``` + +## Cache Management + +### Clearing Cache +```php +$config_helper = ConfigurationHelper::get_instance(); +$config_helper->clear_cache(); // Clears cache, next access will reload from DB +``` + +### Force Reload +```php +$config_helper = ConfigurationHelper::get_instance(); +$config_helper->reload_config(); // Clears cache and immediately reloads +``` + +## Benefits + +1. **Performance**: Configuration is cached in memory and only loaded once per request +2. **Consistency**: Centralized access point prevents inconsistent configuration retrieval +3. **Convenience**: Convenient methods for common access patterns +4. **Cache Management**: Automatic cache invalidation when settings are updated +5. **Type Safety**: Better type hints and documentation + +## Automatic Cache Invalidation + +The ConfigurationHelper automatically clears its cache when WordPress settings are updated. This is initialized in the main Plugin class: + +```php +// This is already set up in src/Plugin.php +ConfigurationHelper::init_cache_hooks(); +``` + +The cache hooks listen for: +- `update_option_{$option_key}` +- `add_option_{$option_key}` +- `delete_option_{$option_key}` + +## Performance Notes + +- Configuration is cached using WordPress's `wp_cache_*` functions +- Multiple cache groups are used for optimal performance +- Cache duration is set to 1 hour by default +- In-memory caching ensures subsequent accesses within the same request are instant diff --git a/plugins/wpgraphql-logging/docs/admin.md b/plugins/wpgraphql-logging/docs/admin.md index 09e35b47..cbaab46b 100644 --- a/plugins/wpgraphql-logging/docs/admin.md +++ b/plugins/wpgraphql-logging/docs/admin.md @@ -6,34 +6,34 @@ This document explains how the WPGraphQL Logging admin settings UI is built and ## Architecture Overview -- **Settings page entry**: `WPGraphQL\Logging\Admin\Settings_Page` +- **Settings page entry**: `WPGraphQL\Logging\Admin\SettingsPage` - Registers the submenu page and orchestrates fields and tabs - Hooks added: `init` (init fields), `admin_menu` (page), `admin_init` (fields), `admin_enqueue_scripts` (assets) -- **Menu page**: `WPGraphQL\Logging\Admin\Settings\Menu\Menu_Page` +- **Menu page**: `WPGraphQL\Logging\Admin\Settings\Menu\MenuPage` - Adds a submenu under Settings → WPGraphQL Logging (`wpgraphql-logging`) - Renders template `src/Admin/Settings/Templates/admin.php` -- **Form manager**: `WPGraphQL\Logging\Admin\Settings\Settings_Form_Manager` +- **Form manager**: `WPGraphQL\Logging\Admin\Settings\SettingsFormManager` - Registers the settings (`register_setting`) and sections/fields per tab - Sanitizes and saves values per tab; unknown fields are pruned -- **Field collection**: `WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Collection` - - Holds all tabs and fields. A default `Basic_Configuration_Tab` is registered -- **Tabs**: Implement `Settings_Tab_Interface` with `get_name()`, `get_label()`, `get_fields()` -- **Fields**: Implement `Settings_Field_Interface` or use built-ins: - - `Field\Checkbox_Field` - - `Field\Text_Input_Field` - - `Field\Select_Field` +- **Field collection**: `WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldCollection` + - Holds all tabs and fields. A default `BasicConfigurationTab` is registered +- **Tabs**: Implement `SettingsTabInterface` with `get_name()`, `get_label()`, `get_fields()` +- **Fields**: Implement `SettingsFieldInterface` or use built-ins: + - `Field\CheckboxField` + - `Field\TextInputField` + - `Field\SelectField` Settings are stored in an array option. Keys are filterable: - Option key: `wpgraphql_logging_settings` (filter `wpgraphql_logging_settings_group_option_key`) - Settings group: `wpgraphql_logging_settings_group` (filter `wpgraphql_logging_settings_group_settings_group`) -To read values at runtime, use `WPGraphQL\Logging\Admin\Settings\Logging_Settings_Service`: +To read values at runtime, use `WPGraphQL\Logging\Admin\Settings\LoggingSettingsService`: ```php -use WPGraphQL\Logging\Admin\Settings\Logging_Settings_Service; +use WPGraphQL\Logging\Admin\Settings\LoggingSettingsService; -$settings = new Logging_Settings_Service(); +$settings = new LoggingSettingsService(); $enabled = $settings->get_setting('basic_configuration', 'enabled', false); ``` @@ -41,11 +41,11 @@ $enabled = $settings->get_setting('basic_configuration', 'enabled', false); ## Hooks Reference (Admin) -- Action: `wpgraphql_logging_settings_init( Settings_Page $instance )` +- Action: `wpgraphql_logging_settings_init( SettingsPage $instance )` - Fired after the settings page is initialized -- Action: `wpgraphql_logging_settings_field_collection_init( Settings_Field_Collection $collection )` +- Action: `wpgraphql_logging_settings_field_collection_init( SettingsFieldCollection $collection )` - Fired after default tabs/fields are registered; primary extension point to add tabs/fields -- Action: `wpgraphql_logging_settings_form_manager_init( Settings_Form_Manager $manager )` +- Action: `wpgraphql_logging_settings_form_manager_init( SettingsFormManager $manager )` - Fired when the form manager is constructed - Filter: `wpgraphql_logging_settings_group_option_key( string $option_key )` - Change the option key used to store settings @@ -53,14 +53,14 @@ $enabled = $settings->get_setting('basic_configuration', 'enabled', false); - Change the settings group name used in `register_setting` - Filter: `wpgraphql_logging_basic_configuration_fields( array $fields )` - - Modify the default fields rendered in the `basic_configuration` tab. You can add, remove, or replace fields by returning a modified associative array of `field_id => Settings_Field_Interface`. + - Modify the default fields rendered in the `basic_configuration` tab. You can add, remove, or replace fields by returning a modified associative array of `field_id => SettingsFieldInterface`. - Example: ```php - use WPGraphQL\Logging\Admin\Settings\Fields\Field\Checkbox_Field; + use WPGraphQL\Logging\Admin\Settings\Fields\Field\CheckboxField; add_filter('wpgraphql_logging_basic_configuration_fields', function(array $fields): array { // Add a custom toggle into the Basic Configuration tab - $fields['enable_feature_x'] = new Checkbox_Field( + $fields['enable_feature_x'] = new CheckboxField( 'enable_feature_x', 'basic_configuration', 'Enable Feature X', @@ -69,7 +69,7 @@ $enabled = $settings->get_setting('basic_configuration', 'enabled', false); ); // Optionally remove an existing field - // unset($fields[ WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab::DATA_SAMPLING ]); + // unset($fields[ WPGraphQL\Logging\Admin\Settings\Fields\Tab\BasicConfigurationTab::DATA_SAMPLING ]); return $fields; }); @@ -84,17 +84,17 @@ Related (non-admin) hooks for context: ## Add a New Tab -Create a tab class implementing `Settings_Tab_Interface` and register it during `wpgraphql_logging_settings_field_collection_init`. +Create a tab class implementing `SettingsTabInterface` and register it during `wpgraphql_logging_settings_field_collection_init`. ```php new Text_Input_Field( + 'my_setting' => new TextInputField( 'my_setting', $this->get_name(), 'My Setting', @@ -117,7 +117,7 @@ class My_Custom_Tab implements Settings_Tab_Interface { } } -add_action('wpgraphql_logging_settings_field_collection_init', function (Settings_Field_Collection $collection): void { +add_action('wpgraphql_logging_settings_field_collection_init', function (SettingsFieldCollection $collection): void { $collection->add_tab(new My_Custom_Tab()); }); ``` @@ -137,13 +137,13 @@ You can add fields directly to the shared field collection. Ensure the field’s add_field( 'enable_feature_x', - new Checkbox_Field( + new CheckboxField( 'enable_feature_x', 'basic_configuration', // target the built-in Basic Configuration tab 'Enable Feature X', @@ -170,9 +170,9 @@ Tips: Example of reading a value elsewhere: ```php -use WPGraphQL\Logging\Admin\Settings\Logging_Settings_Service; +use WPGraphQL\Logging\Admin\Settings\LoggingSettingsService; -$settings = new Logging_Settings_Service(); +$settings = new LoggingSettingsService(); $thresholdSeconds = (float) $settings->get_setting('basic_configuration', 'performance_metrics', '0'); ``` diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/ConfigurationHelper.php b/plugins/wpgraphql-logging/src/Admin/Settings/ConfigurationHelper.php new file mode 100644 index 00000000..7cea4f49 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Admin/Settings/ConfigurationHelper.php @@ -0,0 +1,222 @@ +get_setting('basic_configuration', 'log_level', 'info'); + * + * // Check if a feature is enabled + * $is_enabled = $config->is_enabled('data_management', 'data_sanitization_enabled'); + * + * // Get an entire configuration section + * $basic_config = $config->get_basic_config(); + * ``` + * + * @package WPGraphQL\Logging + * + * @since 0.0.1 + */ +class ConfigurationHelper { + /** + * Cache group for configuration. + * + * @var string + */ + public const CACHE_GROUP = 'wpgraphql_logging_config'; + + /** + * Cache duration in seconds (1 hour). + * + * @var int + */ + public const CACHE_DURATION = 3600; + + /** + * The cached configuration values. + * + * @var array|null + */ + protected ?array $config = null; + + /** + * The single instance of this class. + * + * @var \WPGraphQL\Logging\Admin\Settings\ConfigurationHelper|null + */ + protected static ?ConfigurationHelper $instance = null; + + /** + * Get the singleton instance. + */ + public static function get_instance(): ConfigurationHelper { + if ( null === self::$instance ) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Get the full configuration array. + * + * @return array + */ + public function get_config(): array { + if ( null === $this->config ) { + $this->load_config(); + } + + return $this->config ?? []; + } + + /** + * Get configuration for a specific section (tab). + * + * @param string $section The configuration section key. + * @param array $default_value Default value if section not found. + * + * @return array + */ + public function get_section_config( string $section, array $default_value = [] ): array { + $config = $this->get_config(); + return $config[ $section ] ?? $default_value; + } + + /** + * Get a specific setting value from a configuration section. + * + * @param string $section The configuration section key. + * @param string $setting_key The setting key within the section. + * @param mixed $default_value Default value if setting not found. + */ + public function get_setting( string $section, string $setting_key, $default_value = null ): mixed { + $section_config = $this->get_section_config( $section ); + return $section_config[ $setting_key ] ?? $default_value; + } + + /** + * Get the basic configuration section. + * + * @return array + */ + public function get_basic_config(): array { + return $this->get_section_config( 'basic_configuration' ); + } + + /** + * Get the data management configuration section. + * + * @return array + */ + public function get_data_management_config(): array { + return $this->get_section_config( 'data_management' ); + } + + /** + * Check if a specific feature is enabled. + * + * @param string $section The configuration section. + * @param string $setting_key The setting key for the feature. + */ + public function is_enabled( string $section, string $setting_key ): bool { + return (bool) $this->get_setting( $section, $setting_key, false ); + } + + /** + * Clear the configuration cache. + * This forces a reload of the configuration on the next access. + */ + public function clear_cache(): void { + $this->config = null; + $option_key = $this->get_option_key(); + wp_cache_delete( $option_key, self::CACHE_GROUP ); + wp_cache_delete( $option_key, $this->get_settings_group() ); + } + + /** + * Reload the configuration from the database. + * This bypasses any cache and forces a fresh load. + */ + public function reload_config(): void { + $this->clear_cache(); + $this->load_config(); + } + + /** + * Get the option key for the settings. + */ + public function get_option_key(): string { + return (string) apply_filters( 'wpgraphql_logging_settings_group_option_key', WPGRAPHQL_LOGGING_SETTINGS_KEY ); + } + + /** + * Get the settings group for caching. + */ + public function get_settings_group(): string { + return (string) apply_filters( 'wpgraphql_logging_settings_group_settings_group', WPGRAPHQL_LOGGING_SETTINGS_GROUP ); + } + + /** + * Hook into WordPress to clear cache when settings are updated. + * This should be called during plugin initialization. + * + * @psalm-suppress PossiblyInvalidArgument + */ + public static function init_cache_hooks(): void { + $instance = self::get_instance(); + $option_key = $instance->get_option_key(); + + // Clear cache when the option is updated. + add_action( "update_option_{$option_key}", [ $instance, 'clear_cache' ] ); + add_action( "add_option_{$option_key}", [ $instance, 'clear_cache' ] ); + add_action( "delete_option_{$option_key}", [ $instance, 'clear_cache' ] ); + } + + /** + * Load the configuration from cache or database. + * + * @phpcs:disable WordPressVIPMinimum.Performance.LowExpiryCacheTime.CacheTimeUndetermined + */ + protected function load_config(): void { + $option_key = $this->get_option_key(); + + $cache_duration = self::CACHE_DURATION; + + // Try to get from wp_cache first (in-memory cache). + $cached_config = wp_cache_get( $option_key, self::CACHE_GROUP ); + if ( is_array( $cached_config ) ) { + $this->config = $cached_config; + return; + } + + // Try to get from the WordPress object cache (could be Redis, Memcached, etc.). + $cached_config = wp_cache_get( $option_key, $this->get_settings_group() ); + if ( is_array( $cached_config ) ) { + $this->config = $cached_config; + // Store in our custom cache group for faster access next time. + wp_cache_set( $option_key, $cached_config, self::CACHE_GROUP, $cache_duration ); + return; + } + + // Load from database. + $this->config = (array) get_option( $option_key, [] ); + + // Cache the result in both cache groups. + wp_cache_set( $option_key, $this->config, self::CACHE_GROUP, $cache_duration ); + wp_cache_set( $option_key, $this->config, $this->get_settings_group(), $cache_duration ); + } +} diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Abstract_Settings_Field.php b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/AbstractSettingsField.php similarity index 92% rename from plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Abstract_Settings_Field.php rename to plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/AbstractSettingsField.php index f42c0276..ab8270be 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Abstract_Settings_Field.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/AbstractSettingsField.php @@ -4,7 +4,8 @@ namespace WPGraphQL\Logging\Admin\Settings\Fields\Field; -use WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Interface; +use WPGraphQL\Logging\Admin\Settings\ConfigurationHelper; +use WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldInterface; /** * Abstract Settings Field class for WPGraphQL Logging. @@ -15,7 +16,7 @@ * * @since 0.0.1 */ -abstract class Abstract_Settings_Field implements Settings_Field_Interface { +abstract class AbstractSettingsField implements SettingsFieldInterface { /** * Constructor. * @@ -84,7 +85,8 @@ public function render_field_callback( array $args ): void { $tab_key = (string) ( $args['tab_key'] ?? '' ); $settings_key = (string) ( $args['settings_key'] ?? '' ); - $option_value = (array) get_option( $settings_key, [] ); + $config_helper = ConfigurationHelper::get_instance(); + $option_value = $config_helper->get_config(); $id = $this->get_field_name( $settings_key, $tab_key, $this->get_id() ); diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Checkbox_Field.php b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/CheckboxField.php similarity index 96% rename from plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Checkbox_Field.php rename to plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/CheckboxField.php index ba3a5f13..711d55b7 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Checkbox_Field.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/CheckboxField.php @@ -13,7 +13,7 @@ * * @since 0.0.1 */ -class Checkbox_Field extends Abstract_Settings_Field { +class CheckboxField extends AbstractSettingsField { /** * Render the checkbox field. * diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Select_Field.php b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/SelectField.php similarity index 98% rename from plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Select_Field.php rename to plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/SelectField.php index 1f25b812..68a061e8 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Select_Field.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/SelectField.php @@ -13,7 +13,7 @@ * * @since 0.0.1 */ -class Select_Field extends Abstract_Settings_Field { +class SelectField extends AbstractSettingsField { /** * Constructor. * diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Text_Input_Field.php b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/TextInputField.php similarity index 97% rename from plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Text_Input_Field.php rename to plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/TextInputField.php index 2c4d79ad..4d897327 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/Text_Input_Field.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/TextInputField.php @@ -13,7 +13,7 @@ * * @since 0.0.1 */ -class Text_Input_Field extends Abstract_Settings_Field { +class TextInputField extends AbstractSettingsField { /** * Constructor. * diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/TextIntegerField.php b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/TextIntegerField.php new file mode 100644 index 00000000..44abdba2 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Field/TextIntegerField.php @@ -0,0 +1,39 @@ + + * @var array<\WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldInterface> */ protected array $fields = []; /** * Array of tabs * - * @var array<\WPGraphQL\Logging\Admin\Settings\Fields\Tab\Settings_Tab_Interface> + * @var array<\WPGraphQL\Logging\Admin\Settings\Fields\Tab\SettingsTabInterface> */ protected array $tabs = []; @@ -33,12 +34,13 @@ class Settings_Field_Collection { * Constructor to initialize the fields. */ public function __construct() { - $this->add_tab( new Basic_Configuration_Tab() ); + $this->add_tab( new BasicConfigurationTab() ); + $this->add_tab( new DataManagementTab() ); do_action( 'wpgraphql_logging_settings_field_collection_init', $this ); } /** - * @return array<\WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Interface> + * @return array<\WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldInterface> */ public function get_fields(): array { return $this->fields; @@ -49,19 +51,19 @@ public function get_fields(): array { * * @param string $key The key of the field to retrieve. * - * @return \WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Interface|null The field if found, null otherwise. + * @return \WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldInterface|null The field if found, null otherwise. */ - public function get_field( string $key ): ?Settings_Field_Interface { + public function get_field( string $key ): ?SettingsFieldInterface { return $this->fields[ $key ] ?? null; } /** * Add a field to the collection. * - * @param string $key The key for the field. - * @param \WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Interface $field The field to add. + * @param string $key The key for the field. + * @param \WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldInterface $field The field to add. */ - public function add_field( string $key, Settings_Field_Interface $field ): void { + public function add_field( string $key, SettingsFieldInterface $field ): void { $this->fields[ $key ] = $field; } @@ -77,9 +79,9 @@ public function remove_field( string $key ): void { /** * Add a tab to the collection. * - * @param \WPGraphQL\Logging\Admin\Settings\Fields\Tab\Settings_Tab_Interface $tab The tab to add. + * @param \WPGraphQL\Logging\Admin\Settings\Fields\Tab\SettingsTabInterface $tab The tab to add. */ - public function add_tab( Settings_Tab_Interface $tab ): void { + public function add_tab( SettingsTabInterface $tab ): void { $this->tabs[ $tab->get_name() ] = $tab; foreach ( $tab->get_fields() as $field_key => $field ) { @@ -90,7 +92,7 @@ public function add_tab( Settings_Tab_Interface $tab ): void { /** * Get all tabs. * - * @return array<\WPGraphQL\Logging\Admin\Settings\Fields\Tab\Settings_Tab_Interface> + * @return array<\WPGraphQL\Logging\Admin\Settings\Fields\Tab\SettingsTabInterface> */ public function get_tabs(): array { return $this->tabs; @@ -101,9 +103,9 @@ public function get_tabs(): array { * * @param string $tab_name The name of the tab to retrieve. * - * @return \WPGraphQL\Logging\Admin\Settings\Fields\Tab\Settings_Tab_Interface|null The tab if found, null otherwise. + * @return \WPGraphQL\Logging\Admin\Settings\Fields\Tab\SettingsTabInterface|null The tab if found, null otherwise. */ - public function get_tab( string $tab_name ): ?Settings_Tab_Interface { + public function get_tab( string $tab_name ): ?SettingsTabInterface { return $this->tabs[ $tab_name ] ?? null; } } diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Settings_Field_Interface.php b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/SettingsFieldInterface.php similarity index 96% rename from plugins/wpgraphql-logging/src/Admin/Settings/Fields/Settings_Field_Interface.php rename to plugins/wpgraphql-logging/src/Admin/Settings/Fields/SettingsFieldInterface.php index 0332e3b0..a2a1cd95 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Settings_Field_Interface.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/SettingsFieldInterface.php @@ -11,7 +11,7 @@ * * @since 0.0.1 */ -interface Settings_Field_Interface { +interface SettingsFieldInterface { /** * Render the settings field * diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/Basic_Configuration_Tab.php b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/BasicConfigurationTab.php similarity index 85% rename from plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/Basic_Configuration_Tab.php rename to plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/BasicConfigurationTab.php index 92d49ed0..7d9c66b5 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/Basic_Configuration_Tab.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/BasicConfigurationTab.php @@ -4,9 +4,9 @@ namespace WPGraphQL\Logging\Admin\Settings\Fields\Tab; -use WPGraphQL\Logging\Admin\Settings\Fields\Field\Checkbox_Field; -use WPGraphQL\Logging\Admin\Settings\Fields\Field\Select_Field; -use WPGraphQL\Logging\Admin\Settings\Fields\Field\Text_Input_Field; +use WPGraphQL\Logging\Admin\Settings\Fields\Field\CheckboxField; +use WPGraphQL\Logging\Admin\Settings\Fields\Field\SelectField; +use WPGraphQL\Logging\Admin\Settings\Fields\Field\TextInputField; use WPGraphQL\Logging\Events\Events; /** @@ -16,7 +16,7 @@ * * @since 0.0.1 */ -class Basic_Configuration_Tab implements Settings_Tab_Interface { +class BasicConfigurationTab implements SettingsTabInterface { /** * The field ID for the enabled checkbox. * @@ -85,12 +85,12 @@ public function get_label(): string { /** * Get the fields for this tab. * - * @return array Array of fields keyed by field ID. + * @return array Array of fields keyed by field ID. */ public function get_fields(): array { $fields = []; - $fields[ self::ENABLED ] = new Checkbox_Field( + $fields[ self::ENABLED ] = new CheckboxField( self::ENABLED, $this->get_name(), __( 'Enabled', 'wpgraphql-logging' ), @@ -98,7 +98,7 @@ public function get_fields(): array { __( 'Enable or disable WPGraphQL logging.', 'wpgraphql-logging' ), ); - $fields[ self::IP_RESTRICTIONS ] = new Text_Input_Field( + $fields[ self::IP_RESTRICTIONS ] = new TextInputField( self::IP_RESTRICTIONS, $this->get_name(), __( 'IP Restrictions', 'wpgraphql-logging' ), @@ -107,7 +107,7 @@ 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( + $fields[ self::EXCLUDE_QUERY ] = new TextInputField( self::EXCLUDE_QUERY, $this->get_name(), __( 'Exclude Queries', 'wpgraphql-logging' ), @@ -116,7 +116,7 @@ public function get_fields(): array { __( 'e.g., __schema,SeedNode,__typename', 'wpgraphql-logging' ) ); - $fields[ self::ADMIN_USER_LOGGING ] = new Checkbox_Field( + $fields[ self::ADMIN_USER_LOGGING ] = new CheckboxField( self::ADMIN_USER_LOGGING, $this->get_name(), __( 'Admin User Logging', 'wpgraphql-logging' ), @@ -124,7 +124,7 @@ public function get_fields(): array { __( 'Log only for admin users.', 'wpgraphql-logging' ) ); - $fields[ self::DATA_SAMPLING ] = new Select_Field( + $fields[ self::DATA_SAMPLING ] = new SelectField( self::DATA_SAMPLING, $this->get_name(), __( 'Data Sampling Rate', 'wpgraphql-logging' ), @@ -140,7 +140,7 @@ public function get_fields(): array { false ); - $fields[ self::EVENT_LOG_SELECTION ] = new Select_Field( + $fields[ self::EVENT_LOG_SELECTION ] = new SelectField( self::EVENT_LOG_SELECTION, $this->get_name(), __( 'Log Points', 'wpgraphql-logging' ), @@ -157,7 +157,7 @@ public function get_fields(): array { true ); - $fields[ self::LOG_RESPONSE ] = new Checkbox_Field( + $fields[ self::LOG_RESPONSE ] = new CheckboxField( self::LOG_RESPONSE, $this->get_name(), __( 'Log Response', 'wpgraphql-logging' ), diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/DataManagementTab.php b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/DataManagementTab.php new file mode 100644 index 00000000..94cd5625 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/DataManagementTab.php @@ -0,0 +1,162 @@ + Array of fields keyed by field ID. + */ + public function get_fields(): array { + $fields = []; + + $fields[ self::DATA_DELETION_ENABLED ] = new CheckboxField( + self::DATA_DELETION_ENABLED, + $this->get_name(), + __( 'Data Deletion Enabled', 'wpgraphql-logging' ), + '', + __( 'Enable or disable data deletion for WPGraphQL logging.', 'wpgraphql-logging' ), + ); + + $fields[ self::DATA_RETENTION_DAYS ] = new TextIntegerField( + self::DATA_RETENTION_DAYS, + $this->get_name(), + __( 'Number of Days to Retain Logs', 'wpgraphql-logging' ), + '', + __( 'Number of days to retain log data before deletion.', 'wpgraphql-logging' ), + __( 'e.g., 30', 'wpgraphql-logging' ), + '30' + ); + + $fields[ self::DATA_SANITIZATION_ENABLED ] = new CheckboxField( + self::DATA_SANITIZATION_ENABLED, + $this->get_name(), + __( 'Data Sanitization Enabled', 'wpgraphql-logging' ), + '', + __( 'Enable or disable data sanitization for WPGraphQL logging.', 'wpgraphql-logging' ), + ); + + + $fields[ self::DATA_SANITIZATION_METHOD ] = new SelectField( + self::DATA_SANITIZATION_METHOD, + $this->get_name(), + __( 'Data Sanitization Method', 'wpgraphql-logging' ), + [ + 'recommended' => __( 'Recommended', 'wpgraphql-logging' ), + 'custom' => __( 'Custom', 'wpgraphql-logging' ), + ], + '', + __( 'Select the method to use for data sanitization.', 'wpgraphql-logging' ), + false + ); + + + $fields[ self::DATA_SANITIZATION_CUSTOM_FIELD_ANONYMIZE ] = new TextInputField( + self::DATA_SANITIZATION_CUSTOM_FIELD_ANONYMIZE, + $this->get_name(), + __( 'Custom Fields to Anonymize', 'wpgraphql-logging' ), + 'wpgraphql-logging-custom', + __( 'Comma-separated list of custom fields to anonymize.', 'wpgraphql-logging' ), + 'e.g., user_email, user_ip' + ); + + $fields[ self::DATA_SANITIZATION_CUSTOM_FIELD_REMOVE ] = new TextInputField( + self::DATA_SANITIZATION_CUSTOM_FIELD_REMOVE, + $this->get_name(), + __( 'Custom Fields to Remove', 'wpgraphql-logging' ), + 'wpgraphql-logging-custom', + __( 'Comma-separated list of custom fields to remove.', 'wpgraphql-logging' ), + ); + + $fields[ self::DATA_SANITIZATION_CUSTOM_FIELD_TRUNCATE ] = new TextInputField( + self::DATA_SANITIZATION_CUSTOM_FIELD_TRUNCATE, + $this->get_name(), + __( 'Custom Fields to Truncate', 'wpgraphql-logging' ), + 'wpgraphql-logging-custom', + __( 'Comma-separated list of custom fields to truncate.', 'wpgraphql-logging' ), + ); + + return apply_filters( 'wpgraphql_logging_data_management_fields', $fields ); + } +} diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/Settings_Tab_Interface.php b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/SettingsTabInterface.php similarity index 88% rename from plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/Settings_Tab_Interface.php rename to plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/SettingsTabInterface.php index 7c102479..08d417d5 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/Settings_Tab_Interface.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/SettingsTabInterface.php @@ -14,7 +14,7 @@ * * @since 0.0.1 */ -interface Settings_Tab_Interface { +interface SettingsTabInterface { /** * Get the name of the tab. * @@ -32,7 +32,7 @@ public function get_label(): string; /** * Get the fields for this tab. * - * @return array Array of fields keyed by field ID. + * @return array Array of fields keyed by field ID. */ public function get_fields(): array; } diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Logging_Settings_Service.php b/plugins/wpgraphql-logging/src/Admin/Settings/LoggingSettingsService.php similarity index 52% rename from plugins/wpgraphql-logging/src/Admin/Settings/Logging_Settings_Service.php rename to plugins/wpgraphql-logging/src/Admin/Settings/LoggingSettingsService.php index 833d447d..e84caa79 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Logging_Settings_Service.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/LoggingSettingsService.php @@ -13,19 +13,19 @@ * * @since 0.0.1 */ -class Logging_Settings_Service { +class LoggingSettingsService { /** - * The settings value + * The configuration helper instance. * - * @var array + * @var \WPGraphQL\Logging\Admin\Settings\ConfigurationHelper */ - protected array $settings_values = []; + protected ConfigurationHelper $config_helper; /** * Initialize the settings service. */ public function __construct() { - $this->setup(); + $this->config_helper = ConfigurationHelper::get_instance(); } /** @@ -34,7 +34,7 @@ public function __construct() { * @return array */ public function get_settings_values(): array { - return $this->settings_values; + return $this->config_helper->get_config(); } /** @@ -45,7 +45,8 @@ public function get_settings_values(): array { * @return array|null */ public function get_tab_config( string $tab_key ): ?array { - return $this->settings_values[ $tab_key ] ?? null; + $config = $this->config_helper->get_section_config( $tab_key ); + return empty( $config ) ? null : $config; } /** @@ -56,40 +57,20 @@ public function get_tab_config( string $tab_key ): ?array { * @param mixed $default_value The default value if not found. */ public function get_setting( string $tab_key, string $setting_key, $default_value = null ): mixed { - $tab_config = $this->get_tab_config( $tab_key ); - return $tab_config[ $setting_key ] ?? $default_value; + return $this->config_helper->get_setting( $tab_key, $setting_key, $default_value ); } /** * The option key for the settings group. */ public static function get_option_key(): string { - return (string) apply_filters( 'wpgraphql_logging_settings_group_option_key', WPGRAPHQL_LOGGING_SETTINGS_KEY ); + return ConfigurationHelper::get_instance()->get_option_key(); } /** * The settings group for the options. */ public static function get_settings_group(): string { - return (string) apply_filters( 'wpgraphql_logging_settings_group_settings_group', WPGRAPHQL_LOGGING_SETTINGS_GROUP ); - } - - /** - * Set up the settings values by retrieving them from the database or cache. - * This method is called in the constructor to ensure settings are available. - */ - protected function setup(): void { - $option_key = self::get_option_key(); - $settings_group = self::get_settings_group(); - - $value = wp_cache_get( $option_key, $settings_group ); - if ( is_array( $value ) ) { - $this->settings_values = $value; - - return; - } - - $this->settings_values = (array) get_option( $option_key, [] ); - wp_cache_set( $option_key, $this->settings_values, $settings_group ); + return ConfigurationHelper::get_instance()->get_settings_group(); } } diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Menu/Menu_Page.php b/plugins/wpgraphql-logging/src/Admin/Settings/Menu/MenuPage.php similarity index 99% rename from plugins/wpgraphql-logging/src/Admin/Settings/Menu/Menu_Page.php rename to plugins/wpgraphql-logging/src/Admin/Settings/Menu/MenuPage.php index 99e5052e..db66bb0d 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Menu/Menu_Page.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Menu/MenuPage.php @@ -13,7 +13,7 @@ * * @since 0.0.1 */ -class Menu_Page { +class MenuPage { /** * The title of the page. * diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Settings_Form_Manager.php b/plugins/wpgraphql-logging/src/Admin/Settings/SettingsFormManager.php similarity index 84% rename from plugins/wpgraphql-logging/src/Admin/Settings/Settings_Form_Manager.php rename to plugins/wpgraphql-logging/src/Admin/Settings/SettingsFormManager.php index 2fc32bd2..07c736e9 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Settings_Form_Manager.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/SettingsFormManager.php @@ -4,7 +4,7 @@ namespace WPGraphQL\Logging\Admin\Settings; -use WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Collection; +use WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldCollection; /** * Settings Form Manager class for WPGraphQL Logging. @@ -15,15 +15,15 @@ * * @since 0.0.1 */ -class Settings_Form_Manager { +class SettingsFormManager { /** - * @param \WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Collection $field_collection Collection of fields to be registered in the settings sections. + * @param \WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldCollection $field_collection Collection of fields to be registered in the settings sections. */ - public function __construct(readonly Settings_Field_Collection $field_collection ) { + public function __construct(readonly SettingsFieldCollection $field_collection ) { /** * Fire off init action. * - * @param \WPGraphQL\Logging\Admin\Settings\Settings_Form_Manager $instance the instance of the settings class. + * @param \WPGraphQL\Logging\Admin\Settings\SettingsFormManager $instance the instance of the settings class. */ do_action( 'wpgraphql_logging_settings_form_manager_init', $this ); } @@ -110,14 +110,14 @@ public function sanitize_settings( ?array $new_input ): array { * Get the option key for the settings group. */ public function get_option_key(): string { - return Logging_Settings_Service::get_option_key(); + return LoggingSettingsService::get_option_key(); } /** * Get the settings group for the options. */ public function get_settings_group(): string { - return Logging_Settings_Service::get_settings_group(); + return LoggingSettingsService::get_settings_group(); } /** @@ -135,7 +135,7 @@ protected function render_tab_section( string $tab_key, string $label ): void { add_settings_section( $page_id, $label, static fn() => null, $page_uri ); - /** @var \WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Interface $field */ + /** @var \WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldInterface $field */ foreach ( $fields as $field ) { if ( ! $field->should_render_for_tab( $tab_key ) ) { continue; diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Templates/admin.php b/plugins/wpgraphql-logging/src/Admin/Settings/Templates/admin.php index ef74a8dd..1a743da0 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Templates/admin.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Templates/admin.php @@ -9,7 +9,7 @@ declare(strict_types=1); -use WPGraphQL\Logging\Admin\Settings\Logging_Settings_Service; +use WPGraphQL\Logging\Admin\Settings\LoggingSettingsService; $wpgraphql_logging_tabs_config = (array) get_query_var( 'wpgraphql_logging_main_page_config' ); $wpgraphql_logging_current_tab = (string) ( $wpgraphql_logging_tabs_config['current_tab'] ?? '' ); @@ -37,7 +37,7 @@
@@ -67,24 +67,23 @@
  • -
  • -
  • -
  • +
  • +
  • +
  • +
  • -

    -

    - -

    -

    +

    +
      +
    • +
    • +
    • +
    • +
    - +
    @@ -106,7 +105,7 @@
    • -
    • +

    diff --git a/plugins/wpgraphql-logging/src/Admin/Settings_Page.php b/plugins/wpgraphql-logging/src/Admin/SettingsPage.php similarity index 80% rename from plugins/wpgraphql-logging/src/Admin/Settings_Page.php rename to plugins/wpgraphql-logging/src/Admin/SettingsPage.php index f49650a8..6a1244dd 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings_Page.php +++ b/plugins/wpgraphql-logging/src/Admin/SettingsPage.php @@ -4,13 +4,13 @@ namespace WPGraphQL\Logging\Admin; -use WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Collection; -use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Settings_Tab_Interface; -use WPGraphQL\Logging\Admin\Settings\Menu\Menu_Page; -use WPGraphQL\Logging\Admin\Settings\Settings_Form_Manager; +use WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldCollection; +use WPGraphQL\Logging\Admin\Settings\Fields\Tab\SettingsTabInterface; +use WPGraphQL\Logging\Admin\Settings\Menu\MenuPage; +use WPGraphQL\Logging\Admin\Settings\SettingsFormManager; /** - * Settings_Page class for WPGraphQL Logging. + * SettingsPage class for WPGraphQL Logging. * * This class handles the registration of the settings page, settings fields, and loading of scripts and styles for the plugin. * @@ -18,7 +18,7 @@ * * @since 0.0.1 */ -class Settings_Page { +class SettingsPage { /** * @var string The slug for the plugin menu. */ @@ -27,21 +27,21 @@ class Settings_Page { /** * The field collection. * - * @var \WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Collection|null + * @var \WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldCollection|null */ - protected ?Settings_Field_Collection $field_collection = null; + protected ?SettingsFieldCollection $field_collection = null; /** * The instance of the plugin. * - * @var \WPGraphQL\Logging\Admin\Settings_Page|null + * @var \WPGraphQL\Logging\Admin\SettingsPage|null */ - protected static ?Settings_Page $instance = null; + protected static ?SettingsPage $instance = null; /** * Initializes the settings page. */ - public static function init(): ?Settings_Page { + public static function init(): ?SettingsPage { if ( ! current_user_can( 'manage_options' ) ) { return null; } @@ -54,7 +54,7 @@ public static function init(): ?Settings_Page { /** * Fire off init action. * - * @param \WPGraphQL\Logging\Admin\Settings_Page $instance the instance of the plugin class. + * @param \WPGraphQL\Logging\Admin\SettingsPage $instance the instance of the plugin class. */ do_action( 'wpgraphql_logging_settings_init', self::$instance ); @@ -75,7 +75,7 @@ public function setup(): void { * Initialize the field collection. */ public function init_field_collection(): void { - $this->field_collection = new Settings_Field_Collection(); + $this->field_collection = new SettingsFieldCollection(); } /** @@ -90,14 +90,14 @@ public function register_settings_page(): void { $tab_labels = []; foreach ( $tabs as $tab_key => $tab ) { - if ( ! is_a( $tab, Settings_Tab_Interface::class ) ) { + if ( ! is_a( $tab, SettingsTabInterface::class ) ) { continue; } $tab_labels[ $tab_key ] = $tab->get_label(); } - $page = new Menu_Page( + $page = new MenuPage( __( 'WPGraphQL Logging Settings', 'wpgraphql-logging' ), 'WPGraphQL Logging', self::PLUGIN_MENU_SLUG, @@ -120,14 +120,14 @@ public function register_settings_fields(): void { if ( ! isset( $this->field_collection ) ) { return; } - $settings_manager = new Settings_Form_Manager( $this->field_collection ); + $settings_manager = new SettingsFormManager( $this->field_collection ); $settings_manager->render_form(); } /** * Get the current tab for the settings page. * - * @param array $tabs Optional. The available tabs. If not provided, uses the instance tabs. + * @param array $tabs Optional. The available tabs. If not provided, uses the instance tabs. * * @return string The current tab slug. */ @@ -195,9 +195,9 @@ public function load_scripts_styles( string $hook_suffix ): void { /** * Get the tabs for the settings page. * - * @param array $tabs Optional. The available tabs. If not provided, uses the instance tabs. + * @param array $tabs Optional. The available tabs. If not provided, uses the instance tabs. * - * @return array The tabs. + * @return array The tabs. */ protected function get_tabs(array $tabs = []): array { if ( ! empty( $tabs ) ) { diff --git a/plugins/wpgraphql-logging/src/Admin/View/Download/Download_Log_Service.php b/plugins/wpgraphql-logging/src/Admin/View/Download/DownloadLogService.php similarity index 98% rename from plugins/wpgraphql-logging/src/Admin/View/Download/Download_Log_Service.php rename to plugins/wpgraphql-logging/src/Admin/View/Download/DownloadLogService.php index b16f8ba5..4032be55 100644 --- a/plugins/wpgraphql-logging/src/Admin/View/Download/Download_Log_Service.php +++ b/plugins/wpgraphql-logging/src/Admin/View/Download/DownloadLogService.php @@ -14,7 +14,7 @@ * * @since 0.0.1 */ -class Download_Log_Service { +class DownloadLogService { /** * Generates and serves a CSV file for a single log entry. * diff --git a/plugins/wpgraphql-logging/src/Admin/View/List/List_Table.php b/plugins/wpgraphql-logging/src/Admin/View/List/ListTable.php similarity index 98% rename from plugins/wpgraphql-logging/src/Admin/View/List/List_Table.php rename to plugins/wpgraphql-logging/src/Admin/View/List/ListTable.php index 670d8e12..f6e16d76 100644 --- a/plugins/wpgraphql-logging/src/Admin/View/List/List_Table.php +++ b/plugins/wpgraphql-logging/src/Admin/View/List/ListTable.php @@ -14,7 +14,7 @@ } /** - * List_Table class for WPGraphQL Logging. + * ListTable class for WPGraphQL Logging. * * This class handles the display of the logs in a table format. * @@ -22,7 +22,7 @@ * * @since 0.0.1 */ -class List_Table extends WP_List_Table { +class ListTable extends WP_List_Table { /** * Default number of items per page. * @@ -299,7 +299,7 @@ public function column_cb( $item ): string { * @return string The rendered ID column or null. */ public function column_id( DatabaseEntity $item ): string { - $url = \WPGraphQL\Logging\Admin\View_Logs_Page::ADMIN_PAGE_SLUG; + $url = \WPGraphQL\Logging\Admin\ViewLogsPage::ADMIN_PAGE_SLUG; $actions = [ 'view' => sprintf( '%s', diff --git a/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-list.php b/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-list.php index 398ed9dc..fa26bef5 100644 --- a/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-list.php +++ b/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-list.php @@ -7,7 +7,7 @@ * * @package WPGraphQL\Logger\Admin\View\List\Templates * - * @var \WPGraphQL\Logging\Admin\View\List\List_Table $list_table List table instance. + * @var \WPGraphQL\Logging\Admin\View\List\ListTable $list_table List table instance. * * @since 0.0.1 */ diff --git a/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-view.php b/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-view.php index 7a782aac..cd105433 100644 --- a/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-view.php +++ b/plugins/wpgraphql-logging/src/Admin/View/Templates/wpgraphql-logger-view.php @@ -17,7 +17,7 @@ admin_url( sprintf( 'admin.php?page=%s&action=%s&log=%d', - \WPGraphQL\Logging\Admin\View_Logs_Page::ADMIN_PAGE_SLUG, + \WPGraphQL\Logging\Admin\ViewLogsPage::ADMIN_PAGE_SLUG, 'download', $log->get_id() ) @@ -71,7 +71,7 @@

    - +

    diff --git a/plugins/wpgraphql-logging/src/Admin/View_Logs_Page.php b/plugins/wpgraphql-logging/src/Admin/ViewLogsPage.php similarity index 95% rename from plugins/wpgraphql-logging/src/Admin/View_Logs_Page.php rename to plugins/wpgraphql-logging/src/Admin/ViewLogsPage.php index 5116eb59..6942b05d 100644 --- a/plugins/wpgraphql-logging/src/Admin/View_Logs_Page.php +++ b/plugins/wpgraphql-logging/src/Admin/ViewLogsPage.php @@ -4,8 +4,8 @@ namespace WPGraphQL\Logging\Admin; -use WPGraphQL\Logging\Admin\View\Download\Download_Log_Service; -use WPGraphQL\Logging\Admin\View\List\List_Table; +use WPGraphQL\Logging\Admin\View\Download\DownloadLogService; +use WPGraphQL\Logging\Admin\View\List\ListTable; use WPGraphQL\Logging\Logger\Database\LogsRepository; /** @@ -15,7 +15,7 @@ * * @since 0.0.1 */ -class View_Logs_Page { +class ViewLogsPage { /** * The admin page slug. * @@ -40,7 +40,7 @@ class View_Logs_Page { /** * Initializes the view logs page. */ - public static function init(): ?View_Logs_Page { + public static function init(): ?ViewLogsPage { if ( ! current_user_can( 'manage_options' ) ) { return null; } @@ -231,7 +231,7 @@ protected function get_post_value(string $key): ?string { */ protected function render_list_page(): void { // Variable required for list template. - $list_table = new List_Table( new LogsRepository() ); // @phpcs:ignore SlevomatCodingStandard.Variables.UnusedVariable.UnusedVariable + $list_table = new ListTable( new LogsRepository() ); // @phpcs:ignore SlevomatCodingStandard.Variables.UnusedVariable.UnusedVariable $list_template = apply_filters( 'wpgraphql_logging_list_template', __DIR__ . '/View/Templates/wpgraphql-logger-list.php' @@ -248,7 +248,7 @@ protected function process_log_download(): void { } $log_id = isset( $_GET['log'] ) ? absint( $_GET['log'] ) : 0; // @phpcs:ignore WordPress.Security.NonceVerification.Recommended - $downloader = new Download_Log_Service(); + $downloader = new DownloadLogService(); $downloader->generate_csv( $log_id ); } diff --git a/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php b/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php index 015358d4..ab18d09f 100644 --- a/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php +++ b/plugins/wpgraphql-logging/src/Events/QueryActionLogger.php @@ -6,7 +6,7 @@ use GraphQL\Executor\ExecutionResult; use Monolog\Level; -use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab; +use WPGraphQL\Logging\Admin\Settings\Fields\Tab\BasicConfigurationTab; use WPGraphQL\Logging\Logger\LoggerService; use WPGraphQL\Logging\Logger\LoggingHelper; use WPGraphQL\Request; @@ -64,7 +64,7 @@ public function log_pre_request( ?string $query, ?string $operation_name, ?array if ( ! $this->is_logging_enabled( $this->config, $query ) ) { return; } - $selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? []; + $selected_events = $this->config[ BasicConfigurationTab::EVENT_LOG_SELECTION ] ?? []; if ( ! is_array( $selected_events ) || empty( $selected_events ) ) { return; } @@ -107,7 +107,7 @@ public function log_graphql_before_execute( Request $request ): void { if ( ! $this->is_logging_enabled( $this->config, $params->query ) ) { return; } - $selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? []; + $selected_events = $this->config[ BasicConfigurationTab::EVENT_LOG_SELECTION ] ?? []; if ( ! is_array( $selected_events ) || empty( $selected_events ) ) { return; } @@ -157,20 +157,21 @@ public function log_before_response_returned( if ( ! $this->is_logging_enabled( $this->config, $query ) ) { return; } - $selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? []; + $selected_events = $this->config[ BasicConfigurationTab::EVENT_LOG_SELECTION ] ?? []; if ( ! is_array( $selected_events ) || empty( $selected_events ) ) { return; } if ( ! in_array( Events::BEFORE_RESPONSE_RETURNED, $selected_events, true ) ) { return; } - $context = [ + $encoded_request = wp_json_encode( $request ); + $context = [ 'response' => $response, 'schema' => $schema, 'operation_name' => $operation, 'query' => $query, 'variables' => $variables, - 'request' => $request, + 'request' => false !== $encoded_request ? json_decode( $encoded_request, true ) : null, 'query_id' => $query_id, ]; if ( ! $this->should_log_response( $this->config ) ) { diff --git a/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php b/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php index 2e309431..86d82607 100644 --- a/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php +++ b/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php @@ -4,6 +4,7 @@ namespace WPGraphQL\Logging\Events; +use WPGraphQL\Logging\Admin\Settings\ConfigurationHelper; use WPGraphQL\Logging\Logger\LoggerService; /** @@ -58,9 +59,9 @@ class QueryEventLifecycle { * @param \WPGraphQL\Logging\Logger\LoggerService $logger The logger instance. */ protected function __construct( LoggerService $logger ) { - $this->logger = $logger; - $full_config = get_option( WPGRAPHQL_LOGGING_SETTINGS_KEY, [] ); - $this->config = $full_config['basic_configuration'] ?? []; + $this->logger = $logger; + $config_helper = ConfigurationHelper::get_instance(); + $this->config = $config_helper->get_basic_config(); // Initialize the specialized logger components. $this->action_logger = new QueryActionLogger( $this->logger, $this->config ); diff --git a/plugins/wpgraphql-logging/src/Events/QueryFilterLogger.php b/plugins/wpgraphql-logging/src/Events/QueryFilterLogger.php index c6a5ca06..4a7d1891 100644 --- a/plugins/wpgraphql-logging/src/Events/QueryFilterLogger.php +++ b/plugins/wpgraphql-logging/src/Events/QueryFilterLogger.php @@ -6,7 +6,7 @@ use GraphQL\Executor\ExecutionResult; use Monolog\Level; -use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab; +use WPGraphQL\Logging\Admin\Settings\Fields\Tab\BasicConfigurationTab; use WPGraphQL\Logging\Logger\LoggerService; use WPGraphQL\Logging\Logger\LoggingHelper; use WPGraphQL\Logging\Logger\Rules\EnabledRule; @@ -63,7 +63,7 @@ public function __construct( LoggerService $logger, array $config ) { */ public function log_graphql_request_data( array $query_data ): array { try { - $selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? []; + $selected_events = $this->config[ BasicConfigurationTab::EVENT_LOG_SELECTION ] ?? []; if ( ! is_array( $selected_events ) || empty( $selected_events ) ) { return $query_data; } @@ -121,7 +121,7 @@ public function log_graphql_request_results( return $response; } - $selected_events = $this->config[ Basic_Configuration_Tab::EVENT_LOG_SELECTION ] ?? []; + $selected_events = $this->config[ BasicConfigurationTab::EVENT_LOG_SELECTION ] ?? []; if ( ! is_array( $selected_events ) || empty( $selected_events ) ) { return $response; } @@ -130,13 +130,14 @@ public function log_graphql_request_results( } /** @var \GraphQL\Server\OperationParams $params */ - $params = $request->params; - $context = [ + $params = $request->params; + $encoded_request = wp_json_encode( $request ); + $context = [ 'response' => $response, 'operation_name' => $params->operation, 'query' => $params->query, 'variables' => $params->variables, - 'request' => $request, + 'request' => false !== $encoded_request ? json_decode( $encoded_request, true ) : null, 'query_id' => $query_id, ]; if ( ! $this->should_log_response( $this->config ) ) { diff --git a/plugins/wpgraphql-logging/src/Logger/Database/LogsRepository.php b/plugins/wpgraphql-logging/src/Logger/Database/LogsRepository.php index d7f14398..bca698bf 100644 --- a/plugins/wpgraphql-logging/src/Logger/Database/LogsRepository.php +++ b/plugins/wpgraphql-logging/src/Logger/Database/LogsRepository.php @@ -4,6 +4,8 @@ namespace WPGraphQL\Logging\Logger\Database; +use DateTime; + /** * LogsRepository class for WPGraphQL Logging. * @@ -109,6 +111,23 @@ public function delete(int $id): bool { return false !== $result; } + /** + * Delete a logs entry older than a specific date. + * + * @param \DateTime $date The date to delete logs older than. + */ + public function delete_log_older_than(DateTime $date): bool { + global $wpdb; + $table_name = DatabaseEntity::get_table_name(); + + $result = $wpdb->query( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery + 'DELETE FROM %i WHERE datetime < %s', + $table_name, + $date->format( 'Y-m-d H:i:s' ) + ) ); + return false !== $result; + } + /** * Delete all log entries. */ diff --git a/plugins/wpgraphql-logging/src/Logger/LoggerService.php b/plugins/wpgraphql-logging/src/Logger/LoggerService.php index 30851de3..0c5111ed 100644 --- a/plugins/wpgraphql-logging/src/Logger/LoggerService.php +++ b/plugins/wpgraphql-logging/src/Logger/LoggerService.php @@ -12,6 +12,7 @@ use Monolog\Processor\ProcessorInterface; use Monolog\Processor\WebProcessor; use WPGraphQL\Logging\Logger\Handlers\WordPressDatabaseHandler; +use WPGraphQL\Logging\Logger\Processors\DataSanitizationProcessor; use WPGraphQL\Logging\Logger\Processors\RequestHeadersProcessor; /** @@ -225,6 +226,7 @@ public static function get_default_processors(): array { new WebProcessor(), // Logs web request data. e.g. IP address, request method, URI, etc. new ProcessIdProcessor(), // Logs the process ID. new RequestHeadersProcessor(), // Custom processor to capture request headers. + new DataSanitizationProcessor(), // Custom processor to sanitize data in log records. ]; // Filter for users to add their own processors. diff --git a/plugins/wpgraphql-logging/src/Logger/Processors/DataSanitizationProcessor.php b/plugins/wpgraphql-logging/src/Logger/Processors/DataSanitizationProcessor.php new file mode 100644 index 00000000..4fdd3ce4 --- /dev/null +++ b/plugins/wpgraphql-logging/src/Logger/Processors/DataSanitizationProcessor.php @@ -0,0 +1,206 @@ +> + */ + protected array $config; + + /** + * DataSanitizationProcessor constructor. + */ + public function __construct() { + $config_helper = ConfigurationHelper::get_instance(); + $this->config = $config_helper->get_data_management_config(); + } + + /** + * Check if data sanitization is enabled. + */ + protected function is_enabled(): bool { + $is_enabled = (bool) ( $this->config[ DataManagementTab::DATA_SANITIZATION_ENABLED ] ?? false ); + return apply_filters( 'wpgraphql_logging_data_sanitization_enabled', $is_enabled ); + } + + /** + * Get the sanitization rules based on the settings. + * + * @return array The sanitization rules. + */ + protected function get_rules(): array { + $method = $this->config[ DataManagementTab::DATA_SANITIZATION_METHOD ] ?? 'none'; + + + if ( 'recommended' === $method ) { + return apply_filters( 'wpgraphql_logging_data_sanitization_rules', $this->get_recommended_rules() ); + } + return apply_filters( 'wpgraphql_logging_data_sanitization_rules', $this->get_custom_rules() ); + } + + /** + * Get the recommended sanitization rules. + * + * @return array The recommended sanitization rules. + */ + protected function get_recommended_rules(): array { + $rules = [ + 'request.app_context.viewer.data' => 'remove', + 'request.app_context.viewer.allcaps' => 'remove', + 'request.app_context.viewer.cap_key' => 'remove', + 'request.app_context.viewer.caps' => 'remove', + ]; + + return apply_filters( 'wpgraphql_logging_data_sanitization_recommended_rules', $rules ); + } + + /** + * Get the custom sanitization rules based on the settings. + * + * @return array The custom sanitization rules. + */ + protected function get_custom_rules(): array { + + $rules = []; + $fields = [ + 'anonymize' => $this->config[ DataManagementTab::DATA_SANITIZATION_CUSTOM_FIELD_ANONYMIZE ] ?? [], + 'remove' => $this->config[ DataManagementTab::DATA_SANITIZATION_CUSTOM_FIELD_REMOVE ] ?? [], + 'truncate' => $this->config[ DataManagementTab::DATA_SANITIZATION_CUSTOM_FIELD_TRUNCATE ] ?? [], + ]; + + foreach ( $fields as $action => $field_string ) { + if ( empty( $field_string ) || ! is_string( $field_string ) ) { + continue; + } + + $field_string = trim( $field_string ); + $field_list = array_filter( + explode( ',', $field_string ), + static function ($value) { + return '' !== $value; + } + ); + + foreach ( $field_list as $field ) { + $rules[ $field ] = $action; + } + } + return $rules; + } + + /** + * Apply a sanitization rule to a specific key in the data array. + * + * @param array $data The data array to sanitize. + * @param string $key The key to apply the rule to (dot notation for nested keys). + * @param string $rule The sanitization rule ('anonymize', 'remove', 'truncate'). + */ + protected function apply_rule(array &$data, string $key, string $rule): void { + if ( empty( $data ) ) { + return; + } + + $keys = explode( '.', $key ); + $last_key = array_pop( $keys ); + $current = &$this->navigate_to_parent( $data, $keys ); + + if ( null === $current || ! array_key_exists( $last_key, $current ) ) { + return; + } + + $this->apply_sanitization_rule( $current, $last_key, $rule ); + } + + /** + * Navigate to the parent array of the target key. + * + * @param array $data The data array to navigate. + * @param array $keys The keys to navigate through. + * + * @return array|null The parent array or null if not found. + */ + protected function &navigate_to_parent(array &$data, array $keys): ?array { + $current = &$data; + foreach ( $keys as $segment ) { + if ( ! is_array( $current ) || ! isset( $current[ $segment ] ) ) { + $null = null; + return $null; + } + $current = &$current[ $segment ]; + } + return $current; + } + + /** + * Apply the sanitization rule to the target value. + * + * @param array $current The current array containing the target key. + * @param string $key The key to sanitize. + * @param string $rule The sanitization rule to apply. + * + * @phpcs:disable Generic.Metrics.NestingLevel.TooHigh + */ + protected function apply_sanitization_rule(array &$current, string $key, string $rule): void { + switch ( $rule ) { + case 'anonymize': + $current[ $key ] = '***'; + break; + case 'remove': + unset( $current[ $key ] ); + break; + case 'truncate': + if ( is_string( $current[ $key ] ) ) { + $current[ $key ] = substr( $current[ $key ], 0, 47 ) . '...'; + } + break; + } + } + + /** + * This method is called for each log record. It adds the captured + * request headers 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 ( ! $this->is_enabled() ) { + return $record; + } + + $rules = $this->get_rules(); + + if ( empty( $rules ) ) { + return $record; + } + + $context = $record['context'] ?? []; + $extra = $record['extra'] ?? []; + foreach ( $rules as $key => $rule ) { + $this->apply_rule( $context, $key, $rule ); + $this->apply_rule( $extra, $key, $rule ); + } + + $record = $record->with( context: $context, extra: $extra ); + return apply_filters( 'wpgraphql_logging_data_sanitization_record', $record ); + } +} diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/AdminUserRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/AdminUserRule.php index e4d1e0e2..dd346efa 100644 --- a/plugins/wpgraphql-logging/src/Logger/Rules/AdminUserRule.php +++ b/plugins/wpgraphql-logging/src/Logger/Rules/AdminUserRule.php @@ -4,7 +4,7 @@ namespace WPGraphQL\Logging\Logger\Rules; -use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab; +use WPGraphQL\Logging\Admin\Settings\Fields\Tab\BasicConfigurationTab; /** * Rule to check if logging should occur based on admin user setting. @@ -24,7 +24,7 @@ class AdminUserRule implements LoggingRuleInterface { */ public function passes(array $config, ?string $query_string = null): bool { - $is_admin_user = (bool) ( $config[ Basic_Configuration_Tab::ADMIN_USER_LOGGING ] ?? false ); + $is_admin_user = (bool) ( $config[ BasicConfigurationTab::ADMIN_USER_LOGGING ] ?? false ); if ( ! $is_admin_user ) { return true; } diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/EnabledRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/EnabledRule.php index d8f98415..2b95ffba 100644 --- a/plugins/wpgraphql-logging/src/Logger/Rules/EnabledRule.php +++ b/plugins/wpgraphql-logging/src/Logger/Rules/EnabledRule.php @@ -4,7 +4,7 @@ namespace WPGraphQL\Logging\Logger\Rules; -use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab; +use WPGraphQL\Logging\Admin\Settings\Fields\Tab\BasicConfigurationTab; /** * Rule to check if logging is enabled. @@ -23,7 +23,7 @@ class EnabledRule implements LoggingRuleInterface { * @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 ); + return (bool) ( $config[ BasicConfigurationTab::ENABLED ] ?? false ); } /** diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/ExcludeQueryRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/ExcludeQueryRule.php index 2b57fc96..9a2bf6e0 100644 --- a/plugins/wpgraphql-logging/src/Logger/Rules/ExcludeQueryRule.php +++ b/plugins/wpgraphql-logging/src/Logger/Rules/ExcludeQueryRule.php @@ -4,7 +4,7 @@ namespace WPGraphQL\Logging\Logger\Rules; -use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab; +use WPGraphQL\Logging\Admin\Settings\Fields\Tab\BasicConfigurationTab; /** * Rule to check if the query is excluded from logging. @@ -23,8 +23,8 @@ class ExcludeQueryRule implements LoggingRuleInterface { * @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 ) { + $queries = $config[ BasicConfigurationTab::EXCLUDE_QUERY ] ?? ''; + if ( null === $query_string || '' === trim( $queries ) ) { return true; } diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/IpRestrictionsRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/IpRestrictionsRule.php index da291574..2b1ff8ef 100644 --- a/plugins/wpgraphql-logging/src/Logger/Rules/IpRestrictionsRule.php +++ b/plugins/wpgraphql-logging/src/Logger/Rules/IpRestrictionsRule.php @@ -4,7 +4,7 @@ namespace WPGraphQL\Logging\Logger\Rules; -use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab; +use WPGraphQL\Logging\Admin\Settings\Fields\Tab\BasicConfigurationTab; /** * Rule to check if logging should occur based on IP restrictions. @@ -24,7 +24,7 @@ class IpRestrictionsRule implements LoggingRuleInterface { */ public function passes(array $config, ?string $query_string = null): bool { - $ip_restrictions = $config[ Basic_Configuration_Tab::IP_RESTRICTIONS ] ?? ''; + $ip_restrictions = $config[ BasicConfigurationTab::IP_RESTRICTIONS ] ?? ''; if ( empty( $ip_restrictions ) ) { return true; } diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/LogResponseRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/LogResponseRule.php index 6c734b30..4c2b1c09 100644 --- a/plugins/wpgraphql-logging/src/Logger/Rules/LogResponseRule.php +++ b/plugins/wpgraphql-logging/src/Logger/Rules/LogResponseRule.php @@ -4,7 +4,7 @@ namespace WPGraphQL\Logging\Logger\Rules; -use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab; +use WPGraphQL\Logging\Admin\Settings\Fields\Tab\BasicConfigurationTab; /** * Rule to check if we should log the response. @@ -23,7 +23,7 @@ class LogResponseRule implements LoggingRuleInterface { * @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 ); + return (bool) ( $config[ BasicConfigurationTab::LOG_RESPONSE ] ?? false ); } /** diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/SamplingRateRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/SamplingRateRule.php index 450a8f65..0358469e 100644 --- a/plugins/wpgraphql-logging/src/Logger/Rules/SamplingRateRule.php +++ b/plugins/wpgraphql-logging/src/Logger/Rules/SamplingRateRule.php @@ -4,7 +4,7 @@ namespace WPGraphQL\Logging\Logger\Rules; -use WPGraphQL\Logging\Admin\Settings\Fields\Tab\Basic_Configuration_Tab; +use WPGraphQL\Logging\Admin\Settings\Fields\Tab\BasicConfigurationTab; /** * Rule to check if logging is enabled. @@ -23,7 +23,7 @@ class SamplingRateRule implements LoggingRuleInterface { * @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 ); + $sampling_rate = (int) ( $config[ BasicConfigurationTab::DATA_SAMPLING ] ?? 100 ); $rand = wp_rand( 0, 100 ); return $rand <= $sampling_rate; } diff --git a/plugins/wpgraphql-logging/src/Logger/Scheduler/DataDeletionScheduler.php b/plugins/wpgraphql-logging/src/Logger/Scheduler/DataDeletionScheduler.php new file mode 100644 index 00000000..9acf5d7c --- /dev/null +++ b/plugins/wpgraphql-logging/src/Logger/Scheduler/DataDeletionScheduler.php @@ -0,0 +1,136 @@ +> + */ + protected array $config; + + /** + * The single instance of the class. + * + * @var \WPGraphQL\Logging\Logger\Scheduler\DataDeletionScheduler|null + */ + protected static ?DataDeletionScheduler $instance = null; + + /** + * Private constructor to prevent direct instantiation. + */ + protected function __construct(readonly LogsRepository $repository) { + $config_helper = ConfigurationHelper::get_instance(); + $this->config = $config_helper->get_data_management_config(); + } + + /** + * Initialize the scheduler. + */ + public static function init(): self { + + if ( null === self::$instance ) { + self::$instance = new self( new LogsRepository() ); + self::$instance->setup(); + } + + return self::$instance; + } + + /** + * Schedule the daily deletion task if it's not already scheduled. + */ + public function schedule_deletion(): void { + if ( false === wp_next_scheduled( self::DELETION_HOOK ) ) { + wp_schedule_event( time(), 'daily', self::DELETION_HOOK ); + } + } + + /** + * Perform the actual deletion of old log data. + * + * This method is called by the WordPress cron system. + */ + public function perform_deletion(): void { + + if ( false === (bool) $this->config[ DataManagementTab::DATA_DELETION_ENABLED ] ) { + return; + } + + $retention_days = $this->config[ DataManagementTab::DATA_RETENTION_DAYS ]; + if ( ! is_numeric( $retention_days ) ) { + return; + } + $retention_days = (int) $retention_days; + if ( $retention_days < 1 ) { + return; + } + + try { + self::delete_old_logs( $retention_days ); + } catch ( \Throwable $e ) { + do_action('wpgraphql_logging_cleanup_error', [ + 'error_message' => $e->getMessage(), + 'retention_days' => $retention_days, + 'timestamp' => current_time( 'mysql' ), + ]); + } + } + + /** + * Clear the scheduled cleanup task. + * + * This is typically called on plugin deactivation. + */ + public static function clear_scheduled_deletion(): void { + $timestamp = wp_next_scheduled( self::DELETION_HOOK ); + if ( false !== $timestamp ) { + wp_unschedule_event( $timestamp, self::DELETION_HOOK ); + } + } + + /** + * Initialize the scheduler by registering hooks. + */ + protected function setup(): void { + add_action( 'init', [ $this, 'schedule_deletion' ], 10, 0 ); + add_action( self::DELETION_HOOK, [ $this, 'perform_deletion' ], 10, 0 ); + + // Clear scheduled event on deactivation. + register_deactivation_hook( __FILE__, [ $this, 'clear_scheduled_deletion' ] ); + } + + /** + * Delete log entries older than the specified number of days. + * + * @param int $retention_days Number of days to retain logs. + */ + protected function delete_old_logs(int $retention_days): void { + $date_time = new \DateTime(); + $date_time->modify( "-{$retention_days} days" ); + $this->repository->delete_log_older_than( $date_time ); + } +} diff --git a/plugins/wpgraphql-logging/src/Plugin.php b/plugins/wpgraphql-logging/src/Plugin.php index 0c590a96..ab86a0b0 100644 --- a/plugins/wpgraphql-logging/src/Plugin.php +++ b/plugins/wpgraphql-logging/src/Plugin.php @@ -4,11 +4,13 @@ namespace WPGraphQL\Logging; -use WPGraphQL\Logging\Admin\Settings_Page; -use WPGraphQL\Logging\Admin\View_Logs_Page; +use WPGraphQL\Logging\Admin\Settings\ConfigurationHelper; +use WPGraphQL\Logging\Admin\SettingsPage; +use WPGraphQL\Logging\Admin\ViewLogsPage; use WPGraphQL\Logging\Events\EventManager; use WPGraphQL\Logging\Events\QueryEventLifecycle; use WPGraphQL\Logging\Logger\Database\DatabaseEntity; +use WPGraphQL\Logging\Logger\Scheduler\DataDeletionScheduler; /** * Plugin class for WPGraphQL Logging. @@ -54,9 +56,13 @@ public static function init(): self { * Initialize the plugin admin, frontend & api functionality. */ public function setup(): void { - Settings_Page::init(); - View_Logs_Page::init(); + // Initialize configuration caching hooks. + ConfigurationHelper::init_cache_hooks(); + + SettingsPage::init(); + ViewLogsPage::init(); QueryEventLifecycle::init(); + DataDeletionScheduler::init(); } /** @@ -105,6 +111,9 @@ public static function activate(): void { * @since 0.0.1 */ public static function deactivate(): void { + + DataDeletionScheduler::clear_scheduled_deletion(); + if ( ! defined( 'WP_GRAPHQL_LOGGING_UNINSTALL_PLUGIN' ) ) { return; } diff --git a/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/CheckboxFieldTest.php b/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/CheckboxFieldTest.php index 1ff8290f..e456f5db 100644 --- a/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/CheckboxFieldTest.php +++ b/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/CheckboxFieldTest.php @@ -4,17 +4,17 @@ namespace WPGraphQL\Logging\wpunit\Admin\Settings\Fields\Field; -use WPGraphQL\Logging\Admin\Settings\Fields\Field\Checkbox_Field; -use WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Interface; +use WPGraphQL\Logging\Admin\Settings\Fields\Field\CheckboxField; +use WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldInterface; use lucatume\WPBrowser\TestCase\WPTestCase; class CheckboxFieldTest extends WPTestCase { - protected ?Checkbox_Field $field = null; + protected ?CheckboxField $field = null; protected function setUp(): void { parent::setUp(); - $this->field = new Checkbox_Field( + $this->field = new CheckboxField( 'enable_logging', 'basic_configuration', 'Enable Logging', diff --git a/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/SelectFieldTest.php b/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/SelectFieldTest.php index eef473b8..aa07150e 100644 --- a/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/SelectFieldTest.php +++ b/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/SelectFieldTest.php @@ -4,18 +4,18 @@ namespace WPGraphQL\Logging\wpunit\Admin\Settings\Fields\Field; -use WPGraphQL\Logging\Admin\Settings\Fields\Field\Select_Field; -use WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Interface; +use WPGraphQL\Logging\Admin\Settings\Fields\Field\SelectField; +use WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldInterface; use lucatume\WPBrowser\TestCase\WPTestCase; class SelectFieldTest extends WPTestCase { - protected ?Select_Field $field = null; - protected ?Select_Field $multipleField = null; + protected ?SelectField $field = null; + protected ?SelectField $multipleField = null; protected function setUp(): void { parent::setUp(); - $this->field = new Select_Field( + $this->field = new SelectField( 'log_level', 'basic_configuration', 'Log Level', @@ -30,7 +30,7 @@ protected function setUp(): void { false ); - $this->multipleField = new Select_Field( + $this->multipleField = new SelectField( 'query_types', 'basic_configuration', 'Query Types', diff --git a/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/TextInputFieldTest.php b/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/TextInputFieldTest.php index d2c877dc..0b436cc1 100644 --- a/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/TextInputFieldTest.php +++ b/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings/Fields/Field/TextInputFieldTest.php @@ -4,17 +4,17 @@ namespace WPGraphQL\Logging\wpunit\Admin\Settings\Fields\Field; -use WPGraphQL\Logging\Admin\Settings\Fields\Field\Text_Input_Field; -use WPGraphQL\Logging\Admin\Settings\Fields\Settings_Field_Interface; +use WPGraphQL\Logging\Admin\Settings\Fields\Field\TextInputField; +use WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldInterface; use lucatume\WPBrowser\TestCase\WPTestCase; class TextInputFieldTest extends WPTestCase { - protected ?Text_Input_Field $field = null; + protected ?TextInputField $field = null; protected function setUp(): void { parent::setUp(); - $this->field = new Text_Input_Field( + $this->field = new TextInputField( 'ip_restrictions', 'basic_configuration', 'IP Restrictions', @@ -79,7 +79,7 @@ public function test_render_field() { public function test_sanitize_field_email() { - $field = new Text_Input_Field( + $field = new TextInputField( 'email_address', 'basic_configuration', 'Email Address', @@ -95,7 +95,7 @@ public function test_sanitize_field_email() { public function test_sanitize_field_url() { - $field = new Text_Input_Field( + $field = new TextInputField( 'url', 'basic_configuration', 'URL', @@ -143,7 +143,7 @@ public function test_render_field_callback() { public function test_get_field_value() { // Use a field with a non-empty default value to verify fallbacks - $field = new Text_Input_Field( + $field = new TextInputField( 'test_field', 'basic_configuration', 'Test Field', @@ -180,7 +180,7 @@ public function test_get_field_value() { // 5) Empty field ID -> default // Create a subclass overriding get_id to simulate empty ID - $emptyIdField = new class('ignored', $tab_key, 'Title', 'css', 'desc', 'ph', 'DEF') extends Text_Input_Field { + $emptyIdField = new class('ignored', $tab_key, 'Title', 'css', 'desc', 'ph', 'DEF') extends TextInputField { public function get_id(): string { return ''; } }; $html = $emptyIdField->render_field( [ $tab_key => [ 'ignored' => 'value' ] ], $settings_key, $tab_key ); diff --git a/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings_Page_Test.php b/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings_Page_Test.php index eb6b8fe8..2682123b 100644 --- a/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings_Page_Test.php +++ b/plugins/wpgraphql-logging/tests/wpunit/Admin/Settings_Page_Test.php @@ -5,7 +5,7 @@ namespace WPGraphQL\Logging\wpunit\Admin; use lucatume\WPBrowser\TestCase\WPTestCase; -use WPGraphQL\Logging\Admin\Settings_Page; +use WPGraphQL\Logging\Admin\SettingsPage; use ReflectionClass; class Settings_Page_Test extends WPTestCase { @@ -26,20 +26,20 @@ protected function tearDown(): void { } public function test_settings_page_instance() { - $reflection = new ReflectionClass( Settings_Page::class ); + $reflection = new ReflectionClass( SettingsPage::class ); $instanceProperty = $reflection->getProperty( 'instance' ); $instanceProperty->setAccessible( true ); $instanceProperty->setValue( null ); $this->assertNull( $instanceProperty->getValue() ); - $instance = Settings_Page::init(); + $instance = SettingsPage::init(); - $this->assertInstanceOf( Settings_Page::class, $instanceProperty->getValue() ); - $this->assertSame( $instance, $instanceProperty->getValue(), 'Settings_Page::init() should set the static instance property' ); + $this->assertInstanceOf( SettingsPage::class, $instanceProperty->getValue() ); + $this->assertSame( $instance, $instanceProperty->getValue(), 'SettingsPage::init() should set the static instance property' ); } public function test_setup_registers_hooks(): void { - $page = new Settings_Page(); + $page = new SettingsPage(); $page->setup(); $this->assertEquals(10, has_action('init', [$page, 'init_field_collection'])); @@ -52,7 +52,7 @@ public function test_register_settings_page_no_field_collection_does_nothing(): global $submenu; $submenu = []; // reset - $page = new Settings_Page(); + $page = new SettingsPage(); // Do not call init_field_collection() to trigger early return $page->register_settings_page(); @@ -60,19 +60,19 @@ public function test_register_settings_page_no_field_collection_does_nothing(): } public function test_get_current_tab_behaviour(): void { - $page = new Settings_Page(); + $page = new SettingsPage(); // With no tabs provided -> default $this->assertSame('basic_configuration', $page->get_current_tab([])); // Provide custom tabs and no $_GET -> default $tabs = [ - 'basic_configuration' => new class implements \WPGraphQL\Logging\Admin\Settings\Fields\Tab\Settings_Tab_Interface { + 'basic_configuration' => new class implements \WPGraphQL\Logging\Admin\Settings\Fields\Tab\SettingsTabInterface { public function get_name(): string { return 'basic_configuration'; } public function get_label(): string { return 'Basic Configuration'; } public function get_fields(): array { return []; } }, - 'advanced' => new class implements \WPGraphQL\Logging\Admin\Settings\Fields\Tab\Settings_Tab_Interface { + 'advanced' => new class implements \WPGraphQL\Logging\Admin\Settings\Fields\Tab\SettingsTabInterface { public function get_name(): string { return 'advanced'; } public function get_label(): string { return 'Advanced'; } public function get_fields(): array { return []; } @@ -91,7 +91,7 @@ public function get_fields(): array { return []; } } public function test_load_scripts_styles_enqueues_assets_conditionally(): void { - $page = new Settings_Page(); + $page = new SettingsPage(); // Wrong page hook -> nothing enqueued $page->load_scripts_styles('some_other_page'); @@ -99,7 +99,7 @@ public function test_load_scripts_styles_enqueues_assets_conditionally(): void { $this->assertFalse(wp_script_is('wpgraphql-logging-settings-js', 'enqueued')); // Correct page hook -> stylesheet should enqueue if file exists; script only if file exists - $page->load_scripts_styles('settings_page_' . Settings_Page::PLUGIN_MENU_SLUG); + $page->load_scripts_styles('settings_page_' . SettingsPage::PLUGIN_MENU_SLUG); // CSS is present in this repository, so this should be enqueued $this->assertTrue(wp_style_is('wpgraphql-logging-settings-css', 'enqueued')); diff --git a/plugins/wpgraphql-logging/wpgraphql-logging.php b/plugins/wpgraphql-logging/wpgraphql-logging.php index 32e78125..d9667991 100644 --- a/plugins/wpgraphql-logging/wpgraphql-logging.php +++ b/plugins/wpgraphql-logging/wpgraphql-logging.php @@ -12,7 +12,7 @@ * Domain Path: /languages * Requires at least: 6.5 * Tested up to: 6.8.2 - * Requires PHP: 8.1+ + * Requires PHP: 8.1.2+ * License: GPLv2 or later * License URI: https://www.gnu.org/licenses/gpl-2.0.html * Requires Plugins: wp-graphql