Skip to content

Abilities: Sync core/content-query with WordPress core - #1208

Merged
dkotter merged 9 commits into
developfrom
update/sync-content-query-with-core
Oct 27, 2026
Merged

dkotter merged 9 commits into
developfrom
update/sync-content-query-with-core

Conversation

@gziolo

@gziolo gziolo commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

What?

See #66268, r64388, r64680, r64688.

This PR syncs the core/content-query ability with the version that landed in WordPress core. The plugin acts as a polyfill until WordPress 7.2 is out. It also removes the deprecated core/read-content alias.

Why?

The ability was first built in this plugin (#739) and then moved to WordPress core. During the core review, the input and output shape changed a lot. The plugin should behave the same as core, so code written for the plugin keeps working on WordPress 7.2.

The old alias cannot keep the old contract after these changes, so it is removed. This is the same as what #1206 does for core/read-settings and #1207 does for core/read-users.

How?

The PR has four main commits:

  1. Sync with core. includes/Abilities/Content/Content.php is now a copy of the core class WP_Abilities_Content. Each difference from core is marked with a // Plugin: comment: the init() and register_category() methods, unregistering the copy from core, an explicit show_in_rest, and the ai text domain. A few phpcs:ignore and @phpstan-ignore comments are needed for the plugin's stricter rules.
  2. Remove the alias. core/read-content is no longer registered.
  3. Sync the author filter. Core r64680 replaced the author_slug filter with an author object. The plugin now does the same.
  4. Sync author visibility. Core r64688 shows the author name and slug only to users who may see that author. The plugin now does the same.

These are breaking changes for callers:

  • Input: it is now { where, fields, pagination }. where requires the post type in type, and takes ids, slugs, status, author, and parent. author is an object with the id or the slug that the author field of a post returns. The old root-level id, post_type, slug, include, page, and per_page are removed.
  • Output: it is always { items, pagination: { total, total_pages } }. A single post is no longer returned as a bare object.
  • Fields: fields are nested, for example title.rendered instead of title_rendered, and type instead of post_type. A field such as title returns all of its sub-fields.
  • Errors: a filter that cannot be used is rejected, not ignored. A page after the last one returns a 404 error. An author slug that names no user returns no posts.
  • Author: the author name and slug are null unless the current user may see the author. This is the author, users who can edit them or list users, and users who can read one of their published posts. The author.slug filter follows the same rule.
  • Label: the label is now "Query Content".

Other changes:

  • show_in_abilities polyfill: as in core, only true exposes a post type. The polyfill now stores any other value as false. It still stands down once core declares the flag.
  • ai/get-post-details: the deprecation hint now points to core/content-query with where.ids.

Tests:

  • ContentTest.php is ported from the core test wpRegisterCoreContentAbility.php. It also keeps a test that only the plugin needs: the plugin replaces a copy core already registered.
  • Content_RestTest.php is new. It is ported from the core test wpRestAbilitiesContentController.php and runs the ability through the REST run endpoint.
  • The e2e specs for the ability and for the Custom Abilities experiment now use the new input and output.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Comparing the core commit with the plugin, porting the code and tests, updating the e2e specs, and running the checks. I reviewed the diff and the test results.

Testing Instructions

  1. Run npm run test:php -- --filter Content.
  2. Run the multisite tests: npx wp-env --config=.wp-env.test.json run cli --env-cwd=wp-content/plugins/ai bash -c "WP_MULTISITE=1 vendor/bin/phpunit -c phpunit.xml.dist --filter Content".
  3. Reactivate the plugin with npx wp-env --config=.wp-env.test.json run cli wp plugin activate ai. Then run npx wp-scripts test-playwright --config tests/e2e/playwright.config.ts tests/e2e/specs/abilities/core-content-query.spec.js tests/e2e/specs/experiments/custom-abilities.spec.js.
  4. Manual check: enable the Custom Abilities experiment. Then run core/content-query with { "where": { "type": "post" }, "fields": [ "id", "title.rendered" ] } and check that the result has items and pagination.

Changelog Entry

Changed - The core/content-query ability now matches WordPress core. The input is now where, fields, and pagination, and the output is always items and pagination. Posts are looked up through the ids or slugs lists in where, which also requires the post type in type. Posts are filtered by author through where.author, with the author id or slug. Fields are nested, such as title.rendered. A filter that cannot be used is rejected, and a page after the last one returns an error. The author name and slug are only returned to users who may see the author. Only a show_in_abilities value of true exposes a post type.

Removed - The deprecated core/read-content ability alias. Use core/content-query with the new where, fields, and pagination input. The alias could not keep the old input and response shape after the sync with WordPress core.

🤖 Generated with Claude Code

Open WordPress Playground Preview

Apply the changes that shipped with the `core/content-query` ability in
WordPress core (r64388, Trac #66268):

- The input has three members: `where` selects the posts, `fields` the
  fields returned for each post, and `pagination` the page.
- `where` requires the post type in `type`, and filters by `ids`,
  `slugs`, `status`, `author_slug`, or `parent`. The
  single-post lookups by `id` and by post type and `slug` are removed.
- The output always holds the posts in `items` and the totals in
  `pagination`.
- Fields are nested, such as `title.rendered` and `content.raw`, and a
  field such as `title` returns all of its sub-fields.
- Filters that cannot be honored are rejected instead of dropped, and a
  page past the last one returns a 404 error.
- Only a `show_in_abilities` value of `true` exposes a post type. The
  polyfill stores any other value as `false`, as core does.
- The REST run endpoint tests from core are ported as well.
The `core/read-content` alias can no longer keep the old input and output
contract after syncing `core/content-query` with WordPress core, so it is
removed. Use `core/content-query` with the new `where`, `fields`, and
`pagination` input instead.
@gziolo
gziolo requested a review from a team October 19, 2026 10:19
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: jeffpaul <jeffpaul@git.wordpress.org>
Co-authored-by: dkotter <dkotter@git.wordpress.org>
Co-authored-by: jorgefilipecosta <jorgefilipecosta@git.wordpress.org>
Co-authored-by: saarnilauri <laurisaarni@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

@codecov

codecov Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 84.68%. Comparing base (6d53858) to head (dcdbe47).

Files with missing lines Patch % Lines
includes/Abilities/Show_In_Abilities.php 90.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             develop    #1208      +/-   ##
=============================================
+ Coverage      84.41%   84.68%   +0.27%     
- Complexity      3709     3718       +9     
=============================================
  Files            149      149              
  Lines          14139    14239     +100     
=============================================
+ Hits           11936    12059     +123     
+ Misses          2203     2180      -23     
Flag Coverage Δ
unit 84.68% <90.90%> (+0.27%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gziolo gziolo self-assigned this Oct 19, 2026
@gziolo gziolo added this to the 1.5.0 milestone Oct 19, 2026
@jeffpaul jeffpaul moved this from Triage to Needs review in WordPress AI Roadmap Oct 19, 2026
dkotter
dkotter previously approved these changes Oct 19, 2026
WordPress core replaced the `where.author_slug` filter with a
`where.author` object in r64680. The object takes the `id` or the
`slug` that the `author` field of a post returns.

A slug that names no user, or an `id` and a `slug` that name different
users, now match no post instead of returning an error.

The tests are ported from the updated core test.

See WordPress/wordpress-develop@e29e51d.
@gziolo

gziolo commented Oct 23, 2026 •

Copy link
Copy Markdown
Member Author

I pushed one more commit to keep up with core r64680. The author_slug filter is now an author object that takes an id or a slug, the same as the author field returns. An author that matches no user now returns no posts instead of an error. I also ported the updated tests from core and refreshed the PR description.

WordPress core now returns the name and slug of a post author only to
users who may see that author, in r64688. These users are the author,
users who can edit them or list users, and users who can read one of
their published posts in a post type exposed to abilities.

The `author.slug` filter follows the same rule, so it cannot confirm a
user that the `author` field hides.

The tests are ported from the updated core test.

See WordPress/wordpress-develop@38a613a.
@gziolo

gziolo commented Oct 23, 2026

Copy link
Copy Markdown
Member Author

I pushed another commit to keep up with core r64688. The author name and slug of a post are now shown only to users who may see that author, and the author slug filter follows the same rule. I also merged the latest develop, ported the new tests from core, and refreshed the PR description.

@jorgefilipecosta jorgefilipecosta left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@jeffpaul
jeffpaul enabled auto-merge (squash) October 23, 2026 13:21
@saarnilauri

Copy link
Copy Markdown
Contributor

@gziolo I did security audit for this PR and also other abilitys PRs that sync the abilities to match up with upcoming core abilities.

No high level findings in any of the PRs.

The only concern Mythos raised was that all three classes unconditionally unregister the core-provided ability and register their own copy at wp_abilities_api_init priority 11:

includes/Abilities/Settings/Settings.php:92
includes/Abilities/Users/Users.php:148
includes/Abilities/Content/Content.php:205

Once WordPress 7.2 ships these abilities, any 7.2.x security release that patches them will be silently overridden on sites running this plugin until the plugin re-syncs. The same mechanism also means a stale plugin on 7.3+ would pin an older contract.

Recommendation: Gate the override on a version check (for example, only override when $wp_version is below the core revision the class was synced from, or when core does not register the ability at all), or at minimum document this in the release notes and keep a tracked "synced from xxx" marker in each class so the sync status is auditable.

This is not a vulnerability in the PRs themselves, but it is the most likely way these PRs lead to a real exposure later.

@saarnilauri

Copy link
Copy Markdown
Contributor

The audit LLM also noticed a small author visibility inconsistency. core/content-query reveals an author's name and slug if count_user_posts() over all abilities-exposed post types is non-zero, but that counts published posts in non-viewable CPTs that the caller cannot actually read. core/users-query correctly restricts to viewable post types. This only bites when a third-party plugin exposes a non-public CPT. Should be fixed upstream too.

@gziolo

gziolo commented Oct 26, 2026 •

Copy link
Copy Markdown
Member Author

Thanks for the audit, @saarnilauri! I confirmed both findings on the latest branch.

@jorgefilipecosta, could you please check them too? I will leave both decisions to you.

  1. Core abilities get replaced. Valid. One option is to register the plugin's copies only when WordPress core does not register them. This would cover Abilities: Sync core/settings-get with WordPress core #1206, Abilities: Sync core/users-query with WordPress core #1207 and this PR. If you agree, I'm happy to open a follow-up PR.
  2. Author visibility. Valid, and the same code is in WordPress core. The count includes published posts in post types that are not publicly viewable. Should we fix this in core?

I'd like to land this PR as is, and sync any core fix to the plugin right after it lands.

@gziolo
gziolo requested a review from dkotter October 27, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants