WordPress.org

Make WordPress Core

Opened 5 years ago

Last modified 4 months ago

#28568 reopened defect (bug)

Can't get private posts/pages via WP_Query when not logged in as administrator

Reported by: mklusak Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version: 3.9
Component: Query Keywords:
Focuses: Cc:

Description

I want to use WP_Query to display content of one private page on some place in a template.

// ... theme template sidebar.php, for example
<?php
$the_query = new WP_Query( array( 'page_id' => 25, 'post_status' => 'private'  )); // page #25 is set to "private"
if ($the_query->have_posts())
	{
	// ... nope, no posts for you today
	while ($the_query->have_posts())
		{
		$the_query->the_post();
		the_content();
		} 
	wp_reset_postdata();
	}
?>

But it only works when I am logged in as administrator.

When I am not logged in, and print_r() the $the_query, there is "request" index filled with:

SELECT wp_posts.* FROM wp_posts WHERE 1=1 AND wp_posts.ID = 25 AND wp_posts.post_type = 'page' AND ((wp_posts.post_status = 'private')) ORDER BY wp_posts.post_date DESC

Querying database with it directly in PHPMyAdmin, it returns one row as expected (page ID 25 row). But template loop does not anything. "Posts" index in $the_query is empty.

I have expected WP_Query works like "just give me anything I want (defined by the arguments)" ... and I want page with ID 25.

Change History (9)

#1 @ocean90
5 years ago

  • Keywords close reporter-feedback added

The post status private should be understood as as non-public-stuff. Why is the page private if it's public?

#2 follow-up: @mklusak
5 years ago

  • Keywords reporter-feedback removed

Well, I have my reasons. For example I don't want that page URL to be indexable, but I want its content displayed somewhere...

This is "magic" behaviour for me. I want exact post/page... using the most proper WP function... and I don't get it. Even when I know it is private and I am declaring it in arguments array.

If this is a feature, then it should be mentioned in Codex: "You can't use post_status => private on front-end, it works only for logged in users".

#3 in reply to: ↑ 2 @SergeyBiryukov
5 years ago

Replying to mklusak:

If this is a feature, then it should be mentioned in Codex: "You can't use post_status => private on front-end, it works only for logged in users".

It is a feature, see the code in WP_Query: tags/3.9.1/src/wp-includes/query.php#L3307.

And it's actually mentioned in WP_Query docs: ('private' – not visible to users who are not logged in).

#4 @ocean90
5 years ago

  • Keywords close removed
  • Milestone Awaiting Review deleted
  • Resolution set to invalid
  • Status changed from new to closed

#5 @mklusak
5 years ago

Well, thank you for your responses. It's obvious that the Codex line is a little bit misleading ... because of hours of my time spent by googling any solution and composing Trac ticket. I have read it as "post_status => private - you can get programmatically posts, which URL access is restricted for not logged in users (--> not visible to users who are not logged in)" ... but as I see now, I was wrong. Well, now I am smarter.

#6 @marco.marsala
14 months ago

I came across this thread because I had the same issue.

I noticed that you can't get a private post by id if not logged, but it works if you remove 'page_id' argument; specifying only 'post_status' = 'private' without 'page_id', you'll get all posts including private ones.

Feature or (security) bug?

Last edited 14 months ago by marco.marsala (previous) (diff)

#7 @dnaber-de
14 months ago

  • Resolution invalid deleted
  • Status changed from closed to reopened

For everyone who stumble over this bug there is a solution to bypass:

<?php
$posts = [];
$query = new \WP_Query();
$fetchPosts = function ( array $postResults, \WP_Query $currentQuery ) use ( &$posts, $query ): array {

        if ( $currentQuery === $query ) {
                $posts = $postResults;
        }
        return $postResults;
};
add_filter( 'posts_results', $fetchPosts, 10, 2 );
$query->query( ['post_status' => 'any'] );
remove_filter( 'posts_results', $fetchPosts, 10 );

@ocean90 Maybe you want to rethink your decision. I know of no reason why WP_Query (which is the only API to fetch posts from database) should depend on global state whether a user is logged in or not. That makes no sense. These security «protections» inside WP_Query::get_posts() should at least only act if it's the main query or should be bypassed by the suppress_filters parameter.

#8 @stracker.phil
4 months ago

I agree with @dnaber-de and @mklusak:

There's an inconsistency between get_post() and get_posts() which is confusing:

$private_id = 123;
$item1 = get_post( $private_id );
$item2 = get_posts( [ 'p' => $private_id, 'post_type' => 'page' ] );

// $item1 is a WP_Post object of the private post. The private-state is not checked here.
// $item2 is an empty array, because I am not logged in.

There should be a new option for get_posts like ignore_private => true.

#9 @SergeyBiryukov
4 months ago

  • Milestone set to Awaiting Review
Note: See TracTickets for help on using tickets.