This repository was archived by the owner on Feb 5, 2026. It is now read-only.

Propose filters to use with the registered ability - #37

Closed
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values
Closed

Propose filters to use with the registered ability#37
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values

Conversation

@gziolo

@gziologziolo commented Aug 21, 2025

Copy link
Copy Markdown
Member

Closes#39.

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result.
  • Includes comprehensive test coverage for all filter integration scenarios.
  • Updates phpcs configuration to accommodate the new hook prefixes.

Example for how to revoke access to the ability:

$filter_cb = staticfunction ( $permission, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$permission;
}
returnfalse;
};
add_filter( 'ability_permission_result', $filter_cb, 10, 2 );

Example for how to change the output value:

$filter_cb = staticfunction ( $result, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$result;
}
return'modified-' . $result;
};
add_filter( 'ability_execute_result', $filter_cb, 10, 2 );

Testing instructions

6 new unit tests were added to cover possible scenarios. You can run them with

npm run test:php

@gziologziolo self-assigned this Aug 21, 2025
@gziologziolo added [Type] Enhancement New feature or request [Status] In Progress Assigned work scheduled labels Aug 21, 2025
@gziolo
gzioloforce-pushed the update/ability-filter-values branch 2 times, most recently from 80c0d3f to 7dca275CompareAugust 21, 2025 18:37
@gziolo
gziolo requested a review from CopilotAugust 22, 2025 04:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result
  • Includes comprehensive test coverage for all filter integration scenarios
  • Updates phpcs configuration to accommodate the new hook prefixes

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
includes/abilities-api/class-wp-ability.phpImplements filter integration in schema getters, permission checks, and execution methods
tests/unit/abilities-api/wpAbilityFilters.phpAdds comprehensive test suite for all filter functionality
phpcs.xml.distUpdates coding standards configuration to allow new hook prefixes
includes/abilities-api.phpAdds phpcs disable comment for global naming conventions
tests/bootstrap.phpRemoves unnecessary phpcs disable comment
tests/unit/rest-api/*.phpAdds descriptive comments to test files
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdds descriptive comment to test file

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadphpcs.xml.dist Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
@gziologziolo removed the [Status] In Progress Assigned work scheduled label Aug 22, 2025
@gziolo
gziolo marked this pull request as ready for review August 22, 2025 05:26
@gziolo

Copy link
Copy Markdown
MemberAuthor

@jonathanbossenger, this is something to keep on the radar for inclusion in the docs in case we agree that this level of extensibility is expected.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo is there an expectation for people to be calling public WP_Ability methods more than once per lifecycle?

My two concerns are

  1. Because this breaks SRP, folks extending the class (e.g. dev!: require string $name for registration and introduce ability_class arg #21 ) now either need more boilerplate (or not implement the hooks).
  2. The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

@gziolo

Copy link
Copy Markdown
MemberAuthor

The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

That's the reality in the WordPress land:

"With the great power (of extensibility) comes great responsibility"

Some recent examples:

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L600-L617

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L619-L636

Because this breaks SRP, folks extending the class (e.g. #21 ) now either need more boilerplate (or not implement the hooks).

There are different audiences here:

  • developers implementing abilities
  • plugin developers and site admins who want to customize these abilities

If the author of the ability uses a custom class that extends WP_Ability, it's their responsibility to use parent methods to keep these hooks. However, they might not want that for some reason that we can't anticipate. This works both ways, so I think it's fine to leave these considerations to implementers. WordPress core will never register abilities that skip these hooks, making things unpredictable.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo my question wasn't critique, I was looking for some direction for code review 🙇


I don't think we disagree in premise. The nuance here is our extremely short timetable before merging. To use your example of get_variations()

Just because we don't have time to evolve it holistically doesn't mean that we can't design our initial API with intention and around specific use cases. And if we don't want to wait to merge these until we have a specific use case from e.g. MCP Adapter or AI Experiments, then we should at least take a bit here to consider the use cases/ code flows for these proposed hooks (instead of designing code to conform with non-rubberducked hooks after the fact).


So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

  • Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).
  • Are the methods that contain these hooks the most likely going to be overloaded.
  • And conversely, are there hooks that we want to be triggered every time people use, or consider it a good think if they need to actively take steps to reinclude the hook in their downstream extends *.

@gziolo

Copy link
Copy Markdown
MemberAuthor

So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

I'm mostly concerned with this proposal about making it possible to customize the abilities that will get registered through WordPress Core.

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

In the frontend context, WordPress core won't even process individual abilities because they are never consumed by default, so only the abilitis_api_init hooks get registered.

Inside REST API context, they should be called once per ability, depending on the endpoint type:

  • get all would call input and out schema hooks to produce the result
  • get single would call input and output schema hooks to produce the result
  • execute, would call all 4 hooks, it could be that input and output schema hooks, when defined, would get called twice - during permissions check and during the execution

WP Admin is less predictable at this point, as we don't know how wide the usage will become. That said, WP hooks are everywhere in the codebase. I would be surprised learning that the abilities registry would have a larger impact than the rest of the codebase. What's the part that worries you the most?

@justlevine

Copy link
Copy Markdown
Contributor

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

The one that prompted my question is $ability->has_permissions() e.g

add_filter( 'ability_permission_result', staticfunction( $result ) {
if ( $resultinstanceof \WP_Error ) {
return$result;
}
return MyCustom::check_user_permissions_in_enterprise_sso( wp_get_current_user() );
}
... add_filter( 'ability_execute_result', staticfunction ( $result, string$ability_name, array$input ) {
if ( 'my/complex-tool-with-state-and-context' !== $ability_name ) {
return$result;
}
$prev_ability = wp_get_ability( $input['prevAbilityId'] );
if ( $prev_ability->has_permissions() ) {
...
}
}

What's the part that worries you the most?

Solely the timeline to core merge has us theorycrafting these, nothing blocking.

The combination of (a.) public getters that (b.) don't just "get" but break SRP to do something, while (c.) the filters run on each call instead of caching and (d) we already have people hankering to extends WP_Ability makes this class a recipe for refactor (IMO), but it's hard to justify the extra complexity of "doing it right" ( e.g. separate prepare_*() methods, applying filters once etc ) in an initial release.

@gziolo

Copy link
Copy Markdown
MemberAuthor

All valid concerns. We don’t have to rush introducing these filters, and wait for feedback from folks what’s limiting them.

Aside, caching and filters is often incompatible in WordPress reality because there are two primary challenges:

  • filters can be added or removed at different stages of page rendering, I saw many examples where filter gets added before executing the function and immediately removed after
  • result from filters and functions are not guaranteed to be idempotent as they often depend on thr current global state

@gziolo
gzioloforce-pushed the update/ability-filter-values branch from 8c93454 to 614aae6CompareAugust 27, 2025 12:02
@gziolo

Copy link
Copy Markdown
MemberAuthor

This is possible in two different ways:

Let's close this one as not planned for now.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Provide extensibility for individual abilities

3 participants

@gziolo@justlevine
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

Propose filters to use with the registered ability - #37

Closed
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values
Closed

Propose filters to use with the registered ability#37
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values

Conversation

@gziolo

@gziologziolo commented Aug 21, 2025

Copy link
Copy Markdown
Member

Closes#39.

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result.
  • Includes comprehensive test coverage for all filter integration scenarios.
  • Updates phpcs configuration to accommodate the new hook prefixes.

Example for how to revoke access to the ability:

$filter_cb = staticfunction ( $permission, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$permission;
}
returnfalse;
};
add_filter( 'ability_permission_result', $filter_cb, 10, 2 );

Example for how to change the output value:

$filter_cb = staticfunction ( $result, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$result;
}
return'modified-' . $result;
};
add_filter( 'ability_execute_result', $filter_cb, 10, 2 );

Testing instructions

6 new unit tests were added to cover possible scenarios. You can run them with

npm run test:php

@gziologziolo self-assigned this Aug 21, 2025
@gziologziolo added [Type] Enhancement New feature or request [Status] In Progress Assigned work scheduled labels Aug 21, 2025
@gziolo
gzioloforce-pushed the update/ability-filter-values branch 2 times, most recently from 80c0d3f to 7dca275CompareAugust 21, 2025 18:37
@gziolo
gziolo requested a review from CopilotAugust 22, 2025 04:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result
  • Includes comprehensive test coverage for all filter integration scenarios
  • Updates phpcs configuration to accommodate the new hook prefixes

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
includes/abilities-api/class-wp-ability.phpImplements filter integration in schema getters, permission checks, and execution methods
tests/unit/abilities-api/wpAbilityFilters.phpAdds comprehensive test suite for all filter functionality
phpcs.xml.distUpdates coding standards configuration to allow new hook prefixes
includes/abilities-api.phpAdds phpcs disable comment for global naming conventions
tests/bootstrap.phpRemoves unnecessary phpcs disable comment
tests/unit/rest-api/*.phpAdds descriptive comments to test files
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdds descriptive comment to test file

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadphpcs.xml.dist Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
@gziologziolo removed the [Status] In Progress Assigned work scheduled label Aug 22, 2025
@gziolo
gziolo marked this pull request as ready for review August 22, 2025 05:26
@gziolo

Copy link
Copy Markdown
MemberAuthor

@jonathanbossenger, this is something to keep on the radar for inclusion in the docs in case we agree that this level of extensibility is expected.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo is there an expectation for people to be calling public WP_Ability methods more than once per lifecycle?

My two concerns are

  1. Because this breaks SRP, folks extending the class (e.g. dev!: require string $name for registration and introduce ability_class arg #21 ) now either need more boilerplate (or not implement the hooks).
  2. The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

@gziolo

Copy link
Copy Markdown
MemberAuthor

The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

That's the reality in the WordPress land:

"With the great power (of extensibility) comes great responsibility"

Some recent examples:

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L600-L617

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L619-L636

Because this breaks SRP, folks extending the class (e.g. #21 ) now either need more boilerplate (or not implement the hooks).

There are different audiences here:

  • developers implementing abilities
  • plugin developers and site admins who want to customize these abilities

If the author of the ability uses a custom class that extends WP_Ability, it's their responsibility to use parent methods to keep these hooks. However, they might not want that for some reason that we can't anticipate. This works both ways, so I think it's fine to leave these considerations to implementers. WordPress core will never register abilities that skip these hooks, making things unpredictable.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo my question wasn't critique, I was looking for some direction for code review 🙇


I don't think we disagree in premise. The nuance here is our extremely short timetable before merging. To use your example of get_variations()

Just because we don't have time to evolve it holistically doesn't mean that we can't design our initial API with intention and around specific use cases. And if we don't want to wait to merge these until we have a specific use case from e.g. MCP Adapter or AI Experiments, then we should at least take a bit here to consider the use cases/ code flows for these proposed hooks (instead of designing code to conform with non-rubberducked hooks after the fact).


So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

  • Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).
  • Are the methods that contain these hooks the most likely going to be overloaded.
  • And conversely, are there hooks that we want to be triggered every time people use, or consider it a good think if they need to actively take steps to reinclude the hook in their downstream extends *.

@gziolo

Copy link
Copy Markdown
MemberAuthor

So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

I'm mostly concerned with this proposal about making it possible to customize the abilities that will get registered through WordPress Core.

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

In the frontend context, WordPress core won't even process individual abilities because they are never consumed by default, so only the abilitis_api_init hooks get registered.

Inside REST API context, they should be called once per ability, depending on the endpoint type:

  • get all would call input and out schema hooks to produce the result
  • get single would call input and output schema hooks to produce the result
  • execute, would call all 4 hooks, it could be that input and output schema hooks, when defined, would get called twice - during permissions check and during the execution

WP Admin is less predictable at this point, as we don't know how wide the usage will become. That said, WP hooks are everywhere in the codebase. I would be surprised learning that the abilities registry would have a larger impact than the rest of the codebase. What's the part that worries you the most?

@justlevine

Copy link
Copy Markdown
Contributor

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

The one that prompted my question is $ability->has_permissions() e.g

add_filter( 'ability_permission_result', staticfunction( $result ) {
if ( $resultinstanceof \WP_Error ) {
return$result;
}
return MyCustom::check_user_permissions_in_enterprise_sso( wp_get_current_user() );
}
... add_filter( 'ability_execute_result', staticfunction ( $result, string$ability_name, array$input ) {
if ( 'my/complex-tool-with-state-and-context' !== $ability_name ) {
return$result;
}
$prev_ability = wp_get_ability( $input['prevAbilityId'] );
if ( $prev_ability->has_permissions() ) {
...
}
}

What's the part that worries you the most?

Solely the timeline to core merge has us theorycrafting these, nothing blocking.

The combination of (a.) public getters that (b.) don't just "get" but break SRP to do something, while (c.) the filters run on each call instead of caching and (d) we already have people hankering to extends WP_Ability makes this class a recipe for refactor (IMO), but it's hard to justify the extra complexity of "doing it right" ( e.g. separate prepare_*() methods, applying filters once etc ) in an initial release.

@gziolo

Copy link
Copy Markdown
MemberAuthor

All valid concerns. We don’t have to rush introducing these filters, and wait for feedback from folks what’s limiting them.

Aside, caching and filters is often incompatible in WordPress reality because there are two primary challenges:

  • filters can be added or removed at different stages of page rendering, I saw many examples where filter gets added before executing the function and immediately removed after
  • result from filters and functions are not guaranteed to be idempotent as they often depend on thr current global state

@gziolo
gzioloforce-pushed the update/ability-filter-values branch from 8c93454 to 614aae6CompareAugust 27, 2025 12:02
@gziolo

Copy link
Copy Markdown
MemberAuthor

This is possible in two different ways:

Let's close this one as not planned for now.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Provide extensibility for individual abilities

3 participants

@gziolo@justlevine
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

Propose filters to use with the registered ability - #37

Closed
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values
Closed

Propose filters to use with the registered ability#37
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values

Conversation

@gziolo

@gziologziolo commented Aug 21, 2025

Copy link
Copy Markdown
Member

Closes#39.

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result.
  • Includes comprehensive test coverage for all filter integration scenarios.
  • Updates phpcs configuration to accommodate the new hook prefixes.

Example for how to revoke access to the ability:

$filter_cb = staticfunction ( $permission, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$permission;
}
returnfalse;
};
add_filter( 'ability_permission_result', $filter_cb, 10, 2 );

Example for how to change the output value:

$filter_cb = staticfunction ( $result, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$result;
}
return'modified-' . $result;
};
add_filter( 'ability_execute_result', $filter_cb, 10, 2 );

Testing instructions

6 new unit tests were added to cover possible scenarios. You can run them with

npm run test:php

@gziologziolo self-assigned this Aug 21, 2025
@gziologziolo added [Type] Enhancement New feature or request [Status] In Progress Assigned work scheduled labels Aug 21, 2025
@gziolo
gzioloforce-pushed the update/ability-filter-values branch 2 times, most recently from 80c0d3f to 7dca275CompareAugust 21, 2025 18:37
@gziolo
gziolo requested a review from CopilotAugust 22, 2025 04:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result
  • Includes comprehensive test coverage for all filter integration scenarios
  • Updates phpcs configuration to accommodate the new hook prefixes

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
includes/abilities-api/class-wp-ability.phpImplements filter integration in schema getters, permission checks, and execution methods
tests/unit/abilities-api/wpAbilityFilters.phpAdds comprehensive test suite for all filter functionality
phpcs.xml.distUpdates coding standards configuration to allow new hook prefixes
includes/abilities-api.phpAdds phpcs disable comment for global naming conventions
tests/bootstrap.phpRemoves unnecessary phpcs disable comment
tests/unit/rest-api/*.phpAdds descriptive comments to test files
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdds descriptive comment to test file

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadphpcs.xml.dist Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
@gziologziolo removed the [Status] In Progress Assigned work scheduled label Aug 22, 2025
@gziolo
gziolo marked this pull request as ready for review August 22, 2025 05:26
@gziolo

Copy link
Copy Markdown
MemberAuthor

@jonathanbossenger, this is something to keep on the radar for inclusion in the docs in case we agree that this level of extensibility is expected.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo is there an expectation for people to be calling public WP_Ability methods more than once per lifecycle?

My two concerns are

  1. Because this breaks SRP, folks extending the class (e.g. dev!: require string $name for registration and introduce ability_class arg #21 ) now either need more boilerplate (or not implement the hooks).
  2. The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

@gziolo

Copy link
Copy Markdown
MemberAuthor

The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

That's the reality in the WordPress land:

"With the great power (of extensibility) comes great responsibility"

Some recent examples:

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L600-L617

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L619-L636

Because this breaks SRP, folks extending the class (e.g. #21 ) now either need more boilerplate (or not implement the hooks).

There are different audiences here:

  • developers implementing abilities
  • plugin developers and site admins who want to customize these abilities

If the author of the ability uses a custom class that extends WP_Ability, it's their responsibility to use parent methods to keep these hooks. However, they might not want that for some reason that we can't anticipate. This works both ways, so I think it's fine to leave these considerations to implementers. WordPress core will never register abilities that skip these hooks, making things unpredictable.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo my question wasn't critique, I was looking for some direction for code review 🙇


I don't think we disagree in premise. The nuance here is our extremely short timetable before merging. To use your example of get_variations()

Just because we don't have time to evolve it holistically doesn't mean that we can't design our initial API with intention and around specific use cases. And if we don't want to wait to merge these until we have a specific use case from e.g. MCP Adapter or AI Experiments, then we should at least take a bit here to consider the use cases/ code flows for these proposed hooks (instead of designing code to conform with non-rubberducked hooks after the fact).


So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

  • Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).
  • Are the methods that contain these hooks the most likely going to be overloaded.
  • And conversely, are there hooks that we want to be triggered every time people use, or consider it a good think if they need to actively take steps to reinclude the hook in their downstream extends *.

@gziolo

Copy link
Copy Markdown
MemberAuthor

So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

I'm mostly concerned with this proposal about making it possible to customize the abilities that will get registered through WordPress Core.

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

In the frontend context, WordPress core won't even process individual abilities because they are never consumed by default, so only the abilitis_api_init hooks get registered.

Inside REST API context, they should be called once per ability, depending on the endpoint type:

  • get all would call input and out schema hooks to produce the result
  • get single would call input and output schema hooks to produce the result
  • execute, would call all 4 hooks, it could be that input and output schema hooks, when defined, would get called twice - during permissions check and during the execution

WP Admin is less predictable at this point, as we don't know how wide the usage will become. That said, WP hooks are everywhere in the codebase. I would be surprised learning that the abilities registry would have a larger impact than the rest of the codebase. What's the part that worries you the most?

@justlevine

Copy link
Copy Markdown
Contributor

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

The one that prompted my question is $ability->has_permissions() e.g

add_filter( 'ability_permission_result', staticfunction( $result ) {
if ( $resultinstanceof \WP_Error ) {
return$result;
}
return MyCustom::check_user_permissions_in_enterprise_sso( wp_get_current_user() );
}
... add_filter( 'ability_execute_result', staticfunction ( $result, string$ability_name, array$input ) {
if ( 'my/complex-tool-with-state-and-context' !== $ability_name ) {
return$result;
}
$prev_ability = wp_get_ability( $input['prevAbilityId'] );
if ( $prev_ability->has_permissions() ) {
...
}
}

What's the part that worries you the most?

Solely the timeline to core merge has us theorycrafting these, nothing blocking.

The combination of (a.) public getters that (b.) don't just "get" but break SRP to do something, while (c.) the filters run on each call instead of caching and (d) we already have people hankering to extends WP_Ability makes this class a recipe for refactor (IMO), but it's hard to justify the extra complexity of "doing it right" ( e.g. separate prepare_*() methods, applying filters once etc ) in an initial release.

@gziolo

Copy link
Copy Markdown
MemberAuthor

All valid concerns. We don’t have to rush introducing these filters, and wait for feedback from folks what’s limiting them.

Aside, caching and filters is often incompatible in WordPress reality because there are two primary challenges:

  • filters can be added or removed at different stages of page rendering, I saw many examples where filter gets added before executing the function and immediately removed after
  • result from filters and functions are not guaranteed to be idempotent as they often depend on thr current global state

@gziolo
gzioloforce-pushed the update/ability-filter-values branch from 8c93454 to 614aae6CompareAugust 27, 2025 12:02
@gziolo

Copy link
Copy Markdown
MemberAuthor

This is possible in two different ways:

Let's close this one as not planned for now.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Provide extensibility for individual abilities

3 participants

@gziolo@justlevine
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

Propose filters to use with the registered ability - #37

Closed
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values
Closed

Propose filters to use with the registered ability#37
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values

Conversation

@gziolo

@gziologziolo commented Aug 21, 2025

Copy link
Copy Markdown
Member

Closes#39.

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result.
  • Includes comprehensive test coverage for all filter integration scenarios.
  • Updates phpcs configuration to accommodate the new hook prefixes.

Example for how to revoke access to the ability:

$filter_cb = staticfunction ( $permission, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$permission;
}
returnfalse;
};
add_filter( 'ability_permission_result', $filter_cb, 10, 2 );

Example for how to change the output value:

$filter_cb = staticfunction ( $result, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$result;
}
return'modified-' . $result;
};
add_filter( 'ability_execute_result', $filter_cb, 10, 2 );

Testing instructions

6 new unit tests were added to cover possible scenarios. You can run them with

npm run test:php

@gziologziolo self-assigned this Aug 21, 2025
@gziologziolo added [Type] Enhancement New feature or request [Status] In Progress Assigned work scheduled labels Aug 21, 2025
@gziolo
gzioloforce-pushed the update/ability-filter-values branch 2 times, most recently from 80c0d3f to 7dca275CompareAugust 21, 2025 18:37
@gziolo
gziolo requested a review from CopilotAugust 22, 2025 04:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result
  • Includes comprehensive test coverage for all filter integration scenarios
  • Updates phpcs configuration to accommodate the new hook prefixes

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
includes/abilities-api/class-wp-ability.phpImplements filter integration in schema getters, permission checks, and execution methods
tests/unit/abilities-api/wpAbilityFilters.phpAdds comprehensive test suite for all filter functionality
phpcs.xml.distUpdates coding standards configuration to allow new hook prefixes
includes/abilities-api.phpAdds phpcs disable comment for global naming conventions
tests/bootstrap.phpRemoves unnecessary phpcs disable comment
tests/unit/rest-api/*.phpAdds descriptive comments to test files
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdds descriptive comment to test file

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadphpcs.xml.dist Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
@gziologziolo removed the [Status] In Progress Assigned work scheduled label Aug 22, 2025
@gziolo
gziolo marked this pull request as ready for review August 22, 2025 05:26
@gziolo

Copy link
Copy Markdown
MemberAuthor

@jonathanbossenger, this is something to keep on the radar for inclusion in the docs in case we agree that this level of extensibility is expected.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo is there an expectation for people to be calling public WP_Ability methods more than once per lifecycle?

My two concerns are

  1. Because this breaks SRP, folks extending the class (e.g. dev!: require string $name for registration and introduce ability_class arg #21 ) now either need more boilerplate (or not implement the hooks).
  2. The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

@gziolo

Copy link
Copy Markdown
MemberAuthor

The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

That's the reality in the WordPress land:

"With the great power (of extensibility) comes great responsibility"

Some recent examples:

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L600-L617

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L619-L636

Because this breaks SRP, folks extending the class (e.g. #21 ) now either need more boilerplate (or not implement the hooks).

There are different audiences here:

  • developers implementing abilities
  • plugin developers and site admins who want to customize these abilities

If the author of the ability uses a custom class that extends WP_Ability, it's their responsibility to use parent methods to keep these hooks. However, they might not want that for some reason that we can't anticipate. This works both ways, so I think it's fine to leave these considerations to implementers. WordPress core will never register abilities that skip these hooks, making things unpredictable.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo my question wasn't critique, I was looking for some direction for code review 🙇


I don't think we disagree in premise. The nuance here is our extremely short timetable before merging. To use your example of get_variations()

Just because we don't have time to evolve it holistically doesn't mean that we can't design our initial API with intention and around specific use cases. And if we don't want to wait to merge these until we have a specific use case from e.g. MCP Adapter or AI Experiments, then we should at least take a bit here to consider the use cases/ code flows for these proposed hooks (instead of designing code to conform with non-rubberducked hooks after the fact).


So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

  • Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).
  • Are the methods that contain these hooks the most likely going to be overloaded.
  • And conversely, are there hooks that we want to be triggered every time people use, or consider it a good think if they need to actively take steps to reinclude the hook in their downstream extends *.

@gziolo

Copy link
Copy Markdown
MemberAuthor

So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

I'm mostly concerned with this proposal about making it possible to customize the abilities that will get registered through WordPress Core.

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

In the frontend context, WordPress core won't even process individual abilities because they are never consumed by default, so only the abilitis_api_init hooks get registered.

Inside REST API context, they should be called once per ability, depending on the endpoint type:

  • get all would call input and out schema hooks to produce the result
  • get single would call input and output schema hooks to produce the result
  • execute, would call all 4 hooks, it could be that input and output schema hooks, when defined, would get called twice - during permissions check and during the execution

WP Admin is less predictable at this point, as we don't know how wide the usage will become. That said, WP hooks are everywhere in the codebase. I would be surprised learning that the abilities registry would have a larger impact than the rest of the codebase. What's the part that worries you the most?

@justlevine

Copy link
Copy Markdown
Contributor

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

The one that prompted my question is $ability->has_permissions() e.g

add_filter( 'ability_permission_result', staticfunction( $result ) {
if ( $resultinstanceof \WP_Error ) {
return$result;
}
return MyCustom::check_user_permissions_in_enterprise_sso( wp_get_current_user() );
}
... add_filter( 'ability_execute_result', staticfunction ( $result, string$ability_name, array$input ) {
if ( 'my/complex-tool-with-state-and-context' !== $ability_name ) {
return$result;
}
$prev_ability = wp_get_ability( $input['prevAbilityId'] );
if ( $prev_ability->has_permissions() ) {
...
}
}

What's the part that worries you the most?

Solely the timeline to core merge has us theorycrafting these, nothing blocking.

The combination of (a.) public getters that (b.) don't just "get" but break SRP to do something, while (c.) the filters run on each call instead of caching and (d) we already have people hankering to extends WP_Ability makes this class a recipe for refactor (IMO), but it's hard to justify the extra complexity of "doing it right" ( e.g. separate prepare_*() methods, applying filters once etc ) in an initial release.

@gziolo

Copy link
Copy Markdown
MemberAuthor

All valid concerns. We don’t have to rush introducing these filters, and wait for feedback from folks what’s limiting them.

Aside, caching and filters is often incompatible in WordPress reality because there are two primary challenges:

  • filters can be added or removed at different stages of page rendering, I saw many examples where filter gets added before executing the function and immediately removed after
  • result from filters and functions are not guaranteed to be idempotent as they often depend on thr current global state

@gziolo
gzioloforce-pushed the update/ability-filter-values branch from 8c93454 to 614aae6CompareAugust 27, 2025 12:02
@gziolo

Copy link
Copy Markdown
MemberAuthor

This is possible in two different ways:

Let's close this one as not planned for now.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Provide extensibility for individual abilities

3 participants

@gziolo@justlevine
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

Propose filters to use with the registered ability - #37

Closed
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values
Closed

Propose filters to use with the registered ability#37
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values

Conversation

@gziolo

@gziologziolo commented Aug 21, 2025

Copy link
Copy Markdown
Member

Closes#39.

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result.
  • Includes comprehensive test coverage for all filter integration scenarios.
  • Updates phpcs configuration to accommodate the new hook prefixes.

Example for how to revoke access to the ability:

$filter_cb = staticfunction ( $permission, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$permission;
}
returnfalse;
};
add_filter( 'ability_permission_result', $filter_cb, 10, 2 );

Example for how to change the output value:

$filter_cb = staticfunction ( $result, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$result;
}
return'modified-' . $result;
};
add_filter( 'ability_execute_result', $filter_cb, 10, 2 );

Testing instructions

6 new unit tests were added to cover possible scenarios. You can run them with

npm run test:php

@gziologziolo self-assigned this Aug 21, 2025
@gziologziolo added [Type] Enhancement New feature or request [Status] In Progress Assigned work scheduled labels Aug 21, 2025
@gziolo
gzioloforce-pushed the update/ability-filter-values branch 2 times, most recently from 80c0d3f to 7dca275CompareAugust 21, 2025 18:37
@gziolo
gziolo requested a review from CopilotAugust 22, 2025 04:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result
  • Includes comprehensive test coverage for all filter integration scenarios
  • Updates phpcs configuration to accommodate the new hook prefixes

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
includes/abilities-api/class-wp-ability.phpImplements filter integration in schema getters, permission checks, and execution methods
tests/unit/abilities-api/wpAbilityFilters.phpAdds comprehensive test suite for all filter functionality
phpcs.xml.distUpdates coding standards configuration to allow new hook prefixes
includes/abilities-api.phpAdds phpcs disable comment for global naming conventions
tests/bootstrap.phpRemoves unnecessary phpcs disable comment
tests/unit/rest-api/*.phpAdds descriptive comments to test files
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdds descriptive comment to test file

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadphpcs.xml.dist Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
@gziologziolo removed the [Status] In Progress Assigned work scheduled label Aug 22, 2025
@gziolo
gziolo marked this pull request as ready for review August 22, 2025 05:26
@gziolo

Copy link
Copy Markdown
MemberAuthor

@jonathanbossenger, this is something to keep on the radar for inclusion in the docs in case we agree that this level of extensibility is expected.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo is there an expectation for people to be calling public WP_Ability methods more than once per lifecycle?

My two concerns are

  1. Because this breaks SRP, folks extending the class (e.g. dev!: require string $name for registration and introduce ability_class arg #21 ) now either need more boilerplate (or not implement the hooks).
  2. The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

@gziolo

Copy link
Copy Markdown
MemberAuthor

The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

That's the reality in the WordPress land:

"With the great power (of extensibility) comes great responsibility"

Some recent examples:

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L600-L617

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L619-L636

Because this breaks SRP, folks extending the class (e.g. #21 ) now either need more boilerplate (or not implement the hooks).

There are different audiences here:

  • developers implementing abilities
  • plugin developers and site admins who want to customize these abilities

If the author of the ability uses a custom class that extends WP_Ability, it's their responsibility to use parent methods to keep these hooks. However, they might not want that for some reason that we can't anticipate. This works both ways, so I think it's fine to leave these considerations to implementers. WordPress core will never register abilities that skip these hooks, making things unpredictable.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo my question wasn't critique, I was looking for some direction for code review 🙇


I don't think we disagree in premise. The nuance here is our extremely short timetable before merging. To use your example of get_variations()

Just because we don't have time to evolve it holistically doesn't mean that we can't design our initial API with intention and around specific use cases. And if we don't want to wait to merge these until we have a specific use case from e.g. MCP Adapter or AI Experiments, then we should at least take a bit here to consider the use cases/ code flows for these proposed hooks (instead of designing code to conform with non-rubberducked hooks after the fact).


So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

  • Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).
  • Are the methods that contain these hooks the most likely going to be overloaded.
  • And conversely, are there hooks that we want to be triggered every time people use, or consider it a good think if they need to actively take steps to reinclude the hook in their downstream extends *.

@gziolo

Copy link
Copy Markdown
MemberAuthor

So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

I'm mostly concerned with this proposal about making it possible to customize the abilities that will get registered through WordPress Core.

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

In the frontend context, WordPress core won't even process individual abilities because they are never consumed by default, so only the abilitis_api_init hooks get registered.

Inside REST API context, they should be called once per ability, depending on the endpoint type:

  • get all would call input and out schema hooks to produce the result
  • get single would call input and output schema hooks to produce the result
  • execute, would call all 4 hooks, it could be that input and output schema hooks, when defined, would get called twice - during permissions check and during the execution

WP Admin is less predictable at this point, as we don't know how wide the usage will become. That said, WP hooks are everywhere in the codebase. I would be surprised learning that the abilities registry would have a larger impact than the rest of the codebase. What's the part that worries you the most?

@justlevine

Copy link
Copy Markdown
Contributor

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

The one that prompted my question is $ability->has_permissions() e.g

add_filter( 'ability_permission_result', staticfunction( $result ) {
if ( $resultinstanceof \WP_Error ) {
return$result;
}
return MyCustom::check_user_permissions_in_enterprise_sso( wp_get_current_user() );
}
... add_filter( 'ability_execute_result', staticfunction ( $result, string$ability_name, array$input ) {
if ( 'my/complex-tool-with-state-and-context' !== $ability_name ) {
return$result;
}
$prev_ability = wp_get_ability( $input['prevAbilityId'] );
if ( $prev_ability->has_permissions() ) {
...
}
}

What's the part that worries you the most?

Solely the timeline to core merge has us theorycrafting these, nothing blocking.

The combination of (a.) public getters that (b.) don't just "get" but break SRP to do something, while (c.) the filters run on each call instead of caching and (d) we already have people hankering to extends WP_Ability makes this class a recipe for refactor (IMO), but it's hard to justify the extra complexity of "doing it right" ( e.g. separate prepare_*() methods, applying filters once etc ) in an initial release.

@gziolo

Copy link
Copy Markdown
MemberAuthor

All valid concerns. We don’t have to rush introducing these filters, and wait for feedback from folks what’s limiting them.

Aside, caching and filters is often incompatible in WordPress reality because there are two primary challenges:

  • filters can be added or removed at different stages of page rendering, I saw many examples where filter gets added before executing the function and immediately removed after
  • result from filters and functions are not guaranteed to be idempotent as they often depend on thr current global state

@gziolo
gzioloforce-pushed the update/ability-filter-values branch from 8c93454 to 614aae6CompareAugust 27, 2025 12:02
@gziolo

Copy link
Copy Markdown
MemberAuthor

This is possible in two different ways:

Let's close this one as not planned for now.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Provide extensibility for individual abilities

3 participants

@gziolo@justlevine
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

Propose filters to use with the registered ability - #37

Closed
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values
Closed

Propose filters to use with the registered ability#37
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values

Conversation

@gziolo

@gziologziolo commented Aug 21, 2025

Copy link
Copy Markdown
Member

Closes#39.

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result.
  • Includes comprehensive test coverage for all filter integration scenarios.
  • Updates phpcs configuration to accommodate the new hook prefixes.

Example for how to revoke access to the ability:

$filter_cb = staticfunction ( $permission, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$permission;
}
returnfalse;
};
add_filter( 'ability_permission_result', $filter_cb, 10, 2 );

Example for how to change the output value:

$filter_cb = staticfunction ( $result, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$result;
}
return'modified-' . $result;
};
add_filter( 'ability_execute_result', $filter_cb, 10, 2 );

Testing instructions

6 new unit tests were added to cover possible scenarios. You can run them with

npm run test:php

@gziologziolo self-assigned this Aug 21, 2025
@gziologziolo added [Type] Enhancement New feature or request [Status] In Progress Assigned work scheduled labels Aug 21, 2025
@gziolo
gzioloforce-pushed the update/ability-filter-values branch 2 times, most recently from 80c0d3f to 7dca275CompareAugust 21, 2025 18:37
@gziolo
gziolo requested a review from CopilotAugust 22, 2025 04:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result
  • Includes comprehensive test coverage for all filter integration scenarios
  • Updates phpcs configuration to accommodate the new hook prefixes

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
includes/abilities-api/class-wp-ability.phpImplements filter integration in schema getters, permission checks, and execution methods
tests/unit/abilities-api/wpAbilityFilters.phpAdds comprehensive test suite for all filter functionality
phpcs.xml.distUpdates coding standards configuration to allow new hook prefixes
includes/abilities-api.phpAdds phpcs disable comment for global naming conventions
tests/bootstrap.phpRemoves unnecessary phpcs disable comment
tests/unit/rest-api/*.phpAdds descriptive comments to test files
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdds descriptive comment to test file

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadphpcs.xml.dist Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
@gziologziolo removed the [Status] In Progress Assigned work scheduled label Aug 22, 2025
@gziolo
gziolo marked this pull request as ready for review August 22, 2025 05:26
@gziolo

Copy link
Copy Markdown
MemberAuthor

@jonathanbossenger, this is something to keep on the radar for inclusion in the docs in case we agree that this level of extensibility is expected.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo is there an expectation for people to be calling public WP_Ability methods more than once per lifecycle?

My two concerns are

  1. Because this breaks SRP, folks extending the class (e.g. dev!: require string $name for registration and introduce ability_class arg #21 ) now either need more boilerplate (or not implement the hooks).
  2. The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

@gziolo

Copy link
Copy Markdown
MemberAuthor

The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

That's the reality in the WordPress land:

"With the great power (of extensibility) comes great responsibility"

Some recent examples:

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L600-L617

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L619-L636

Because this breaks SRP, folks extending the class (e.g. #21 ) now either need more boilerplate (or not implement the hooks).

There are different audiences here:

  • developers implementing abilities
  • plugin developers and site admins who want to customize these abilities

If the author of the ability uses a custom class that extends WP_Ability, it's their responsibility to use parent methods to keep these hooks. However, they might not want that for some reason that we can't anticipate. This works both ways, so I think it's fine to leave these considerations to implementers. WordPress core will never register abilities that skip these hooks, making things unpredictable.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo my question wasn't critique, I was looking for some direction for code review 🙇


I don't think we disagree in premise. The nuance here is our extremely short timetable before merging. To use your example of get_variations()

Just because we don't have time to evolve it holistically doesn't mean that we can't design our initial API with intention and around specific use cases. And if we don't want to wait to merge these until we have a specific use case from e.g. MCP Adapter or AI Experiments, then we should at least take a bit here to consider the use cases/ code flows for these proposed hooks (instead of designing code to conform with non-rubberducked hooks after the fact).


So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

  • Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).
  • Are the methods that contain these hooks the most likely going to be overloaded.
  • And conversely, are there hooks that we want to be triggered every time people use, or consider it a good think if they need to actively take steps to reinclude the hook in their downstream extends *.

@gziolo

Copy link
Copy Markdown
MemberAuthor

So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

I'm mostly concerned with this proposal about making it possible to customize the abilities that will get registered through WordPress Core.

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

In the frontend context, WordPress core won't even process individual abilities because they are never consumed by default, so only the abilitis_api_init hooks get registered.

Inside REST API context, they should be called once per ability, depending on the endpoint type:

  • get all would call input and out schema hooks to produce the result
  • get single would call input and output schema hooks to produce the result
  • execute, would call all 4 hooks, it could be that input and output schema hooks, when defined, would get called twice - during permissions check and during the execution

WP Admin is less predictable at this point, as we don't know how wide the usage will become. That said, WP hooks are everywhere in the codebase. I would be surprised learning that the abilities registry would have a larger impact than the rest of the codebase. What's the part that worries you the most?

@justlevine

Copy link
Copy Markdown
Contributor

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

The one that prompted my question is $ability->has_permissions() e.g

add_filter( 'ability_permission_result', staticfunction( $result ) {
if ( $resultinstanceof \WP_Error ) {
return$result;
}
return MyCustom::check_user_permissions_in_enterprise_sso( wp_get_current_user() );
}
... add_filter( 'ability_execute_result', staticfunction ( $result, string$ability_name, array$input ) {
if ( 'my/complex-tool-with-state-and-context' !== $ability_name ) {
return$result;
}
$prev_ability = wp_get_ability( $input['prevAbilityId'] );
if ( $prev_ability->has_permissions() ) {
...
}
}

What's the part that worries you the most?

Solely the timeline to core merge has us theorycrafting these, nothing blocking.

The combination of (a.) public getters that (b.) don't just "get" but break SRP to do something, while (c.) the filters run on each call instead of caching and (d) we already have people hankering to extends WP_Ability makes this class a recipe for refactor (IMO), but it's hard to justify the extra complexity of "doing it right" ( e.g. separate prepare_*() methods, applying filters once etc ) in an initial release.

@gziolo

Copy link
Copy Markdown
MemberAuthor

All valid concerns. We don’t have to rush introducing these filters, and wait for feedback from folks what’s limiting them.

Aside, caching and filters is often incompatible in WordPress reality because there are two primary challenges:

  • filters can be added or removed at different stages of page rendering, I saw many examples where filter gets added before executing the function and immediately removed after
  • result from filters and functions are not guaranteed to be idempotent as they often depend on thr current global state

@gziolo
gzioloforce-pushed the update/ability-filter-values branch from 8c93454 to 614aae6CompareAugust 27, 2025 12:02
@gziolo

Copy link
Copy Markdown
MemberAuthor

This is possible in two different ways:

Let's close this one as not planned for now.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Provide extensibility for individual abilities

3 participants

@gziolo@justlevine
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

Propose filters to use with the registered ability - #37

Closed
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values
Closed

Propose filters to use with the registered ability#37
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values

Conversation

@gziolo

@gziologziolo commented Aug 21, 2025

Copy link
Copy Markdown
Member

Closes#39.

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result.
  • Includes comprehensive test coverage for all filter integration scenarios.
  • Updates phpcs configuration to accommodate the new hook prefixes.

Example for how to revoke access to the ability:

$filter_cb = staticfunction ( $permission, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$permission;
}
returnfalse;
};
add_filter( 'ability_permission_result', $filter_cb, 10, 2 );

Example for how to change the output value:

$filter_cb = staticfunction ( $result, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$result;
}
return'modified-' . $result;
};
add_filter( 'ability_execute_result', $filter_cb, 10, 2 );

Testing instructions

6 new unit tests were added to cover possible scenarios. You can run them with

npm run test:php

@gziologziolo self-assigned this Aug 21, 2025
@gziologziolo added [Type] Enhancement New feature or request [Status] In Progress Assigned work scheduled labels Aug 21, 2025
@gziolo
gzioloforce-pushed the update/ability-filter-values branch 2 times, most recently from 80c0d3f to 7dca275CompareAugust 21, 2025 18:37
@gziolo
gziolo requested a review from CopilotAugust 22, 2025 04:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result
  • Includes comprehensive test coverage for all filter integration scenarios
  • Updates phpcs configuration to accommodate the new hook prefixes

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
includes/abilities-api/class-wp-ability.phpImplements filter integration in schema getters, permission checks, and execution methods
tests/unit/abilities-api/wpAbilityFilters.phpAdds comprehensive test suite for all filter functionality
phpcs.xml.distUpdates coding standards configuration to allow new hook prefixes
includes/abilities-api.phpAdds phpcs disable comment for global naming conventions
tests/bootstrap.phpRemoves unnecessary phpcs disable comment
tests/unit/rest-api/*.phpAdds descriptive comments to test files
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdds descriptive comment to test file

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadphpcs.xml.dist Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
@gziologziolo removed the [Status] In Progress Assigned work scheduled label Aug 22, 2025
@gziolo
gziolo marked this pull request as ready for review August 22, 2025 05:26
@gziolo

Copy link
Copy Markdown
MemberAuthor

@jonathanbossenger, this is something to keep on the radar for inclusion in the docs in case we agree that this level of extensibility is expected.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo is there an expectation for people to be calling public WP_Ability methods more than once per lifecycle?

My two concerns are

  1. Because this breaks SRP, folks extending the class (e.g. dev!: require string $name for registration and introduce ability_class arg #21 ) now either need more boilerplate (or not implement the hooks).
  2. The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

@gziolo

Copy link
Copy Markdown
MemberAuthor

The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

That's the reality in the WordPress land:

"With the great power (of extensibility) comes great responsibility"

Some recent examples:

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L600-L617

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L619-L636

Because this breaks SRP, folks extending the class (e.g. #21 ) now either need more boilerplate (or not implement the hooks).

There are different audiences here:

  • developers implementing abilities
  • plugin developers and site admins who want to customize these abilities

If the author of the ability uses a custom class that extends WP_Ability, it's their responsibility to use parent methods to keep these hooks. However, they might not want that for some reason that we can't anticipate. This works both ways, so I think it's fine to leave these considerations to implementers. WordPress core will never register abilities that skip these hooks, making things unpredictable.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo my question wasn't critique, I was looking for some direction for code review 🙇


I don't think we disagree in premise. The nuance here is our extremely short timetable before merging. To use your example of get_variations()

Just because we don't have time to evolve it holistically doesn't mean that we can't design our initial API with intention and around specific use cases. And if we don't want to wait to merge these until we have a specific use case from e.g. MCP Adapter or AI Experiments, then we should at least take a bit here to consider the use cases/ code flows for these proposed hooks (instead of designing code to conform with non-rubberducked hooks after the fact).


So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

  • Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).
  • Are the methods that contain these hooks the most likely going to be overloaded.
  • And conversely, are there hooks that we want to be triggered every time people use, or consider it a good think if they need to actively take steps to reinclude the hook in their downstream extends *.

@gziolo

Copy link
Copy Markdown
MemberAuthor

So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

I'm mostly concerned with this proposal about making it possible to customize the abilities that will get registered through WordPress Core.

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

In the frontend context, WordPress core won't even process individual abilities because they are never consumed by default, so only the abilitis_api_init hooks get registered.

Inside REST API context, they should be called once per ability, depending on the endpoint type:

  • get all would call input and out schema hooks to produce the result
  • get single would call input and output schema hooks to produce the result
  • execute, would call all 4 hooks, it could be that input and output schema hooks, when defined, would get called twice - during permissions check and during the execution

WP Admin is less predictable at this point, as we don't know how wide the usage will become. That said, WP hooks are everywhere in the codebase. I would be surprised learning that the abilities registry would have a larger impact than the rest of the codebase. What's the part that worries you the most?

@justlevine

Copy link
Copy Markdown
Contributor

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

The one that prompted my question is $ability->has_permissions() e.g

add_filter( 'ability_permission_result', staticfunction( $result ) {
if ( $resultinstanceof \WP_Error ) {
return$result;
}
return MyCustom::check_user_permissions_in_enterprise_sso( wp_get_current_user() );
}
... add_filter( 'ability_execute_result', staticfunction ( $result, string$ability_name, array$input ) {
if ( 'my/complex-tool-with-state-and-context' !== $ability_name ) {
return$result;
}
$prev_ability = wp_get_ability( $input['prevAbilityId'] );
if ( $prev_ability->has_permissions() ) {
...
}
}

What's the part that worries you the most?

Solely the timeline to core merge has us theorycrafting these, nothing blocking.

The combination of (a.) public getters that (b.) don't just "get" but break SRP to do something, while (c.) the filters run on each call instead of caching and (d) we already have people hankering to extends WP_Ability makes this class a recipe for refactor (IMO), but it's hard to justify the extra complexity of "doing it right" ( e.g. separate prepare_*() methods, applying filters once etc ) in an initial release.

@gziolo

Copy link
Copy Markdown
MemberAuthor

All valid concerns. We don’t have to rush introducing these filters, and wait for feedback from folks what’s limiting them.

Aside, caching and filters is often incompatible in WordPress reality because there are two primary challenges:

  • filters can be added or removed at different stages of page rendering, I saw many examples where filter gets added before executing the function and immediately removed after
  • result from filters and functions are not guaranteed to be idempotent as they often depend on thr current global state

@gziolo
gzioloforce-pushed the update/ability-filter-values branch from 8c93454 to 614aae6CompareAugust 27, 2025 12:02
@gziolo

Copy link
Copy Markdown
MemberAuthor

This is possible in two different ways:

Let's close this one as not planned for now.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Provide extensibility for individual abilities

3 participants

@gziolo@justlevine
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

Propose filters to use with the registered ability - #37

Closed
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values
Closed

Propose filters to use with the registered ability#37
gziolo wants to merge 9 commits into
trunkfrom
update/ability-filter-values

Conversation

@gziolo

@gziologziolo commented Aug 21, 2025

Copy link
Copy Markdown
Member

Closes#39.

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result.
  • Includes comprehensive test coverage for all filter integration scenarios.
  • Updates phpcs configuration to accommodate the new hook prefixes.

Example for how to revoke access to the ability:

$filter_cb = staticfunction ( $permission, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$permission;
}
returnfalse;
};
add_filter( 'ability_permission_result', $filter_cb, 10, 2 );

Example for how to change the output value:

$filter_cb = staticfunction ( $result, $ability_name ) {
if ( 'test/filter-ability' !== $ability_name ) {
return$result;
}
return'modified-' . $result;
};
add_filter( 'ability_execute_result', $filter_cb, 10, 2 );

Testing instructions

6 new unit tests were added to cover possible scenarios. You can run them with

npm run test:php

@gziologziolo self-assigned this Aug 21, 2025
@gziologziolo added [Type] Enhancement New feature or request [Status] In Progress Assigned work scheduled labels Aug 21, 2025
@gziolo
gzioloforce-pushed the update/ability-filter-values branch 2 times, most recently from 80c0d3f to 7dca275CompareAugust 21, 2025 18:37
@gziolo
gziolo requested a review from CopilotAugust 22, 2025 04:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces filter support for the WP_Ability class, allowing developers to customize ability behavior through WordPress hook filters. The main purpose is to provide extensibility points for input/output schemas, permission checks, and execution results.

  • Adds four new WordPress filters: ability_input_schema, ability_output_schema, ability_permission_result, and ability_execute_result
  • Includes comprehensive test coverage for all filter integration scenarios
  • Updates phpcs configuration to accommodate the new hook prefixes

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
includes/abilities-api/class-wp-ability.phpImplements filter integration in schema getters, permission checks, and execution methods
tests/unit/abilities-api/wpAbilityFilters.phpAdds comprehensive test suite for all filter functionality
phpcs.xml.distUpdates coding standards configuration to allow new hook prefixes
includes/abilities-api.phpAdds phpcs disable comment for global naming conventions
tests/bootstrap.phpRemoves unnecessary phpcs disable comment
tests/unit/rest-api/*.phpAdds descriptive comments to test files
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdds descriptive comment to test file

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadphpcs.xml.dist Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadtests/unit/abilities-api/wpAbilityFilters.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
Comment threadincludes/abilities-api/class-wp-ability.php Outdated
@gziologziolo removed the [Status] In Progress Assigned work scheduled label Aug 22, 2025
@gziolo
gziolo marked this pull request as ready for review August 22, 2025 05:26
@gziolo

Copy link
Copy Markdown
MemberAuthor

@jonathanbossenger, this is something to keep on the radar for inclusion in the docs in case we agree that this level of extensibility is expected.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo is there an expectation for people to be calling public WP_Ability methods more than once per lifecycle?

My two concerns are

  1. Because this breaks SRP, folks extending the class (e.g. dev!: require string $name for registration and introduce ability_class arg #21 ) now either need more boilerplate (or not implement the hooks).
  2. The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

@gziolo

Copy link
Copy Markdown
MemberAuthor

The possibility of hooks being triggered multiple times introduces perf and behavior unpredictability.

That's the reality in the WordPress land:

"With the great power (of extensibility) comes great responsibility"

Some recent examples:

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L600-L617

https://github.com/WordPress/wordpress-develop/blob/55644dc608d3a9c9d2541379d8a5b48d4b2c3d99/src/wp-includes/class-wp-block-type.php#L619-L636

Because this breaks SRP, folks extending the class (e.g. #21 ) now either need more boilerplate (or not implement the hooks).

There are different audiences here:

  • developers implementing abilities
  • plugin developers and site admins who want to customize these abilities

If the author of the ability uses a custom class that extends WP_Ability, it's their responsibility to use parent methods to keep these hooks. However, they might not want that for some reason that we can't anticipate. This works both ways, so I think it's fine to leave these considerations to implementers. WordPress core will never register abilities that skip these hooks, making things unpredictable.

@justlevine

Copy link
Copy Markdown
Contributor

@gziolo my question wasn't critique, I was looking for some direction for code review 🙇


I don't think we disagree in premise. The nuance here is our extremely short timetable before merging. To use your example of get_variations()

Just because we don't have time to evolve it holistically doesn't mean that we can't design our initial API with intention and around specific use cases. And if we don't want to wait to merge these until we have a specific use case from e.g. MCP Adapter or AI Experiments, then we should at least take a bit here to consider the use cases/ code flows for these proposed hooks (instead of designing code to conform with non-rubberducked hooks after the fact).


So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

  • Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).
  • Are the methods that contain these hooks the most likely going to be overloaded.
  • And conversely, are there hooks that we want to be triggered every time people use, or consider it a good think if they need to actively take steps to reinclude the hook in their downstream extends *.

@gziolo

Copy link
Copy Markdown
MemberAuthor

So with that context, and putting yourself into the mind of an "implementer" for a minute - how do you envision these hooks to be used?

I'm mostly concerned with this proposal about making it possible to customize the abilities that will get registered through WordPress Core.

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

In the frontend context, WordPress core won't even process individual abilities because they are never consumed by default, so only the abilitis_api_init hooks get registered.

Inside REST API context, they should be called once per ability, depending on the endpoint type:

  • get all would call input and out schema hooks to produce the result
  • get single would call input and output schema hooks to produce the result
  • execute, would call all 4 hooks, it could be that input and output schema hooks, when defined, would get called twice - during permissions check and during the execution

WP Admin is less predictable at this point, as we don't know how wide the usage will become. That said, WP hooks are everywhere in the codebase. I would be surprised learning that the abilities registry would have a larger impact than the rest of the codebase. What's the part that worries you the most?

@justlevine

Copy link
Copy Markdown
Contributor

Are there public * methods that are obviously gonna be called multiple times (where if the filter is repeated there will be detrimental).

Can you provide a real-life example of how often you anticipate the same hooks being called multiple times?

The one that prompted my question is $ability->has_permissions() e.g

add_filter( 'ability_permission_result', staticfunction( $result ) {
if ( $resultinstanceof \WP_Error ) {
return$result;
}
return MyCustom::check_user_permissions_in_enterprise_sso( wp_get_current_user() );
}
... add_filter( 'ability_execute_result', staticfunction ( $result, string$ability_name, array$input ) {
if ( 'my/complex-tool-with-state-and-context' !== $ability_name ) {
return$result;
}
$prev_ability = wp_get_ability( $input['prevAbilityId'] );
if ( $prev_ability->has_permissions() ) {
...
}
}

What's the part that worries you the most?

Solely the timeline to core merge has us theorycrafting these, nothing blocking.

The combination of (a.) public getters that (b.) don't just "get" but break SRP to do something, while (c.) the filters run on each call instead of caching and (d) we already have people hankering to extends WP_Ability makes this class a recipe for refactor (IMO), but it's hard to justify the extra complexity of "doing it right" ( e.g. separate prepare_*() methods, applying filters once etc ) in an initial release.

@gziolo

Copy link
Copy Markdown
MemberAuthor

All valid concerns. We don’t have to rush introducing these filters, and wait for feedback from folks what’s limiting them.

Aside, caching and filters is often incompatible in WordPress reality because there are two primary challenges:

  • filters can be added or removed at different stages of page rendering, I saw many examples where filter gets added before executing the function and immediately removed after
  • result from filters and functions are not guaranteed to be idempotent as they often depend on thr current global state

@gziolo
gzioloforce-pushed the update/ability-filter-values branch from 8c93454 to 614aae6CompareAugust 27, 2025 12:02
@gziolo

Copy link
Copy Markdown
MemberAuthor

This is possible in two different ways:

Let's close this one as not planned for now.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Provide extensibility for individual abilities

3 participants

@gziolo@justlevine