From a110c61cfb6f008f0064345d43c1d9786bc760be Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 10 Apr 2026 19:11:41 +0800 Subject: [PATCH 1/4] Revoke and Remove Tokens on Uninstall --- .github/workflows/tests.yml | 1 + tests/EndToEnd/uninstall/UninstallCest.php | 102 +++++++++++++++++++++ tests/Support/Helper/Plugin.php | 13 +++ tests/Support/Helper/ThirdPartyPlugin.php | 33 +++++++ uninstall.php | 83 +++++++++++++++++ 5 files changed, 232 insertions(+) create mode 100644 tests/EndToEnd/uninstall/UninstallCest.php create mode 100644 uninstall.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1bc543f..f5ca0aa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,6 +59,7 @@ jobs: 'EndToEnd/forms', 'EndToEnd/general', 'EndToEnd/recommendations', + 'EndToEnd/uninstall', 'Integration' ] diff --git a/tests/EndToEnd/uninstall/UninstallCest.php b/tests/EndToEnd/uninstall/UninstallCest.php new file mode 100644 index 0000000..48aa1d1 --- /dev/null +++ b/tests/EndToEnd/uninstall/UninstallCest.php @@ -0,0 +1,102 @@ +activateConvertKitPlugin($I); + + // Generate an access token and refresh token by API key and secret. + // We don't use the tokens from the environment, as revoking those + // would result in later tests failing. + $result = wp_remote_post( + 'https://api.kit.com/wordpress/accounts/oauth_access_token', + [ + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode( + [ + 'api_key' => $_ENV['CONVERTKIT_API_KEY'], + 'api_secret' => $_ENV['CONVERTKIT_API_SECRET'], + 'client_id' => $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], + 'tenant_name' => wp_generate_password( 10, false ), // Random tenant name to produce a token for this request only. + ] + ), + ] + ); + $tokens = json_decode(wp_remote_retrieve_body($result), true)['oauth']; + + // Store the tokens and API keys in the Plugin's settings. + $I->setupConvertKitPlugin( + $I, + accessToken: $tokens['access_token'], + refreshToken: $tokens['refresh_token'], + apiKey: $_ENV['CONVERTKIT_API_KEY'], + apiSecret: $_ENV['CONVERTKIT_API_SECRET'] + ); + + // Deactivate the Plugin. + $I->deactivateConvertKitPlugin($I); + + // Delete the Plugin. + $I->deleteKitPlugin($I); + + // Confirm the credentials have been removed from the Plugin's settings. + $I->wait(3); + $settings = $I->grabOptionFromDatabase('woocommerce_ckwc_settings'); + $I->assertEmpty($settings['access_token']); + $I->assertEmpty($settings['refresh_token']); + $I->assertEmpty($settings['api_key']); + $I->assertEmpty($settings['api_secret']); + + // Confirm attempting to use the revoked access token no longer works. + $result = wp_remote_get( + 'https://api.kit.com/v4/account', + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $tokens['access_token'], + ], + ] + ); + $data = json_decode(wp_remote_retrieve_body($result), true); + $I->assertArrayHasKey( 'errors', $data ); + $I->assertEquals( 'The access token was revoked', $data['errors'][0] ); + + // Confirm attempting to use the revoked refresh token no longer works. + $result = wp_remote_post( + 'https://api.kit.com/v4/oauth/token', + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $tokens['access_token'], + ], + 'body' => [ + 'client_id' => $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], + 'grant_type' => 'refresh_token', + 'refresh_token' => $tokens['refresh_token'], + ], + ] + ); + $data = json_decode(wp_remote_retrieve_body($result), true); + $I->assertArrayHasKey( 'error', $data ); + $I->assertEquals( 'invalid_grant', $data['error'] ); + } +} \ No newline at end of file diff --git a/tests/Support/Helper/Plugin.php b/tests/Support/Helper/Plugin.php index 2aece54..57fab38 100644 --- a/tests/Support/Helper/Plugin.php +++ b/tests/Support/Helper/Plugin.php @@ -35,6 +35,19 @@ public function deactivateConvertKitPlugin($I) $I->deactivateThirdPartyPlugin($I, 'integrate-convertkit-wpforms'); } + /** + * Helper method to delete the Kit Plugin, checking + * it deleted and no errors were output. + * + * @since 1.9.2 + * + * @param EndToEndTester $I EndToEndTester. + */ + public function deleteKitPlugin($I) + { + $I->deleteThirdPartyPlugin($I, 'integrate-convertkit-wpforms'); + } + /** * Helper method to determine that the order of the Form resources in the given * select element are in the expected alphabetical order. diff --git a/tests/Support/Helper/ThirdPartyPlugin.php b/tests/Support/Helper/ThirdPartyPlugin.php index edf0ac5..9ca7f73 100644 --- a/tests/Support/Helper/ThirdPartyPlugin.php +++ b/tests/Support/Helper/ThirdPartyPlugin.php @@ -74,6 +74,39 @@ public function deactivateThirdPartyPlugin($I, $name) $I->waitForElementVisible('table.plugins tr[data-slug=' . $name . '].inactive'); } + /** + * Helper method to delete a third party Plugin, checking + * it deleted and no errors were output. + * + * @since 1.9.2 + * + * @param EndToEndTester $I EndToEnd Tester. + * @param string $name Plugin Slug. + */ + public function deleteThirdPartyPlugin($I, $name) + { + // Login as the Administrator, if we're not already logged in. + if ( ! $this->amLoggedInAsAdmin($I) ) { + $this->doLoginAsAdmin($I); + } + + // Go to the Plugins screen in the WordPress Administration interface. + $I->amOnPluginsPage(); + + // Wait for the Plugins page to load. + $I->waitForElementVisible('body.plugins-php'); + + // Delete the Plugin. + $I->waitForElementVisible('a#delete-' . $name); + $I->click('a#delete-' . $name); + + // Click the confirmation dialog. + $I->acceptPopup(); + + // Wait for the Plugin to be marked as deleted. + $I->waitForElementNotVisible('table.plugins tr.deleted[data-slug=' . $name . ']'); + } + /** * Helper method to check if the Administrator is logged in. * diff --git a/uninstall.php b/uninstall.php new file mode 100644 index 0000000..b8ea19d --- /dev/null +++ b/uninstall.php @@ -0,0 +1,83 @@ + Delete. + * + * @package CKWC + * @author ConvertKit + */ + +// If uninstall.php is not called by WordPress, die. +if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { + die; +} + +// Only WordPress and PHP methods can be used. Plugin classes and methods +// are not reliably available due to the Plugin being deactivated and going +// through deletion now. + +// Get providers. +$providers = get_option( 'wpforms_providers' ); + +// Bail if no providers exist. +if ( ! $providers ) { + return; +} + +// Bail if no Kit connections exist. +if ( ! array_key_exists( 'convertkit', $providers ) ) { + return; +} + +// Iterate through each connection, revoking the tokens. +foreach ( $providers['convertkit'] as $account_id =>$connection ) { + // Revoke Access Token. + if ( array_key_exists( 'access_token', $connection ) && ! empty( $connection['access_token'] ) ) { + wp_remote_post( + 'https://api.kit.com/v4/oauth/revoke', + array( + 'headers' => array( + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ), + 'body' => wp_json_encode( + array( + 'client_id' => 'L0kyADsB3WP5zO5MvUpXQU64gIntQg9BBAIme17r_7A', + 'token' => $connection['access_token'], + ) + ), + 'timeout' => 5, + ) + ); + } + + // Revoke Refresh Token. + if ( array_key_exists( 'refresh_token', $connection ) && ! empty( $connection['refresh_token'] ) ) { + wp_remote_post( + 'https://api.kit.com/v4/oauth/revoke', + array( + 'headers' => array( + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ), + 'body' => wp_json_encode( + array( + 'client_id' => 'L0kyADsB3WP5zO5MvUpXQU64gIntQg9BBAIme17r_7A', + 'token' => $connection['refresh_token'], + ) + ), + 'timeout' => 5, + ) + ); + } + + // Remove credentials from settings. + $providers['convertkit'][ $account_id ]['access_token'] = ''; + $providers['convertkit'][ $account_id ]['refresh_token'] = ''; + $providers['convertkit'][ $account_id ]['token_expires'] = ''; + $providers['convertkit'][ $account_id ]['api_key'] = ''; + $providers['convertkit'][ $account_id ]['api_secret'] = ''; +} + +// Save settings. +update_option( 'wpforms_providers', $providers ); \ No newline at end of file From 4fc10cea34d6a650662e91c59cd92a565cda0296 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 10 Apr 2026 19:18:29 +0800 Subject: [PATCH 2/4] Run UninstallCest --- .github/workflows/tests.yml | 8 +- tests/EndToEnd/uninstall/UninstallCest.php | 15 ++-- uninstall.php | 92 +++++++++++----------- 3 files changed, 56 insertions(+), 59 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f5ca0aa..35775d9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,15 +52,11 @@ jobs: fail-fast: false matrix: wp-versions: [ 'latest' ] #[ '6.1.1', 'latest' ] - php-versions: [ '8.1', '8.2', '8.3', '8.4' ] #[ '7.3', '7.4', '8.0', '8.1' ] + php-versions: [ '8.1' ] #[ '7.3', '7.4', '8.0', '8.1' ] # Folder names within the 'tests' folder to run tests in parallel. test-groups: [ - 'EndToEnd/forms', - 'EndToEnd/general', - 'EndToEnd/recommendations', - 'EndToEnd/uninstall', - 'Integration' + 'EndToEnd/uninstall' ] # Steps to install, configure and run tests diff --git a/tests/EndToEnd/uninstall/UninstallCest.php b/tests/EndToEnd/uninstall/UninstallCest.php index 48aa1d1..83aa463 100644 --- a/tests/EndToEnd/uninstall/UninstallCest.php +++ b/tests/EndToEnd/uninstall/UninstallCest.php @@ -46,7 +46,7 @@ public function testPluginDeletionRevokesAndRemovesTokens(EndToEndTester $I) $tokens = json_decode(wp_remote_retrieve_body($result), true)['oauth']; // Store the tokens and API keys in the Plugin's settings. - $I->setupConvertKitPlugin( + $I->setupWPFormsIntegration( $I, accessToken: $tokens['access_token'], refreshToken: $tokens['refresh_token'], @@ -62,11 +62,12 @@ public function testPluginDeletionRevokesAndRemovesTokens(EndToEndTester $I) // Confirm the credentials have been removed from the Plugin's settings. $I->wait(3); - $settings = $I->grabOptionFromDatabase('woocommerce_ckwc_settings'); - $I->assertEmpty($settings['access_token']); - $I->assertEmpty($settings['refresh_token']); - $I->assertEmpty($settings['api_key']); - $I->assertEmpty($settings['api_secret']); + $settings = $I->grabOptionFromDatabase('wpforms_providers'); + $connection = reset($settings['convertkit']); + $I->assertEmpty($connection['access_token']); + $I->assertEmpty($connection['refresh_token']); + $I->assertEmpty($connection['api_key']); + $I->assertEmpty($connection['api_secret']); // Confirm attempting to use the revoked access token no longer works. $result = wp_remote_get( @@ -99,4 +100,4 @@ public function testPluginDeletionRevokesAndRemovesTokens(EndToEndTester $I) $I->assertArrayHasKey( 'error', $data ); $I->assertEquals( 'invalid_grant', $data['error'] ); } -} \ No newline at end of file +} diff --git a/uninstall.php b/uninstall.php index b8ea19d..84f2cce 100644 --- a/uninstall.php +++ b/uninstall.php @@ -30,54 +30,54 @@ } // Iterate through each connection, revoking the tokens. -foreach ( $providers['convertkit'] as $account_id =>$connection ) { - // Revoke Access Token. - if ( array_key_exists( 'access_token', $connection ) && ! empty( $connection['access_token'] ) ) { - wp_remote_post( - 'https://api.kit.com/v4/oauth/revoke', - array( - 'headers' => array( - 'Accept' => 'application/json', - 'Content-Type' => 'application/json', - ), - 'body' => wp_json_encode( - array( - 'client_id' => 'L0kyADsB3WP5zO5MvUpXQU64gIntQg9BBAIme17r_7A', - 'token' => $connection['access_token'], - ) - ), - 'timeout' => 5, - ) - ); - } +foreach ( $providers['convertkit'] as $account_id => $connection ) { + // Revoke Access Token. + if ( array_key_exists( 'access_token', $connection ) && ! empty( $connection['access_token'] ) ) { + wp_remote_post( + 'https://api.kit.com/v4/oauth/revoke', + array( + 'headers' => array( + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ), + 'body' => wp_json_encode( + array( + 'client_id' => 'L0kyADsB3WP5zO5MvUpXQU64gIntQg9BBAIme17r_7A', + 'token' => $connection['access_token'], + ) + ), + 'timeout' => 5, + ) + ); + } - // Revoke Refresh Token. - if ( array_key_exists( 'refresh_token', $connection ) && ! empty( $connection['refresh_token'] ) ) { - wp_remote_post( - 'https://api.kit.com/v4/oauth/revoke', - array( - 'headers' => array( - 'Accept' => 'application/json', - 'Content-Type' => 'application/json', - ), - 'body' => wp_json_encode( - array( - 'client_id' => 'L0kyADsB3WP5zO5MvUpXQU64gIntQg9BBAIme17r_7A', - 'token' => $connection['refresh_token'], - ) - ), - 'timeout' => 5, - ) - ); - } + // Revoke Refresh Token. + if ( array_key_exists( 'refresh_token', $connection ) && ! empty( $connection['refresh_token'] ) ) { + wp_remote_post( + 'https://api.kit.com/v4/oauth/revoke', + array( + 'headers' => array( + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ), + 'body' => wp_json_encode( + array( + 'client_id' => 'L0kyADsB3WP5zO5MvUpXQU64gIntQg9BBAIme17r_7A', + 'token' => $connection['refresh_token'], + ) + ), + 'timeout' => 5, + ) + ); + } - // Remove credentials from settings. - $providers['convertkit'][ $account_id ]['access_token'] = ''; - $providers['convertkit'][ $account_id ]['refresh_token'] = ''; - $providers['convertkit'][ $account_id ]['token_expires'] = ''; - $providers['convertkit'][ $account_id ]['api_key'] = ''; - $providers['convertkit'][ $account_id ]['api_secret'] = ''; + // Remove credentials from settings. + $providers['convertkit'][ $account_id ]['access_token'] = ''; + $providers['convertkit'][ $account_id ]['refresh_token'] = ''; + $providers['convertkit'][ $account_id ]['token_expires'] = ''; + $providers['convertkit'][ $account_id ]['api_key'] = ''; + $providers['convertkit'][ $account_id ]['api_secret'] = ''; } // Save settings. -update_option( 'wpforms_providers', $providers ); \ No newline at end of file +update_option( 'wpforms_providers', $providers ); From 7cabf01cc891ac932bce0b8c584549204b198865 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 10 Apr 2026 19:29:13 +0800 Subject: [PATCH 3/4] Reinstate all tests --- .github/workflows/tests.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 35775d9..f5ca0aa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,11 +52,15 @@ jobs: fail-fast: false matrix: wp-versions: [ 'latest' ] #[ '6.1.1', 'latest' ] - php-versions: [ '8.1' ] #[ '7.3', '7.4', '8.0', '8.1' ] + php-versions: [ '8.1', '8.2', '8.3', '8.4' ] #[ '7.3', '7.4', '8.0', '8.1' ] # Folder names within the 'tests' folder to run tests in parallel. test-groups: [ - 'EndToEnd/uninstall' + 'EndToEnd/forms', + 'EndToEnd/general', + 'EndToEnd/recommendations', + 'EndToEnd/uninstall', + 'Integration' ] # Steps to install, configure and run tests From 4538844d4e7499f098960be7be85f664b1ba604c Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 13 Apr 2026 11:13:45 +0800 Subject: [PATCH 4/4] Fix docblock comment --- tests/Support/Helper/Plugin.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Support/Helper/Plugin.php b/tests/Support/Helper/Plugin.php index 57fab38..4492308 100644 --- a/tests/Support/Helper/Plugin.php +++ b/tests/Support/Helper/Plugin.php @@ -36,8 +36,7 @@ public function deactivateConvertKitPlugin($I) } /** - * Helper method to delete the Kit Plugin, checking - * it deleted and no errors were output. + * Helper method to delete the Kit Plugin. * * @since 1.9.2 *