diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f2264149a..0b9b9ab9e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -214,11 +214,11 @@ jobs: uses: actions/upload-artifact@v2 with: name: test-results - path: /home/runner/work/convertkit-wordpress/convertkit-wordpress/wordpress/wp-content/plugins/convertkit/tests/_output/ + path: ${{ env.PLUGIN_DIR }}/tests/_output/ - name: Upload Plugin Log File to Artifact if: always() uses: actions/upload-artifact@v2 with: name: log - path: /home/runner/work/convertkit-wordpress/convertkit-wordpress/wordpress/wp-content/plugins/convertkit/log.txt \ No newline at end of file + path: ${{ env.PLUGIN_DIR }}/log.txt \ No newline at end of file diff --git a/.scripts/read-actions-filters.php b/.scripts/read-actions-filters.php index 3a55b3486..4dbb17980 100644 --- a/.scripts/read-actions-filters.php +++ b/.scripts/read-actions-filters.php @@ -40,7 +40,7 @@ public function run( $folders, $extract_filters = true, $extract_actions = true, // Check the target folder exists if ( ! is_dir( $folder ) ) { - return false; + continue; } // Iterate through the folder and subfolders, finding any PHP files @@ -61,7 +61,7 @@ public function run( $folders, $extract_filters = true, $extract_actions = true, } } - + // Check if any PHP files were found if ( count( $php_files ) == 0 ) { return false; diff --git a/admin/class-convertkit-admin-post.php b/admin/class-convertkit-admin-post.php index cd8d841a9..66e09db49 100644 --- a/admin/class-convertkit-admin-post.php +++ b/admin/class-convertkit-admin-post.php @@ -172,7 +172,15 @@ public function save_post_meta( $post_id ) { // Save metadata. $convertkit_post = new ConvertKit_Post( $post_id ); - return $convertkit_post->save( $meta ); + $convertkit_post->save( $meta ); + + // If a Form or Landing Page was specified, request a review. + // This can safely be called multiple times, as the review request + // class will ensure once a review request is dismissed by the user, + // it is never displayed again. + if ( $meta['form'] || $meta['landing_page'] ) { + WP_ConvertKit()->get_class( 'review_request' )->request_review(); + } } diff --git a/admin/section/class-convertkit-settings-base.php b/admin/section/class-convertkit-settings-base.php index 060dd50c4..7ad9d108d 100644 --- a/admin/section/class-convertkit-settings-base.php +++ b/admin/section/class-convertkit-settings-base.php @@ -316,6 +316,15 @@ private function get_description( $description ) { */ public function sanitize_settings( $settings ) { + // If a Form or Landing Page was specified, request a review. + // This can safely be called multiple times, as the review request + // class will ensure once a review request is dismissed by the user, + // it is never displayed again. + if ( ( isset( $settings['page_form'] ) && $settings['page_form'] ) || + ( isset( $settings['post_form'] ) && $settings['post_form'] ) ) { + WP_ConvertKit()->get_class( 'review_request' )->request_review(); + } + return wp_parse_args( $settings, $this->settings->get_defaults() ); } diff --git a/includes/class-convertkit-review-request.php b/includes/class-convertkit-review-request.php new file mode 100644 index 000000000..ca756912d --- /dev/null +++ b/includes/class-convertkit-review-request.php @@ -0,0 +1,207 @@ +plugin_name = $plugin_name; + $this->plugin_slug = $plugin_slug; + + // Register an AJAX action to dismiss the review. + add_action( 'wp_ajax_' . $this->plugin_slug . '_dismiss_review', array( $this, 'dismiss_review' ) ); + + // Maybe display a review request in the WordPress Admin notices. + add_action( 'admin_notices', array( $this, 'maybe_display_review_request' ) ); + + } + + /** + * Displays a dismissible WordPress Administration notice requesting a review, if requested + * by the main Plugin and the Review Request hasn't been disabled. + * + * @since 1.9.6.7 + */ + public function maybe_display_review_request() { + + // If we're not an Admin user, bail. + if ( ! function_exists( 'current_user_can' ) ) { + return; + } + if ( ! current_user_can( 'activate_plugins' ) ) { + return; + } + + // Don't display a review request on multisite. This is so that existing Plugin + // users who existed prior to this feature don't get bombarded with the same + // notification across 100+ of their sites on a multisite network. + if ( is_multisite() ) { + return; + } + + // If the review request was dismissed by the user, bail. + if ( $this->dismissed_review() ) { + return; + } + + // If no review request has been set by the plugin, bail. + if ( ! $this->requested_review() ) { + return; + } + + // If here, display the request for a review. + include_once CONVERTKIT_PLUGIN_PATH . '/views/backend/review/notice.php'; + + } + + /** + * Sets a flag in the options table requesting a review notification be displayed + * in the WordPress Administration. + * + * @since 1.9.6.7 + */ + public function request_review() { + + // If a review has already been requested, bail. + $time = get_option( $this->plugin_slug . '-review-request' ); + if ( ! empty( $time ) ) { + return; + } + + // Request a review notification to be displayed beginning at a future timestamp. + update_option( $this->plugin_slug . '-review-request', time() + ( $this->number_of_days_in_future * DAY_IN_SECONDS ) ); + + } + + /** + * Flag to indicate whether a review has been requested by the Plugin, + * and the minimum time has passed between the Plugin requesting a review + * and now. + * + * @since 1.9.6.7 + * + * @return bool Review Requested + */ + public function requested_review() { + + // Bail if no review was requested by the Plugin. + $start_displaying_review_at = get_option( $this->plugin_slug . '-review-request' ); + if ( empty( $start_displaying_review_at ) ) { + return false; + } + + // Bail if a review was requested by the Plugin, but it's too early to display it. + if ( $start_displaying_review_at > time() ) { + return false; + } + + // The Plugin requested a review and it's time to display the notification. + return true; + + } + + /** + * Dismisses the review notification, so it isn't displayed again. + * + * @since 1.9.6.7 + */ + public function dismiss_review() { + + update_option( $this->plugin_slug . '-review-dismissed', 1 ); + + // Send success response if called via AJAX. + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + wp_send_json_success( 1 ); + } + + } + + /** + * Flag to indicate whether a review request has been dismissed by the user. + * + * @since 1.9.6.7 + * + * @return bool Review Dismissed + */ + public function dismissed_review() { + + return get_option( $this->plugin_slug . '-review-dismissed' ); + + } + + /** + * Returns the Review URL for this Plugin. + * + * @since 1.9.6.7 + * + * @return string Review URL + */ + public function get_review_url() { + + return 'https://wordpress.org/support/plugin/' . $this->plugin_slug . '/reviews/?filter=5#new-post'; + + } + + /** + * Returns the Support URL for this Plugin. + * + * @since 1.9.6.7 + * + * @return string Review URL + */ + public function get_support_url() { + + return 'https://convertkit.com/support'; + + } + +} diff --git a/includes/class-wp-convertkit.php b/includes/class-wp-convertkit.php index 5b7ae40e1..0352bf5f1 100644 --- a/includes/class-wp-convertkit.php +++ b/includes/class-wp-convertkit.php @@ -160,6 +160,7 @@ private function initialize_global() { $this->classes['blocks_convertkit_content'] = new ConvertKit_Block_Content(); $this->classes['blocks_convertkit_form'] = new ConvertKit_Block_Form(); $this->classes['gutenberg'] = new ConvertKit_Gutenberg(); + $this->classes['review_request'] = new ConvertKit_Review_Request( 'ConvertKit', 'convertkit' ); $this->classes['shortcodes'] = new ConvertKit_Shortcodes(); $this->classes['widgets'] = new ConvertKit_Widgets(); diff --git a/phpcs.xml b/phpcs.xml index bbab70f46..2f69e9188 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -4,9 +4,10 @@ /.scripts/* + /.wordpress-org/* /lib/* - /vendor/* /tests/* + /vendor/* *.min.js diff --git a/tests/_support/Helper/Acceptance.php b/tests/_support/Helper/Acceptance.php index 09497cf5a..a3ec42fc2 100644 --- a/tests/_support/Helper/Acceptance.php +++ b/tests/_support/Helper/Acceptance.php @@ -114,6 +114,18 @@ public function deactivateConvertKitPlugin($I) $I->checkNoWarningsAndNoticesOnScreen($I); } + /** + * Helper method to delete option table rows for review requests. + * Useful for resetting the review state between tests. + * + * @since 1.9.6.7 + */ + public function deleteConvertKitReviewRequestOptions($I) + { + $I->dontHaveOptionInDatabase('convertkit-review-request'); + $I->dontHaveOptionInDatabase('convertkit-review-dismissed'); + } + /** * Helper method to setup the Plugin's API Key and Secret. * diff --git a/tests/acceptance/ReviewRequestCest.php b/tests/acceptance/ReviewRequestCest.php new file mode 100644 index 000000000..a369af25d --- /dev/null +++ b/tests/acceptance/ReviewRequestCest.php @@ -0,0 +1,260 @@ +activateConvertKitPlugin($I); + } + + /** + * Test that the review request is set in the options table when the Plugin's + * Settings are saved with a Default Page Form specified in the Settings. + * + * @since 1.9.6.7 + * + * @param AcceptanceTester $I Tester + */ + public function testReviewRequestOnSaveSettings(AcceptanceTester $I) + { + // Clear options table settings for review request. + $I->deleteConvertKitReviewRequestOptions($I); + + // Setup ConvertKit Plugin. + $I->setupConvertKitPlugin($I); + + // Define Default Form. + $I->setupConvertKitPluginDefaultForm($I); + + // Check that the options table does have a review request set. + $I->seeOptionInDatabase('convertkit-review-request'); + + // Check that the option table does not yet have a review dismissed set. + $I->dontSeeOptionInDatabase('convertkit-review-dismissed'); + } + + /** + * Test that no review request is set in the options table when the Plugin's + * Settings are saved with no Forms specified in the Settings. + * + * @since 1.9.6.7 + * + * @param AcceptanceTester $I Tester + */ + public function testReviewRequestOnSaveBlankSettings(AcceptanceTester $I) + { + // Clear options table settings for review request. + $I->deleteConvertKitReviewRequestOptions($I); + + // Go to the Plugin's Settings Screen. + $I->loadConvertKitSettingsGeneralScreen($I); + + // Click the Save Changes button. + $I->click('Save Changes'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Check that the options table doesn't have a review request set. + $I->dontSeeOptionInDatabase('convertkit-review-request'); + $I->dontSeeOptionInDatabase('convertkit-review-dismissed'); + } + + /** + * Test that the review request is set in the options table when a + * WordPress Page is created and saved with a Form specified in + * the ConvertKit Meta Box. + * + * @since 1.9.6.7 + * + * @param AcceptanceTester $I Tester + */ + public function testReviewRequestOnSavePageWithFormSpecified(AcceptanceTester $I) + { + + // Clear options table settings for review request. + $I->deleteConvertKitReviewRequestOptions($I); + + // Flush Permalinks. + $I->amOnAdminPage('options-permalink.php'); + + // Setup ConvertKit Plugin. + $I->setupConvertKitPlugin($I); + + // Define Default Form. + $I->setupConvertKitPluginDefaultForm($I); + + // Navigate to Pages > Add New + $I->amOnAdminPage('post-new.php?post_type=page'); + + // Close the Gutenberg "Welcome to the block editor" dialog if it's displayed. + $I->maybeCloseGutenbergWelcomeModal($I); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Check that the metabox is displayed. + $I->seeElementInDOM('#wp-convertkit-meta-box'); + + // Check that the Form option is displayed. + $I->seeElementInDOM('#wp-convertkit-form'); + + // Change Form to value specified in the .env file. + $I->fillSelect2Field($I, '#select2-wp-convertkit-form-container', $_ENV['CONVERTKIT_API_FORM_NAME']); + + // Define a Page Title. + $I->fillField('.editor-post-title__input', 'abc123'); + + // Click the Publish button. + $I->click('.editor-post-publish-button__button'); + + // When the pre-publish panel displays, click Publish again. + $I->performOn('.editor-post-publish-panel__prepublish', function($I) { + $I->click('.editor-post-publish-panel__header-publish-button .editor-post-publish-button__button'); + }); + + $I->wait(3); + + // Navigate to a screen in the WordPress Administration. + $I->amOnAdminPage('index.php'); + + // Check that the options table does have a review request set. + $I->seeOptionInDatabase('convertkit-review-request'); + + // Check that the option table does not yet have a review dismissed set. + $I->dontSeeOptionInDatabase('convertkit-review-dismissed'); + } + + /** + * Test that the review request is set in the options table when a + * WordPress Page is created and saved with a Landing Page specified in + * the ConvertKit Meta Box. + * + * @since 1.9.6.7 + * + * @param AcceptanceTester $I Tester + */ + public function testReviewRequestOnSavePageWithLandingPageSpecified(AcceptanceTester $I) + { + // Clear options table settings for review request. + $I->deleteConvertKitReviewRequestOptions($I); + + // Setup ConvertKit Plugin. + $I->setupConvertKitPlugin($I); + + // Define Default Form. + $I->setupConvertKitPluginDefaultForm($I); + + // Navigate to Pages > Add New + $I->amOnAdminPage('post-new.php?post_type=page'); + + // Close the Gutenberg "Welcome to the block editor" dialog if it's displayed. + $I->maybeCloseGutenbergWelcomeModal($I); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Check that the metabox is displayed. + $I->seeElementInDOM('#wp-convertkit-meta-box'); + + // Check that the Form option is displayed. + $I->seeElementInDOM('#wp-convertkit-landing_page'); + + // Change Landing Page to value specified in the .env file. + $I->fillSelect2Field($I, '#select2-wp-convertkit-landing_page-container', $_ENV['CONVERTKIT_API_LANDING_PAGE_NAME']); + + // Define a Page Title. + $I->fillField('.editor-post-title__input', 'ConvertKit: Review Request: Landing Page'); + + // Click the Publish button. + $I->click('.editor-post-publish-button__button'); + + // When the pre-publish panel displays, click Publish again. + $I->performOn('.editor-post-publish-panel__prepublish', function($I) { + $I->click('.editor-post-publish-panel__header-publish-button .editor-post-publish-button__button'); + }); + + $I->wait(3); + + // Navigate to a screen in the WordPress Administration. + $I->amOnAdminPage('index.php'); + + // Check that the options table does have a review request set. + $I->seeOptionInDatabase('convertkit-review-request'); + + // Check that the option table does not yet have a review dismissed set. + $I->dontSeeOptionInDatabase('convertkit-review-dismissed'); + } + + /** + * Test that the review request is displayed when the options table entries + * have the required values to display the review request notification. + * + * @since 1.9.6.7 + * + * @param AcceptanceTester $I Tester + */ + public function testReviewRequestNotificationDisplayed(AcceptanceTester $I) + { + // Clear options table settings for review request. + $I->deleteConvertKitReviewRequestOptions($I); + + // Set review request option with a timestamp in the past, to emulate + // the Plugin having set this a few days ago. + $I->haveOptionInDatabase('convertkit-review-request', time() - 3600 ); + + // Navigate to a screen in the WordPress Administration. + $I->amOnAdminPage('index.php'); + + // Confirm the review displays. + $I->seeElementInDOM('div.review-convertkit'); + + // Confirm links are correct. + $I->seeInSource(''); + $I->seeInSource(''); + } + + /** + * Test that the review request is dismissed and does not reappear + * on a subsequent page load. + * + * @since 1.9.6.7 + * + * @param AcceptanceTester $I Tester + */ + public function testReviewRequestNotificationDismissed(AcceptanceTester $I) + { + // Clear options table settings for review request. + $I->deleteConvertKitReviewRequestOptions($I); + + // Set review request option with a timestamp in the past, to emulate + // the Plugin having set this a few days ago. + $I->haveOptionInDatabase('convertkit-review-request', time() - 3600 ); + + // Navigate to a screen in the WordPress Administration. + $I->amOnAdminPage('index.php'); + + // Confirm the review displays. + $I->seeElementInDOM('div.review-convertkit'); + + // Dismiss the review request. + $I->click('div.review-convertkit button.notice-dismiss'); + + // Navigate to a screen in the WordPress Administration. + $I->amOnAdminPage('index.php'); + + // Confirm the review notification no longer displays. + $I->dontSeeElementInDOM('div.review-convertkit'); + } +} \ No newline at end of file diff --git a/views/backend/review/notice.php b/views/backend/review/notice.php new file mode 100644 index 000000000..9db2548b8 --- /dev/null +++ b/views/backend/review/notice.php @@ -0,0 +1,62 @@ + + +
+

+ plugin_name + ) + ); + ?> +

+

+ + + + + plugin_name + ) + ); + ?> + +

+ + +
+ diff --git a/wp-convertkit.php b/wp-convertkit.php index ea22c58e2..8c4c7260b 100644 --- a/wp-convertkit.php +++ b/wp-convertkit.php @@ -39,6 +39,7 @@ require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-resource-forms.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-resource-landing-pages.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-resource-tags.php'; +require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-review-request.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-settings.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-shortcodes.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-system-info.php';