Posts, Post Types: Additional functions to check if a post is publicly viewable.

Introduces `is_post_status_viewable()` as a sibling to `is_post_type_viewable()`. Internal and protected statuses are never considered viewable. For built in posts statuses the `public` attribute is checked, for custom statuses the `publicly_queryable` attribute is checked.

Introduces `is_post_publicly_viewable()` for determining if an individual post can be viewed by logged out users. A post is considered viewable if both `is_post_status_viewable()` and `is_post_type_viewable()` return `true` for the post's attributes.

Additionally modifies `is_post_type_viewable()` to return `false` if an unregistered post type is passed to the function to avoid attempting to access properties on a non-object.

Props peterwilsoncc, SergeyBiryukov, whyisjake, TimothyBlynJacobs.
Fixes #49380.



git-svn-id: https://develop.svn.wordpress.org/trunk@50130 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Peter Wilson 2021-02-01 23:31:54 +00:00
parent 193a5dae21
commit 4cfbf629ca
3 changed files with 337 additions and 0 deletions

View File

@ -2019,9 +2019,67 @@ function is_post_type_viewable( $post_type ) {
}
}
if ( ! is_object( $post_type ) ) {
return false;
}
return $post_type->publicly_queryable || ( $post_type->_builtin && $post_type->public );
}
/**
* Determine whether a post status is considered "viewable".
*
* For built-in post statuses such as publish and private, the 'public' value will be evaluted.
* For all others, the 'publicly_queryable' value will be used.
*
* @since 5.7.0
*
* @param string|stdClass $post_status Post status name or object.
* @return bool Whether the post status should be considered viewable.
*/
function is_post_status_viewable( $post_status ) {
if ( is_scalar( $post_status ) ) {
$post_status = get_post_status_object( $post_status );
if ( ! $post_status ) {
return false;
}
}
if (
! is_object( $post_status ) ||
$post_status->internal ||
$post_status->protected
) {
return false;
}
return $post_status->publicly_queryable || ( $post_status->_builtin && $post_status->public );
}
/**
* Determine whether a post is publicly viewable.
*
* Posts are considered publicly viewable if both the post status and post type
* are viewable.
*
* @since 5.7.0
*
* @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
* @return bool Whether the post is publicly viewable.
*/
function is_post_publicly_viewable( $post = null ) {
$post = get_post( $post );
if ( ! $post ) {
return false;
}
$post_type = get_post_type( $post );
$post_status = get_post_status( $post );
return is_post_type_viewable( $post_type ) && is_post_status_viewable( $post_status );
}
/**
* Retrieves an array of the latest posts, or posts matching the given criteria.
*

View File

@ -0,0 +1,108 @@
<?php
/**
* @group post
*/
class Tests_Post_IsPostPubliclyViewable extends WP_UnitTestCase {
/**
* Array of post IDs to use as parents.
*
* @var array
*/
public static $parent_post_ids = array();
public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
$post_statuses = array( 'publish', 'private', 'future', 'trash', 'delete' );
foreach ( $post_statuses as $post_status ) {
$date = '';
$actual_status = $post_status;
if ( 'future' === $post_status ) {
$date = strftime( '%Y-%m-%d %H:%M:%S', strtotime( '+1 year' ) );
} elseif ( in_array( $post_status, array( 'trash', 'delete' ), true ) ) {
$actual_status = 'publish';
}
self::$parent_post_ids[ $post_status ] = $factory->post->create(
array(
'post_status' => $actual_status,
'post_name' => "$post_status-post",
'post_date' => $date,
'post_type' => 'page',
)
);
}
wp_trash_post( self::$parent_post_ids['trash'] );
wp_delete_post( self::$parent_post_ids['delete'], true );
}
/**
* Unit tests for is_post_publicly_viewable().
*
* @dataProvider data_is_post_publicly_viewable
* @ticket 49380
*
* @param string $post_type The post type.
* @param string $post_status The post status.
* @param bool $expected The expected result of the function call.
* @param string $parent_key The parent key as set up in shared fixtures.
*/
public function test_is_post_publicly_viewable( $post_type, $post_status, $expected, $parent_key = '' ) {
$date = '';
if ( 'future' === $post_status ) {
$date = strftime( '%Y-%m-%d %H:%M:%S', strtotime( '+1 year' ) );
}
$post_id = $this->factory()->post->create(
array(
'post_type' => $post_type,
'post_status' => $post_status,
'post_parent' => $parent_key ? self::$parent_post_ids[ $parent_key ] : 0,
'post_date' => $date,
)
);
$this->assertSame( $expected, is_post_publicly_viewable( $post_id ) );
}
/**
* Data provider for test_is_post_publicly_viewable().
*
* return array[] {
* @type string $post_type The post type.
* @type string $post_status The post status.
* @type bool $expected The expected result of the function call.
* @type string $parent_key The parent key as set up in shared fixtures.
* }
*/
public function data_is_post_publicly_viewable() {
return array(
array( 'post', 'publish', true ),
array( 'post', 'private', false ),
array( 'post', 'future', false ),
array( 'page', 'publish', true ),
array( 'page', 'private', false ),
array( 'page', 'future', false ),
array( 'unregistered_cpt', 'publish', false ),
array( 'unregistered_cpt', 'private', false ),
array( 'post', 'unregistered_cps', false ),
array( 'page', 'unregistered_cps', false ),
array( 'attachment', 'inherit', true, 'publish' ),
array( 'attachment', 'inherit', false, 'private' ),
array( 'attachment', 'inherit', false, 'future' ),
array( 'attachment', 'inherit', true, 'trash' ),
array( 'attachment', 'inherit', true, 'delete' ),
array( 'page', 'publish', true, 'publish' ),
array( 'page', 'publish', true, 'private' ),
array( 'page', 'publish', true, 'future' ),
array( 'page', 'publish', true, 'trash' ),
array( 'page', 'publish', true, 'delete' ),
);
}
}

View File

@ -0,0 +1,171 @@
<?php
/**
* @group post
*/
class Tests_Post_IsPostStatusViewable extends WP_UnitTestCase {
/**
* Remove the test status from the global when finished.
*
* @global $wp_post_statuses
*/
static function wpTearDownAfterClass() {
global $wp_post_statuses;
unset( $wp_post_statuses['wp_tests_ps'] );
}
/**
* Test custom post status.
*
* This may include emulations of built in (_builtin) statuses.
*
* @ticket 49380
* @dataProvider data_custom_post_statuses
*
* @param array $cps_args Registration arguments.
* @param bool $expected Expected result.
*/
public function test_custom_post_statuses( $cps_args, $expected ) {
register_post_status(
'wp_tests_ps',
$cps_args
);
// Test status passed as string.
$this->assertSame( $expected, is_post_status_viewable( 'wp_tests_ps' ) );
// Test status passed as object.
$this->assertSame( $expected, is_post_status_viewable( get_post_status_object( 'wp_tests_ps' ) ) );
}
/**
* Data provider for custom post status tests.
*
* @return array[] {
* array CPS registration args.
* bool Expected result.
* }
*/
public function data_custom_post_statuses() {
return array(
// 0. False for non-publically queryable types.
array(
array(
'publicly_queryable' => false,
'_builtin' => false,
'public' => true,
),
false,
),
// 1. True for publically queryable types.
array(
array(
'publicly_queryable' => true,
'_builtin' => false,
'public' => false,
),
true,
),
// 2. False for built-in non-public types.
array(
array(
'publicly_queryable' => false,
'_builtin' => true,
'public' => false,
),
false,
),
// 3. False for non-built-in public types.
array(
array(
'publicly_queryable' => false,
'_builtin' => false,
'public' => true,
),
false,
),
// 4. True for built-in public types.
array(
array(
'publicly_queryable' => false,
'_builtin' => true,
'public' => true,
),
true,
),
);
}
/**
* Test built-in and unregistered post status.
*
* @dataProvider data_built_unregistered_in_status_types
* @ticket 49380
*
* @param mixed $status Post status to check.
* @param bool $expected Expected viewable status.
*/
function test_built_unregistered_in_status_types( $status, $expected ) {
// Test status passed as string.
$this->assertSame( $expected, is_post_status_viewable( $status ) );
// Test status passed as object.
$this->assertSame( $expected, is_post_status_viewable( get_post_status_object( $status ) ) );
}
/**
* Data provider for built-in and unregistered post status tests.
*
* @return array[] {
* @type mixed $status Post status to check.
* @type bool $expected Expected viewable status.
* }
*/
public function data_built_unregistered_in_status_types() {
return array(
array( 'publish', true ),
array( 'future', false ),
array( 'draft', false ),
array( 'pending', false ),
array( 'private', false ),
array( 'trash', false ),
array( 'auto-draft', false ),
array( 'inherit', false ),
array( 'request-pending', false ),
array( 'request-confirmed', false ),
array( 'request-failed', false ),
array( 'request-completed', false ),
// Various unregistered statuses.
array( 'unregistered-status', false ),
array( false, false ),
array( true, false ),
array( 20, false ),
array( null, false ),
array( '', false ),
);
}
/**
* Sanitize key should not be run when testing.
*
* @ticket 49380
*/
public function test_sanitize_key_not_run() {
register_post_status(
'WP_Tests_ps',
array(
'publicly_queryable' => true,
'_builtin' => false,
'public' => true,
)
);
// Sanitized key should return true.
$this->assertTrue( is_post_status_viewable( 'wp_tests_ps' ) );
$this->assertTrue( is_post_status_viewable( get_post_status_object( 'wp_tests_ps' ) ) );
// Unsanitized key should return false.
$this->assertFalse( is_post_status_viewable( 'WP_tests_ps' ) );
$this->assertFalse( is_post_status_viewable( get_post_status_object( 'WP_tests_ps' ) ) );
}
}