Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post statuses that have been allowed to be viewable by anonymous users not returned in GraphQL #2819

Open
2 of 3 tasks
gblicharz opened this issue May 16, 2023 · 4 comments
Open
2 of 3 tasks
Labels
Component: Connections Issues related to connections Status: 🚀 Actionable Issues that have been curated, have enough info to take action, and are ready to be worked on Type: Bug Something isn't working

Comments

@gblicharz
Copy link

gblicharz commented May 16, 2023

Description

We have extended the functionality of our WordPress site to allow posts with a status of "future" to be viewable to anonymous users. Making this change allows the posts with the "future" status to be viewable on our website, as well as be available via the JSON API.

This override is not respected by a graphQL query where the "stati" is specified, unless I toggle the "logged in user" in the WordPress, the GraphiQL IDE. Then the correct data is returned.

The WPGraphQL query should respect the current permissions/settings that allow posts to be visible to anonymous users similar to the logic in the WordPress front-end or JSON APIs.

Steps to reproduce

  1. Create a new post that will be published at a future date
  2. Take note of the post ID after saving
  3. Update the functions.php file for the active theme with the following:
function change_future_post_status_permissions() {
  global  $wp_post_statuses;
  $wp_post_statuses['future']->public = true;
}
add_action('init','change_future_post_status_permissions');
  1. Verify that the post is viewable on the website
  2. Verify that the post is viewable via the JSON API path: https://wfmt.lndo.site/wp-json/wp/v2/posts/[post_id], and that the "status" attribute has a value of "future".
  3. Go to the GraphiQL IDE and create the following query:
  posts(first: 1000, where: {stati: FUTURE}) {
    nodes {
      title
      postId
    }
  }
}

Actual results:

  "data": {
    "posts": {
      "nodes": []
    }
  }
}

Expected results:

  "data": {
    "posts": {
      "nodes": [
        {
          "postId": 159489,
          "status": "future"
        }      
     ]
    }
  }
}

Clicking the "Switch to execute as the logged-in user" button in the GraphiQL IDE and re-running the query produces the expected results.

Additional context

No response

WPGraphQL Version

1.13.7

WordPress Version

6.2

PHP Version

8.4.33

Additional enviornment details

No response

Please confirm that you have searched existing issues in the repo.

  • Yes

Please confirm that you have disabled ALL plugins except for WPGraphQL.

  • Yes
  • My issue is with compatibility with a specific WordPress plugin, and I have listed all my installed plugins (and version info) above.
@justlevine
Copy link
Collaborator

Hey @gblicharz ,

Does the issue persist using the latest version of WPGraphQL (v1.14.3)?

@gblicharz
Copy link
Author

@justlevine - Yes, I confirmed it is still an issue in the latest version - 1.14.3

@justlevine
Copy link
Collaborator

Thanks for the confirmation.

Looks like the PostObjectConnectionResolver has some hard-coded logic around sanitizing the provided Post statuses, which is whats causing the issue.

That can probably be worked on (imo its a great spot for a filter), but in the interim you can use the graphql_map_input_fields_to_wp_query filter as so (untested, written from my phone so check for typos):

add_filter
  'graphql_map_input_fields_to_wp_query'
  function( array $wp_query_args, $_where_args, $_source, array $graphql_args ) : array {
    // Skip if we arent setting a status.
    if ( empty( $graphql_args['where']['status'] ) && empty( $graphql_args['where']['stati'] ) ) {
      return $wp_query_args;
    }
    
    // The 'status' arg is a string, lets make it an array.
    $stati = ! empty( $graphql_args['where']['status'] ) ? [ $graphql_args['where']['status'] ] : [];
    
    // statusescan be a string or an array.
    if ( ! empty( $graphql_args['where']['stati'] ) ) {
      $stati = array_merge(
        $stati,
        is_array( $graphql_args['where']['stati'] ) ? $graphql_args['where']['stati'] : [ $graphql_args['where']['stati'],
      );
    }
    
    // Remove disallowed statuses, you need to define an allow list of _all_ statuses.
    foreach ( $stati as $index => $status ) {
      if ( ! in_array( $stati, $_MY_ALLOWED_STATI, true ) ) { 
      unset( $stati[ $index ] );
    }
    
    // Set the WP_Query arg.
    $wp_query_args['post_status'] = $stati;
    
    return $wp_query_args;
  },
  10,
  4
);

@gblicharz
Copy link
Author

gblicharz commented May 16, 2023

Thanks @justlevine. I corrected the function:

add_filter ('graphql_map_input_fields_to_wp_query', function ( array $wp_query_args, $_where_args, $_source, array $graphql_args ) {
  $_MY_ALLOWED_STATI = ['publish', 'future'];
  // Skip if we arent setting a status.
  if ( empty( $graphql_args['where']['status'] ) && empty( $graphql_args['where']['stati'] ) ) {
    return $wp_query_args;
  }
  
  // The 'status' arg is a string, lets make it an array.
  $stati = ! empty( $graphql_args['where']['status'] ) ? [ $graphql_args['where']['status'] ] : [];
  
  // statusescan be a string or an array.
  if ( ! empty( $graphql_args['where']['stati'] ) ) {
    $stati = array_merge(
      $stati,
      is_array( $graphql_args['where']['stati'] ) ? $graphql_args['where']['stati'] : $graphql_args['where']['stati'] );
  }
  
  // Remove disallowed statuses, you need to define an allow list of _all_ statuses.
  foreach ( $stati as $index => $status ) {
    if ( ! in_array( $status, $_MY_ALLOWED_STATI, true ) ) { 
      unset( $stati[ $index ] );
    }
  }
  
  // Set the WP_Query arg.
  $wp_query_args['post_status'] = $stati;
  return $wp_query_args;
},  10,  4);

But I'm still not seeing the results returned.

Looking at the PostObjectConnectionResolver.php and Post.php code, there are several other places where 'future' posts are being prevented. Specifically, in these functions:

  • sanitize_post_stati, where $allowed_statuses is being set to null because of this code:
if ( ! isset( $post_type_object->cap->edit_posts ) || ! current_user_can( $post_type_object->cap->edit_posts ) ) {
	return null;
}

This results in "$this->should_execute" being set to false.

  • get_restricted_cap, where 'future' is hard-coded as a status that sets 'edit_others_posts'
  • is_post_private, where it returns true because of this code:
/**
* If the status is NOT publish and the user does NOT have capabilities to edit posts,
* consider the post private.
*/
if ( ! isset( $post_type_object->cap->edit_posts ) || ! current_user_can( $post_type_object->cap->edit_posts ) ) {
	return true;
}

For the is_post_private function, the addition of testing for the $post_type_object->public being "false" is needed, but I'm not sure this is the correct solution for that specific function:

if (! $post_type_object->public && ( ! isset( $post_type_object->cap->edit_posts ) || ! current_user_can( $post_type_object->cap->edit_posts ) ) ) {			

@jasonbahl jasonbahl added Type: Bug Something isn't working Status: 🚀 Actionable Issues that have been curated, have enough info to take action, and are ready to be worked on Component: Connections Issues related to connections labels Sep 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Component: Connections Issues related to connections Status: 🚀 Actionable Issues that have been curated, have enough info to take action, and are ready to be worked on Type: Bug Something isn't working
Projects
Status: 🎯Actionable
Development

No branches or pull requests

3 participants