diff --git a/examples/next/webhooks-isr/README.md b/examples/next/webhooks-isr/README.md index 9c25f5ed..aa1dfae7 100644 --- a/examples/next/webhooks-isr/README.md +++ b/examples/next/webhooks-isr/README.md @@ -4,19 +4,19 @@ This integration enables seamless communication between a WordPress backend and ## Features -*Incremental Static Regeneration (ISR) Showcase – Demonstrates Next.js ISR fully working with WordPress-triggered webhooks. +* Incremental Static Regeneration (ISR) Showcase – Demonstrates Next.js ISR fully working with WordPress-triggered webhooks. -*On-Demand Revalidation – Webhooks notify Next.js to revalidate specific pages when WordPress content changes. +* On-Demand Revalidation – Webhooks notify Next.js to revalidate specific pages when WordPress content changes. -*Relative Path Payloads – Webhook payloads send clean relative paths (e.g., /posts/my-post) for accurate revalidation. +* Relative Path Payloads – Webhook payloads send clean relative paths (e.g., /posts/my-post) for accurate revalidation. -*Secure Webhook Requests – Uses secret tokens in headers to authenticate webhook calls. +* Secure Webhook Requests – Uses secret tokens in headers to authenticate webhook calls. -*Flexible HTTP Methods & Headers – Supports POST requests with custom headers for integration flexibility. +* Flexible HTTP Methods & Headers – Supports POST requests with custom headers for integration flexibility. -*WordPress Native Integration – Uses WordPress Custom Post Types and hooks for managing webhooks. +* WordPress Native Integration – Uses WordPress Custom Post Types and hooks for managing webhooks. -*Extensible & Developer Friendly – Easily customizable payloads and event triggers via WordPress filters and actions. +* Extensible & Developer Friendly – Easily customizable payloads and event triggers via WordPress filters and actions. ## Prerequisites @@ -33,36 +33,31 @@ NEXT_PUBLIC_WORDPRESS_URL=http://your-wordpress-site.com WEBHOOK_REVALIDATE_SECRET=your_webhook_secret_token ``` -### Creating a Test Webhook in WordPress -Add this PHP snippet to your theme’s `functions.php` or a custom plugin to create a webhook that triggers on post updates and calls your Next.js revalidation API: +### Creating a Webhook via the Admin UI +You can easily create and manage webhooks directly from the WordPress admin dashboard, without writing any code. The intuitive Webhooks UI allows you to specify the event, target URL, HTTP method, and custom headers for each webhook. This makes it simple to connect WordPress events to external services and automate your workflows. -```php -function create_test_post_published_webhook() { - // Get the repository instance from your plugin - $repository = \WPGraphQL\Webhooks\Plugin::instance()->get_repository(); - - // Define webhook properties - $name = 'Test Post Published Webhook'; - $event = 'post_updated'; - $url = 'http://localhost:3000/api/revalidate'; // Update to your Next.js API URL - $method = 'POST'; - - $headers = [ - 'X-Webhook-Secret' => 'your_webhook_secret_token', // Must match Next.js secret - 'Content-Type' => 'application/json', - ]; - $result = $repository->create( $name, $event, $url, $method, $headers ); - - if ( is_wp_error( $result ) ) { - error_log( 'Failed to create webhook: ' . $result->get_error_message() ); - } else { - error_log( 'Webhook created successfully with ID: ' . $result ); - } -} +Follow these steps to create a webhook using the UI: -// Run once, for example on admin_init or manually trigger it -add_action( 'admin_init', 'create_test_post_published_webhook' ); -``` +1. **Navigate to the Webhooks Admin Page** + In your WordPress dashboard, go to the sidebar and click on Webhooks. + +2. **Click "Add New" or Edit an Existing Webhook** + To create a new webhook, click the Add New button. + To edit, click the webhook you want to update. + +3. **Fill in the Webhook Details** + * **Name**: Enter a descriptive name (e.g., Test Post Published Webhook). + * **Event**: Select the event that will trigger the webhook (e.g., Post Updated). + * **URL**: Enter the endpoint URL where the webhook payload should be sent (e.g., http://localhost:3000/api/revalidate). + **HTTP Method**: Choose the HTTP method (e.g., POST). + **Headers**: Add any HTTP headers required. For example: + **X-Webhook-Secret**: d9f8a7e2b6c4d3f1a9e7b5c2df1f0e8a3 + **Content-Type**: application/json + * Click Add Header to add more headers as needed. +4. **Save the Webhook** + Click Create Webhook (or Update Webhook if editing) to save your settings. + +![Create Webhook view](./screenshots/create_webhook-ui.png) ## Modifying the Webhook Payload to Send Relative Paths Add this filter to your WordPress plugin or theme to ensure the webhook payload sends a relative path (required by Next.js revalidate API): @@ -95,7 +90,44 @@ add_filter( 'graphql_webhooks_payload', function( array $payload, $webhook ) { }, 10, 2 ); ``` +## Testing the Integration with the Example Project + +To verify that your webhook integration is working correctly, follow these steps: + +1. Run the example project in production mode. (see ## Command Reference section) + +2. In WordPress, update or create a new post, for example with the slug `/posts/new-post`. +3. Visit the corresponding page on your headless site at: + +`http://localhost:3000/posts/new-post` + + +Refresh the page to see the updated content served via Incremental Static Regeneration (ISR). + +4. Check the Next.js server logs. You should see logs indicating that the webhook revalidation request was received and processed successfully, similar to the following: + +```bash +[Webhook] Received revalidation request +[Webhook] Secret from header: Provided +[Webhook] Expected secret is set: Yes +[Webhook] Secret token validated successfully +[Webhook] Request body parsed: { + key: 'cG9zdDoyNDI=', + path: '/posts/new-post/', + graphql_endpoint: 'mysite.local/graphql', + smart_cache_keys: [ 'cG9zdDoyNDI=' ], + _webhook_meta: { + sent_at: '2025-06-24 12:19:15', + webhook_id: 254, + webhook_name: 'Test Post Published Webhook', + event_type: 'post_updated' + } +} +[Webhook] Path to revalidate: /posts/new-post/ +``` + +This confirms that the webhook triggered by your WordPress post update is successfully revalidating the page on your headless Next.js site. ## How It Works This integration: @@ -143,7 +175,6 @@ Congratulations, WordPress should now be fully set up. > **Note:** The login details for the admin is username "admin" and password "password" - ## Command Reference | Command | Description | diff --git a/examples/next/webhooks-isr/screenshots/create_webhook-ui.png b/examples/next/webhooks-isr/screenshots/create_webhook-ui.png new file mode 100644 index 00000000..24432262 Binary files /dev/null and b/examples/next/webhooks-isr/screenshots/create_webhook-ui.png differ diff --git a/plugins/wp-graphql-headless-webhooks/README.md b/plugins/wp-graphql-headless-webhooks/README.md index a3b09a08..1592a7b5 100644 --- a/plugins/wp-graphql-headless-webhooks/README.md +++ b/plugins/wp-graphql-headless-webhooks/README.md @@ -23,5 +23,10 @@ A WordPress plugin that extends [WPGraphQL](https://www.wpgraphql.com/) to suppo Clone the repository or download the latest release and place it in your WordPress `plugins` directory: +## Documentation + +For detailed usage instructions, developer references, and examples, please visit the [Documentation](docs/index.md) folder included with this plugin. + + ## License WP GPL 2 diff --git a/plugins/wp-graphql-headless-webhooks/assets/js/admin.js b/plugins/wp-graphql-headless-webhooks/assets/js/admin.js index 26e58e08..0a9f1c0e 100644 --- a/plugins/wp-graphql-headless-webhooks/assets/js/admin.js +++ b/plugins/wp-graphql-headless-webhooks/assets/js/admin.js @@ -4,7 +4,6 @@ (function ($) { 'use strict'; - $( document ).ready( function () { // Handle adding new header fields diff --git a/plugins/wp-graphql-headless-webhooks/composer.lock b/plugins/wp-graphql-headless-webhooks/composer.lock index cd233022..ac224b43 100644 --- a/plugins/wp-graphql-headless-webhooks/composer.lock +++ b/plugins/wp-graphql-headless-webhooks/composer.lock @@ -70,36 +70,43 @@ "packages-dev": [ { "name": "amphp/amp", - "version": "v3.1.0", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9" + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9", - "reference": "7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9", + "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", "shasum": "" }, "require": { - "php": ">=8.1", - "revolt/event-loop": "^1 || ^0.2" + "php": ">=7.1" }, "require-dev": { - "amphp/php-cs-fixer-config": "^2", - "phpunit/phpunit": "^9", - "psalm/phar": "5.23.1" + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^7 | ^8 | ^9", + "react/promise": "^2", + "vimeo/psalm": "^3.12" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, "autoload": { "files": [ - "src/functions.php", - "src/Future/functions.php", - "src/Internal/functions.php" + "lib/functions.php", + "lib/Internal/functions.php" ], "psr-4": { - "Amp\\": "src" + "Amp\\": "lib" } }, "notification-url": "https://packagist.org/downloads/", @@ -107,6 +114,10 @@ "MIT" ], "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, { "name": "Aaron Piotrowski", "email": "aaron@trowski.com" @@ -118,10 +129,6 @@ { "name": "Niklas Keller", "email": "me@kelunik.com" - }, - { - "name": "Daniel Lowrey", - "email": "rdlowrey@php.net" } ], "description": "A non-blocking concurrency framework for PHP applications.", @@ -138,8 +145,9 @@ "promise" ], "support": { + "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v3.1.0" + "source": "https://github.com/amphp/amp/tree/v2.6.4" }, "funding": [ { @@ -147,45 +155,41 @@ "type": "github" } ], - "time": "2025-01-26T16:07:39+00:00" + "time": "2024-03-21T18:52:26+00:00" }, { "name": "amphp/byte-stream", - "version": "v2.1.2", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46" + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46", - "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", "shasum": "" }, "require": { - "amphp/amp": "^3", - "amphp/parser": "^1.1", - "amphp/pipeline": "^1", - "amphp/serialization": "^1", - "amphp/sync": "^2", - "php": ">=8.1", - "revolt/event-loop": "^1 || ^0.2.3" + "amphp/amp": "^2", + "php": ">=7.1" }, "require-dev": { - "amphp/php-cs-fixer-config": "^2", - "amphp/phpunit-util": "^3", - "phpunit/phpunit": "^9", - "psalm/phar": "5.22.1" + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" }, "type": "library", "autoload": { "files": [ - "src/functions.php", - "src/Internal/functions.php" + "lib/functions.php" ], "psr-4": { - "Amp\\ByteStream\\": "src" + "Amp\\ByteStream\\": "lib" } }, "notification-url": "https://packagist.org/downloads/", @@ -214,269 +218,7 @@ ], "support": { "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v2.1.2" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2025-03-16T17:10:27+00:00" - }, - { - "name": "amphp/parser", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/amphp/parser.git", - "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7", - "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7", - "shasum": "" - }, - "require": { - "php": ">=7.4" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "^2", - "phpunit/phpunit": "^9", - "psalm/phar": "^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Amp\\Parser\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "A generator parser to make streaming parsers simple.", - "homepage": "https://github.com/amphp/parser", - "keywords": [ - "async", - "non-blocking", - "parser", - "stream" - ], - "support": { - "issues": "https://github.com/amphp/parser/issues", - "source": "https://github.com/amphp/parser/tree/v1.1.1" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2024-03-21T19:16:53+00:00" - }, - { - "name": "amphp/pipeline", - "version": "v1.2.3", - "source": { - "type": "git", - "url": "https://github.com/amphp/pipeline.git", - "reference": "7b52598c2e9105ebcddf247fc523161581930367" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367", - "reference": "7b52598c2e9105ebcddf247fc523161581930367", - "shasum": "" - }, - "require": { - "amphp/amp": "^3", - "php": ">=8.1", - "revolt/event-loop": "^1" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "^2", - "amphp/phpunit-util": "^3", - "phpunit/phpunit": "^9", - "psalm/phar": "^5.18" - }, - "type": "library", - "autoload": { - "psr-4": { - "Amp\\Pipeline\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "Asynchronous iterators and operators.", - "homepage": "https://amphp.org/pipeline", - "keywords": [ - "amp", - "amphp", - "async", - "io", - "iterator", - "non-blocking" - ], - "support": { - "issues": "https://github.com/amphp/pipeline/issues", - "source": "https://github.com/amphp/pipeline/tree/v1.2.3" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2025-03-16T16:33:53+00:00" - }, - { - "name": "amphp/serialization", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/amphp/serialization.git", - "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1", - "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "phpunit/phpunit": "^9 || ^8 || ^7" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Amp\\Serialization\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "Serialization tools for IPC and data storage in PHP.", - "homepage": "https://github.com/amphp/serialization", - "keywords": [ - "async", - "asynchronous", - "serialization", - "serialize" - ], - "support": { - "issues": "https://github.com/amphp/serialization/issues", - "source": "https://github.com/amphp/serialization/tree/master" - }, - "time": "2020-03-25T21:39:07+00:00" - }, - { - "name": "amphp/sync", - "version": "v2.3.0", - "source": { - "type": "git", - "url": "https://github.com/amphp/sync.git", - "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1", - "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1", - "shasum": "" - }, - "require": { - "amphp/amp": "^3", - "amphp/pipeline": "^1", - "amphp/serialization": "^1", - "php": ">=8.1", - "revolt/event-loop": "^1 || ^0.2" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "^2", - "amphp/phpunit-util": "^3", - "phpunit/phpunit": "^9", - "psalm/phar": "5.23" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Amp\\Sync\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - }, - { - "name": "Stephen Coakley", - "email": "me@stephencoakley.com" - } - ], - "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.", - "homepage": "https://github.com/amphp/sync", - "keywords": [ - "async", - "asynchronous", - "mutex", - "semaphore", - "synchronization" - ], - "support": { - "issues": "https://github.com/amphp/sync/issues", - "source": "https://github.com/amphp/sync/tree/v2.3.0" + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" }, "funding": [ { @@ -484,7 +226,7 @@ "type": "github" } ], - "time": "2024-08-03T19:31:26+00:00" + "time": "2024-04-13T18:00:56+00:00" }, { "name": "automattic/vipwpcs", @@ -599,16 +341,16 @@ }, { "name": "axepress/wp-graphql-stubs", - "version": "v2.3.0", + "version": "v2.3.3", "source": { "type": "git", "url": "https://github.com/AxeWP/wp-graphql-stubs.git", - "reference": "c210ac91cada7869ba60a371c16d6424e6dd73f1" + "reference": "6ca5c5ee37012743e09e2f9e7c3c272c5e273bb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/AxeWP/wp-graphql-stubs/zipball/c210ac91cada7869ba60a371c16d6424e6dd73f1", - "reference": "c210ac91cada7869ba60a371c16d6424e6dd73f1", + "url": "https://api.github.com/repos/AxeWP/wp-graphql-stubs/zipball/6ca5c5ee37012743e09e2f9e7c3c272c5e273bb8", + "reference": "6ca5c5ee37012743e09e2f9e7c3c272c5e273bb8", "shasum": "" }, "require": { @@ -639,7 +381,7 @@ ], "support": { "issues": "https://github.com/AxeWP/wp-graphql-stubs/issues", - "source": "https://github.com/AxeWP/wp-graphql-stubs/tree/v2.3.0" + "source": "https://github.com/AxeWP/wp-graphql-stubs/tree/v2.3.3" }, "funding": [ { @@ -647,35 +389,29 @@ "type": "github" } ], - "time": "2025-04-29T12:54:31+00:00" + "time": "2025-06-18T02:06:46+00:00" }, { "name": "behat/gherkin", - "version": "v4.14.0", + "version": "v4.10.0", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "34c9b59c59355a7b4c53b9f041c8dbd1c8acc3b4" + "reference": "cbb83c4c435dd8d05a161f2a5ae322e61b2f4db6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/34c9b59c59355a7b4c53b9f041c8dbd1c8acc3b4", - "reference": "34c9b59c59355a7b4c53b9f041c8dbd1c8acc3b4", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/cbb83c4c435dd8d05a161f2a5ae322e61b2f4db6", + "reference": "cbb83c4c435dd8d05a161f2a5ae322e61b2f4db6", "shasum": "" }, "require": { - "composer-runtime-api": "^2.2", - "php": "8.1.* || 8.2.* || 8.3.* || 8.4.*" + "php": "~7.2|~8.0" }, "require-dev": { - "cucumber/gherkin-monorepo": "dev-gherkin-v32.1.1", - "friendsofphp/php-cs-fixer": "^3.65", - "mikey179/vfsstream": "^1.6", - "phpstan/extension-installer": "^1", - "phpstan/phpstan": "^2", - "phpstan/phpstan-phpunit": "^2", - "phpunit/phpunit": "^10.5", - "symfony/yaml": "^5.4 || ^6.4 || ^7.0" + "cucumber/cucumber": "dev-gherkin-24.1.0", + "phpunit/phpunit": "~8|~9", + "symfony/yaml": "~3|~4|~5|~6|~7" }, "suggest": { "symfony/yaml": "If you want to parse features, represented in YAML files" @@ -687,8 +423,8 @@ } }, "autoload": { - "psr-4": { - "Behat\\Gherkin\\": "src/" + "psr-0": { + "Behat\\Gherkin": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -699,11 +435,11 @@ { "name": "Konstantin Kudryashov", "email": "ever.zet@gmail.com", - "homepage": "https://everzet.com" + "homepage": "http://everzet.com" } ], "description": "Gherkin DSL parser for PHP", - "homepage": "https://behat.org/", + "homepage": "http://behat.org/", "keywords": [ "BDD", "Behat", @@ -714,9 +450,9 @@ ], "support": { "issues": "https://github.com/Behat/Gherkin/issues", - "source": "https://github.com/Behat/Gherkin/tree/v4.14.0" + "source": "https://github.com/Behat/Gherkin/tree/v4.10.0" }, - "time": "2025-05-23T15:06:40+00:00" + "time": "2024-10-19T14:46:06+00:00" }, { "name": "codeception/codeception", @@ -2202,30 +1938,30 @@ }, { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -2252,7 +1988,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" }, "funding": [ { @@ -2268,7 +2004,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "eftec/bladeone", @@ -3443,27 +3179,25 @@ }, { "name": "nikic/php-parser", - "version": "v5.5.0", + "version": "v4.19.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", "shasum": "" }, "require": { - "ext-ctype": "*", - "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.4" + "php": ">=7.1" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -3471,7 +3205,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "4.9-dev" } }, "autoload": { @@ -3495,9 +3229,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" }, - "time": "2025-05-31T08:24:38+00:00" + "time": "2024-09-29T15:01:53+00:00" }, { "name": "phar-io/manifest", @@ -5232,27 +4966,22 @@ }, { "name": "psr/container", - "version": "2.0.2", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { "php": ">=7.4.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -5279,9 +5008,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "source": "https://github.com/php-fig/container/tree/1.1.2" }, - "time": "2021-11-05T16:47:00+00:00" + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/event-dispatcher", @@ -5495,30 +5224,30 @@ }, { "name": "psr/log", - "version": "2.0.0", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "src" + "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5539,9 +5268,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/2.0.0" + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "time": "2021-07-14T16:41:46+00:00" + "time": "2021-05-03T11:20:27+00:00" }, { "name": "ralouphie/getallheaders", @@ -5660,78 +5389,6 @@ ], "time": "2024-05-24T10:39:05+00:00" }, - { - "name": "revolt/event-loop", - "version": "v1.0.7", - "source": { - "type": "git", - "url": "https://github.com/revoltphp/event-loop.git", - "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/09bf1bf7f7f574453efe43044b06fafe12216eb3", - "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "ext-json": "*", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^9", - "psalm/phar": "^5.15" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Revolt\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "ceesjank@gmail.com" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "Rock-solid event loop for concurrent PHP applications.", - "keywords": [ - "async", - "asynchronous", - "concurrency", - "event", - "event-loop", - "non-blocking", - "scheduler" - ], - "support": { - "issues": "https://github.com/revoltphp/event-loop/issues", - "source": "https://github.com/revoltphp/event-loop/tree/v1.0.7" - }, - "time": "2025-01-25T19:27:39+00:00" - }, { "name": "sebastian/cli-parser", "version": "1.0.2", @@ -6992,28 +6649,29 @@ }, { "name": "softcreatr/jsonpath", - "version": "0.8.3", + "version": "0.7.6", "source": { "type": "git", "url": "https://github.com/SoftCreatR/JSONPath.git", - "reference": "fc12dee0b46f3fa3a175c4051dbab60984acef4b" + "reference": "e04c02cb78bcc242c69d17dac5b29436bf3e1076" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SoftCreatR/JSONPath/zipball/fc12dee0b46f3fa3a175c4051dbab60984acef4b", - "reference": "fc12dee0b46f3fa3a175c4051dbab60984acef4b", + "url": "https://api.github.com/repos/SoftCreatR/JSONPath/zipball/e04c02cb78bcc242c69d17dac5b29436bf3e1076", + "reference": "e04c02cb78bcc242c69d17dac5b29436bf3e1076", "shasum": "" }, "require": { "ext-json": "*", - "php": ">=8.0" + "php": ">=7.1,<8.0" }, "replace": { "flow/jsonpath": "*" }, "require-dev": { - "phpunit/phpunit": "^9.6", - "roave/security-advisories": "dev-latest" + "phpunit/phpunit": ">=7.0", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "autoload": { @@ -7056,37 +6714,33 @@ "type": "github" } ], - "time": "2023-08-17T20:14:00+00:00" + "time": "2022-09-27T09:27:12+00:00" }, { "name": "spatie/array-to-xml", - "version": "3.4.0", + "version": "2.17.1", "source": { "type": "git", "url": "https://github.com/spatie/array-to-xml.git", - "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67" + "reference": "5cbec9c6ab17e320c58a259f0cebe88bde4a7c46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", - "reference": "7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/5cbec9c6ab17e320c58a259f0cebe88bde4a7c46", + "reference": "5cbec9c6ab17e320c58a259f0cebe88bde4a7c46", "shasum": "" }, "require": { "ext-dom": "*", - "php": "^8.0" + "php": "^7.4|^8.0" }, "require-dev": { "mockery/mockery": "^1.2", "pestphp/pest": "^1.21", + "phpunit/phpunit": "^9.0", "spatie/pest-plugin-snapshots": "^1.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, "autoload": { "psr-4": { "Spatie\\ArrayToXml\\": "src" @@ -7112,7 +6766,7 @@ "xml" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.4.0" + "source": "https://github.com/spatie/array-to-xml/tree/2.17.1" }, "funding": [ { @@ -7124,20 +6778,20 @@ "type": "github" } ], - "time": "2024-12-16T12:45:15+00:00" + "time": "2022-12-26T08:22:07+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.1", + "version": "3.13.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "1b71b4dd7e7ef651ac749cea67e513c0c832f4bd" + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1b71b4dd7e7ef651ac749cea67e513c0c832f4bd", - "reference": "1b71b4dd7e7ef651ac749cea67e513c0c832f4bd", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", "shasum": "" }, "require": { @@ -7208,7 +6862,7 @@ "type": "thanks_dev" } ], - "time": "2025-06-12T15:04:34+00:00" + "time": "2025-06-17T22:17:01+00:00" }, { "name": "symfony/browser-kit", @@ -7284,34 +6938,38 @@ }, { "name": "symfony/config", - "version": "v6.4.22", + "version": "v5.4.46", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "af5917a3b1571f54689e56677a3f06440d2fe4c7" + "reference": "977c88a02d7d3f16904a81907531b19666a08e78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/af5917a3b1571f54689e56677a3f06440d2fe4c7", - "reference": "af5917a3b1571f54689e56677a3f06440d2fe4c7", + "url": "https://api.github.com/repos/symfony/config/zipball/977c88a02d7d3f16904a81907531b19666a08e78", + "reference": "977c88a02d7d3f16904a81907531b19666a08e78", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^5.4|^6.0|^7.0", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22" }, "conflict": { - "symfony/finder": "<5.4", - "symfony/service-contracts": "<2.5" + "symfony/finder": "<4.4" }, "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" }, "type": "library", "autoload": { @@ -7339,7 +6997,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v6.4.22" + "source": "https://github.com/symfony/config/tree/v5.4.46" }, "funding": [ { @@ -7355,7 +7013,7 @@ "type": "tidelift" } ], - "time": "2025-05-14T06:00:01+00:00" + "time": "2024-10-30T07:58:02+00:00" }, { "name": "symfony/console", @@ -7524,20 +7182,20 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.1" }, "type": "library", "extra": { @@ -7546,7 +7204,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "2.5-dev" } }, "autoload": { @@ -7571,7 +7229,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" }, "funding": [ { @@ -7587,7 +7245,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/dom-crawler", @@ -7751,22 +7409,25 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", + "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=7.2.5", "psr/event-dispatcher": "^1" }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, "type": "library", "extra": { "thanks": { @@ -7774,7 +7435,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "2.5-dev" } }, "autoload": { @@ -7807,7 +7468,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.4" }, "funding": [ { @@ -7823,29 +7484,30 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/filesystem", - "version": "v7.3.0", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^5.4|^6.4" }, "type": "library", "autoload": { @@ -7873,7 +7535,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.0" + "source": "https://github.com/symfony/filesystem/tree/v5.4.45" }, "funding": [ { @@ -7889,7 +7551,7 @@ "type": "tidelift" } ], - "time": "2024-10-25T15:15:23+00:00" + "time": "2024-10-22T13:05:35+00:00" }, { "name": "symfony/finder", @@ -8507,26 +8169,29 @@ }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, + "suggest": { + "symfony/service-implementation": "" + }, "type": "library", "extra": { "thanks": { @@ -8534,16 +8199,13 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "2.5-dev" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8570,7 +8232,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" }, "funding": [ { @@ -8586,25 +8248,25 @@ "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/stopwatch", - "version": "v7.3.0", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" + "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fb2c199cf302eb207f8c23e7ee174c1c31a5c004", + "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/service-contracts": "^2.5|^3" + "php": ">=7.2.5", + "symfony/service-contracts": "^1|^2|^3" }, "type": "library", "autoload": { @@ -8632,7 +8294,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" + "source": "https://github.com/symfony/stopwatch/tree/v5.4.45" }, "funding": [ { @@ -8648,38 +8310,38 @@ "type": "tidelift" } ], - "time": "2025-02-24T10:49:57+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/string", - "version": "v6.4.21", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "73e2c6966a5aef1d4892873ed5322245295370c6" + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/73e2c6966a5aef1d4892873ed5322245295370c6", - "reference": "73e2c6966a5aef1d4892873ed5322245295370c6", + "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" }, "conflict": { - "symfony/translation-contracts": "<2.5" + "symfony/translation-contracts": ">=3.0" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/intl": "^6.2|^7.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" }, "type": "library", "autoload": { @@ -8718,7 +8380,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.21" + "source": "https://github.com/symfony/string/tree/v5.4.47" }, "funding": [ { @@ -8734,7 +8396,7 @@ "type": "tidelift" } ], - "time": "2025-04-18T15:23:29+00:00" + "time": "2024-11-10T20:33:58+00:00" }, { "name": "symfony/yaml", @@ -8925,21 +8587,21 @@ }, { "name": "vimeo/psalm", - "version": "6.0.0", + "version": "5.26.1", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "b8e96bb617bf59382113b1b56cef751f648a7dc9" + "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/b8e96bb617bf59382113b1b56cef751f648a7dc9", - "reference": "b8e96bb617bf59382113b1b56cef751f648a7dc9", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d747f6500b38ac4f7dfc5edbcae6e4b637d7add0", + "reference": "d747f6500b38ac4f7dfc5edbcae6e4b637d7add0", "shasum": "" }, "require": { - "amphp/amp": "^3", - "amphp/byte-stream": "^2", + "amphp/amp": "^2.4.2", + "amphp/byte-stream": "^1.5", "composer-runtime-api": "^2", "composer/semver": "^1.4 || ^2.0 || ^3.0", "composer/xdebug-handler": "^2.0 || ^3.0", @@ -8952,24 +8614,26 @@ "ext-simplexml": "*", "ext-tokenizer": "*", "felixfbecker/advanced-json-rpc": "^3.1", - "felixfbecker/language-server-protocol": "^1.5.3", + "felixfbecker/language-server-protocol": "^1.5.2", "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^5.0.0", - "php": "~8.1.17 || ~8.2.4 || ~8.3.0 || ~8.4.0", + "nikic/php-parser": "^4.17", + "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", "sebastian/diff": "^4.0 || ^5.0 || ^6.0", "spatie/array-to-xml": "^2.17.0 || ^3.0", "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" }, + "conflict": { + "nikic/php-parser": "4.17.0" + }, "provide": { "psalm/psalm": "self.version" }, "require-dev": { - "amphp/phpunit-util": "^3", + "amphp/phpunit-util": "^2.0", "bamarni/composer-bin-plugin": "^1.4", "brianium/paratest": "^6.9", - "dg/bypass-finals": "^1.5", "ext-curl": "*", "mockery/mockery": "^1.5", "nunomaduro/mock-final-classes": "^1.1", @@ -8977,7 +8641,7 @@ "phpstan/phpdoc-parser": "^1.6", "phpunit/phpunit": "^9.6", "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.19", + "psalm/plugin-phpunit": "^0.18", "slevomat/coding-standard": "^8.4", "squizlabs/php_codesniffer": "^3.6", "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0" @@ -9000,9 +8664,7 @@ "dev-2.x": "2.x-dev", "dev-3.x": "3.x-dev", "dev-4.x": "4.x-dev", - "dev-5.x": "5.x-dev", - "dev-6.x": "6.x-dev", - "dev-master": "7.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -9031,7 +8693,7 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2025-01-26T12:03:19+00:00" + "time": "2024-09-08T18:53:08+00:00" }, { "name": "webmozart/assert", @@ -11576,6 +11238,6 @@ "platform": { "php": "^7.4 || ^8.0" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/plugins/wp-graphql-headless-webhooks/docs/index.md b/plugins/wp-graphql-headless-webhooks/docs/index.md new file mode 100644 index 00000000..c96b5921 --- /dev/null +++ b/plugins/wp-graphql-headless-webhooks/docs/index.md @@ -0,0 +1,69 @@ +# WPGraphQL Webhooks Plugin Documentation + +## Introduction + +The **WPGraphQL Webhooks** plugin extends your WordPress site with powerful webhook management capabilities tailored for GraphQL-driven headless setups. It allows you to create, update, and manage webhooks that trigger on various WordPress and WPGraphQL events, enabling seamless integration with external services and static site generators. + +This plugin is designed to work hand-in-hand with WordPress and WPGraphQL, providing a flexible and reliable way to automate workflows such as cache invalidation, content revalidation, and more. + +## Features + +- **Easy Webhook Management UI**: Create, update, and delete webhooks directly from the WordPress admin dashboard. +- **Event-Based Triggers**: Configure webhooks to fire on a variety of WordPress and WPGraphQL events. +- **Custom HTTP Methods and Headers**: Specify HTTP methods (POST, GET, etc.) and custom headers for your webhook requests. +- **Built-in Integration with WPGraphQL Smart Cache**: Leverages smart cache invalidation events for efficient revalidation. +- **Fallback Invalidation System**: If WPGraphQL Smart Cache is not installed, the plugin falls back to a simpler event invalidation mechanism. +- **Security**: Supports nonce verification and capability checks to secure webhook management. +- **AJAX Testing**: Test webhook endpoints directly from the admin UI. +- **Extensible**: Provides filters and actions for developers to customize behavior. + +## Creating and Updating Webhooks + +You can manage webhooks easily via the WordPress admin UI: + +### Creating a Webhook + +1. **Access the Webhooks Admin Page** + - In your WordPress dashboard sidebar, click on **Webhooks**. + +2. **Add a New Webhook** + - Click the **Add New** button. + +3. **Fill in Webhook Details** + - **Name**: Enter a descriptive name for your webhook (e.g., `Post Published Webhook`). + - **Event**: Select the event that triggers the webhook (e.g., `post_published`). + - **URL**: Enter the target URL that will receive the webhook payload. + - **HTTP Method**: Choose the HTTP method (default is `POST`). + - **Headers**: Add any necessary HTTP headers (e.g., authentication tokens). + +4. **Save the Webhook** + - Click **Create Webhook** to save. + +### Updating a Webhook + +1. **Navigate to the Webhooks List** + - Click on **Webhooks** in the admin sidebar. + +2. **Edit an Existing Webhook** + - Click the webhook you want to update. + +3. **Modify the Details** + - Change any fields like name, event, URL, method, or headers. + +4. **Save Changes** + - Click **Update Webhook** to apply the changes. + +![Create Webhook view](./screenshots/create_webhook-ui.png) + +## Integration with WPGraphQL Smart Cache (Recommended) + +The **WPGraphQL Webhooks** plugin has built-in integration with the [WPGraphQL Smart Cache](https://wordpress.org/plugins/wpgraphql-smart-cache/) plugin. This integration enables advanced smart cache invalidation events, allowing your webhooks to trigger only when necessary, improving performance and reducing unnecessary rebuilds or revalidations. + +**Important:** It is strongly recommended to install and activate the WPGraphQL Smart Cache plugin alongside WPGraphQL Webhooks to take full advantage of these features. + +If WPGraphQL Smart Cache is not installed, WPGraphQL Webhooks will gracefully fall back to a simpler event invalidation system, which may be less efficient but still functional. + +## Where to Go Next + +For a detailed reference of all available filters, actions, and hooks provided by WPGraphQL Webhooks, please see the [Reference Documentation](reference.md). + diff --git a/plugins/wp-graphql-headless-webhooks/docs/reference.md b/plugins/wp-graphql-headless-webhooks/docs/reference.md new file mode 100644 index 00000000..0cbcb321 --- /dev/null +++ b/plugins/wp-graphql-headless-webhooks/docs/reference.md @@ -0,0 +1,234 @@ +# WPGraphQL Webhooks — Actions & Filters Reference + +## 1. Action: `graphql_webhooks_init` + +**Description:** +Fires once when the WPGraphQL Webhooks plugin singleton instance is initialized. Useful for hooking into the plugin setup process to add custom initialization logic. + +**Parameters:** +- `$instance` (object): The singleton instance of the WPGraphQL Webhooks plugin. + +**Example Usage:** + +```php +add_action( 'graphql_webhooks_init', function( $instance ) { + error_log( 'WPGraphQL Webhooks plugin initialized.' ); + // Custom initialization logic here +}); +``` + +## 2. Filter: `graphql_webhooks_allowed_events` + +**Description:** +Filters the list of allowed webhook events. By default, this is an associative array of event keys and their labels. Use this filter to add, remove, or modify the events your webhooks can listen to. + +**Parameters:** +- `$events` (array): Associative array of event keys and labels. + +**Returns:** +Modified array of allowed events. + +**Example Usage:** + +```php +add_filter( 'graphql_webhooks_allowed_events', function( $events ) { + // Add a custom event + $events['custom_event'] = 'Custom Event'; + return $events; +}); +``` + +## 3. Filter: `graphql_webhooks_allowed_methods` + +**Description:** +Filters the list of allowed HTTP methods for sending webhook requests. Defaults to `['POST', 'GET']`. + +**Parameters:** +- `$methods` (array): Array of allowed HTTP methods. + +**Returns:** +Modified array of allowed HTTP methods. + +**Example Usage:** + +```php +add_filter( 'graphql_webhooks_allowed_methods', function( $methods ) { + // Allow PUT as an additional HTTP method + $methods[] = 'PUT'; + return $methods; +}); +``` +## 4. Filter: `graphql_webhooks_validate_data` + +**Description:** +Filters the result of webhook data validation. This filter allows you to add custom validation logic or override the default validation outcome. + +**Parameters:** +- `$is_valid` (bool|WP_Error): Current validation result (`true` if valid, `WP_Error` if invalid). +- `$event` (string): Event key of the webhook. +- `$url` (string): Target URL of the webhook. +- `$method` (string): HTTP method of the webhook. + +**Returns:** +`true` if valid, or a `WP_Error` object if invalid. + +**Example Usage:** + +```php +add_filter( 'graphql_webhooks_validate_data', function( $is_valid, $event, $url, $method ) { + if ( strpos( $url, 'https://' ) !== 0 ) { + return new WP_Error( 'invalid_url_scheme', 'Webhook URL must use HTTPS.' ); + } + return $is_valid; +}, 10, 4 ); +``` + +## 5. Filter: `graphql_webhooks_payload` + +**Description:** +Filters the payload data sent to the webhook URL before the HTTP request is made. Use this to modify, enrich, or sanitize the webhook payload. + +**Parameters:** +- `$payload` (array): The current payload data. +- `$webhook` (Webhook): The webhook entity instance. + +**Returns:** +Modified payload array. + +**Example Usage:** + +```php +add_filter( 'graphql_webhooks_payload', function( $payload, $webhook ) { + // Add a custom field to the payload + $payload['custom_field'] = 'Custom Value'; + return $payload; +}, 10, 2 ); +``` + +## 6. Filter: `graphql_webhooks_timeout` + +**Description:** +Filters the timeout (in seconds) used for the HTTP request when sending the webhook. Default is 15 seconds. + +**Parameters:** +- `$timeout` (int): Current timeout value. +- `$webhook` (Webhook): The webhook entity instance. + +**Returns:** +Modified timeout value. + +**Example Usage:** +```php +add_filter( 'graphql_webhooks_timeout', function( $timeout, $webhook ) { + // Increase timeout for specific webhook + if ( $webhook->name === 'Slow Endpoint' ) { + return 30; + } + return $timeout; +}, 10, 2 ); +``` + +## 7. Filter: `graphql_webhooks_sslverify` + +**Description:** +Filters whether SSL verification should be enabled when sending the webhook HTTP request. Defaults to `true`. + +**Parameters:** +- `$sslverify` (bool): Current SSL verification setting. +- `$webhook` (Webhook): The webhook entity instance. + +**Returns:** +Modified SSL verification setting. + +**Example Usage:** + +```php +add_filter( 'graphql_webhooks_sslverify', function( $sslverify, $webhook ) { + // Disable SSL verification for local development webhook + if ( strpos( $webhook->url, 'localhost' ) !== false ) { + return false; + } + return $sslverify; +}, 10, 2 ); +``` + +## 8. Filter: `graphql_webhooks_test_mode` + +**Description:** +Filters whether the webhook HTTP request should be sent in blocking mode. This is useful for debugging or testing webhook delivery. + +**Parameters:** +- `$test_mode` (bool): Whether test mode is enabled (default: `false`). +- `$webhook` (Webhook): The webhook entity instance. + +**Returns:** +`true` to enable blocking mode, `false` otherwise. + +**Example Usage:** + +```php +add_filter( 'graphql_webhooks_test_mode', function( $test_mode, $webhook ) { + // Enable test mode for a specific webhook + if ( $webhook->name === 'Test Webhook' ) { + return true; + } + return $test_mode; +}, 10, 2 ); +``` + +## 9. Action: `graphql_webhooks_sent` + +**Description:** +Fires after a webhook HTTP request has been sent. Useful for logging, debugging, or triggering additional side effects. + +**Parameters:** +- `$webhook` (Webhook): The webhook entity instance. +- `$payload` (array): The payload sent to the webhook. +- `$response` (WP_HTTP_Response|WP_Error): The response or error returned from the HTTP request. + +**Example Usage:** + +```php +add_action( 'graphql_webhooks_sent', function( $webhook, $payload, $response ) { + if ( is_wp_error( $response ) ) { + error_log( "Webhook '{$webhook->name}' failed: " . $response->get_error_message() ); + } else { + error_log( "Webhook '{$webhook->name}' sent successfully with response code: " . wp_remote_retrieve_response_code( $response ) ); + } +}, 10, 3 ); +``` + +## 10. Action: `graphql_webhooks_before_trigger` + +**Description:** +Fires before webhooks are triggered for a specific event. Useful for modifying payload or short-circuiting webhook triggers. + +**Parameters:** +- `$event` (string): The event key being triggered. +- `$payload` (array): The payload data for the event. + +**Example Usage:** +```php +add_action( 'graphql_webhooks_before_trigger', function( $event, &$payload ) { + if ( $event === 'post_published' ) { + // Add extra data before triggering webhooks + $payload['extra_info'] = 'Additional context'; + } +}, 10, 2 ); +``` + +## 11. Action: `graphql_webhooks_after_trigger` + +**Description:** +Fires after webhooks have been triggered for a specific event. Useful for cleanup or logging. + +**Parameters:** +- `$event` (string): The event key that was triggered. +- `$payload` (array): The payload data that was sent. + +**Example Usage:** +```php +add_action( 'graphql_webhooks_after_trigger', function( $event, $payload ) { + error_log( "Completed triggering webhooks for event: $event" ); +}, 10, 2 ); +``` \ No newline at end of file diff --git a/plugins/wp-graphql-headless-webhooks/docs/screenshots/create_webhook-ui.png b/plugins/wp-graphql-headless-webhooks/docs/screenshots/create_webhook-ui.png new file mode 100644 index 00000000..24432262 Binary files /dev/null and b/plugins/wp-graphql-headless-webhooks/docs/screenshots/create_webhook-ui.png differ diff --git a/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksAdmin.php b/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksAdmin.php index c2b6d408..399ce8b5 100644 --- a/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksAdmin.php +++ b/plugins/wp-graphql-headless-webhooks/src/Admin/WebhooksAdmin.php @@ -13,74 +13,77 @@ use WPGraphQL\Webhooks\Repository\Interfaces\WebhookRepositoryInterface; /** - * Admin interface class for managing webhooks. + * Class WebhooksAdmin + * + * Provides the WordPress admin UI for managing GraphQL webhooks. + * */ class WebhooksAdmin { /** - * Admin page slug constant. + * The admin page slug for the webhooks UI. + * + * @var string */ const ADMIN_PAGE_SLUG = 'graphql-webhooks'; /** - * Repository instance. + * Webhook repository instance. * * @var WebhookRepositoryInterface */ private WebhookRepositoryInterface $repository; /** - * Constructor + * WebhooksAdmin constructor. * - * @param WebhookRepositoryInterface $repository Webhook repository. + * @param WebhookRepositoryInterface $repository Webhook repository instance. */ public function __construct( WebhookRepositoryInterface $repository ) { $this->repository = $repository; - // Hook into WordPress admin add_action( 'admin_menu', [ $this, 'add_admin_menu' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); - - // Handle form submissions via admin-post.php add_action( 'admin_post_graphql_webhook_save', [ $this, 'handle_webhook_save' ] ); add_action( 'admin_post_graphql_webhook_delete', [ $this, 'handle_webhook_delete' ] ); - - // Handle admin actions add_action( 'admin_init', [ $this, 'handle_admin_actions' ] ); - - // Handle AJAX webhook test add_action( 'wp_ajax_test_webhook', [ $this, 'ajax_test_webhook' ] ); } /** - * Initialize admin hooks. + * Optionally initialize additional admin hooks. + * + * @return void */ public function init(): void { add_action( 'admin_init', [ $this, 'handle_actions' ] ); } /** - * Add admin menu. + * Registers the top-level "Webhooks" admin menu. + * + * @return void */ public function add_admin_menu(): void { - add_submenu_page( - 'graphiql-ide', + add_menu_page( __( 'Webhooks', 'wp-graphql-headless-webhooks' ), __( 'Webhooks', 'wp-graphql-headless-webhooks' ), 'manage_options', self::ADMIN_PAGE_SLUG, - [ $this, 'render_admin_page' ] + [ $this, 'render_admin_page' ], + 'dashicons-rss', + 25 ); } /** - * Generate admin URL. + * Generates the admin URL for the webhooks page. * - * @param array $args Query arguments. - * @return string Admin URL. + * @param array $args Optional. Additional query arguments. + * @return string The admin URL. */ public function get_admin_url( array $args = [] ): string { - $defaults = [ + $defaults = [ 'page' => self::ADMIN_PAGE_SLUG, ]; $args = array_merge( $defaults, $args ); @@ -88,15 +91,12 @@ public function get_admin_url( array $args = [] ): string { } /** - * Enqueue admin assets. + * Enqueues admin CSS and JS assets for the webhooks UI. * - * @param string $hook Current admin page hook. + * @param string $hook The current admin page hook. + * @return void */ public function enqueue_assets( string $hook ): void { - if ( 'graphql_page_' . self::ADMIN_PAGE_SLUG !== $hook ) { - return; - } - wp_enqueue_style( 'graphql-webhooks-admin', WPGRAPHQL_HEADLESS_WEBHOOKS_PLUGIN_URL . 'assets/css/admin.css', @@ -115,18 +115,18 @@ public function enqueue_assets( string $hook ): void { wp_localize_script( 'graphql-webhooks-admin', 'wpGraphQLWebhooks', - [ - 'ajaxUrl' => admin_url( 'admin-ajax.php' ), - 'restUrl' => rest_url( 'graphql-webhooks/v1/' ), - 'nonce' => wp_create_nonce( 'wp_rest' ), - 'confirmDelete' => __( 'Are you sure you want to delete this webhook?', 'wp-graphql-headless-webhooks' ), + [ + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'restUrl' => rest_url( 'graphql-webhooks/v1/' ), + 'nonce' => wp_create_nonce( 'wp_rest' ), + 'confirmDelete' => __( 'Are you sure you want to delete this webhook?', 'wp-graphql-headless-webhooks' ), 'headerTemplate' => $this->get_header_row_template(), ] ); } /** - * Get header row template for JavaScript. + * Returns the HTML template for the webhook header row (for JS rendering). * * @return string HTML template. */ @@ -137,7 +137,9 @@ private function get_header_row_template(): string { } /** - * Handle admin actions. + * Handles admin actions from the webhooks page. + * + * @return void */ public function handle_actions(): void { if ( ! isset( $_GET['page'] ) || self::ADMIN_PAGE_SLUG !== $_GET['page'] ) { @@ -154,9 +156,9 @@ public function handle_actions(): void { } /** - * Verify admin permission. + * Checks if the current user has permission to manage options. * - * @return bool Whether user has permission. + * @return bool True if user has permission, false otherwise. */ private function verify_admin_permission(): bool { if ( ! current_user_can( 'manage_options' ) ) { @@ -167,11 +169,11 @@ private function verify_admin_permission(): bool { } /** - * Verify nonce. + * Verifies a nonce for security. * - * @param string $nonce_name Nonce name. - * @param string $action Nonce action. - * @return bool Whether nonce is valid. + * @param string $nonce_name Nonce field name. + * @param string $action Nonce action. + * @return bool True if nonce is valid, false otherwise. */ private function verify_nonce( string $nonce_name, string $action ): bool { if ( ! isset( $_REQUEST[ $nonce_name ] ) || ! wp_verify_nonce( $_REQUEST[ $nonce_name ], $action ) ) { @@ -182,59 +184,67 @@ private function verify_nonce( string $nonce_name, string $action ): bool { } /** - * Handle webhook save + * Handles saving of a webhook (add or update). + * + * @return void */ public function handle_webhook_save() { - // Verify permissions and nonce - if ( ! $this->verify_admin_permission() || ! $this->verify_nonce( 'webhook_save', 'webhook_nonce' ) ) { + if ( ! $this->verify_admin_permission() || ! $this->verify_nonce( 'webhook_nonce', 'webhook_save' ) ) { wp_die( __( 'Unauthorized', 'wp-graphql-webhooks' ) ); } $webhook_id = isset( $_POST['webhook_id'] ) ? intval( $_POST['webhook_id'] ) : 0; - $data = [ - 'name' => sanitize_text_field( $_POST['webhook_name'] ?? '' ), - 'event' => sanitize_text_field( $_POST['webhook_event'] ?? '' ), - 'url' => esc_url_raw( $_POST['webhook_url'] ?? '' ), - 'method' => sanitize_text_field( $_POST['webhook_method'] ?? 'POST' ), - 'headers' => $this->sanitize_headers( $_POST['webhook_headers'] ?? [] ), - ]; + if ( ! $this->verify_admin_permission() || ! $this->verify_nonce( 'webhook_nonce', 'webhook_save' ) ) { + wp_die( __( 'Unauthorized', 'wp-graphql-webhooks' ) ); + } - // Validate data - $validation = $this->repository->validate_data( $data ); + $webhook_id = isset( $_POST['webhook_id'] ) ? intval( $_POST['webhook_id'] ) : 0; + $webhook = new Webhook( + $webhook_id, + sanitize_text_field( $_POST['webhook_name'] ?? '' ), + sanitize_text_field( $_POST['webhook_event'] ?? '' ), + esc_url_raw( $_POST['webhook_url'] ?? '' ), + sanitize_text_field( $_POST['webhook_method'] ?? 'POST' ), + $this->sanitize_headers( $_POST['webhook_headers'] ?? [] ) + ); + + $validation = $this->repository->validate( $webhook ); if ( is_wp_error( $validation ) ) { wp_die( $validation->get_error_message() ); } - // Save webhook if ( $webhook_id > 0 ) { - $result = $this->repository->update( $webhook_id, $data ); + $result = $this->repository->update( $webhook_id, $webhook ); $redirect_args = $result ? [ 'updated' => 1 ] : [ 'error' => 1 ]; } else { - $result = $this->repository->create( $data ); + $result = $this->repository->create( $webhook ); $redirect_args = $result ? [ 'added' => 1 ] : [ 'error' => 1 ]; } - // Redirect back to list page + wp_redirect( add_query_arg( $redirect_args, $this->get_admin_url() ) ); exit; } /** - * Handle webhook delete + * Handles deleting a webhook. + * + * @return void */ public function handle_webhook_delete() { - // This method will be called via bulk actions from WP_List_Table - // Individual deletes are handled through the list table's handle_row_actions + // To be implemented: Individual deletes are handled through the list table's handle_row_actions. } /** - * Handle admin actions + * Handles bulk admin actions (such as bulk delete). + * + * @return void */ public function handle_admin_actions() { - // Handle bulk actions from WP_List_Table - if ( isset( $_REQUEST['action'] ) && 'delete' === $_REQUEST['action'] || - isset( $_REQUEST['action2'] ) && 'delete' === $_REQUEST['action2'] ) { - + if ( + ( isset( $_REQUEST['action'] ) && 'delete' === $_REQUEST['action'] ) || + ( isset( $_REQUEST['action2'] ) && 'delete' === $_REQUEST['action2'] ) + ) { if ( ! $this->verify_admin_permission() || ! $this->verify_nonce( 'bulk-webhooks', '_wpnonce' ) ) { return; } @@ -256,11 +266,12 @@ public function handle_admin_actions() { } /** - * Render the admin page + * Renders the webhooks admin page. + * + * @return void */ public function render_admin_page() { $action = isset( $_GET['action'] ) ? sanitize_text_field( $_GET['action'] ) : 'list'; - switch ( $action ) { case 'add': case 'edit': @@ -273,30 +284,27 @@ public function render_admin_page() { } /** - * Render the list page using WP_List_Table + * Renders the list page using WP_List_Table. + * + * @return void */ private function render_list_page() { - // Include the custom list table class require_once __DIR__ . '/WebhooksListTable.php'; - - // Create an instance of our list table $list_table = new WebhooksListTable( $this->repository ); - - // Include the list view template include __DIR__ . '/views/webhooks-list.php'; } /** - * Render the form page (add/edit) + * Renders the form page for adding or editing a webhook. * * @param string $action The action (add or edit). + * @return void */ private function render_form_page( $action ) { $webhook = null; $form_title = 'add' === $action ? __( 'Add New Webhook', 'wp-graphql-webhooks' ) : __( 'Edit Webhook', 'wp-graphql-webhooks' ); $submit_text = 'add' === $action ? __( 'Add Webhook', 'wp-graphql-webhooks' ) : __( 'Update Webhook', 'wp-graphql-webhooks' ); - // Default values for new webhook $name = ''; $event = ''; $url = ''; @@ -311,7 +319,6 @@ private function render_form_page( $action ) { wp_die( __( 'Webhook not found.', 'wp-graphql-webhooks' ) ); } - // Extract values from webhook entity $name = $webhook->name; $event = $webhook->event; $url = $webhook->url; @@ -321,13 +328,13 @@ private function render_form_page( $action ) { $events = $this->repository->get_allowed_events(); $methods = $this->repository->get_allowed_methods(); - $admin = $this; // Pass admin instance to template + $admin = $this; include __DIR__ . '/views/webhook-form.php'; } /** - * Sanitize headers + * Sanitizes webhook headers from the form input. * * @param array $headers Headers to sanitize. * @return array Sanitized headers. @@ -335,7 +342,6 @@ private function render_form_page( $action ) { private function sanitize_headers( array $headers ): array { $sanitized_headers = []; - // Handle the form data structure where headers come as separate arrays if ( isset( $headers['name'] ) && isset( $headers['value'] ) ) { $names = (array) $headers['name']; $values = (array) $headers['value']; @@ -354,75 +360,63 @@ private function sanitize_headers( array $headers ): array { } /** - * Handle AJAX webhook test request. + * Handles AJAX requests to test a webhook. + * + * @return void */ public function ajax_test_webhook(): void { - // Verify nonce if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'wp_rest' ) ) { - wp_send_json_error( [ + wp_send_json_error( [ 'message' => __( 'Invalid security token.', 'wp-graphql-headless-webhooks' ), 'error_code' => 'invalid_nonce' ] ); } - // Check permissions if ( ! current_user_can( 'manage_options' ) ) { - wp_send_json_error( [ + wp_send_json_error( [ 'message' => __( 'You do not have permission to test webhooks.', 'wp-graphql-headless-webhooks' ), 'error_code' => 'insufficient_permissions' ] ); } - // Get webhook ID $webhook_id = isset( $_POST['webhook_id'] ) ? intval( $_POST['webhook_id'] ) : 0; if ( ! $webhook_id ) { - wp_send_json_error( [ + wp_send_json_error( [ 'message' => __( 'Invalid webhook ID.', 'wp-graphql-headless-webhooks' ), 'error_code' => 'invalid_webhook_id' ] ); } - // Get webhook $webhook = $this->repository->get( $webhook_id ); if ( ! $webhook ) { - wp_send_json_error( [ + wp_send_json_error( [ 'message' => __( 'Webhook not found.', 'wp-graphql-headless-webhooks' ), 'error_code' => 'webhook_not_found' ] ); } - // Create test payload - $test_payload = [ + $test_payload = [ 'event' => 'test_webhook', 'timestamp' => current_time( 'mysql' ), - 'webhook' => [ + 'webhook' => [ 'id' => $webhook->id, 'name' => $webhook->name, 'url' => $webhook->url, ], - 'test_data' => [ + 'test_data' => [ 'message' => 'This is a test webhook dispatch', 'random' => wp_generate_password( 12, false ), ], ]; - // Log the test attempt - error_log( sprintf( - '[WPGraphQL Webhooks] Testing webhook #%d (%s) to %s', - $webhook->id, - $webhook->name, - $webhook->url - ) ); - - // Prepare request args - $args = [ + $args = [ 'method' => $webhook->method, 'timeout' => 15, 'redirection' => 5, 'httpversion' => '1.1', - 'blocking' => true, // We want to wait for the response + 'blocking' => true, 'headers' => array_merge( - [ + [ 'Content-Type' => 'application/json', 'User-Agent' => 'WPGraphQL-Webhooks/' . WPGRAPHQL_HEADLESS_WEBHOOKS_VERSION, ], @@ -432,28 +426,15 @@ public function ajax_test_webhook(): void { 'sslverify' => apply_filters( 'graphql_webhooks_sslverify', true ), ]; - // Add webhook metadata to headers $args['headers']['X-WPGraphQL-Webhook-Event'] = 'test_webhook'; $args['headers']['X-WPGraphQL-Webhook-ID'] = (string) $webhook->id; - // Start timing $start_time = microtime( true ); - - // Make the request $response = wp_remote_request( $webhook->url, $args ); - - // Calculate duration $duration_ms = round( ( microtime( true ) - $start_time ) * 1000, 2 ); - // Check for errors if ( is_wp_error( $response ) ) { - error_log( sprintf( - '[WPGraphQL Webhooks] Test failed for webhook #%d: %s', - $webhook->id, - $response->get_error_message() - ) ); - - wp_send_json_error( [ + wp_send_json_error( [ 'message' => sprintf( __( 'Failed to send test webhook: %s', 'wp-graphql-headless-webhooks' ), $response->get_error_message() @@ -463,26 +444,14 @@ public function ajax_test_webhook(): void { ] ); } - // Get response details $response_code = wp_remote_retrieve_response_code( $response ); $response_body = wp_remote_retrieve_body( $response ); - $response_headers = wp_remote_retrieve_headers( $response ); - - // Log the response - error_log( sprintf( - '[WPGraphQL Webhooks] Test response for webhook #%d: HTTP %d in %sms', - $webhook->id, - $response_code, - $duration_ms - ) ); - // Determine if successful (2xx status codes) $is_success = $response_code >= 200 && $response_code < 300; - // Prepare response data - $response_data = [ + $response_data = [ 'success' => $is_success, - 'message' => $is_success + 'message' => $is_success ? sprintf( __( 'Test webhook sent successfully to %s', 'wp-graphql-headless-webhooks' ), $webhook->url ) : sprintf( __( 'Webhook returned HTTP %d', 'wp-graphql-headless-webhooks' ), $response_code ), 'webhook_id' => $webhook->id, @@ -495,14 +464,12 @@ public function ajax_test_webhook(): void { 'test_payload' => $test_payload, ]; - // Add response body if available (limit to 1000 chars for UI) if ( ! empty( $response_body ) ) { - $response_data['response_body'] = strlen( $response_body ) > 1000 - ? substr( $response_body, 0, 1000 ) . '...' + $response_data['response_body'] = strlen( $response_body ) > 1000 + ? substr( $response_body, 0, 1000 ) . '...' : $response_body; } - // Send success response (even if webhook returned non-2xx, the test itself succeeded) wp_send_json_success( $response_data ); } } diff --git a/plugins/wp-graphql-headless-webhooks/src/DTO/WebhookDTO.php b/plugins/wp-graphql-headless-webhooks/src/DTO/WebhookDTO.php deleted file mode 100644 index 4ee7e3e7..00000000 --- a/plugins/wp-graphql-headless-webhooks/src/DTO/WebhookDTO.php +++ /dev/null @@ -1,23 +0,0 @@ -type = $type; - $this->label = $label ?: $type; - $this->description = $description; - $this->config = $config; - $this->events = $events; - } -} \ No newline at end of file diff --git a/plugins/wp-graphql-headless-webhooks/src/Events/Event.php b/plugins/wp-graphql-headless-webhooks/src/Events/Event.php deleted file mode 100644 index 6c259fd2..00000000 --- a/plugins/wp-graphql-headless-webhooks/src/Events/Event.php +++ /dev/null @@ -1,77 +0,0 @@ -name = $name; - $this->hookName = $hookName; - $this->callback = $callback; - $this->priority = $priority; - $this->argCount = $argCount; - } -} \ No newline at end of file diff --git a/plugins/wp-graphql-headless-webhooks/src/Events/EventMonitor.php b/plugins/wp-graphql-headless-webhooks/src/Events/EventMonitor.php deleted file mode 100644 index 31886470..00000000 --- a/plugins/wp-graphql-headless-webhooks/src/Events/EventMonitor.php +++ /dev/null @@ -1,22 +0,0 @@ -monitor = $monitor; - } - - /** - * Handle the given event with provided arguments. - * - * @param Event $event The event object containing metadata and callback. - * @param array $args Arguments passed from the WordPress hook. - * - * @return void - */ - public function dispatch(Event $event, array $args): void { - // Allow skipping event handling via filter - $shouldHandle = apply_filters("graphql_webhooks_event_should_handle_{$event->name}", null, ...$args); - if ($shouldHandle === false) { - return; - } - $payload = null; - if (is_callable($event->callback)) { - $payload = call_user_func($event->callback, ...$args); - } - if (is_wp_error($payload)) { - do_action("graphql_webhooks_event_error_{$event->name}", $payload, $args); - return; - } - $payload = apply_filters("graphql_webhooks_event_payload_{$event->name}", $payload, $args); - - // Track the event payload via the EventMonitor - $this->monitor->track($event->name, $payload); - } -} \ No newline at end of file diff --git a/plugins/wp-graphql-headless-webhooks/src/Events/GraphQLEventRegistry.php b/plugins/wp-graphql-headless-webhooks/src/Events/GraphQLEventRegistry.php deleted file mode 100644 index d1b80d02..00000000 --- a/plugins/wp-graphql-headless-webhooks/src/Events/GraphQLEventRegistry.php +++ /dev/null @@ -1,98 +0,0 @@ -dispatcher = $dispatcher; - } - - /** - * Initialize the registry — triggers the registration action and attaches events. - */ - public function init(): void { - do_action( 'graphql_webhooks_register_events', $this ); - $this->events = apply_filters( 'graphql_webhooks_registered_events', $this->events, $this ); - $this->attach_events(); - } - - /** - * Register an event with the registry. - * - * @param Event $event Event object containing event metadata. - * - * @return bool True if event was registered; false if event with same name exists. - */ - public function register_event( Event $event ): bool { - if ( isset( $this->events[ $event->name ] ) ) { - return false; - } - - $this->events[ $event->name ] = $event; - return true; - } - - /** - * Attach registered event callbacks to WordPress actions. - */ - public function attach_events(): void { - foreach ($this->events as $event) { - add_action( - $event->hookName, - function (...$args) use ($event) { - $this->dispatcher->dispatch($event, $args); - }, - $event->priority, - $event->argCount - ); - } - } - - /** - * Get all registered events. - * - * @return array> - */ - public function get_events(): array { - return $this->events; - } - - /** - * Get a specific event by name. - * - * @param string $eventName - * - * @return array|null - */ - public function get_event( string $eventName ): ?Event { - return $this->events[ $eventName ] ?? null; - } -} \ No newline at end of file diff --git a/plugins/wp-graphql-headless-webhooks/src/Events/GraphQLEventSubscriber.php b/plugins/wp-graphql-headless-webhooks/src/Events/GraphQLEventSubscriber.php deleted file mode 100644 index 53b07c3d..00000000 --- a/plugins/wp-graphql-headless-webhooks/src/Events/GraphQLEventSubscriber.php +++ /dev/null @@ -1,97 +0,0 @@ - 'post_saved', - * 'hook' => 'save_post', - * 'callback' => [ $this, 'onSavePost' ], - * 'priority' => 10, - * 'arg_count' => 3, - * ], - * ] - * - * @var array> - */ - protected array $events = []; - - /** - * Returns the event registrations with callbacks. - * - * @return array> - */ - public function get_event_registrations(): array { - $registrations = []; - - foreach ( $this->events as $event ) { - if ( empty( $event['name'] ) || empty( $event['hook'] ) ) { - continue; - } - $callback = $event['callback'] ?? null; - if ( is_string( $callback ) ) { - $callback = [ $this, $callback ]; - } - $registrations[] = [ - 'name' => $event['name'], - 'hook_name' => $event['hook'], - 'callback' => [ $this, $event['callback'] ], - 'priority' => $event['priority'] ?? 10, - 'arg_count' => $event['arg_count'] ?? 1, - ]; - } - - return $registrations; - } - - /** - * Subscribes to WPGraphQL tracked events. - * - * Hooks into graphql_webhooks_event_tracked_{eventName} actions and dispatches to handler methods. - */ - public function subscribe(): void { - foreach ( $this->events as $event ) { - // Register the event for tracking - $handlerMethodName = $this->get_handler_method_name( $event['name'] ); - if ( method_exists( $this, method: $handlerMethodName ) ) { - - add_action( - "graphql_webhooks_event_tracked_{$event['name']}", - [ $this, $handlerMethodName ] - ); - } - } - } - - /** - * Converts an event name like 'post_saved' to handler method name 'handlePostSavedEvent'. - * - * @param string $eventName - * @return string - */ - protected function get_handler_method_name( string $eventName ): string { - $parts = explode( '_', $eventName ); - $camelCase = array_map( 'ucfirst', $parts ); - return 'handle' . implode( '', $camelCase ) . 'Event'; - } -} \ No newline at end of file diff --git a/plugins/wp-graphql-headless-webhooks/src/Events/Interfaces/EventDispatcher.php b/plugins/wp-graphql-headless-webhooks/src/Events/Interfaces/EventDispatcher.php deleted file mode 100644 index 453feaf0..00000000 --- a/plugins/wp-graphql-headless-webhooks/src/Events/Interfaces/EventDispatcher.php +++ /dev/null @@ -1,41 +0,0 @@ - $args The arguments passed from the WordPress hook. - * - * @return void - */ - public function dispatch(Event $event, array $args): void; -} \ No newline at end of file diff --git a/plugins/wp-graphql-headless-webhooks/src/Events/Interfaces/EventRegistry.php b/plugins/wp-graphql-headless-webhooks/src/Events/Interfaces/EventRegistry.php deleted file mode 100644 index f59ce1c7..00000000 --- a/plugins/wp-graphql-headless-webhooks/src/Events/Interfaces/EventRegistry.php +++ /dev/null @@ -1,46 +0,0 @@ - 'Manages GraphQL Webhooks', 'taxonomies' => [], 'public' => false, - 'show_ui' => true, - 'show_in_menu' => true, + 'show_ui' => false, + 'show_in_menu' => false, 'show_in_admin_bar' => false, 'menu_icon' => 'dashicons-share-alt', 'show_in_nav_menus' => false, diff --git a/plugins/wp-graphql-headless-webhooks/src/Repository/Interfaces/WebhookRepositoryInterface.php b/plugins/wp-graphql-headless-webhooks/src/Repository/Interfaces/WebhookRepositoryInterface.php index 885eeb9e..ad4ed724 100644 --- a/plugins/wp-graphql-headless-webhooks/src/Repository/Interfaces/WebhookRepositoryInterface.php +++ b/plugins/wp-graphql-headless-webhooks/src/Repository/Interfaces/WebhookRepositoryInterface.php @@ -10,75 +10,62 @@ */ interface WebhookRepositoryInterface { - /** - * Retrieves all webhooks. - * - * @return Webhook[] Array of Webhook entities. - */ - public function get_all(): array; + /** + * Retrieves all webhooks. + * + * @return Webhook[] Array of Webhook entities. + */ + public function get_all(): array; - /** - * Retrieves a single webhook by its ID. - * - * @param int $id The webhook post ID. - * - * @return Webhook|null The Webhook entity, or null if not found. - */ - public function get(int $id): ?Webhook; + /** + * Retrieves a single webhook by its ID. + * + * @param int $id The webhook post ID. + * + * @return Webhook|null The Webhook entity, or null if not found. + */ + public function get( int $id ): ?Webhook; - /** - * Creates a new webhook. - * - * @param array $data The webhook data containing: - * - name (string): The name (title) of the webhook. - * - event (string): The event identifier the webhook listens to. - * - url (string): The target URL the webhook will send data to. - * - method (string): The HTTP method to use when sending the webhook (e.g., 'POST'). - * - headers (array): Optional associative array of headers to send with the request. - * - * @return int|\WP_Error The new webhook's post ID on success, or WP_Error on failure. - */ - public function create(array $data); + /** + * Creates a new webhook. + * + * @param Webhook $webhook The webhook entity to create. + * + * @return int|\WP_Error The new webhook's post ID on success, or WP_Error on failure. + */ + public function create( Webhook $webhook ); - /** - * Updates an existing webhook. - * - * @param int $id The webhook post ID. - * @param array $data The webhook data containing: - * - name (string): The updated name (title) of the webhook. - * - event (string): The updated event identifier. - * - url (string): The updated target URL. - * - method (string): The updated HTTP method. - * - headers (array): The updated array of headers. - * - * @return bool|\WP_Error True on success, or WP_Error on failure. - */ - public function update(int $id, array $data); + /** + * Updates an existing webhook. + * + * @param int $id The webhook post ID. + * @param Webhook $webhook The webhook entity with updated data. + * + * @return bool|\WP_Error True on success, or WP_Error on failure. + */ + public function update( int $id, Webhook $webhook ); - /** - * Deletes a webhook by its ID. - * - * @param int $id The webhook post ID. - * - * @return bool True on successful deletion, false otherwise. - */ - public function delete(int $id): bool; + /** + * Deletes a webhook by its ID. + * + * @param int $id The webhook post ID. + * + * @return bool True on successful deletion, false otherwise. + */ + public function delete( int $id ): bool; - /** - * Retrieves the list of allowed webhook events. - * - * @return string[] Array of allowed event identifiers. - */ - public function get_allowed_events(): array; + /** + * Retrieves the list of allowed webhook events. + * + * @return string[] Array of allowed event identifiers. + */ + public function get_allowed_events(): array; - /** - * Validates webhook data before creation or update. - * - * @param string $event The event identifier. - * @param string $url The target URL. - * @param string $method The HTTP method. - * - * @return bool|\WP_Error True if data is valid, or WP_Error with a descriptive message. - */ - public function validate_data(string $event, string $url, string $method); + /** + * Validates the webhook data. + * + * @param Webhook $webhook The webhook entity to validate. + * @return true|\WP_Error True if valid, or WP_Error on failure. + */ + public function validate( Webhook $webhook ); } \ No newline at end of file diff --git a/plugins/wp-graphql-headless-webhooks/src/Repository/WebhookRepository.php b/plugins/wp-graphql-headless-webhooks/src/Repository/WebhookRepository.php index 2f48a191..3ac45bef 100644 --- a/plugins/wp-graphql-headless-webhooks/src/Repository/WebhookRepository.php +++ b/plugins/wp-graphql-headless-webhooks/src/Repository/WebhookRepository.php @@ -106,32 +106,18 @@ public function get( $id ): ?Webhook { /** * Create a new webhook entity. * - * @param array $data { - * Webhook data. - * - * @type string $name Name/title of the webhook. - * @type string $event Event key the webhook listens to. - * @type string $url Target URL for the webhook request. - * @type string $method HTTP method (GET, POST, etc). - * @type array $headers Associative array of HTTP headers. - * } + * @param Webhook $webhook The webhook entity to create. * * @return int|WP_Error Post ID on success, or WP_Error on failure. */ - public function create( $data ) { - $name = $data['name'] ?? ''; - $event = $data['event'] ?? ''; - $url = $data['url'] ?? ''; - $method = $data['method'] ?? 'POST'; - $headers = $data['headers'] ?? []; - - $validation = $this->validate_data( $event, $url, $method ); + public function create( Webhook $webhook ) { + $validation = $this->validate( $webhook ); if ( is_wp_error( $validation ) ) { return $validation; } $postId = wp_insert_post( [ - 'post_title' => $name, + 'post_title' => $webhook->name, 'post_type' => 'graphql_webhook', 'post_status' => 'publish', ], true ); @@ -140,10 +126,10 @@ public function create( $data ) { return $postId; } - update_post_meta( $postId, '_webhook_event', sanitize_text_field( $event ) ); - update_post_meta( $postId, '_webhook_url', esc_url_raw( $url ) ); - update_post_meta( $postId, '_webhook_method', strtoupper( $method ) ); - update_post_meta( $postId, '_webhook_headers', wp_json_encode( $headers ) ); + update_post_meta( $postId, '_webhook_event', sanitize_text_field( $webhook->event ) ); + update_post_meta( $postId, '_webhook_url', esc_url_raw( $webhook->url ) ); + update_post_meta( $postId, '_webhook_method', strtoupper( $webhook->method ) ); + update_post_meta( $postId, '_webhook_headers', wp_json_encode( $webhook->headers ) ); return $postId; } @@ -151,39 +137,26 @@ public function create( $data ) { /** * Update an existing webhook entity. * - * @param int $id Post ID of the webhook to update. - * @param array $data { - * Webhook data. - * - * @type string $name New name/title of the webhook. - * @type string $event New event key. - * @type string $url New target URL. - * @type string $method New HTTP method. - * @type array $headers New HTTP headers. - * } + * @param int $id Post ID of the webhook to update. + * @param Webhook $webhook The webhook entity with updated data. * * @return bool|WP_Error True on success, or WP_Error on failure. */ - public function update( $id, $data ) { + public function update( int $id, Webhook $webhook ) { $post = get_post( $id ); if ( ! $post || $post->post_type !== 'graphql_webhook' ) { return new WP_Error( 'invalid_webhook', __( 'Webhook not found.', 'wp-graphql-headless-webhooks' ) ); } - $name = $data['name'] ?? ''; - $event = $data['event'] ?? ''; - $url = $data['url'] ?? ''; - $method = $data['method'] ?? 'POST'; - $headers = $data['headers'] ?? []; - - $validation = $this->validate_data( $event, $url, $method ); + // Validate using the Webhook entity + $validation = $this->validate( $webhook ); if ( is_wp_error( $validation ) ) { return $validation; } $postData = [ 'ID' => $id, - 'post_title' => sanitize_text_field( $name ), + 'post_title' => sanitize_text_field( $webhook->name ), ]; $updated = wp_update_post( $postData, true ); @@ -191,10 +164,10 @@ public function update( $id, $data ) { return $updated; } - update_post_meta( $id, '_webhook_event', sanitize_text_field( $event ) ); - update_post_meta( $id, '_webhook_url', esc_url_raw( $url ) ); - update_post_meta( $id, '_webhook_method', strtoupper( $method ) ); - update_post_meta( $id, '_webhook_headers', wp_json_encode( $headers ) ); + update_post_meta( $id, '_webhook_event', sanitize_text_field( $webhook->event ) ); + update_post_meta( $id, '_webhook_url', esc_url_raw( $webhook->url ) ); + update_post_meta( $id, '_webhook_method', strtoupper( $webhook->method ) ); + update_post_meta( $id, '_webhook_headers', wp_json_encode( $webhook->headers ) ); return true; } @@ -217,15 +190,16 @@ public function delete( $id ): bool { } /** - * Validate webhook data before creation or update. - * - * @param string $event Event key to validate. - * @param string $url URL to validate. - * @param string $method HTTP method to validate. + * Validate webhook entity before creation or update. * + * @param Webhook $webhook The webhook entity to validate. * @return bool|WP_Error True if valid, WP_Error if invalid. */ - public function validate_data( $event, $url, $method ) { + public function validate( Webhook $webhook ) { + $event = $webhook->event; + $url = $webhook->url; + $method = $webhook->method; + if ( ! isset( $this->get_allowed_events()[ $event ] ) ) { return new WP_Error( 'invalid_event', 'Invalid event type.' ); } diff --git a/plugins/wp-graphql-headless-webhooks/src/WebhookTypeRegistry.php b/plugins/wp-graphql-headless-webhooks/src/WebhookTypeRegistry.php deleted file mode 100644 index b2c6eee5..00000000 --- a/plugins/wp-graphql-headless-webhooks/src/WebhookTypeRegistry.php +++ /dev/null @@ -1,83 +0,0 @@ -> Array of webhook types keyed by type identifier. - */ - private array $webhook_types = []; - - private ?EventRegistry $eventRegistry = null; - - /** - * Instance of the registry - * - * @var WebhookTypeRegistry|null - */ - private static ?WebhookTypeRegistry $instance = null; - - public function __construct( EventRegistry $eventRegistry ) { - $this->eventRegistry = $eventRegistry; - } - - public function set_event_registry( EventRegistry $eventRegistry ): void { - $this->eventRegistry = $eventRegistry; - $this->eventRegistry->init(); - } - - /** - * Registers a webhook type and its events. - * - * @param WebhookDTO $webhookType - * - * @return bool True if registered successfully, false if already exists. - */ - public function register_webhook_type( WebhookDTO $webhookType ): bool { - if ( isset( $this->webhook_types[ $webhookType->type ] ) ) { - return false; - } - - $this->webhook_types[ $webhookType->type ] = $webhookType; - - // Register events with the event registry - foreach ( $webhookType->events as $event ) { - $this->eventRegistry->register_event( $event ); - } - - return true; - } - - /** - * Get all registered webhook types - * - * @return array> - */ - public function get_webhook_types(): array { - return $this->webhook_types; - } - - /** - * Get a specific webhook type - * - * @param string $type The webhook type. - * @return array|null - */ - public function get_webhook_type( string $type ): ?array { - return $this->webhook_types[ $type ] ?? null; - } -}