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..83aa463 --- /dev/null +++ b/tests/EndToEnd/uninstall/UninstallCest.php @@ -0,0 +1,103 @@ +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->setupWPFormsIntegration( + $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('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( + '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'] ); + } +} diff --git a/tests/Support/Helper/Plugin.php b/tests/Support/Helper/Plugin.php index 2aece54..4492308 100644 --- a/tests/Support/Helper/Plugin.php +++ b/tests/Support/Helper/Plugin.php @@ -35,6 +35,18 @@ public function deactivateConvertKitPlugin($I) $I->deactivateThirdPartyPlugin($I, 'integrate-convertkit-wpforms'); } + /** + * Helper method to delete the Kit Plugin. + * + * @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..84f2cce --- /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 );