From 098e4fcfb8946ecb117cd948ec3584f9166e33f0 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 27 Jan 2026 12:05:50 +0800 Subject: [PATCH] REST API: Refresh Resources --- .../class-ckwc-refresh-resources.php | 19 ++---- includes/class-ckwc-rest-api.php | 21 ++++++ includes/class-wp-ckwc.php | 28 ++++---- resources/backend/js/refresh-resources.js | 34 ++++------ tests/Integration/RESTAPITest.php | 65 +++++++++++++++++++ woocommerce-convertkit.php | 2 +- 6 files changed, 120 insertions(+), 49 deletions(-) rename admin/class-ckwc-admin-refresh-resources.php => includes/class-ckwc-refresh-resources.php (80%) diff --git a/admin/class-ckwc-admin-refresh-resources.php b/includes/class-ckwc-refresh-resources.php similarity index 80% rename from admin/class-ckwc-admin-refresh-resources.php rename to includes/class-ckwc-refresh-resources.php index 20ab3d56..922daf06 100644 --- a/admin/class-ckwc-admin-refresh-resources.php +++ b/includes/class-ckwc-refresh-resources.php @@ -13,7 +13,7 @@ * @package CKWC * @author ConvertKit */ -class CKWC_Admin_Refresh_Resources { +class CKWC_Refresh_Resources { /** * Registers action and filter hooks. @@ -22,7 +22,6 @@ class CKWC_Admin_Refresh_Resources { */ public function __construct() { - add_action( 'wp_ajax_ckwc_admin_refresh_resources', array( $this, 'refresh_resources' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); } @@ -34,9 +33,6 @@ public function __construct() { */ public function refresh_resources() { - // Check nonce. - check_ajax_referer( 'ckwc_admin_refresh_resources', 'nonce' ); - // Define an array to store resources in. $resources = array(); @@ -46,7 +42,7 @@ public function refresh_resources() { // Bail if an error occured. if ( is_wp_error( $resources['forms'] ) ) { - wp_send_json_error( $resources['forms']->get_error_message() ); + return $resources['forms']; } // Fetch sequences. @@ -55,7 +51,7 @@ public function refresh_resources() { // Bail if an error occured. if ( is_wp_error( $resources['sequences'] ) ) { - wp_send_json_error( $resources['sequences']->get_error_message() ); + return $resources['sequences']; } // Fetch tags. @@ -64,7 +60,7 @@ public function refresh_resources() { // Bail if an error occured. if ( is_wp_error( $resources['tags'] ) ) { - wp_send_json_error( $resources['tags']->get_error_message() ); + return $resources['tags']; } // Return resources as a zero based sequential array, so that JS retains the order of resources. @@ -72,7 +68,7 @@ public function refresh_resources() { $resources['sequences'] = array_values( $resources['sequences'] ); $resources['tags'] = array_values( $resources['tags'] ); - wp_send_json_success( $resources ); + return $resources; } @@ -104,10 +100,9 @@ public function enqueue_scripts( $hook ) { 'ckwc-admin-refresh-resources', 'ckwc_admin_refresh_resources', array( - 'action' => 'ckwc_admin_refresh_resources', - 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'ajaxurl' => rest_url( 'kit/v1/woocommerce/resources/refresh' ), 'debug' => $integration->get_option_bool( 'debug' ), - 'nonce' => wp_create_nonce( 'ckwc_admin_refresh_resources' ), + 'nonce' => wp_create_nonce( 'wp_rest' ), ) ); diff --git a/includes/class-ckwc-rest-api.php b/includes/class-ckwc-rest-api.php index 3ebd3d93..a11f8335 100644 --- a/includes/class-ckwc-rest-api.php +++ b/includes/class-ckwc-rest-api.php @@ -42,6 +42,27 @@ public function __construct() { */ public function register_routes() { + // Register route to refresh resources. + register_rest_route( + 'kit/v1', + '/woocommerce/resources/refresh', + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => function () { + + return rest_ensure_response( WP_CKWC()->get_class( 'refresh_resources' )->refresh_resources() ); + + }, + + // Only refresh resources for users who can edit posts. + 'permission_callback' => function () { + + return current_user_can( 'edit_posts' ); + + }, + ) + ); + // Register route to send a WooCommerce Order to Kit. register_rest_route( 'kit/v1', diff --git a/includes/class-wp-ckwc.php b/includes/class-wp-ckwc.php index 338a503c..77802c1e 100644 --- a/includes/class-wp-ckwc.php +++ b/includes/class-wp-ckwc.php @@ -155,12 +155,11 @@ private function initialize_admin() { return; } - $this->classes['admin_bulk_edit'] = new CKWC_Admin_Bulk_Edit(); - $this->classes['admin_coupon'] = new CKWC_Admin_Coupon(); - $this->classes['admin_plugin'] = new CKWC_Admin_Plugin(); - $this->classes['admin_product'] = new CKWC_Admin_Product(); - $this->classes['admin_quick_edit'] = new CKWC_Admin_Quick_Edit(); - $this->classes['admin_refresh_resources'] = new CKWC_Admin_Refresh_Resources(); + $this->classes['admin_bulk_edit'] = new CKWC_Admin_Bulk_Edit(); + $this->classes['admin_coupon'] = new CKWC_Admin_Coupon(); + $this->classes['admin_plugin'] = new CKWC_Admin_Plugin(); + $this->classes['admin_product'] = new CKWC_Admin_Product(); + $this->classes['admin_quick_edit'] = new CKWC_Admin_Quick_Edit(); /** * Initialize integration classes for the WordPress Administration interface. @@ -247,14 +246,15 @@ private function initialize_frontend() { */ private function initialize_global() { - $this->classes['abandoned_cart'] = new CKWC_Abandoned_Cart(); - $this->classes['admin_notices'] = new CKWC_Admin_Notices(); - $this->classes['checkout'] = new CKWC_Checkout(); - $this->classes['order'] = new CKWC_Order(); - $this->classes['rest_api'] = new CKWC_REST_API(); - $this->classes['review_request'] = new ConvertKit_Review_Request( 'Kit for WooCommerce', 'convertkit-for-woocommerce', CKWC_PLUGIN_PATH ); - $this->classes['setup'] = new CKWC_Setup(); - $this->classes['wc_subscriptions'] = new CKWC_WC_Subscriptions(); + $this->classes['abandoned_cart'] = new CKWC_Abandoned_Cart(); + $this->classes['admin_notices'] = new CKWC_Admin_Notices(); + $this->classes['checkout'] = new CKWC_Checkout(); + $this->classes['order'] = new CKWC_Order(); + $this->classes['rest_api'] = new CKWC_REST_API(); + $this->classes['refresh_resources'] = new CKWC_Refresh_Resources(); + $this->classes['review_request'] = new ConvertKit_Review_Request( 'Kit for WooCommerce', 'convertkit-for-woocommerce', CKWC_PLUGIN_PATH ); + $this->classes['setup'] = new CKWC_Setup(); + $this->classes['wc_subscriptions'] = new CKWC_WC_Subscriptions(); /** * Initialize integration classes for the frontend web site. diff --git a/resources/backend/js/refresh-resources.js b/resources/backend/js/refresh-resources.js index 74fdfd60..61ba8e47 100644 --- a/resources/backend/js/refresh-resources.js +++ b/resources/backend/js/refresh-resources.js @@ -23,11 +23,13 @@ jQuery(document).ready(function ($) { // Perform AJAX request to refresh resource. $.ajax({ type: 'POST', - data: { - action: 'ckwc_admin_refresh_resources', - nonce: ckwc_admin_refresh_resources.nonce, - }, url: ckwc_admin_refresh_resources.ajaxurl, + beforeSend(xhr) { + xhr.setRequestHeader( + 'X-WP-Nonce', + ckwc_admin_refresh_resources.nonce + ); + }, success(response) { if (ckwc_admin_refresh_resources.debug) { console.log(response); @@ -36,19 +38,6 @@ jQuery(document).ready(function ($) { // Remove any existing error notices that might be displayed. ckwcRefreshResourcesRemoveNotices(); - // Show an error if the request wasn't successful. - if (!response.success) { - // Show error notice. - ckwcRefreshResourcesOutputErrorNotice(response.data); - - // Enable button. - $(button) - .prop('disabled', false) - .removeClass('is-refreshing'); - - return; - } - // Get currently selected option. const selectedOption = $(field).val(); @@ -69,9 +58,7 @@ jQuery(document).ready(function ($) { // Populate each resource type with select options from response data into // the application option group. - for (const [resource, resources] of Object.entries( - response.data - )) { + for (const [resource, resources] of Object.entries(response)) { // resource = forms, sequences, tags. // resoruces = array of resources. resources.forEach(function (item) { @@ -108,8 +95,11 @@ jQuery(document).ready(function ($) { ckwcRefreshResourcesOutputErrorNotice( 'Kit for WooCommerce: ' + response.status + - ' ' + - response.statusText + ': ' + + (typeof response.responseJSON !== 'undefined' && + response.responseJSON.message + ? response.responseJSON.message + : response.statusText) ); // Enable button. diff --git a/tests/Integration/RESTAPITest.php b/tests/Integration/RESTAPITest.php index f01b8130..39c0227c 100644 --- a/tests/Integration/RESTAPITest.php +++ b/tests/Integration/RESTAPITest.php @@ -132,6 +132,44 @@ public function testSyncPastOrder() $this->assertStringContainsString( 'WooCommerce Order ID #' . $order->get_id() . ' added to Kit Purchase Data successfully. Kit Purchase ID: #' . get_post_meta( $order->get_id(), 'ckwc_purchase_data_id', true ), $data['data'] ); } + /** + * Test that the /wp-json/kit/v1/woocommerce/resources/refresh REST API route returns a 401 when the user is not authorized. + * + * @since 2.0.6 + */ + public function testRefreshResourcesWhenUnauthorized() + { + // Make request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/woocommerce/resources/refresh' ); + $response = rest_get_server()->dispatch( $request ); + + // Assert response is unsuccessful. + $this->assertSame( 401, $response->get_status() ); + } + + /** + * Test that the /wp-json/kit/v1/woocommerce/resources/refresh REST API route refreshes and returns resources when the user is authorized. + * + * @since 2.0.6 + */ + public function testRefreshResources() + { + // Create and become editor. + $this->actAsEditor(); + + // Send request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/woocommerce/resources/refresh' ); + $response = rest_get_server()->dispatch( $request ); + + // Assert response is successful. + $this->assertSame( 200, $response->get_status() ); + + // Assert response data has the expected keys. + $data = $response->get_data(); + $this->assertIsArray( $data ); + $this->assertArrayHasKeys( $data, [ 'forms', 'sequences', 'tags' ] ); + } + /** * Act as an administrator. * @@ -142,4 +180,31 @@ private function actAsAdministrator() $administrator_id = static::factory()->user->create( [ 'role' => 'administrator' ] ); wp_set_current_user( $administrator_id ); } + + /** + * Act as an editor. + * + * @since 2.0.6 + */ + private function actAsEditor() + { + $editor_id = static::factory()->user->create( [ 'role' => 'editor' ] ); + wp_set_current_user( $editor_id ); + } + + /** + * Assert that an array has the expected keys. + * + * @since 2.0.6 + * + * @param array $arr The array to assert. + * @param array $keys The keys to assert. + * @return void + */ + private function assertArrayHasKeys( $arr, $keys ) + { + foreach ( $keys as $key ) { + $this->assertArrayHasKey( $key, $arr ); + } + } } diff --git a/woocommerce-convertkit.php b/woocommerce-convertkit.php index b86a3a53..31232d54 100755 --- a/woocommerce-convertkit.php +++ b/woocommerce-convertkit.php @@ -59,6 +59,7 @@ require_once CKWC_PLUGIN_PATH . '/includes/class-ckwc-cli-sync-past-orders.php'; require_once CKWC_PLUGIN_PATH . '/includes/class-ckwc-order.php'; require_once CKWC_PLUGIN_PATH . '/includes/class-ckwc-rest-api.php'; +require_once CKWC_PLUGIN_PATH . '/includes/class-ckwc-refresh-resources.php'; require_once CKWC_PLUGIN_PATH . '/includes/class-ckwc-resource.php'; require_once CKWC_PLUGIN_PATH . '/includes/class-ckwc-resource-custom-fields.php'; require_once CKWC_PLUGIN_PATH . '/includes/class-ckwc-resource-forms.php'; @@ -74,7 +75,6 @@ require_once CKWC_PLUGIN_PATH . '/admin/class-ckwc-admin-plugin.php'; require_once CKWC_PLUGIN_PATH . '/admin/class-ckwc-admin-product.php'; require_once CKWC_PLUGIN_PATH . '/admin/class-ckwc-admin-quick-edit.php'; -require_once CKWC_PLUGIN_PATH . '/admin/class-ckwc-admin-refresh-resources.php'; // Register Plugin activation and deactivation functions. register_activation_hook( __FILE__, 'ckwc_plugin_activate' );