I would like to hide any post that has any of a certain set of custom taxonomies from public access.
For example, a post with the "Access Group" taxonomy of "Finance Department" should not appear to exist to a guest user through the WordPress front end or the REST API.
So, I have added a pre_get_posts filter to exclude these access groups from the query:
add_filter('pre_get_posts', [$page_access, 'filter_searches']);
This filter, cutting out some other bits and pieces, adds a default set of exclusions if is_admin() is false
return $query->set('tax_query', [ 'relation' => 'AND', [ 'relation' => 'OR', [ 'taxonomy' => 'access_groups', 'operator' => 'NOT EXISTS' ], [ [ 'taxonomy' => 'access_groups', 'field' => 'slug', 'terms' => $accessible_categories, 'operator' => 'IN' ] ] ] ]);
This works great via the front end. The relevant posts and pages with the given access groups are excluded from searches, archives and listings. However, when I use the REST API to do a taxonomy search (e.g. posts?tag=something) or a general search (e.g. posts?s=something), the results ignore the ?tag or ?s query strings and simply return the results as they would be if those parameters were not set.
I can confirm that the pre_get_posts filter is being run on REST queries because, for example, if I add
die() in that filter, the process dies whether accessed through a WordPress page or the wp-json endpoints.
If I dump the SQL query onto the page and navigate onto, say, /tags/secret, I can see the list of posts that are tagged with 'secret', excluding the hidden access groups, as I would expect:
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) LEFT JOIN wp_term_relationships AS tt1 ON (wp_posts.ID = tt1.object_id) WHERE 1=1 AND ( ( /* Include posts with no access group taxonomy term assigned */ NOT EXISTS ( SELECT 1 FROM wp_term_relationships INNER JOIN wp_term_taxonomy ON wp_term_taxonomy.term_taxonomy_id = wp_term_relationships.term_taxonomy_id WHERE wp_term_taxonomy.taxonomy = 'access_groups' AND wp_term_relationships.object_id = wp_posts.ID ) /* Include posts that have an access group that can be displayed on the front end */ OR ( wp_term_relationships.term_taxonomy_id IN (31,32) ) ) /* AND the taxonomy ID is the one being searched for */ AND tt1.term_taxonomy_id IN (37) ) AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'acf-disabled' OR wp_posts.post_author = 16 AND wp_posts.post_status = 'private') GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 10;
So, despite both the REST API and the WordPress UI using the same filter, the query is working for one but not the other. Can anyone suggest why this might be and what I could do to make the same modification to REST requests? Is the WP_Query object structurally different for REST API requests as opposed to front-end requests?
I am not sure how to dump the SQL query used by the REST API, so I haven't been able to check that to see what's wrong.