wp-graphql / wp-graphql

:rocket: GraphQL API for WordPress
https://www.wpgraphql.com
GNU General Public License v3.0
3.63k stars 441 forks source link

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

Open gblicharz opened 1 year ago

gblicharz commented 1 year ago

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.

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

justlevine commented 1 year ago

Hey @gblicharz ,

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

gblicharz commented 1 year ago

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

justlevine commented 1 year ago

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 commented 1 year ago

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:

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 ) ) ) {            
psulkava commented 4 days ago

Are there any updates on this bug? I'm attempting to do the same thing, query a public custom status with WPGraphQL and it is failing.