diff --git a/admin/class-convertkit-admin-deactivation.php b/admin/class-convertkit-admin-deactivation.php new file mode 100644 index 000000000..aec9ce2bc --- /dev/null +++ b/admin/class-convertkit-admin-deactivation.php @@ -0,0 +1,141 @@ + array( + 'name' => strtolower( CONVERTKIT_PLUGIN_NAME ), + 'version' => CONVERTKIT_PLUGIN_VERSION, + ), + ) + ); + + // Output deactivation modal view in admin footer. + add_action( 'admin_footer', array( $this, 'output_deactivation_modal' ) ); + + } + + /** + * Enqueue CSS on the Plugins screen to handle displaying + * the deactivation modal. + * + * @since 2.0.2 + * + * @param string $hook Hook. + */ + public function enqueue_styles( $hook ) { + + // Bail if we are not on the Plugin screen. + if ( $hook !== 'plugins.php' ) { + return; + } + + // Enqueue styles. + wp_enqueue_style( 'convertkit-admin-deactivation', CONVERTKIT_PLUGIN_URL . 'resources/backend/css/deactivation.css', array(), CONVERTKIT_PLUGIN_VERSION ); + + } + + /** + * Outputs the Deactivation Modal HTML, which is displayed by Javascript. + * + * @since 2.0.2 + */ + public function output_deactivation_modal() { + + // Define the deactivation reasons. + $reasons = array( + 'temporary' => array( + 'label' => __( 'This is temporary / debugging an issue.', 'convertkit' ), + 'placeholder' => __( 'What problem are you experiencing?', 'convertkit' ), + ), + 'not_working' => array( + 'label' => __( 'Could not get the plugin to work.', 'convertkit' ), + 'placeholder' => __( 'What problem are you experiencing?', 'convertkit' ), + ), + 'better_alternative' => array( + 'label' => __( 'Found a better plugin.', 'convertkit' ), + 'placeholder' => __( 'What\'s the plugin\'s name?', 'convertkit' ), + ), + 'not_required' => array( + 'label' => __( 'Don\'t need this plugin.', 'convertkit' ), + 'placeholder' => __( 'What\'s one thing we could improve?', 'convertkit' ), + ), + 'other' => array( + 'label' => __( 'Other.', 'convertkit' ), + 'placeholder' => __( 'What can we do better?', 'convertkit' ), + ), + ); + + // Output modal, which will be displayed when the user clicks deactivate on this plugin. + require_once CONVERTKIT_PLUGIN_PATH . '/views/backend/deactivation/modal.php'; + + } + + /** + * Sends the deactivation reason. + * + * @since 2.0.2 + */ + public function deactivation_modal_submit() { + + // Build args. + // phpcs:disable WordPress.Security.NonceVerification + $args = array( + 'product' => sanitize_text_field( $_REQUEST['product'] ), + 'version' => sanitize_text_field( $_REQUEST['version'] ), + 'reason' => sanitize_text_field( $_REQUEST['reason'] ), + 'reason_text' => sanitize_text_field( $_REQUEST['reason_text'] ), + 'site_url' => str_replace( wp_parse_url( get_bloginfo( 'url' ), PHP_URL_SCHEME ) . '://', '', get_bloginfo( 'url' ) ), + ); + // phpcs:enable + + // Send deactivation reason. + } + +} diff --git a/includes/class-wp-convertkit.php b/includes/class-wp-convertkit.php index cb21aa538..b724bdc28 100644 --- a/includes/class-wp-convertkit.php +++ b/includes/class-wp-convertkit.php @@ -67,6 +67,7 @@ private function initialize_admin() { $this->classes['admin_bulk_edit'] = new ConvertKit_Admin_Bulk_Edit(); $this->classes['admin_cache_plugins'] = new ConvertKit_Admin_Cache_Plugins(); $this->classes['admin_category'] = new ConvertKit_Admin_Category(); + $this->classes['admin_deactivation'] = new ConvertKit_Admin_Deactivation(); $this->classes['admin_landing_page'] = new ConvertKit_Admin_Landing_Page(); $this->classes['admin_notices'] = new ConvertKit_Admin_Notices(); $this->classes['admin_post'] = new ConvertKit_Admin_Post(); diff --git a/resources/backend/css/deactivation.css b/resources/backend/css/deactivation.css new file mode 100644 index 000000000..a97fe784e --- /dev/null +++ b/resources/backend/css/deactivation.css @@ -0,0 +1,60 @@ +.convertkit-modal-overlay { + display: none; + position: fixed; + z-index: 99999999; + top: 0; + left: 0; + opacity: 0.5; + width: 100vw; + height: 100vh; + background-color: #000; +} +.convertkit-modal { + display: none; + position: absolute; + white-space: normal; + width: 400px; + + z-index: 100000000; + box-sizing: border-box; + background-color: #f7f7f7; + box-shadow: 0 0 15px -5px rgba(0, 0, 0, .5); + transition: all .3s ease-out; +} +.convertkit-modal header { + position: relative; + height: 40px; + background: url(../images/logomark-red.svg) 20px 50% no-repeat; + background-size: 20px auto; + background-color: #212f4f; +} +.convertkit-modal header::before { + content: ""; + display: block; + position: absolute; + top: 50%; + transform: translateY(-50%); + left: -10px; + border-top: 20px solid transparent; + border-right: 20px solid #212f4f; + border-bottom: 20px solid transparent; +} +.convertkit-modal header h2 { + font-size: 16px; + line-height: 40px; + font-weight: 400; + margin: 0; + padding: 0 20px 0 50px; + color: #fff; +} +.convertkit-modal form { + padding: 20px; +} +.convertkit-modal form ul { + margin: 0 0 20px 0; + padding: 0; +} +.convertkit-modal form input[type="text"] { + display: none; + margin: 0 0 20px 0; +} diff --git a/resources/backend/js/deactivation.js b/resources/backend/js/deactivation.js new file mode 100644 index 000000000..b48e881f2 --- /dev/null +++ b/resources/backend/js/deactivation.js @@ -0,0 +1,125 @@ +/** + * Shows the deactivation modal window when the Plugin is deactivated + * through the Plugins screen, giving the user an option to specify + * why they are deactivating. + * + * @package ConvertKit + * @author ConvertKit + */ + +var convertkit_deactivation_url; + +jQuery( document ).ready( + function( $ ) { + + /** + * Show deactivation modal if the user is deactivating our plugin. + */ + $( 'span.deactivate a' ).on( + 'click', + function( e ) { + + // If the link slug doesn't exist, let the request through. + var plugin_name = $( this ).closest( 'tr' ).data( 'slug' ); + if ( typeof plugin_name === 'undefined' ) { + return true; + } + + // If the Plugin being deactivated isn't our one, let the request through. + if ( plugin_name != convertkit_deactivation.plugin.name ) { + return true; + } + + // If here, we're deactivating our plugin. + e.preventDefault(); + + // Store the target URL. + convertkit_deactivation_url = $( this ).attr( 'href' ); + + // Position the modal. + $( '#convertkit-deactivation-modal' ).css( + { + top: ( $( this ).offset().top - $( this ).height() - 25 ) + 'px', + left: ( $( this ).offset().left + $( this ).width() + 20 ) + 'px' + } + ); + + // Show the modal. + $( '#convertkit-deactivation-modal, #convertkit-deactivation-modal-overlay' ).show(); + + } + ); + + /** + * Update input text field's placeholder when a reason radio button is clicked + */ + $( 'input[name="convertkit-deactivation-reason"]' ).on( + 'change', + function( e ) { + + $( 'input[name="convertkit-deactivation-reason-text"]' ).attr( + 'placeholder', + $( this ).data( 'placeholder' ) + ).show(); + + } + ); + + /** + * Send the result of the deactivation modal when the submit button is clicked, + * and load the deactivation URL so that the plugin gets deactivated. + */ + $( 'form#convertkit-deactivation-modal-form' ).on( + 'submit', + function( e ) { + + e.preventDefault(); + + var convertkit_deactivation_reason = $( 'input[name=convertkit-deactivation-reason]:checked', $( this ) ).val(), + convertkit_deactivation_reason_text = $( 'input[name=convertkit-deactivation-reason-text]', $( this ) ).val(); + + // Submit the form via AJAX if a reason was given. + if ( typeof convertkit_deactivation_reason !== 'undefined' ) { + $.ajax( + { + url: ajaxurl, + type: 'POST', + async: true, + data: { + action: 'convertkit_deactivation_modal_submit', + product: convertkit_deactivation.plugin.name, + version: convertkit_deactivation.plugin.version, + reason: convertkit_deactivation_reason, + reason_text: convertkit_deactivation_reason_text + }, + error: function( a, b, c ) { + }, + success: function( result ) { + } + } + ); + } + + // Hide the modal. + $( '#convertkit-deactivation-modal, #convertkit-deactivation-modal-overlay' ).hide(); + + // Load the deactivation URL. + window.location.href = convertkit_deactivation_url; + + } + ); + + /** + * Hide the overlay and modal when the overlay is clicked. + */ + $( '#convertkit-deactivation-modal-overlay' ).on( + 'click', + function( e ) { + + $( '#convertkit-deactivation-modal, #convertkit-deactivation-modal-overlay' ).hide(); + + } + ); + + } +); diff --git a/tests/acceptance/general/ActivateDeactivatePluginCest.php b/tests/acceptance/general/ActivateDeactivatePluginCest.php index a7d4f3192..ab27ecb3b 100644 --- a/tests/acceptance/general/ActivateDeactivatePluginCest.php +++ b/tests/acceptance/general/ActivateDeactivatePluginCest.php @@ -43,7 +43,85 @@ public function testPluginActivationAndDeactivationWithOtherPlugins(AcceptanceTe // Use API by loading Settings screen, which will use WordPress Libraries and show errors // if there's a conflict e.g. an older WordPress Library was loaded from another ConvertKit Plugin. - $I->loadConvertKitSettingsGeneralScreen($I); + $I->setupConvertKitPlugin($I); + + // Deactivate this Plugin. + $I->deactivateConvertKitPlugin($I); + } + + /** + * Test that the ConvertKit Plugin deactivates when clicking the Deactivate link on the Plugins + * screen, completing the form fields on the deactivational modal and then submitting. + * + * @since 2.2.3 + * + * @param AcceptanceTester $I Tester. + */ + public function testPluginDeactivationModal(AcceptanceTester $I) + { + $I->activateConvertKitPlugin($I); + + // Deactivate using the 'Deactivate' link on the Plugins screen, which will trigger + // ConvertKit's deactivation modal. + $I->click('a#deactivate-convertkit'); + + // Wait for modal to appear. + $I->waitForElementVisible('#convertkit-deactivation-modal'); + + // Select each reason, ensuring the text input's placeholder updates. + $reasons = [ + 'temporary' => 'What problem are you experiencing?', + 'not_working' => 'What problem are you experiencing?', + 'better_alternative' => 'What\'s the plugin\'s name?', + 'not_required' => 'What\'s one thing we could improve?', + 'other' => 'What can we do better?', + ]; + foreach ($reasons as $reason => $placeholder) { + // Confirm reason can be selected as a radio button. + $I->selectOption('#convertkit-deactivation-modal input[name="convertkit-deactivation-reason"]', $reason); + + // Confirm input field's placeholder has updated, based on the selected reason. + $I->assertEquals( + $I->grabAttributeFrom('input[name="convertkit-deactivation-reason-text"]', 'placeholder'), + $placeholder + ); + } + + // Complete the input field and submit. + $I->fillField('convertkit-deactivation-reason-text', 'Testing'); + $I->click('#convertkit-deactivation-modal input[type="submit"]'); + + // Check that the Plugin deactivated successfully. + $I->seePluginDeactivated('convertkit'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + } + + /** + * Test that the ConvertKit Plugin deactivates when clicking the Deactivate link on the Plugins + * screen, ignoring the form fields on the deactivational modal and then submitting. + * + * @since 2.2.3 + * + * @param AcceptanceTester $I Tester. + */ + public function testPluginDeactivationModalWithNoFormFieldValues(AcceptanceTester $I) + { + $I->activateConvertKitPlugin($I); + + // Deactivate using the 'Deactivate' link on the Plugins screen, which will trigger + // ConvertKit's deactivation modal. + $I->click('a#deactivate-convertkit'); + + // Wait for modal to appear. + $I->waitForElementVisible('#convertkit-deactivation-modal'); + + // Submit deactivation modal. + $I->click('#convertkit-deactivation-modal input[type="submit"]'); + + // Check that the Plugin deactivated successfully. + $I->seePluginDeactivated('convertkit'); // Click the Save Changes button. $I->click('Save Changes'); diff --git a/views/backend/deactivation/modal.php b/views/backend/deactivation/modal.php new file mode 100644 index 000000000..7f1d2042e --- /dev/null +++ b/views/backend/deactivation/modal.php @@ -0,0 +1,42 @@ + +
+