From f6d8b614d0025f407316d509657353f7e06d783d Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 13 Feb 2026 10:01:34 +0800 Subject: [PATCH 01/11] REST API: Classic Editor: Improvements --- admin/class-convertkit-admin-tinymce.php | 18 +++++++++++++---- resources/backend/js/editor.js | 25 +++++++++++++----------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/admin/class-convertkit-admin-tinymce.php b/admin/class-convertkit-admin-tinymce.php index 178f83f11..110408a77 100644 --- a/admin/class-convertkit-admin-tinymce.php +++ b/admin/class-convertkit-admin-tinymce.php @@ -44,16 +44,26 @@ public function register_routes() { // Register route to return all blocks registered by the Plugin. register_rest_route( 'kit/v1', - '/tinymce/output-modal', + '/editor/tinymce/modal/(?P[a-zA-Z0-9-_]+)/(?P[a-zA-Z0-9-_]+)', array( - 'methods' => WP_REST_Server::CREATABLE, + 'methods' => WP_REST_Server::READABLE, 'args' => array( 'shortcode' => array( 'required' => true, + 'validate_callback' => function ( $param ) { + + return is_string( $param ) && in_array( $param, array_keys( convertkit_get_shortcodes() ), true ); + + }, 'sanitize_callback' => 'sanitize_text_field', ), 'editor_type' => array( 'required' => true, + 'validate_callback' => function ( $param ) { + + return is_string( $param ) && in_array( $param, array( 'tinymce', 'quicktags' ), true ); + + }, 'sanitize_callback' => 'sanitize_text_field', ), ), @@ -146,7 +156,7 @@ public function register_quicktags() { 'convertkit-admin-quicktags', 'convertkit_admin_tinymce', array( - 'ajaxurl' => rest_url( 'kit/v1/tinymce/output-modal' ), + 'ajaxurl' => rest_url( 'kit/v1/editor/tinymce/modal' ), 'nonce' => wp_create_nonce( 'wp_rest' ), ) ); @@ -189,7 +199,7 @@ public function register_tinymce_plugins( $plugins ) { 'convertkit-admin-editor', 'convertkit_admin_tinymce', array( - 'ajaxurl' => rest_url( 'kit/v1/tinymce/output-modal' ), + 'ajaxurl' => rest_url( 'kit/v1/editor/tinymce/modal' ), 'nonce' => wp_create_nonce( 'wp_rest' ), ) ); diff --git a/resources/backend/js/editor.js b/resources/backend/js/editor.js index eae530805..06f082374 100644 --- a/resources/backend/js/editor.js +++ b/resources/backend/js/editor.js @@ -55,17 +55,20 @@ function convertKitTinyMCERegisterPlugin(block) { }); // Perform an AJAX call to load the modal's view. - fetch(convertkit_admin_tinymce.ajaxurl, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'X-WP-Nonce': convertkit_admin_tinymce.nonce, - }, - body: new URLSearchParams({ - editor_type: 'tinymce', - shortcode: block.name, - }), - }) + fetch( + convertkit_admin_tinymce.ajaxurl + + '/' + + block.name + + '/' + + 'tinymce', + { + method: 'GET', + headers: { + Accept: 'application/json', + 'X-WP-Nonce': convertkit_admin_tinymce.nonce, + }, + } + ) .then(function (response) { return response.text(); }) From 5d0f6f904eed05c19d4f6d0a388de7f3ea8cb5d0 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 13 Feb 2026 10:31:20 +0800 Subject: [PATCH 02/11] Use rest_ensure_response() --- admin/class-convertkit-admin-tinymce.php | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/admin/class-convertkit-admin-tinymce.php b/admin/class-convertkit-admin-tinymce.php index 110408a77..98c5aacf8 100644 --- a/admin/class-convertkit-admin-tinymce.php +++ b/admin/class-convertkit-admin-tinymce.php @@ -51,26 +51,20 @@ public function register_routes() { 'shortcode' => array( 'required' => true, 'validate_callback' => function ( $param ) { - return is_string( $param ) && in_array( $param, array_keys( convertkit_get_shortcodes() ), true ); - }, 'sanitize_callback' => 'sanitize_text_field', ), 'editor_type' => array( 'required' => true, 'validate_callback' => function ( $param ) { - return is_string( $param ) && in_array( $param, array( 'tinymce', 'quicktags' ), true ); - }, 'sanitize_callback' => 'sanitize_text_field', ), ), 'callback' => function ( $request ) { - ob_start(); - $this->output_modal( $request['shortcode'], $request['editor_type'] ); - return ob_get_clean(); + return rest_ensure_response( $this->output_modal( $request['shortcode'], $request['editor_type'] ) ); }, // Only refresh resources for users who can edit posts. @@ -89,16 +83,20 @@ public function register_routes() { * * @param string $shortcode_name Shortcode Name. * @param string $editor_type Editor Type (tinymce|quicktags). + * @return string */ public function output_modal( $shortcode_name, $editor_type ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed // Get shortcodes. $shortcodes = convertkit_get_shortcodes(); + // Start output buffering. + ob_start(); + // If the shortcode is not registered, return a view in the modal to tell the user. if ( ! isset( $shortcodes[ $shortcode_name ] ) ) { require_once CONVERTKIT_PLUGIN_PATH . '/views/backend/tinymce/modal-missing.php'; - die(); + return ob_get_clean(); } // Define shortcode. @@ -108,25 +106,25 @@ public function output_modal( $shortcode_name, $editor_type ) { // phpcs:ignore if ( array_key_exists( 'has_access_token', $shortcode ) && ! $shortcode['has_access_token'] ) { $notice = $shortcode['no_access_token']; require_once CONVERTKIT_PLUGIN_PATH . '/views/backend/tinymce/modal-notice.php'; - die(); + return ob_get_clean(); } // Show a message in the modal if no resources exist. if ( array_key_exists( 'has_resources', $shortcode ) && ! $shortcode['has_resources'] ) { $notice = $shortcode['no_resources']; require_once CONVERTKIT_PLUGIN_PATH . '/views/backend/tinymce/modal-notice.php'; - die(); + return ob_get_clean(); } // If we have less than two panels defined in the shortcode properties, output a basic modal. if ( count( $shortcode['panels'] ) < 2 ) { require_once CONVERTKIT_PLUGIN_PATH . '/views/backend/tinymce/modal.php'; - die(); + return ob_get_clean(); } // Output tabbed view. require_once CONVERTKIT_PLUGIN_PATH . '/views/backend/tinymce/modal-tabbed.php'; - die(); + return ob_get_clean(); } From 1443d79858372c6100b446995381c77d38992dfa Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 13 Feb 2026 10:31:25 +0800 Subject: [PATCH 03/11] Added tests --- .../RESTAPIEditorTinyMCEModalTest.php | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 tests/Integration/RESTAPIEditorTinyMCEModalTest.php diff --git a/tests/Integration/RESTAPIEditorTinyMCEModalTest.php b/tests/Integration/RESTAPIEditorTinyMCEModalTest.php new file mode 100644 index 000000000..a30db7af8 --- /dev/null +++ b/tests/Integration/RESTAPIEditorTinyMCEModalTest.php @@ -0,0 +1,180 @@ +settings = new \ConvertKit_Settings(); + $this->settings->save( + array( + 'access_token' => $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN'], + 'refresh_token' => $_ENV['CONVERTKIT_OAUTH_REFRESH_TOKEN'], + 'token_expires' => ( time() + 10000 ), + ) + ); + + // Tell WordPress that we're making REST API requests. + // This constant isn't set by the WP_REST_Server class in tests. + if ( ! defined( 'REST_REQUEST' ) ) { + define( 'REST_REQUEST', true ); + } + } + + /** + * Performs actions after each test. + * + * @since 3.1.9 + */ + public function tearDown(): void + { + // Delete Credentials from Plugin's settings. + $this->settings->delete_credentials(); + parent::tearDown(); + } + + /** + * Test that the /wp-json/kit/v1/editor/tinymce/modal REST API route returns a 401 when the user is not authorized. + * + * @since 3.1.9 + */ + public function testWhenUnauthorized() + { + // Make request. + $request = new \WP_REST_Request( 'GET', '/kit/v1/editor/tinymce/modal/form/tinymce' ); + $response = rest_get_server()->dispatch( $request ); + + // Assert response is unsuccessful. + $this->assertSame( 401, $response->get_status() ); + } + + /** + * Test that the /wp-json/kit/v1/editor/tinymce/modal REST API route returns a 404 when no shortcode or editor type is provided. + * + * @since 3.1.9 + */ + public function testWhenMissingParams() + { + // Create and become editor. + $this->actAsEditor(); + + // Make request. + $request = new \WP_REST_Request( 'GET', '/kit/v1/editor/tinymce/modal' ); + $response = rest_get_server()->dispatch( $request ); + + // Assert response is unsuccessful. + $this->assertSame( 404, $response->get_status() ); + } + + /** + * Test that the /wp-json/kit/v1/editor/tinymce/modal REST API route returns a 404 when no shortcode or editor type is provided. + * + * @since 3.1.9 + */ + public function testWhenInvalidParams() + { + // Create and become editor. + $this->actAsEditor(); + + // Make request. + $request = new \WP_REST_Request( 'GET', '/kit/v1/editor/tinymce/modal/invalid-shortcode/tinymce' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + // Assert response is unsuccessful. + $this->assertSame( 400, $response->get_status() ); + $this->assertEquals( 'rest_invalid_param', $data['code'] ); + $this->assertEquals( 'Invalid parameter(s): shortcode', $data['message'] ); + + // Make request. + $request = new \WP_REST_Request( 'GET', '/kit/v1/editor/tinymce/modal/form/invalid-editor-type' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + // Assert response is unsuccessful. + $this->assertSame( 400, $response->get_status() ); + $this->assertEquals( 'rest_invalid_param', $data['code'] ); + $this->assertEquals( 'Invalid parameter(s): editor_type', $data['message'] ); + } + + /** + * Test that the /wp-json/kit/v1/editor/tinymce/modal REST API route returns a 200 when the shortcode and editor type are valid. + * + * @since 3.1.9 + */ + public function testWhenValidParams() + { + // Create and become editor. + $this->actAsEditor(); + + // Define the shortcodes and editor types to test. + $shortcodes = [ + 'broadcasts', + 'content', + 'formtrigger', + 'form', + 'product', + ]; + $editor_types = [ + 'tinymce', + 'quicktags', + ]; + + // Iterate through the shortcodes and editor types and make a request for each combination. + foreach ( $shortcodes as $shortcode ) { + foreach ( $editor_types as $editor_type ) { + $request = new \WP_REST_Request( 'GET', '/kit/v1/editor/tinymce/modal/' . $shortcode . '/' . $editor_type ); + $response = rest_get_server()->dispatch( $request ); + + // Assert response is successful. + $this->assertSame( 200, $response->get_status() ); + } + } + } + + /** + * Act as an editor user. + * + * @since 3.1.9 + */ + private function actAsEditor() + { + $editor_id = static::factory()->user->create( [ 'role' => 'editor' ] ); + wp_set_current_user( $editor_id ); + } +} From e367bcfc07dd492c4d22f29d1b215993b47f80e9 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 13 Feb 2026 10:36:45 +0800 Subject: [PATCH 04/11] Broadcasts: REST API: Improvements --- .../blocks/class-convertkit-block-broadcasts.php | 6 +++--- resources/frontend/js/broadcasts.js | 12 +++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/includes/blocks/class-convertkit-block-broadcasts.php b/includes/blocks/class-convertkit-block-broadcasts.php index ac7abb3fd..82382888c 100644 --- a/includes/blocks/class-convertkit-block-broadcasts.php +++ b/includes/blocks/class-convertkit-block-broadcasts.php @@ -45,9 +45,9 @@ public function register_routes() { register_rest_route( 'kit/v1', - '/broadcasts/render', + '/broadcasts', array( - 'methods' => WP_REST_Server::CREATABLE, + 'methods' => WP_REST_Server::READABLE, 'args' => array( 'date_format' => array( 'default' => $this->get_default_value( 'date_format' ), @@ -122,7 +122,7 @@ public function enqueue_scripts() { 'convertkit_broadcasts', array( // REST API URL endpoint. - 'ajax_url' => rest_url( 'kit/v1/broadcasts/render' ), + 'ajax_url' => rest_url( 'kit/v1/broadcasts' ), // Whether debugging is enabled. 'debug' => $settings->debug_enabled(), diff --git a/resources/frontend/js/broadcasts.js b/resources/frontend/js/broadcasts.js index 36c327ccb..b7dcc39e0 100644 --- a/resources/frontend/js/broadcasts.js +++ b/resources/frontend/js/broadcasts.js @@ -58,14 +58,12 @@ function convertKitBroadcastsRender(blockContainer, atts) { // Show loading indicator. blockContainer.classList.add('convertkit-broadcasts-loading'); + // Build URL with query string parameters. + const params = new URLSearchParams(atts); + const url = `${convertkit_broadcasts.ajax_url}?${params.toString()}`; + // Fetch HTML. - fetch(convertkit_broadcasts.ajax_url, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: new URLSearchParams(atts), - }) + fetch(url) .then(function (response) { if (convertkit_broadcasts.debug) { console.log(response); From 4e0f6613343451f7cd00cd0b0d2b1a8ac3fcf50c Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 13 Feb 2026 11:00:07 +0800 Subject: [PATCH 05/11] Return a blank string if no Broadcasts --- .../blocks/class-convertkit-block-broadcasts.php | 15 +++++++++++++-- resources/frontend/js/broadcasts.js | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/includes/blocks/class-convertkit-block-broadcasts.php b/includes/blocks/class-convertkit-block-broadcasts.php index 82382888c..e416ed1b3 100644 --- a/includes/blocks/class-convertkit-block-broadcasts.php +++ b/includes/blocks/class-convertkit-block-broadcasts.php @@ -95,8 +95,7 @@ public function register_routes() { ), ), 'callback' => function ( $request ) { - $html = $this->render_ajax( $request ); - return rest_ensure_response( array( 'data' => $html ) ); + return rest_ensure_response( $this->render_ajax( $request ) ); }, // No authentication required, as this is on the frontend site. @@ -629,9 +628,21 @@ public function render_ajax( $request ) { // and moving some attributes (such as Gutenberg's styles), if defined. $atts = $this->sanitize_and_declare_atts( $atts ); + // Setup Settings class. + $settings = new ConvertKit_Settings(); + // Fetch Posts. $posts = new ConvertKit_Resource_Posts( 'output_broadcasts' ); + // If no Posts exist, bail. + if ( ! $posts->exist() ) { + if ( $settings->debug_enabled() ) { + return ''; + } + + return ''; + } + // Build HTML. $html = $this->build_html( $posts, $atts, false ); diff --git a/resources/frontend/js/broadcasts.js b/resources/frontend/js/broadcasts.js index b7dcc39e0..6783e817b 100644 --- a/resources/frontend/js/broadcasts.js +++ b/resources/frontend/js/broadcasts.js @@ -80,7 +80,7 @@ function convertKitBroadcastsRender(blockContainer, atts) { blockContainer.classList.remove('convertkit-broadcasts-loading'); // Replace block container's HTML with response data. - blockContainer.innerHTML = result.data; + blockContainer.innerHTML = result; }) .catch(function (error) { if (convertkit.debug) { From 7a22765d7ff64167c87f1a945360292556650afd Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 13 Feb 2026 11:00:10 +0800 Subject: [PATCH 06/11] Added tests --- tests/Integration/RESTAPIBroadcastsTest.php | 106 ++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/Integration/RESTAPIBroadcastsTest.php diff --git a/tests/Integration/RESTAPIBroadcastsTest.php b/tests/Integration/RESTAPIBroadcastsTest.php new file mode 100644 index 000000000..5907ec126 --- /dev/null +++ b/tests/Integration/RESTAPIBroadcastsTest.php @@ -0,0 +1,106 @@ +settings = new \ConvertKit_Settings(); + $this->settings->save( + array( + 'access_token' => $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN'], + 'refresh_token' => $_ENV['CONVERTKIT_OAUTH_REFRESH_TOKEN'], + 'token_expires' => ( time() + 10000 ), + ) + ); + + // Tell WordPress that we're making REST API requests. + // This constant isn't set by the WP_REST_Server class in tests. + if ( ! defined( 'REST_REQUEST' ) ) { + define( 'REST_REQUEST', true ); + } + } + + /** + * Performs actions after each test. + * + * @since 3.1.9 + */ + public function tearDown(): void + { + // Delete Credentials from Plugin's settings. + $this->settings->delete_credentials(); + parent::tearDown(); + } + + /** + * Test that the /wp-json/kit/v1/broadcasts REST API route returns a 200 + * with no data when no broadcasts exist. + * + * @since 3.1.9 + */ + public function testWhenNoBroadcastsExist() + { + $request = new \WP_REST_Request( 'GET', '/kit/v1/broadcasts' ); + $response = rest_get_server()->dispatch( $request ); + + // Assert response is successful. + $this->assertSame( 200, $response->get_status() ); + $this->assertEquals( '', $response->get_data()['data'] ); + } + + /** + * Test that the /wp-json/kit/v1/broadcasts REST API route returns a 200 + * with data when broadcasts exist. + * + * @since 3.1.9 + */ + public function testWhenBroadcastsExist() + { + // Refresh resources. + new \ConvertKit_Resource_Posts( 'output_broadcasts' )->refresh(); + + // Send request. + $request = new \WP_REST_Request( 'GET', '/kit/v1/broadcasts' ); + $response = rest_get_server()->dispatch( $request ); + + // Assert response is successful. + $this->assertSame( 200, $response->get_status() ); + $this->assertNotEmpty( $response->get_data()['data'] ); + } +} From 978914ceb7034b52e20f823da48b1d8afb703114 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 13 Feb 2026 11:05:29 +0800 Subject: [PATCH 07/11] Member Content: REST API: Improvements --- resources/frontend/js/restrict-content.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/frontend/js/restrict-content.js b/resources/frontend/js/restrict-content.js index 48dd67290..9ec1442e7 100644 --- a/resources/frontend/js/restrict-content.js +++ b/resources/frontend/js/restrict-content.js @@ -150,10 +150,10 @@ function convertKitRestrictContentSubscriberAuthenticationSendCode( fetch(convertkit_restrict_content.subscriber_authentication_url, { method: 'POST', headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', 'X-WP-Nonce': nonce, }, - body: new URLSearchParams({ + body: JSON.stringify({ convertkit_email: email, convertkit_resource_type: resource_type, convertkit_resource_id: resource_id, @@ -223,10 +223,10 @@ function convertKitRestrictContentSubscriberVerification( fetch(convertkit_restrict_content.subscriber_verification_url, { method: 'POST', headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Type': 'application/json', 'X-WP-Nonce': nonce, }, - body: new URLSearchParams({ + body: JSON.stringify({ subscriber_code, token, convertkit_post_id: post_id, From 6af7a3669e5a158af1e761a4cc26d186bacb056b Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 13 Feb 2026 11:07:24 +0800 Subject: [PATCH 08/11] Move tests to own test class --- .../RESTAPIRestrictContentTest.php | 309 ++++++++++++++++++ tests/Integration/RESTAPITest.php | 216 ------------ 2 files changed, 309 insertions(+), 216 deletions(-) create mode 100644 tests/Integration/RESTAPIRestrictContentTest.php diff --git a/tests/Integration/RESTAPIRestrictContentTest.php b/tests/Integration/RESTAPIRestrictContentTest.php new file mode 100644 index 000000000..02cd7e5eb --- /dev/null +++ b/tests/Integration/RESTAPIRestrictContentTest.php @@ -0,0 +1,309 @@ +settings = new \ConvertKit_Settings(); + $this->settings->save( + array( + 'access_token' => $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN'], + 'refresh_token' => $_ENV['CONVERTKIT_OAUTH_REFRESH_TOKEN'], + 'token_expires' => ( time() + 10000 ), + ) + ); + } + + /** + * Performs actions after each test. + * + * @since 3.1.9 + */ + public function tearDown(): void + { + // Delete Credentials from Plugin's settings. + $this->settings->delete_credentials(); + parent::tearDown(); + } + + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Form ID and subscriber + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationForm() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], + 'convertkit_resource_type' => 'form', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // Send request. + $response = rest_get_server()->dispatch( $request ); + + // Assert response is successful. + $this->assertSame( 200, $response->get_status() ); + + // Assert response data has the expected keys and data. + $data = $response->get_data(); + $this->assertIsArray( $data ); + $this->assertTrue( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Form ID and an invalid subscriber email is given + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationFormInvalidEmail() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => 'fail@kit.com', + 'convertkit_resource_type' => 'form', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // Send request. + $response = rest_get_server()->dispatch( $request ); + + // Assert response is successful. + $this->assertSame( 200, $response->get_status() ); + + // Assert response data has the expected keys and data. + $data = $response->get_data(); + $this->assertIsArray( $data ); + $this->assertFalse( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Tag ID and subscriber + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationTag() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], + 'convertkit_resource_type' => 'tag', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_TAG_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // Send request. + $response = rest_get_server()->dispatch( $request ); + + // Assert response is successful. + $this->assertSame( 200, $response->get_status() ); + + // Assert response data has the expected keys and data. + $data = $response->get_data(); + $this->assertIsArray( $data ); + $this->assertTrue( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Tag ID and an invalid subscriber email is given + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationTagInvalidEmail() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => 'fail@kit.com', + 'convertkit_resource_type' => 'tag', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_TAG_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // Send request. + $response = rest_get_server()->dispatch( $request ); + + // Assert response is successful. + $this->assertSame( 200, $response->get_status() ); + + // Assert response data has the expected keys and data. + $data = $response->get_data(); + $this->assertIsArray( $data ); + $this->assertFalse( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Product ID and subscriber + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationProduct() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], + 'convertkit_resource_type' => 'product', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_PRODUCT_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // Send request. + $response = rest_get_server()->dispatch( $request ); + + // Assert response is successful. + $this->assertSame( 200, $response->get_status() ); + + // Assert response data has the expected keys and data. + $data = $response->get_data(); + $this->assertIsArray( $data ); + $this->assertTrue( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Product ID and an invalid subscriber email is given + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationProductInvalidEmail() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => 'fail@kit.com', + 'convertkit_resource_type' => 'product', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_PRODUCT_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // Send request. + $response = rest_get_server()->dispatch( $request ); + + // Assert response is successful. + $this->assertSame( 200, $response->get_status() ); + + // Assert response data has the expected keys and data. + $data = $response->get_data(); + $this->assertIsArray( $data ); + $this->assertFalse( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + + /** + * Test that the /wp-json/kit/v1/subscriber/store-email-as-id-in-cookie REST API route stores + * the subscriber ID in a cookie when a valid email address is given. + * + * @since 3.1.7 + */ + public function testStoreEmailAsIDInCookie() + { + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/subscriber/store-email-as-id-in-cookie' ); + $request->set_header( 'Content-Type', 'application/json' ); + $request->set_body_params( + [ + 'email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], + ], + ); + + // Send request. + $response = rest_get_server()->dispatch( $request ); + + // Assert response is successful. + $this->assertSame( 200, $response->get_status() ); + + // Assert response data has the expected keys and data. + $data = $response->get_data(); + $this->assertIsArray( $data ); + $this->assertEquals( (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'], (int) $data['id'] ); + } +} diff --git a/tests/Integration/RESTAPITest.php b/tests/Integration/RESTAPITest.php index e84ce8c4d..ff085d920 100644 --- a/tests/Integration/RESTAPITest.php +++ b/tests/Integration/RESTAPITest.php @@ -306,222 +306,6 @@ public function testRefreshResourcesRestrictContent() $this->assertArrayHasKeys( $data['products'][0], [ 'id', 'name', 'url', 'published' ] ); } - /** - * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when - * requesting the subscriber authentication email to be sent for a given Form ID and subscriber - * - * @since 3.1.0 - */ - public function testRestrictContentSubscriberAuthenticationForm() - { - // Create a Post. - $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); - - // Build request. - $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); - $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); - $request->set_body_params( - [ - 'convertkit_email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], - 'convertkit_resource_type' => 'form', - 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_FORM_ID'], - 'convertkit_post_id' => $post_id, - ] - ); - - // Send request. - $response = rest_get_server()->dispatch( $request ); - - // Assert response is successful. - $this->assertSame( 200, $response->get_status() ); - - // Assert response data has the expected keys and data. - $data = $response->get_data(); - $this->assertIsArray( $data ); - $this->assertTrue( $data['success'] ); - $this->assertArrayHasKey( 'data', $data ); - } - - /** - * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when - * requesting the subscriber authentication email to be sent for a given Form ID and an invalid subscriber email is given - * - * @since 3.1.0 - */ - public function testRestrictContentSubscriberAuthenticationFormInvalidEmail() - { - // Create a Post. - $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); - - // Build request. - $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); - $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); - $request->set_body_params( - [ - 'convertkit_email' => 'fail@kit.com', - 'convertkit_resource_type' => 'form', - 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_FORM_ID'], - 'convertkit_post_id' => $post_id, - ] - ); - - // Send request. - $response = rest_get_server()->dispatch( $request ); - - // Assert response is successful. - $this->assertSame( 200, $response->get_status() ); - - // Assert response data has the expected keys and data. - $data = $response->get_data(); - $this->assertIsArray( $data ); - $this->assertFalse( $data['success'] ); - $this->assertArrayHasKey( 'data', $data ); - } - - /** - * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when - * requesting the subscriber authentication email to be sent for a given Tag ID and subscriber - * - * @since 3.1.0 - */ - public function testRestrictContentSubscriberAuthenticationTag() - { - // Create a Post. - $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); - - // Build request. - $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); - $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); - $request->set_body_params( - [ - 'convertkit_email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], - 'convertkit_resource_type' => 'tag', - 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_TAG_ID'], - 'convertkit_post_id' => $post_id, - ] - ); - - // Send request. - $response = rest_get_server()->dispatch( $request ); - - // Assert response is successful. - $this->assertSame( 200, $response->get_status() ); - - // Assert response data has the expected keys and data. - $data = $response->get_data(); - $this->assertIsArray( $data ); - $this->assertTrue( $data['success'] ); - $this->assertArrayHasKey( 'data', $data ); - } - - /** - * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when - * requesting the subscriber authentication email to be sent for a given Tag ID and an invalid subscriber email is given - * - * @since 3.1.0 - */ - public function testRestrictContentSubscriberAuthenticationTagInvalidEmail() - { - // Create a Post. - $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); - - // Build request. - $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); - $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); - $request->set_body_params( - [ - 'convertkit_email' => 'fail@kit.com', - 'convertkit_resource_type' => 'tag', - 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_TAG_ID'], - 'convertkit_post_id' => $post_id, - ] - ); - - // Send request. - $response = rest_get_server()->dispatch( $request ); - - // Assert response is successful. - $this->assertSame( 200, $response->get_status() ); - - // Assert response data has the expected keys and data. - $data = $response->get_data(); - $this->assertIsArray( $data ); - $this->assertFalse( $data['success'] ); - $this->assertArrayHasKey( 'data', $data ); - } - - /** - * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when - * requesting the subscriber authentication email to be sent for a given Product ID and subscriber - * - * @since 3.1.0 - */ - public function testRestrictContentSubscriberAuthenticationProduct() - { - // Create a Post. - $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); - - // Build request. - $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); - $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); - $request->set_body_params( - [ - 'convertkit_email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], - 'convertkit_resource_type' => 'product', - 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_PRODUCT_ID'], - 'convertkit_post_id' => $post_id, - ] - ); - - // Send request. - $response = rest_get_server()->dispatch( $request ); - - // Assert response is successful. - $this->assertSame( 200, $response->get_status() ); - - // Assert response data has the expected keys and data. - $data = $response->get_data(); - $this->assertIsArray( $data ); - $this->assertTrue( $data['success'] ); - $this->assertArrayHasKey( 'data', $data ); - } - - /** - * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when - * requesting the subscriber authentication email to be sent for a given Product ID and an invalid subscriber email is given - * - * @since 3.1.0 - */ - public function testRestrictContentSubscriberAuthenticationProductInvalidEmail() - { - // Create a Post. - $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); - - // Build request. - $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); - $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); - $request->set_body_params( - [ - 'convertkit_email' => 'fail@kit.com', - 'convertkit_resource_type' => 'product', - 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_PRODUCT_ID'], - 'convertkit_post_id' => $post_id, - ] - ); - - // Send request. - $response = rest_get_server()->dispatch( $request ); - - // Assert response is successful. - $this->assertSame( 200, $response->get_status() ); - - // Assert response data has the expected keys and data. - $data = $response->get_data(); - $this->assertIsArray( $data ); - $this->assertFalse( $data['success'] ); - $this->assertArrayHasKey( 'data', $data ); - } - /** * Test that the /wp-json/kit/v1/subscriber/store-email-as-id-in-cookie REST API route stores * the subscriber ID in a cookie when a valid email address is given. From 270db43c3ef3df812b8f66a89216c7a9a244ab75 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 13 Feb 2026 15:23:10 +0800 Subject: [PATCH 09/11] Return .json, not .text --- resources/backend/js/editor.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/backend/js/editor.js b/resources/backend/js/editor.js index 06f082374..b8cb7c049 100644 --- a/resources/backend/js/editor.js +++ b/resources/backend/js/editor.js @@ -64,13 +64,12 @@ function convertKitTinyMCERegisterPlugin(block) { { method: 'GET', headers: { - Accept: 'application/json', 'X-WP-Nonce': convertkit_admin_tinymce.nonce, }, } ) .then(function (response) { - return response.text(); + return response.json(); }) .then(function (result) { // Inject HTML into modal. From 27fd436f5acfdef5e50bc55daeae4d0f8d00f196 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 13 Feb 2026 15:45:31 +0800 Subject: [PATCH 10/11] Quick Tags: Update fetch() --- resources/backend/js/quicktags.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/resources/backend/js/quicktags.js b/resources/backend/js/quicktags.js index 2bf45c5d2..4700596e7 100644 --- a/resources/backend/js/quicktags.js +++ b/resources/backend/js/quicktags.js @@ -21,19 +21,21 @@ for (const block in convertkit_quicktags) { function convertKitQuickTagRegister(block) { QTags.addButton('convertkit-' + block.name, block.title, function () { // Perform an AJAX call to load the modal's view. - fetch(convertkit_admin_tinymce.ajaxurl, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'X-WP-Nonce': convertkit_admin_tinymce.nonce, - }, - body: new URLSearchParams({ - editor_type: 'quicktags', - shortcode: block.name, - }), - }) + fetch( + convertkit_admin_tinymce.ajaxurl + + '/' + + block.name + + '/' + + 'quicktags', + { + method: 'GET', + headers: { + 'X-WP-Nonce': convertkit_admin_tinymce.nonce, + }, + } + ) .then(function (response) { - return response.text(); + return response.json(); }) .then(function (result) { // Show Modal. From abacf18dcca8564e3c93ee4759f827ce15aabbe6 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 13 Feb 2026 16:27:19 +0800 Subject: [PATCH 11/11] Fix tests --- tests/Integration/RESTAPIBroadcastsTest.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/Integration/RESTAPIBroadcastsTest.php b/tests/Integration/RESTAPIBroadcastsTest.php index 5907ec126..cacbdbbe8 100644 --- a/tests/Integration/RESTAPIBroadcastsTest.php +++ b/tests/Integration/RESTAPIBroadcastsTest.php @@ -81,7 +81,7 @@ public function testWhenNoBroadcastsExist() // Assert response is successful. $this->assertSame( 200, $response->get_status() ); - $this->assertEquals( '', $response->get_data()['data'] ); + $this->assertEquals( '', $response->get_data() ); } /** @@ -93,7 +93,8 @@ public function testWhenNoBroadcastsExist() public function testWhenBroadcastsExist() { // Refresh resources. - new \ConvertKit_Resource_Posts( 'output_broadcasts' )->refresh(); + $broadcasts = new \ConvertKit_Resource_Posts( 'output_broadcasts' ); + $broadcasts->refresh(); // Send request. $request = new \WP_REST_Request( 'GET', '/kit/v1/broadcasts' ); @@ -101,6 +102,6 @@ public function testWhenBroadcastsExist() // Assert response is successful. $this->assertSame( 200, $response->get_status() ); - $this->assertNotEmpty( $response->get_data()['data'] ); + $this->assertNotEmpty( $response->get_data() ); } }