diff --git a/admin/class-convertkit-admin-restrict-content.php b/admin/class-convertkit-admin-restrict-content.php index c6340e82f..3c0b6f8e9 100644 --- a/admin/class-convertkit-admin-restrict-content.php +++ b/admin/class-convertkit-admin-restrict-content.php @@ -106,27 +106,41 @@ public function filter_wp_list_table_output( $query ) { return; } - // Build query. - // Because WordPress stores metadata in a single serialized string for a single key, we have to search - // the string for the Restrict Content setting. However, other settings will also be in this serialized string, - // so to avoid false positives, we define our search value formatted as a serialized string comprising of the - // setting name and value, to be as accurate as possible. - $value = maybe_serialize( - array( - 'restrict_content' => sanitize_text_field( wp_unslash( $_REQUEST['convertkit_restrict_content'] ) ), // phpcs:ignore WordPress.Security.NonceVerification - ) - ); - - // Strip a:1:{ and final }, as a Post's serialized settings will include other settings. - $value = str_replace( 'a:1:{', '', $value ); // e.g. s:16:"restrict_content";s:13:"product_36377";}. - $value = substr( $value, 0, strlen( $value ) - 1 ); // e.g. s:16:"restrict_content";s:13:"product_36377";. + // Store Restrict Content filter value. + $this->restrict_content_filter = sanitize_text_field( wp_unslash( $_REQUEST['convertkit_restrict_content'] ) ); // phpcs:ignore WordPress.Security.NonceVerification - // Add value to query. - $meta_query = array( - 'key' => '_wp_convertkit_post_meta', - 'value' => $value, - 'compare' => 'LIKE', - ); + switch ( $this->restrict_content_filter ) { + case 'all-member-only': + $meta_query = array( + 'key' => '_wp_convertkit_post_meta', + 'value' => '.*?(form|tag|product)_[0-9]+.*?', + 'compare' => 'REGEXP', + ); + break; + default: + // Build query. + // Because WordPress stores metadata in a single serialized string for a single key, we have to search + // the string for the Restrict Content setting. However, other settings will also be in this serialized string, + // so to avoid false positives, we define our search value formatted as a serialized string comprising of the + // setting name and value, to be as accurate as possible. + $value = maybe_serialize( + array( + 'restrict_content' => $this->restrict_content_filter, + ) + ); + + // Strip a:1:{ and final }, as a Post's serialized settings will include other settings. + $value = str_replace( 'a:1:{', '', $value ); // e.g. s:16:"restrict_content";s:13:"product_36377";}. + $value = substr( $value, 0, strlen( $value ) - 1 ); // e.g. s:16:"restrict_content";s:13:"product_36377";. + + // Add value to query. + $meta_query = array( + 'key' => '_wp_convertkit_post_meta', + 'value' => $value, + 'compare' => 'LIKE', + ); + break; + } // If the existing meta query is an array, append our query to it, so we honor // any other constraints that have been defined by WordPress or third party code. @@ -138,9 +152,6 @@ public function filter_wp_list_table_output( $query ) { $query->set( 'meta_query', array( $meta_query ) ); } - // Store Restrict Content filter value. - $this->restrict_content_filter = sanitize_text_field( wp_unslash( $_REQUEST['convertkit_restrict_content'] ) ); // phpcs:ignore WordPress.Security.NonceVerification - } /** diff --git a/tests/EndToEnd/restrict-content/general/RestrictContentFilterCPTCest.php b/tests/EndToEnd/restrict-content/general/RestrictContentFilterCPTCest.php index e702d85c1..837a3256b 100644 --- a/tests/EndToEnd/restrict-content/general/RestrictContentFilterCPTCest.php +++ b/tests/EndToEnd/restrict-content/general/RestrictContentFilterCPTCest.php @@ -242,6 +242,93 @@ public function testFilterByForm(EndToEndTester $I) $I->see('Kit Member Content'); } + /** + * Test that filtering by 'All member-only content' works on the CPT screen. + * + * @since 2.8.3 + * + * @param EndToEndTester $I Tester. + */ + public function testFilterByAllMemberOnlyContent(EndToEndTester $I) + { + // Setup Plugin. + $I->setupKitPlugin($I); + + // Create a mix of Posts restricted and not restricted to Forms, Tags and Products. + $I->createRestrictedContentPage( + $I, + [ + 'post_type' => 'article', + 'post_title' => 'Kit: Article: Restricted Content: Form: Filter Test', + 'restrict_content_setting' => 'form_' . $_ENV['CONVERTKIT_API_FORM_ID'], + ] + ); + $I->createRestrictedContentPage( + $I, + [ + 'post_type' => 'article', + 'post_title' => 'Kit: Article: Restricted Content: Tag: Filter Test', + 'restrict_content_setting' => 'tag_' . $_ENV['CONVERTKIT_API_TAG_ID'], + ] + ); + $I->createRestrictedContentPage( + $I, + [ + 'post_type' => 'article', + 'post_title' => 'Kit: Article: Restricted Content: Product: Filter Test', + 'restrict_content_setting' => 'product_' . $_ENV['CONVERTKIT_API_PRODUCT_ID'], + ] + ); + $I->havePostInDatabase( + [ + 'post_type' => 'article', + 'post_title' => 'Kit: Article: Standard', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => '', + 'tag' => '', + 'restrict_content' => '0', + ], + ], + ] + ); + $I->havePostInDatabase( + [ + 'post_type' => 'article', + 'post_title' => 'Kit: Article: Standard: No Meta', + ] + ); + + // Navigate to Articles. + $I->amOnAdminPage('edit.php?post_type=article'); + + // Wait for the WP_List_Table of Articles to load. + $I->waitForElementVisible('tbody#the-list'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Filter by All member-only content. + $I->selectOption('#wp-convertkit-restrict-content-filter', 'All member-only content'); + $I->click('Filter'); + + // Wait for the WP_List_Table of Articles to load. + $I->waitForElementVisible('tbody#the-list'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Confirm that the Restrict Content Articles are listed. + $I->see('Kit: Article: Restricted Content: Form: Filter Test'); + $I->see('Kit: Article: Restricted Content: Tag: Filter Test'); + $I->see('Kit: Article: Restricted Content: Product: Filter Test'); + + // Confirm that no non-Restrict Content Posts are not listed. + $I->dontSee('Kit: Article: Standard'); + $I->dontSee('Kit: Article: Standard: No Meta'); + } + /** * Deactivate and reset Plugin(s) after each test, if the test passes. * We don't use _after, as this would provide a screenshot of the Plugin diff --git a/tests/EndToEnd/restrict-content/general/RestrictContentFilterPageCest.php b/tests/EndToEnd/restrict-content/general/RestrictContentFilterPageCest.php index 89477f426..15c413907 100644 --- a/tests/EndToEnd/restrict-content/general/RestrictContentFilterPageCest.php +++ b/tests/EndToEnd/restrict-content/general/RestrictContentFilterPageCest.php @@ -165,7 +165,7 @@ public function testFilterByTag(EndToEndTester $I) } /** - * Test that filtering by Form works on the Posts screen. + * Test that filtering by Form works on the Pages screen. * * @since 2.7.3 * @@ -180,16 +180,16 @@ public function testFilterByForm(EndToEndTester $I) $I->createRestrictedContentPage( $I, [ - 'post_type' => 'post', + 'post_type' => 'page', 'post_title' => 'Kit: Page: Restricted Content: Form: Filter Test', 'restrict_content_setting' => 'form_' . $_ENV['CONVERTKIT_API_FORM_ID'], ] ); - // Navigate to Posts. - $I->amOnAdminPage('edit.php?post_type=post'); + // Navigate to Pages. + $I->amOnAdminPage('edit.php?post_type=page'); - // Wait for the WP_List_Table of Posts to load. + // Wait for the WP_List_Table of Pages to load. $I->waitForElementVisible('tbody#the-list'); // Check that no PHP warnings or notices were output. @@ -203,7 +203,7 @@ public function testFilterByForm(EndToEndTester $I) $I->selectOption('#wp-convertkit-restrict-content-filter', $_ENV['CONVERTKIT_API_FORM_NAME']); $I->click('Filter'); - // Wait for the WP_List_Table of Posts to load. + // Wait for the WP_List_Table of Pages to load. $I->waitForElementVisible('tbody#the-list'); // Check that no PHP warnings or notices were output. @@ -214,6 +214,93 @@ public function testFilterByForm(EndToEndTester $I) $I->see('Kit Member Content'); } + /** + * Test that filtering by 'All member-only content' works on the Pages screen. + * + * @since 2.8.3 + * + * @param EndToEndTester $I Tester. + */ + public function testFilterByAllMemberOnlyContent(EndToEndTester $I) + { + // Setup Plugin. + $I->setupKitPlugin($I); + + // Create a mix of Pages restricted and not restricted to Forms, Tags and Products. + $I->createRestrictedContentPage( + $I, + [ + 'post_type' => 'page', + 'post_title' => 'Kit: Page: Restricted Content: Form: Filter Test', + 'restrict_content_setting' => 'form_' . $_ENV['CONVERTKIT_API_FORM_ID'], + ] + ); + $I->createRestrictedContentPage( + $I, + [ + 'post_type' => 'page', + 'post_title' => 'Kit: Page: Restricted Content: Tag: Filter Test', + 'restrict_content_setting' => 'tag_' . $_ENV['CONVERTKIT_API_TAG_ID'], + ] + ); + $I->createRestrictedContentPage( + $I, + [ + 'post_type' => 'page', + 'post_title' => 'Kit: Page: Restricted Content: Product: Filter Test', + 'restrict_content_setting' => 'product_' . $_ENV['CONVERTKIT_API_PRODUCT_ID'], + ] + ); + $I->havePostInDatabase( + [ + 'post_type' => 'page', + 'post_title' => 'Kit: Page: Standard', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => '', + 'tag' => '', + 'restrict_content' => '0', + ], + ], + ] + ); + $I->havePostInDatabase( + [ + 'post_type' => 'page', + 'post_title' => 'Kit: Page: Standard: No Meta', + ] + ); + + // Navigate to Pages. + $I->amOnAdminPage('edit.php?post_type=page'); + + // Wait for the WP_List_Table of Pages to load. + $I->waitForElementVisible('tbody#the-list'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Filter by All member-only content. + $I->selectOption('#wp-convertkit-restrict-content-filter', 'All member-only content'); + $I->click('Filter'); + + // Wait for the WP_List_Table of Pages to load. + $I->waitForElementVisible('tbody#the-list'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Confirm that the Restrict Content Pages are listed. + $I->see('Kit: Page: Restricted Content: Form: Filter Test'); + $I->see('Kit: Page: Restricted Content: Tag: Filter Test'); + $I->see('Kit: Page: Restricted Content: Product: Filter Test'); + + // Confirm that no non-Restrict Content Pages are not listed. + $I->dontSee('Kit: Page: Standard'); + $I->dontSee('Kit: Page: Standard: No Meta'); + } + /** * Deactivate and reset Plugin(s) after each test, if the test passes. * We don't use _after, as this would provide a screenshot of the Plugin diff --git a/tests/EndToEnd/restrict-content/general/RestrictContentFilterPostCest.php b/tests/EndToEnd/restrict-content/general/RestrictContentFilterPostCest.php index 0758bc2b0..939b2b5e7 100644 --- a/tests/EndToEnd/restrict-content/general/RestrictContentFilterPostCest.php +++ b/tests/EndToEnd/restrict-content/general/RestrictContentFilterPostCest.php @@ -216,6 +216,93 @@ public function testFilterByForm(EndToEndTester $I) $I->see('Kit Member Content'); } + /** + * Test that filtering by 'All member-only content' works on the Posts screen. + * + * @since 2.8.3 + * + * @param EndToEndTester $I Tester. + */ + public function testFilterByAllMemberOnlyContent(EndToEndTester $I) + { + // Setup Plugin. + $I->setupKitPlugin($I); + + // Create a mix of Posts restricted and not restricted to Forms, Tags and Products. + $I->createRestrictedContentPage( + $I, + [ + 'post_type' => 'post', + 'post_title' => 'Kit: Post: Restricted Content: Form: Filter Test', + 'restrict_content_setting' => 'form_' . $_ENV['CONVERTKIT_API_FORM_ID'], + ] + ); + $I->createRestrictedContentPage( + $I, + [ + 'post_type' => 'post', + 'post_title' => 'Kit: Post: Restricted Content: Tag: Filter Test', + 'restrict_content_setting' => 'tag_' . $_ENV['CONVERTKIT_API_TAG_ID'], + ] + ); + $I->createRestrictedContentPage( + $I, + [ + 'post_type' => 'post', + 'post_title' => 'Kit: Post: Restricted Content: Product: Filter Test', + 'restrict_content_setting' => 'product_' . $_ENV['CONVERTKIT_API_PRODUCT_ID'], + ] + ); + $I->havePostInDatabase( + [ + 'post_type' => 'post', + 'post_title' => 'Kit: Post: Standard', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => '', + 'tag' => '', + 'restrict_content' => '0', + ], + ], + ] + ); + $I->havePostInDatabase( + [ + 'post_type' => 'post', + 'post_title' => 'Kit: Post: Standard: No Meta', + ] + ); + + // Navigate to Posts. + $I->amOnAdminPage('edit.php?post_type=post'); + + // Wait for the WP_List_Table of Posts to load. + $I->waitForElementVisible('tbody#the-list'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Filter by All member-only content. + $I->selectOption('#wp-convertkit-restrict-content-filter', 'All member-only content'); + $I->click('Filter'); + + // Wait for the WP_List_Table of Posts to load. + $I->waitForElementVisible('tbody#the-list'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Confirm that the Restrict Content Posts are listed. + $I->see('Kit: Post: Restricted Content: Form: Filter Test'); + $I->see('Kit: Post: Restricted Content: Tag: Filter Test'); + $I->see('Kit: Post: Restricted Content: Product: Filter Test'); + + // Confirm that no non-Restrict Content Posts are not listed. + $I->dontSee('Kit: Post: Standard'); + $I->dontSee('Kit: Post: Standard: No Meta'); + } + /** * Deactivate and reset Plugin(s) after each test, if the test passes. * We don't use _after, as this would provide a screenshot of the Plugin diff --git a/views/backend/post/wp-list-table-filter.php b/views/backend/post/wp-list-table-filter.php index 9c10cceb3..7d1883042 100644 --- a/views/backend/post/wp-list-table-filter.php +++ b/views/backend/post/wp-list-table-filter.php @@ -9,6 +9,7 @@ ?>