Skip to content

Conversation

@gziolo
Copy link
Member

@gziolo gziolo commented Oct 22, 2025

Closes #112 – action names changes, and the strategy got unified.
Closes #97 – PHPStan was removed as WP core doesn't support it.

I did an initial pass for syncing back all the changes applied to WordPress Core:

We need to verify the linting errors reported as some of them might be valid. For example:

  • 0c7f621 – this fixes a potential issue reported where the instance could be null.
  • 76f1fad – PHPCS reported wrong order of types, as it should be boolean|null. It's worth updating in WordPress core, too.

There are several linting errors that are expected in WordPress core, so we have to disable them. However, the one about best practices for escaping translations was a bit surprising. It still has to be confirmed, as it isn't enforced in WP core and I simply followed feedback from review when updated esc_html__() to __(). It's mostly for _doing_it_wrong() calls, so maybe that's perfectly fine.

As part of the effort, I had to redo WP_REST_Abilities_Init, which is file-specific only to this repository. I added a check to ensure that the controllers are never re-declared or re-registered if they were previously registered in WordPress core.

@gziolo gziolo requested a review from emdashcodes October 22, 2025 17:15
@codecov
Copy link

codecov bot commented Oct 22, 2025

Codecov Report

❌ Patch coverage is 87.32782% with 46 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.57%. Comparing base (9da857e) to head (a0355d9).
⚠️ Report is 1 commits behind head on trunk.

Files with missing lines Patch % Lines
includes/abilities-api/class-wp-ability.php 72.72% 15 Missing ⚠️
...ities-api/class-wp-ability-categories-registry.php 77.77% 14 Missing ⚠️
includes/bootstrap.php 0.00% 7 Missing ⚠️
...udes/abilities-api/class-wp-abilities-registry.php 91.17% 3 Missing ⚠️
...cludes/abilities-api/class-wp-ability-category.php 76.92% 3 Missing ⚠️
...ints/class-wp-rest-abilities-v1-run-controller.php 94.00% 3 Missing ⚠️
includes/rest-api/class-wp-rest-abilities-init.php 94.73% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##              trunk     #126      +/-   ##
============================================
- Coverage     90.59%   90.57%   -0.03%     
- Complexity      177      188      +11     
============================================
  Files            20       20              
  Lines          1489     1485       -4     
  Branches        117      119       +2     
============================================
- Hits           1349     1345       -4     
  Misses          140      140              
Flag Coverage Δ
javascript 94.31% <100.00%> (+0.10%) ⬆️
unit 89.48% <87.00%> (-0.10%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 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 22, 2025
@gziolo gziolo added [Priority] High Essential focuses for work [Type] Task Issues or PRs that have been broken down into an individual action to take labels Oct 22, 2025
@justlevine
Copy link
Contributor

@gziolo on the off chance I save you 30 seconds, here are the FQCN rules for phpcs that need to be removed . Considering the current plan is to archive, I see no point in conditionally disabling/allowlisting.

  • <rule ref="SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameInAnnotation">
    <properties>
    <!-- Support PHPStan annotations -->
    <property name="ignoredAnnotationNames" type="array">
    <element value="@phpstan-import-type" />
    <element value="@phpstan-param" />
    <element value="@phpstan-return" />
    <element value="@phpstan-template" />
    <element value="@phpstan-type" />
    <element value="@phpstan-var" />
    </property>
    </properties>
    </rule>
    <rule ref="SlevomatCodingStandard.Namespaces.FullyQualifiedExceptions" />

Copy link
Member

@JasonTheAdams JasonTheAdams left a comment

Choose a reason for hiding this comment

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

Just a few questions for you, @gziolo!

Comment on lines +57 to +75
public function register( string $slug, array $args ): ?WP_Ability_Category {
if ( $this->is_registered( $slug ) ) {
_doing_it_wrong(
__METHOD__,
/* translators: %s: Ability category slug. */
sprintf( __( 'Ability category "%s" is already registered.' ), esc_html( $slug ) ),
'6.9.0'
);
return null;
}

if ( ! preg_match( '/^[a-z0-9]+(?:-[a-z0-9]+)*$/', $slug ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Ability category slug must contain only lowercase alphanumeric characters and dashes.' ),
'6.9.0'
);
return null;
}
Copy link
Member

Choose a reason for hiding this comment

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

Are we not checking for the category init hook anymore?

Copy link
Member Author

Choose a reason for hiding this comment

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

It was moved to the wp_register_ability_category() and we switched to did_action( ‘wp_abilities_api_categories_init’ ). It works exactly the same as for abilities now.

I also raised concerns during the merge about the previous implementation. The issue is that wp_register_ability_category() internally calls get_instance(), which triggers the wp_abilities_api_categories_init action before the registry is created and returned on the first run.

This means that if someone tries to register an ability category directly—without using add_action—the code first needs to retrieve the shared registry before calling register and checking whether the action has been processed. As a result, the public method ends up unintentionally satisfying these conditions, creating unexpected behavior.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, I think I'm tracking. Let me say it back to make sure I'm following:

  • Checking here requires that the registry is instantiated (obviously), meaning get_instance() already ran which triggered the hook, therefore it had always run before this function was called
  • Moving the check to wp_register_ability_category() means the hook can be checked before the registry is tentatively instantiated

In the "WP Way" this makes sense. It's admittedly why I'm not a fan of hooks being coupled to instantiation in some way, as it makes these sort of weird conditions. The only other way I could think to do this in WP would be to either:

  1. Procedurally place the hooks somewhere in the WP bootstrapping process
  2. Add a hook to init (or somewhere) that instantiates the registries and calls the hooks

Copy link
Member Author

Choose a reason for hiding this comment

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

Both proposed solutions remove most of the benefits of the current approach because it means all abilities will get registered on every page request when this init fires (no matter where and how it gets initiated). In particular, when rendering pages for visitors, you don't want to register all abilities if they will never be consumed, which I assume is going be the default case in WordPress core. If we don't care about the impact of that registration, then we could simply use init hook as the only mechanism for ensuring the abilities and their categories are registered at the right time.

Copy link
Member

Choose a reason for hiding this comment

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

That's interesting! I didn't realize we were using subsequent hooks as a way of narrowing the registered abilities by context. I don't disagree, it just wasn't a means of "filtering" I'd considered, yet. My assumption so far was that, yes, all abilities would be registered and then subsequent filtering would occur. There are certainly trade-offs of both approaches.

Copy link
Member

Choose a reason for hiding this comment

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

Just to help me wrap my head around the merits, here. Can you give me an example of an ability that would be best registered in a specific hook context?

*/
function wp_register_ability( string $name, array $args ): ?WP_Ability {
if ( ! did_action( 'abilities_api_init' ) ) {
if ( ! did_action( 'wp_abilities_api_init' ) ) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm curious why we're using did_action() and not doing_action? Is an action considered "done" immediately after do_action() is triggered? Or after the last hook function is fired?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, https://developer.wordpress.org/reference/functions/do_action/ does two things, increments the counter for how many times the action was fired, and sets the current action name. Then it runs all added actions. did_action ensures that registration doesn’t happen too early and only when we know the registry is necessary for a given HTTP request, but the point was raised we should not prevent registering more abilities and categories later in the flow, by using doing_action.

Copy link
Member

Choose a reason for hiding this comment

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

Ahhh, thanks for clearing that up. I really should have looked up the code myself. Good to know how that works!

Personally, I'm not convinced on the merits of allowing folks to register at any point after this time, as those are the sort of moments in my WP dev history that are maddening – "wait, why isn't that available? Oh, they hook into that hook later with a priority of 500? Why!?" Not being able to assume that all abilities and categories have been initialized during... initialization is weird. Feels like an established WP quirk that we're perpetuating. 😆

Copy link
Member

Choose a reason for hiding this comment

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

I did provide similar feedback somewhere else (I believe on the Core PR) - I think allowing registration at any arbitrary point means we're being "forgiving" to developers doing it wrong, which can then lead to far more obscure problems down the line.

I think clearly defined APIs and, given WordPress's procedural nature, clearly defined registration timing are crucial to avoid more complex problems. Better tell the developer they should fix something rather than them running into a problem later that they have no idea on why it's occurring.

Copy link
Member Author

Choose a reason for hiding this comment

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

I’m happy to apply further updates. I was a bit on the clock when landing the entire API in WP core. So while I agreed with reasoning, I missed this important nuance at the time.

It’s a tiny change for production logic, but needs some solid tinkering for unit tests so they are in proper state while registering abilities and ability categories. I’ll try to sort out something tomorrow, so it’s ready before beta 3.

@gziolo
Copy link
Member Author

gziolo commented Oct 23, 2025

PHPCS and PHPStan are now good after not-matching rules were removed or ignored... One unit test to fix, and then we need to update the client to use the new REST API endpoint paths. Potentially, we also need to adjust the types to account that meta.annotations has slightly changed.

@gziolo gziolo force-pushed the update/sync-wordpress-core-trunk branch from ab066cc to 0a511e0 Compare October 23, 2025 17:23
@gziolo gziolo marked this pull request as ready for review October 23, 2025 17:24
@github-actions
Copy link

github-actions bot commented Oct 23, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

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: JasonTheAdams <jason_the_adams@git.wordpress.org>
Co-authored-by: jorgefilipecosta <jorgefilipecosta@git.wordpress.org>
Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: galatanovidiu <ovidiu-galatan@git.wordpress.org>

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

@gziolo
Copy link
Member Author

gziolo commented Oct 23, 2025

It should be mostly ready to review.

The only remaining task is to mirror the HTTP method selection when executing the ability, and cover that with unit tests

Client:

const method = !! ability.meta?.annotations?.readonly ? 'GET' : 'POST';

Server:

$expected_method = 'POST';
if ( ! empty( $annotations['readonly'] ) ) {
$expected_method = 'GET';
} elseif ( ! empty( $annotations['destructive'] ) && ! empty( $annotations['idempotent'] ) ) {
$expected_method = 'DELETE';
}

@gziolo
Copy link
Member Author

gziolo commented Oct 23, 2025

It tests correctly when using the client-side API with core abilities:

Screenshot 2025-10-23 at 20 42 57

@gziolo
Copy link
Member Author

gziolo commented Oct 23, 2025

It turned out to be a simple task to add support for DELETE HTTP method also on the client with b70eec3.

pento pushed a commit to WordPress/wordpress-develop that referenced this pull request Oct 24, 2025
Discovered when syncing code back to Abilities API repository in WordPress/abilities-api#126.

Follow-up to [61032].
See #64098.



git-svn-id: https://develop.svn.wordpress.org/trunk@61059 602fd350-edb4-49c9-b593-d223f7449a82
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Oct 24, 2025
Discovered when syncing code back to Abilities API repository in WordPress/abilities-api#126.

Follow-up to [61032].
See #64098.


Built from https://develop.svn.wordpress.org/trunk@61059


git-svn-id: http://core.svn.wordpress.org/trunk@60395 1a063a9b-81f0-0310-95a4-ce76da25c4cd
github-actions bot pushed a commit to platformsh/wordpress-performance that referenced this pull request Oct 24, 2025
Discovered when syncing code back to Abilities API repository in WordPress/abilities-api#126.

Follow-up to [61032].
See #64098.


Built from https://develop.svn.wordpress.org/trunk@61059


git-svn-id: https://core.svn.wordpress.org/trunk@60395 1a063a9b-81f0-0310-95a4-ce76da25c4cd
@gziolo gziolo added this to the WP 6.9 milestone Oct 24, 2025
@gziolo gziolo moved this to Needs review in WordPress Abilities API: planning Oct 24, 2025
Copy link
Member

@jorgefilipecosta jorgefilipecosta left a comment

Choose a reason for hiding this comment

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

I left multiple comments, but all of them seem minor, and this is a sync so we may need to reflect what is in core and then update things on both places.
One thing I think we should consolidate is the since tags, on if we should use core version or plugin version.

Things worked well on the smoke tests I the code seems good 👍

* }
*
* @see WP_Abilities_Category_Registry::get_all_registered()
* @since 6.9.0
Copy link
Member

Choose a reason for hiding this comment

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

On since tags we are using 6.9.0, everywhere, should we be using the plugin version and not the core version?

Copy link
Member Author

Choose a reason for hiding this comment

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

I would keep files the same as in WP core, as we it's a direct copy. In the future, WP core becomes the source of truth and we would only continue keeping this repo up to date so folks can polyfill Abilities API for WP 6.8 and older if they want to start using it but their support policy includes a range of WP major versions. It's been the case for Woo that is actively testing usage of this API.

@gziolo
Copy link
Member Author

gziolo commented Oct 27, 2025

@jorgefilipecosta, good catch with a few inconsistencies in WP core. I will update them here before landing, and then patch in WP core.

@gziolo
Copy link
Member Author

gziolo commented Oct 27, 2025

I updated a patch against WP core to address the feedback first:

pento pushed a commit to WordPress/wordpress-develop that referenced this pull request Oct 27, 2025
This aligns with how translations are handled across all places in the Abilities API codebase. It addresses the feedback raised during syncing back changes to Abilities API repository with WordPress/abilities-api#126.

Developed in #10424.

Follow-up [61032].
Props gziolo, jorgefilipecosta.
See #64098.



git-svn-id: https://develop.svn.wordpress.org/trunk@61071 602fd350-edb4-49c9-b593-d223f7449a82
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Oct 27, 2025
This aligns with how translations are handled across all places in the Abilities API codebase. It addresses the feedback raised during syncing back changes to Abilities API repository with WordPress/abilities-api#126.

Developed in WordPress/wordpress-develop#10424.

Follow-up [61032].
Props gziolo, jorgefilipecosta.
See #64098.


Built from https://develop.svn.wordpress.org/trunk@61071


git-svn-id: http://core.svn.wordpress.org/trunk@60407 1a063a9b-81f0-0310-95a4-ce76da25c4cd
github-actions bot pushed a commit to platformsh/wordpress-performance that referenced this pull request Oct 27, 2025
This aligns with how translations are handled across all places in the Abilities API codebase. It addresses the feedback raised during syncing back changes to Abilities API repository with WordPress/abilities-api#126.

Developed in WordPress/wordpress-develop#10424.

Follow-up [61032].
Props gziolo, jorgefilipecosta.
See #64098.


Built from https://develop.svn.wordpress.org/trunk@61071


git-svn-id: https://core.svn.wordpress.org/trunk@60407 1a063a9b-81f0-0310-95a4-ce76da25c4cd
@gziolo gziolo enabled auto-merge (squash) October 27, 2025 12:02
@gziolo gziolo merged commit 4513526 into trunk Oct 27, 2025
18 of 20 checks passed
@gziolo gziolo deleted the update/sync-wordpress-core-trunk branch October 27, 2025 12:02
@github-project-automation github-project-automation bot moved this from Needs review to Done in WordPress Abilities API: planning Oct 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Priority] High Essential focuses for work [Type] Task Issues or PRs that have been broken down into an individual action to take

Projects

Development

Successfully merging this pull request may close these issues.

Align doing_action() and did_action() usage for category and ability registration in the Abilities API

6 participants