From 87a1484e83b15aa51a519793ac07f34871f642b2 Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Fri, 12 Jul 2024 14:11:42 -0600 Subject: [PATCH 1/3] - prototype concept for sending webhooks to Next for on-demand revalidation in response to purge actions --- src/Cache/Invalidation.php | 113 ++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/src/Cache/Invalidation.php b/src/Cache/Invalidation.php index 67601c3..b46f347 100644 --- a/src/Cache/Invalidation.php +++ b/src/Cache/Invalidation.php @@ -47,9 +47,15 @@ public function init() { // Listen for purge all, purge now request add_action( 'wpgraphql_cache_purge_all', [ $this, 'on_purge_all_cb' ], 10, 0 ); - ## Log Purge Events + // Log Purge Events add_action( 'graphql_purge', [ $this, 'log_purge_events' ], 10, 3 ); + // Send Webhooks + add_action( 'graphql_purge', [ $this, 'send_webhooks' ], 10, 3 ); + add_filter( 'wpgraphql_smart_cache_webhook_payload', [ $this, 'filter_payload_for_faust' ], 10, 2 ); + + + ## POST ACTIONS // listen for posts to transition statuses, so we know when to purge @@ -282,6 +288,111 @@ public function log_purge_events( $key, $event, $hostname ) { error_log( $message, 0 ); } + public function filter_payload_for_faust( array $payload, array $config ) { + + if ( function_exists( '\WPE\FaustWP\Settings\get_secret_key' ) ) { + // \WPE\FaustWP\Settings\faustwp_update_setting( 'secret_key', '12fd5ee6-1492-4e8d-beb3-584bad776bf8' ); + $payload['faustSecret'] = \WPE\FaustWP\Settings\get_secret_key(); + } + + return $payload; + } + + /** + * Log purge events, if enabled + * + * @param string $key The key to purge from teh cache + * @param string $event The Event that triggered the purge + * @param string $hostname The url endpoint associated with the cache key. These match the Url and Key headers provided when the results were cached. + */ + public function send_webhooks( $key, $event, $hostname ) { + + $webhook_configs = [ + [ + 'url' => 'http://localhost:3000/api/revalidate/', + 'method' => 'GET', + 'headers' => [], + ] + ]; + + foreach ( $webhook_configs as $webhook_config ) { + + $default_payload = [ + 'key' => $key, + 'event' => $event, + 'hostname' => $hostname, + ]; + + $node_id = ! empty( $key ) ? Relay::fromGlobalId( $key ) : $key; + $node_type = $node_id['type'] ?? null; + $database_id = $node_id['id'] ?? null; + + if ( ! empty ( $node_type ) ) { + switch ( $node_type ) { + case 'post': + $post_id = absint( $database_id ); + $permalink = get_permalink( $post_id ); + break; + case 'term': + $term_id = absint( $database_id ); + $permalink = get_term_link( $term_id ); + break; + case 'user': + $user_id = absint( $database_id ); + $user = get_user_by( 'id', $user_id ); + $permalink = '/author/' . $user->user_nicename . '/'; + break; + default: + break; + } + + if ( ! empty( $permalink ) ) { + $default_payload['path'] = parse_url( $permalink, PHP_URL_PATH ); + } + } + + /** + * Filter the webhook payload before sending the request + * This allows for modification of the payload before sending the request + * This can be used to add additional data to the payload or modify the payload + * + * @param array $payload The payload to be sent in the webhook request + * @param array $webhook_config The webhook configuration + */ + $filtered_payload = apply_filters( 'wpgraphql_smart_cache_webhook_payload', $default_payload, $webhook_config ); + + switch ( $webhook_config['method'] ) { + case 'GET': + $webhook_config['url'] = add_query_arg( $filtered_payload, $webhook_config['url'] ); + break; + case 'POST': + default: + $webhook_config['headers']['Content-Type'] = 'application/json'; + $webhook_config['body'] = wp_json_encode( $filtered_payload ); + break; + } + + // Send the webhook request + $res = wp_remote_request( $webhook_config['url'], [ + 'method' => $webhook_config['method'], + 'headers' => $webhook_config['headers'] ?? [], + 'body' => $webhook_config['body'] ?? null, + 'blocking' => false, + ] ); + +// wp_send_json([ +// 'res' => $res, +// 'webhook_config' => $webhook_config, +// ]); + + if ( is_wp_error( $res ) ) { + // @phpcs:ignore + error_log( __( 'Error sending webhook: ', 'wp-graphql-smart-cache' ) . $res->get_error_message() ); + } + } + + } + /** * Listen for updates to a post so we can purge caches relevant to the change * From 079a9369cab1c20dd8c7d531bbdb107f4f9ee143 Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Fri, 12 Jul 2024 15:33:02 -0600 Subject: [PATCH 2/3] - rename faust filter and update docblocks - add `get_path_from_key` function to abstract some of the logic around purge event keys and determining paths --- src/Cache/Invalidation.php | 105 +++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/src/Cache/Invalidation.php b/src/Cache/Invalidation.php index b46f347..03aaf15 100644 --- a/src/Cache/Invalidation.php +++ b/src/Cache/Invalidation.php @@ -52,8 +52,7 @@ public function init() { // Send Webhooks add_action( 'graphql_purge', [ $this, 'send_webhooks' ], 10, 3 ); - add_filter( 'wpgraphql_smart_cache_webhook_payload', [ $this, 'filter_payload_for_faust' ], 10, 2 ); - + add_filter( 'wpgraphql_smart_cache_webhook_payload', [ $this, 'temp_filter_payload_for_faust' ], 10, 2 ); ## POST ACTIONS @@ -288,7 +287,17 @@ public function log_purge_events( $key, $event, $hostname ) { error_log( $message, 0 ); } - public function filter_payload_for_faust( array $payload, array $config ) { + /** + * Filter the payload to add the Faust Secret key for revalidation of Faust pages + * + * NOTE: This would likely live in the faustwp plugin, but I'm including it here for POC purposes + * + * @param array $payload The payload to be sent to Faust + * @param array $config The config array + * + * @return array + */ + public function temp_filter_payload_for_faust( array $payload, array $config ): array { if ( function_exists( '\WPE\FaustWP\Settings\get_secret_key' ) ) { // \WPE\FaustWP\Settings\faustwp_update_setting( 'secret_key', '12fd5ee6-1492-4e8d-beb3-584bad776bf8' ); @@ -305,11 +314,17 @@ public function filter_payload_for_faust( array $payload, array $config ) { * @param string $event The Event that triggered the purge * @param string $hostname The url endpoint associated with the cache key. These match the Url and Key headers provided when the results were cached. */ - public function send_webhooks( $key, $event, $hostname ) { + public function send_webhooks( string $key, string $event, string $hostname ): void { + + if ( ! function_exists( '\WPE\FaustWP\Settings\faustwp_get_setting') ) { + return; + } + + $path = \WPE\FaustWP\Settings\faustwp_get_setting( 'frontend_uri' ); $webhook_configs = [ [ - 'url' => 'http://localhost:3000/api/revalidate/', + 'url' => untrailingslashit( $path ) . '/api/revalidate/', 'method' => 'GET', 'headers' => [], ] @@ -321,36 +336,9 @@ public function send_webhooks( $key, $event, $hostname ) { 'key' => $key, 'event' => $event, 'hostname' => $hostname, + 'path' => $this->get_path_from_key( $key ), ]; - $node_id = ! empty( $key ) ? Relay::fromGlobalId( $key ) : $key; - $node_type = $node_id['type'] ?? null; - $database_id = $node_id['id'] ?? null; - - if ( ! empty ( $node_type ) ) { - switch ( $node_type ) { - case 'post': - $post_id = absint( $database_id ); - $permalink = get_permalink( $post_id ); - break; - case 'term': - $term_id = absint( $database_id ); - $permalink = get_term_link( $term_id ); - break; - case 'user': - $user_id = absint( $database_id ); - $user = get_user_by( 'id', $user_id ); - $permalink = '/author/' . $user->user_nicename . '/'; - break; - default: - break; - } - - if ( ! empty( $permalink ) ) { - $default_payload['path'] = parse_url( $permalink, PHP_URL_PATH ); - } - } - /** * Filter the webhook payload before sending the request * This allows for modification of the payload before sending the request @@ -364,6 +352,7 @@ public function send_webhooks( $key, $event, $hostname ) { switch ( $webhook_config['method'] ) { case 'GET': $webhook_config['url'] = add_query_arg( $filtered_payload, $webhook_config['url'] ); + $webhook_config['body'] = null; break; case 'POST': default: @@ -375,16 +364,11 @@ public function send_webhooks( $key, $event, $hostname ) { // Send the webhook request $res = wp_remote_request( $webhook_config['url'], [ 'method' => $webhook_config['method'], - 'headers' => $webhook_config['headers'] ?? [], - 'body' => $webhook_config['body'] ?? null, + 'headers' => $webhook_config['headers'], + 'body' => $webhook_config['body'], 'blocking' => false, ] ); -// wp_send_json([ -// 'res' => $res, -// 'webhook_config' => $webhook_config, -// ]); - if ( is_wp_error( $res ) ) { // @phpcs:ignore error_log( __( 'Error sending webhook: ', 'wp-graphql-smart-cache' ) . $res->get_error_message() ); @@ -393,6 +377,47 @@ public function send_webhooks( $key, $event, $hostname ) { } + /** + * Get the path from the key + * + * @param string $key The key to get the path from + * + * @return string + */ + public function get_path_from_key( string $key ): string { + $path = ''; + $node_id = ! empty( $key ) ? Relay::fromGlobalId( $key ) : $key; + $node_type = $node_id['type'] ?? null; + $database_id = $node_id['id'] ?? null; + + if ( ! empty ( $node_type ) ) { + switch ( $node_type ) { + case 'post': + $post_id = absint( $database_id ); + $permalink = get_permalink( $post_id ); + break; + case 'term': + $term_id = absint( $database_id ); + $permalink = get_term_link( $term_id ); + break; + case 'user': + $user_id = absint( $database_id ); + $user = get_user_by( 'id', $user_id ); + $permalink = $user instanceof WP_User ? '/author/' . $user->user_nicename . '/' : null; + break; + default: + break; + } + + // ensure the permalink is a string and not a WP_Error or anything else + if ( ! empty( $permalink ) && is_string( $permalink ) ) { + $path = parse_url( $permalink, PHP_URL_PATH ); + } + } + + return ! empty( $path ) ? $path : ''; + } + /** * Listen for updates to a post so we can purge caches relevant to the change * From 8e0dd1a809b9f9d9bd00e305e2a7e513b3a1fda2 Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Fri, 12 Jul 2024 15:38:37 -0600 Subject: [PATCH 3/3] - remove secret --- src/Cache/Invalidation.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Cache/Invalidation.php b/src/Cache/Invalidation.php index 03aaf15..5712052 100644 --- a/src/Cache/Invalidation.php +++ b/src/Cache/Invalidation.php @@ -300,7 +300,6 @@ public function log_purge_events( $key, $event, $hostname ) { public function temp_filter_payload_for_faust( array $payload, array $config ): array { if ( function_exists( '\WPE\FaustWP\Settings\get_secret_key' ) ) { - // \WPE\FaustWP\Settings\faustwp_update_setting( 'secret_key', '12fd5ee6-1492-4e8d-beb3-584bad776bf8' ); $payload['faustSecret'] = \WPE\FaustWP\Settings\get_secret_key(); }