diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a01e246..dc3785c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this plugin will be documented in this file. +## 1.0.1 + +- Bug fix from previous use of the archiveless plugin that broke backwards + compatibility: only include archiveless posts when no `post_status` is + specified for the query. For `get_posts()` calls, this means that archiveless + posts will never be included by default since `get_posts()` sets a default + post status. To include archiveless posts with `get_posts()`, you can include + `archiveless` in the `post_status` or pass `include_archiveless` set to `true. +- Introduces `include_archiveless` query paramater. + ## 1.0.0 - Initial release diff --git a/README.md b/README.md index 39dd7fcf..76ed7b78 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,7 @@ older content that shouldn't appear in search results because it is untimely. ## Usage -By default, the plugin will prevent archiveless posts from appearing on page. -This is limited to the [main +By default, the plugin will prevent archiveless posts from appearing on the page. This is limited to the [main query](https://developer.wordpress.org/reference/functions/is_main_query/) of the page. It will not affect other queries by default. @@ -26,11 +25,9 @@ Archiveless posts can be excluded from normal queries by passing `exclude_archiveless`: ```php -// Via get_posts()/WP_Query. -$posts = get_posts( +$query = new WP_Query( [ 'exclude_archiveless' => true, - 'suppress_filters' => false, // ... ] ); @@ -46,6 +43,34 @@ add_action( ); ``` +### Handling archiveless posts with `get_posts()` calls + +Queries made with `get_posts()` will always exclude archiveless posts by default +since `get_posts()` sets a default `post_status` of `publish`. To include +archiveless posts, you can specify the `post_status` of `any`, declare the +`post_status` explicitly with `[ 'publish', 'archiveless' ]`, or pass +`include_archiveless` set to true: + +```php +// $post_ids will include archiveless posts. +$post_ids = get_posts( + [ + 'fields' => 'ids', + 'include_archiveless' => true, + 'suppress_filters' => false, + ] +); + +// Or declare the post_status explicitly. +$post_ids = get_posts( + [ + 'fields' => 'ids', + 'suppress_filters' => false, + 'post_status' => [ 'archiveless', 'publish' ], + ] +); +``` + ### Install The plugin includes uncompiled Javascript. You can install the plugin by diff --git a/inc/class-archiveless.php b/inc/class-archiveless.php index fe2b5556..3f9b88f1 100644 --- a/inc/class-archiveless.php +++ b/inc/class-archiveless.php @@ -330,13 +330,22 @@ public function on_pre_get_posts( $query ) { return; } - // Don't modify the query if the post_status is set. A status of 'any' - // or 'publish' is ignored since get_post() sets 'publish' as the - // default post_status value when not defined. + /** + * Don't modify the query if the post_status is set. + * + * A `post_status` of 'any' is ignored since we need to include archiveless + * posts because the post_status will be excluded by default (the post + * status is set to be excluded from search). + * + * `get_posts()` will set a default `post_status` of `publish` that is + * respected. To include archiveless posts, set `post_status` to + * `['publish', 'archiveless']` or pass `include_archiveless` as true. + */ if ( ! empty( $query->get( 'post_status' ) ) && 'any' !== $query->get( 'post_status' ) - && 'publish' !== $query->get( 'post_status' ) + && ! isset( $query->query_vars['include_archiveless'] ) + && ! isset( $query->query_vars['exclude_archiveless'] ) ) { return; } @@ -348,6 +357,7 @@ public function on_pre_get_posts( $query ) { if ( ( $query->is_main_query() && $query->is_singular() ) || ( ! $query->is_main_query() && ! $query->get( 'exclude_archiveless' ) ) + || $query->get( 'include_archiveless' ) ) { $query->set( 'post_status', @@ -370,14 +380,24 @@ public function on_pre_get_posts( $query ) { * @return string[] */ public function get_default_post_statuses( $query ) { - return array_keys( - get_post_stati( - [ - 'exclude_from_search' => false, - 'publicly_queryable' => true, - ] + $post_statuses = $query->get( 'post_status', [] ); + + if ( ! is_array( $post_statuses ) ) { + $post_statuses = explode( ',', $post_statuses ); + } + + $post_statuses = array_merge( + $post_statuses, + array_keys( + get_post_stati( + [ + 'exclude_from_search' => false, + ] + ) ) ); + + return array_values( array_unique( $post_statuses ) ); } /** diff --git a/tests/test-general.php b/tests/test-general.php index 9f09e559..5352d020 100644 --- a/tests/test-general.php +++ b/tests/test-general.php @@ -20,17 +20,19 @@ class Test_General extends Test_Case { protected $archiveable_post; + protected $archiveable_post_custom_status; + protected function setUp(): void { parent::setUp(); - $category_id = $this->factory->term->create( + $category_id = static::factory()->term->create( [ 'taxonomy' => 'category', 'name' => 'archives', ] ); - $author_id = $this->factory->user->create( + $author_id = static::factory()->user->create( [ 'role' => 'author', 'user_login' => 'test_author', @@ -45,7 +47,7 @@ protected function setUp(): void { 'post_content' => 'Lorem ipsum', ]; - $this->archiveless_post = $this->factory->post->create( + $this->archiveless_post = static::factory()->post->create( array_merge( $defaults, [ @@ -54,7 +56,8 @@ protected function setUp(): void { ] ) ); - $this->archiveable_post = $this->factory->post->create( + + $this->archiveable_post = static::factory()->post->create( array_merge( $defaults, [ @@ -63,6 +66,22 @@ protected function setUp(): void { ] ) ); + + // Register another custom post status that is public. + register_post_status( + 'other-public-status', + [ + 'public' => true, + 'exclude_from_search' => false, + ] + ); + + $this->archiveable_post_custom_status = static::factory()->post->create( + [ + 'post_title' => 'Test Archiveless Post', + 'post_status' => 'other-public-status', + ] + ); } public function test_verify_post_status_exists() { @@ -91,12 +110,85 @@ public function test_archiveless_is_method() { $this->assertFalse( Archiveless::is( get_post( $this->archiveable_post ) ) ); } - public function test_always_included_outside_of_main_query() { + public function test_always_included_outside_of_main_query_by_default_with_wp_query() { + $post_ids = $this->query( + [ + 'fields' => 'ids', + 'posts_per_page' => -1, + 'suppress_filters' => false, + ] + ); + + $this->assertContains( $this->archiveless_post, $post_ids ); + $this->assertContains( $this->archiveable_post, $post_ids ); + } + + /** + * Test that archiveless posts are included in get_posts() calls by default. + * get_posts() sets a default post_status argument of 'publish'. + */ + public function test_not_included_with_get_posts_by_default() { + $post_ids = get_posts( + [ + 'fields' => 'ids', + 'posts_per_page' => -1, + 'suppress_filters' => false, + ] + ); + + $this->assertNotContains( $this->archiveless_post, $post_ids ); + $this->assertContains( $this->archiveable_post, $post_ids ); + } + + public function test_always_included_outside_of_main_query_with_post_status_publish_with_get_posts_and_post_statuses() { + $post_ids = get_posts( + [ + 'fields' => 'ids', + 'post_status' => [ 'archiveless', 'publish' ], + 'posts_per_page' => -1, + 'suppress_filters' => false, + ] + ); + + $this->assertContains( $this->archiveless_post, $post_ids ); + $this->assertContains( $this->archiveable_post, $post_ids ); + } + + public function test_always_included_outside_of_main_query_with_post_status_publish_with_get_posts_and_include_archiveless() { + $post_ids = get_posts( + [ + 'fields' => 'ids', + 'include_archiveless' => true, + 'posts_per_page' => -1, + 'suppress_filters' => false, + ] + ); + + $this->assertContains( $this->archiveless_post, $post_ids ); + $this->assertContains( $this->archiveable_post, $post_ids ); + } + + public function test_always_included_outside_of_main_query_with_post_status_any_with_wp_query() { + $post_ids = $this->query( + [ + 'fields' => 'ids', + 'posts_per_page' => 100, + 'suppress_filters' => false, + 'post_status' => 'any', + ] + ); + + $this->assertContains( $this->archiveless_post, $post_ids ); + $this->assertContains( $this->archiveable_post, $post_ids ); + } + + public function test_always_included_outside_of_main_query_with_post_status_any_with_get_posts() { $post_ids = get_posts( [ 'fields' => 'ids', 'posts_per_page' => 100, 'suppress_filters' => false, + 'post_status' => 'any', ] ); @@ -105,7 +197,7 @@ public function test_always_included_outside_of_main_query() { } public function test_query_archiveless_posts_only() { - $post_ids = get_posts( + $post_ids = $this->query( [ 'fields' => 'ids', 'post_status' => 'archiveless', @@ -118,8 +210,8 @@ public function test_query_archiveless_posts_only() { $this->assertNotContains( $this->archiveable_post, $post_ids ); } - public function test_optionally_excluded_outside_of_main_query() { - $post_ids = get_posts( + public function test_optionally_excluded_outside_of_main_query_with_exclude_archiveless() { + $post_ids = $this->query( [ 'exclude_archiveless' => true, 'fields' => 'ids', @@ -237,4 +329,53 @@ public function test_post_preview() { ->assertOk() ->assertQueriedObjectId( $post_id ); } + + public function test_custom_post_status_singular() { + $this->get( get_permalink( $this->archiveable_post_custom_status ) ) + ->assertOk() + ->assertElementMissing( 'head/meta[@name="robots"][@content="noindex,nofollow"]' ); + } + + public function test_custom_post_status_query_included() { + // Ensure the custom post status is queryable. + $post_ids = $this->query( + [ + 'fields' => 'ids', + 'posts_per_page' => 100, + 'suppress_filters' => false, + ] + ); + + $this->assertContains( $this->archiveable_post_custom_status, $post_ids ); + $this->assertContains( $this->archiveable_post, $post_ids ); + $this->assertContains( $this->archiveless_post, $post_ids ); + + // Make the query again but exclude the archiveless post. + $post_ids = $this->query( + [ + 'exclude_archiveless' => true, + 'fields' => 'ids', + 'posts_per_page' => 100, + 'suppress_filters' => false, + ] + ); + + $this->assertContains( $this->archiveable_post_custom_status, $post_ids ); + $this->assertContains( $this->archiveable_post, $post_ids ); + $this->assertNotContains( $this->archiveless_post, $post_ids ); + } + + /** + * Make a query and retrieve with WP_Query the posts only. + * + * Similar to `get_posts()` but does not set any defaults like `get_posts()` does. + * + * @param array $args Query arguments. + * @return array \WP_Post[]|int[] Array of posts. + */ + protected function query( array $args ): array { + $query = new \WP_Query( $args ); + + return $query->posts; + } }