Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions src/wp-includes/abilities-api.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,9 +19,12 @@
*
* ## Working with Abilities
*
* Abilities must be registered on the `wp_abilities_api_init` action hook.
* Attempting to register an ability outside of this hook will fail and
* trigger a `_doing_it_wrong()` notice.
* Register abilities on the `wp_abilities_api_init` action hook, the recommended
* deterministic registration point. Registration after the `init` action has
* fired is also supported. Callers registering afterward are responsible for
* doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability before the `init` action has fired will
* fail and trigger a `_doing_it_wrong()` notice.

* Example:
*
Expand DownExpand Up@@ -72,7 +75,8 @@
*
* ## Best Practices
*
* - Always register abilities on the `wp_abilities_api_init` hook.
* - Register abilities on the `wp_abilities_api_init` hook, the recommended
* deterministic registration point.
* - Use namespaced ability names to prevent conflicts.
* - Implement robust permission checks in permission callbacks.
* - Provide an `input_schema` to ensure data integrity and document expected inputs.
Expand All@@ -90,7 +94,9 @@
/**
* Registers a new ability using the Abilities API. It requires three steps:
*
* 1. Hook into the `wp_abilities_api_init` action.
* 1. Hook into the `wp_abilities_api_init` action, the recommended deterministic
* registration point. Callers registering after `init` must do so before
* relevant discovery, snapshot, or use.
* 2. Call `wp_register_ability()` with a namespaced name and configuration.
* 3. Provide execute and permission callbacks.
*
Expand DownExpand Up@@ -220,6 +226,9 @@
* This allows abilities to be invoked via HTTP requests to the WordPress REST API.
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Abilities_Registry::register()
* @see wp_register_ability_category()
Expand DownExpand Up@@ -276,16 +285,16 @@
* @return WP_Ability|null The registered ability instance on success, `null` on failure.
*/
function wp_register_ability( string $name, array $args ): ?WP_Ability {
if ( ! doing_action( 'wp_abilities_api_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_init, 2: string value of the ability name. */
__( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ),
'<code>wp_abilities_api_init</code>',
/* translators: 1: init, 2: string value of the ability name. */
__( 'Abilities cannot be registered before the %1$s action has fired. The ability %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $name ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand DownExpand Up@@ -598,7 +607,12 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* discoverability and management. Ability categories must be registered before abilities
* that reference them.
*
* Ability categories must be registered on the `wp_abilities_api_categories_init` action hook.
* Register ability categories on the `wp_abilities_api_categories_init` action
* hook, the recommended deterministic registration point. Registration after
* the `init` action has fired is also supported. Callers registering afterward
* are responsible for doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability category before `init` will fail and
* trigger a `_doing_it_wrong()` notice.
*
* Example:
*
Expand All@@ -614,6 +628,9 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' );
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Ability_Categories_Registry::register()
* @see wp_register_ability()
Expand All@@ -631,16 +648,16 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* @return WP_Ability_Category|null The registered ability category instance on success, `null` on failure.
*/
function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category {
if ( ! doing_action( 'wp_abilities_api_categories_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_categories_init, 2: ability category slug. */
__( 'Ability categories must be registered on the %1$s action. The ability category %2$s was not registered.' ),
'<code>wp_abilities_api_categories_init</code>',
/* translators: 1: init, 2: ability category slug. */
__( 'Ability categories cannot be registered before the %1$s action has fired. The ability category %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $slug ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand Down
19 changes: 11 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbility.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -129,26 +129,31 @@ public function test_register_ability_invalid_name(): void {
}

/**
* Tests registering an ability when `wp_abilities_api_init` action has not fired.
* Tests registering an ability after the `wp_abilities_api_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability
* @ticket 65583
*/
public function test_register_ability_no_abilities_api_init_action(): void {
public function test_register_ability_after_abilities_api_init_action(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_init' ) );

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability::class, $result );
$this->assertTrue( wp_has_ability( self::$test_ability_name ) );
$this->assertSame( $result, wp_get_ability( self::$test_ability_name ) );
$this->assertArrayHasKey( self::$test_ability_name, wp_get_abilities() );
}

/**
* Tests registering an ability when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Abilities_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability
*/
public function test_register_ability_no_init_action(): void {
global $wp_actions;
Expand All@@ -159,8 +164,6 @@ public function test_register_ability_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_abilities_init_action();

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

// Restore the original action count.
Expand Down
17 changes: 9 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbilityCategory.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,29 +54,32 @@ private function simulate_doing_wp_ability_categories_init_action() {
}

/**
* Test registering ability category before `wp_abilities_api_categories_init` hook.
* Tests registering an ability category after the `wp_abilities_api_categories_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability_category
* @ticket 65583
*/
public function test_register_category_before_init_hook(): void {
public function test_register_category_after_categories_init_hook(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_categories_init' ) );

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
);

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability_Category::class, $result );
$this->assertTrue( wp_has_ability_category( self::$test_ability_category_name ) );
}

/**
* Tests registering an ability category when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Ability_Categories_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability_category
*/
public function test_register_ability_category_no_init_action(): void {
global $wp_actions;
Expand All@@ -87,8 +90,6 @@ public function test_register_ability_category_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_ability_categories_init_action();

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
Expand Down
Loading
, '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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions src/wp-includes/abilities-api.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,9 +19,12 @@
*
* ## Working with Abilities
*
* Abilities must be registered on the `wp_abilities_api_init` action hook.
* Attempting to register an ability outside of this hook will fail and
* trigger a `_doing_it_wrong()` notice.
* Register abilities on the `wp_abilities_api_init` action hook, the recommended
* deterministic registration point. Registration after the `init` action has
* fired is also supported. Callers registering afterward are responsible for
* doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability before the `init` action has fired will
* fail and trigger a `_doing_it_wrong()` notice.

* Example:
*
Expand DownExpand Up@@ -72,7 +75,8 @@
*
* ## Best Practices
*
* - Always register abilities on the `wp_abilities_api_init` hook.
* - Register abilities on the `wp_abilities_api_init` hook, the recommended
* deterministic registration point.
* - Use namespaced ability names to prevent conflicts.
* - Implement robust permission checks in permission callbacks.
* - Provide an `input_schema` to ensure data integrity and document expected inputs.
Expand All@@ -90,7 +94,9 @@
/**
* Registers a new ability using the Abilities API. It requires three steps:
*
* 1. Hook into the `wp_abilities_api_init` action.
* 1. Hook into the `wp_abilities_api_init` action, the recommended deterministic
* registration point. Callers registering after `init` must do so before
* relevant discovery, snapshot, or use.
* 2. Call `wp_register_ability()` with a namespaced name and configuration.
* 3. Provide execute and permission callbacks.
*
Expand DownExpand Up@@ -220,6 +226,9 @@
* This allows abilities to be invoked via HTTP requests to the WordPress REST API.
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Abilities_Registry::register()
* @see wp_register_ability_category()
Expand DownExpand Up@@ -276,16 +285,16 @@
* @return WP_Ability|null The registered ability instance on success, `null` on failure.
*/
function wp_register_ability( string $name, array $args ): ?WP_Ability {
if ( ! doing_action( 'wp_abilities_api_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_init, 2: string value of the ability name. */
__( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ),
'<code>wp_abilities_api_init</code>',
/* translators: 1: init, 2: string value of the ability name. */
__( 'Abilities cannot be registered before the %1$s action has fired. The ability %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $name ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand DownExpand Up@@ -598,7 +607,12 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* discoverability and management. Ability categories must be registered before abilities
* that reference them.
*
* Ability categories must be registered on the `wp_abilities_api_categories_init` action hook.
* Register ability categories on the `wp_abilities_api_categories_init` action
* hook, the recommended deterministic registration point. Registration after
* the `init` action has fired is also supported. Callers registering afterward
* are responsible for doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability category before `init` will fail and
* trigger a `_doing_it_wrong()` notice.
*
* Example:
*
Expand All@@ -614,6 +628,9 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' );
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Ability_Categories_Registry::register()
* @see wp_register_ability()
Expand All@@ -631,16 +648,16 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* @return WP_Ability_Category|null The registered ability category instance on success, `null` on failure.
*/
function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category {
if ( ! doing_action( 'wp_abilities_api_categories_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_categories_init, 2: ability category slug. */
__( 'Ability categories must be registered on the %1$s action. The ability category %2$s was not registered.' ),
'<code>wp_abilities_api_categories_init</code>',
/* translators: 1: init, 2: ability category slug. */
__( 'Ability categories cannot be registered before the %1$s action has fired. The ability category %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $slug ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand Down
19 changes: 11 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbility.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -129,26 +129,31 @@ public function test_register_ability_invalid_name(): void {
}

/**
* Tests registering an ability when `wp_abilities_api_init` action has not fired.
* Tests registering an ability after the `wp_abilities_api_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability
* @ticket 65583
*/
public function test_register_ability_no_abilities_api_init_action(): void {
public function test_register_ability_after_abilities_api_init_action(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_init' ) );

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability::class, $result );
$this->assertTrue( wp_has_ability( self::$test_ability_name ) );
$this->assertSame( $result, wp_get_ability( self::$test_ability_name ) );
$this->assertArrayHasKey( self::$test_ability_name, wp_get_abilities() );
}

/**
* Tests registering an ability when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Abilities_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability
*/
public function test_register_ability_no_init_action(): void {
global $wp_actions;
Expand All@@ -159,8 +164,6 @@ public function test_register_ability_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_abilities_init_action();

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

// Restore the original action count.
Expand Down
17 changes: 9 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbilityCategory.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,29 +54,32 @@ private function simulate_doing_wp_ability_categories_init_action() {
}

/**
* Test registering ability category before `wp_abilities_api_categories_init` hook.
* Tests registering an ability category after the `wp_abilities_api_categories_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability_category
* @ticket 65583
*/
public function test_register_category_before_init_hook(): void {
public function test_register_category_after_categories_init_hook(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_categories_init' ) );

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
);

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability_Category::class, $result );
$this->assertTrue( wp_has_ability_category( self::$test_ability_category_name ) );
}

/**
* Tests registering an ability category when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Ability_Categories_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability_category
*/
public function test_register_ability_category_no_init_action(): void {
global $wp_actions;
Expand All@@ -87,8 +90,6 @@ public function test_register_ability_category_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_ability_categories_init_action();

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
Expand Down
Loading
, '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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions src/wp-includes/abilities-api.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,9 +19,12 @@
*
* ## Working with Abilities
*
* Abilities must be registered on the `wp_abilities_api_init` action hook.
* Attempting to register an ability outside of this hook will fail and
* trigger a `_doing_it_wrong()` notice.
* Register abilities on the `wp_abilities_api_init` action hook, the recommended
* deterministic registration point. Registration after the `init` action has
* fired is also supported. Callers registering afterward are responsible for
* doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability before the `init` action has fired will
* fail and trigger a `_doing_it_wrong()` notice.

* Example:
*
Expand DownExpand Up@@ -72,7 +75,8 @@
*
* ## Best Practices
*
* - Always register abilities on the `wp_abilities_api_init` hook.
* - Register abilities on the `wp_abilities_api_init` hook, the recommended
* deterministic registration point.
* - Use namespaced ability names to prevent conflicts.
* - Implement robust permission checks in permission callbacks.
* - Provide an `input_schema` to ensure data integrity and document expected inputs.
Expand All@@ -90,7 +94,9 @@
/**
* Registers a new ability using the Abilities API. It requires three steps:
*
* 1. Hook into the `wp_abilities_api_init` action.
* 1. Hook into the `wp_abilities_api_init` action, the recommended deterministic
* registration point. Callers registering after `init` must do so before
* relevant discovery, snapshot, or use.
* 2. Call `wp_register_ability()` with a namespaced name and configuration.
* 3. Provide execute and permission callbacks.
*
Expand DownExpand Up@@ -220,6 +226,9 @@
* This allows abilities to be invoked via HTTP requests to the WordPress REST API.
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Abilities_Registry::register()
* @see wp_register_ability_category()
Expand DownExpand Up@@ -276,16 +285,16 @@
* @return WP_Ability|null The registered ability instance on success, `null` on failure.
*/
function wp_register_ability( string $name, array $args ): ?WP_Ability {
if ( ! doing_action( 'wp_abilities_api_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_init, 2: string value of the ability name. */
__( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ),
'<code>wp_abilities_api_init</code>',
/* translators: 1: init, 2: string value of the ability name. */
__( 'Abilities cannot be registered before the %1$s action has fired. The ability %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $name ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand DownExpand Up@@ -598,7 +607,12 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* discoverability and management. Ability categories must be registered before abilities
* that reference them.
*
* Ability categories must be registered on the `wp_abilities_api_categories_init` action hook.
* Register ability categories on the `wp_abilities_api_categories_init` action
* hook, the recommended deterministic registration point. Registration after
* the `init` action has fired is also supported. Callers registering afterward
* are responsible for doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability category before `init` will fail and
* trigger a `_doing_it_wrong()` notice.
*
* Example:
*
Expand All@@ -614,6 +628,9 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' );
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Ability_Categories_Registry::register()
* @see wp_register_ability()
Expand All@@ -631,16 +648,16 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* @return WP_Ability_Category|null The registered ability category instance on success, `null` on failure.
*/
function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category {
if ( ! doing_action( 'wp_abilities_api_categories_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_categories_init, 2: ability category slug. */
__( 'Ability categories must be registered on the %1$s action. The ability category %2$s was not registered.' ),
'<code>wp_abilities_api_categories_init</code>',
/* translators: 1: init, 2: ability category slug. */
__( 'Ability categories cannot be registered before the %1$s action has fired. The ability category %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $slug ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand Down
19 changes: 11 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbility.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -129,26 +129,31 @@ public function test_register_ability_invalid_name(): void {
}

/**
* Tests registering an ability when `wp_abilities_api_init` action has not fired.
* Tests registering an ability after the `wp_abilities_api_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability
* @ticket 65583
*/
public function test_register_ability_no_abilities_api_init_action(): void {
public function test_register_ability_after_abilities_api_init_action(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_init' ) );

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability::class, $result );
$this->assertTrue( wp_has_ability( self::$test_ability_name ) );
$this->assertSame( $result, wp_get_ability( self::$test_ability_name ) );
$this->assertArrayHasKey( self::$test_ability_name, wp_get_abilities() );
}

/**
* Tests registering an ability when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Abilities_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability
*/
public function test_register_ability_no_init_action(): void {
global $wp_actions;
Expand All@@ -159,8 +164,6 @@ public function test_register_ability_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_abilities_init_action();

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

// Restore the original action count.
Expand Down
17 changes: 9 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbilityCategory.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,29 +54,32 @@ private function simulate_doing_wp_ability_categories_init_action() {
}

/**
* Test registering ability category before `wp_abilities_api_categories_init` hook.
* Tests registering an ability category after the `wp_abilities_api_categories_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability_category
* @ticket 65583
*/
public function test_register_category_before_init_hook(): void {
public function test_register_category_after_categories_init_hook(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_categories_init' ) );

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
);

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability_Category::class, $result );
$this->assertTrue( wp_has_ability_category( self::$test_ability_category_name ) );
}

/**
* Tests registering an ability category when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Ability_Categories_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability_category
*/
public function test_register_ability_category_no_init_action(): void {
global $wp_actions;
Expand All@@ -87,8 +90,6 @@ public function test_register_ability_category_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_ability_categories_init_action();

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
Expand Down
Loading
, '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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions src/wp-includes/abilities-api.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,9 +19,12 @@
*
* ## Working with Abilities
*
* Abilities must be registered on the `wp_abilities_api_init` action hook.
* Attempting to register an ability outside of this hook will fail and
* trigger a `_doing_it_wrong()` notice.
* Register abilities on the `wp_abilities_api_init` action hook, the recommended
* deterministic registration point. Registration after the `init` action has
* fired is also supported. Callers registering afterward are responsible for
* doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability before the `init` action has fired will
* fail and trigger a `_doing_it_wrong()` notice.

* Example:
*
Expand DownExpand Up@@ -72,7 +75,8 @@
*
* ## Best Practices
*
* - Always register abilities on the `wp_abilities_api_init` hook.
* - Register abilities on the `wp_abilities_api_init` hook, the recommended
* deterministic registration point.
* - Use namespaced ability names to prevent conflicts.
* - Implement robust permission checks in permission callbacks.
* - Provide an `input_schema` to ensure data integrity and document expected inputs.
Expand All@@ -90,7 +94,9 @@
/**
* Registers a new ability using the Abilities API. It requires three steps:
*
* 1. Hook into the `wp_abilities_api_init` action.
* 1. Hook into the `wp_abilities_api_init` action, the recommended deterministic
* registration point. Callers registering after `init` must do so before
* relevant discovery, snapshot, or use.
* 2. Call `wp_register_ability()` with a namespaced name and configuration.
* 3. Provide execute and permission callbacks.
*
Expand DownExpand Up@@ -220,6 +226,9 @@
* This allows abilities to be invoked via HTTP requests to the WordPress REST API.
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Abilities_Registry::register()
* @see wp_register_ability_category()
Expand DownExpand Up@@ -276,16 +285,16 @@
* @return WP_Ability|null The registered ability instance on success, `null` on failure.
*/
function wp_register_ability( string $name, array $args ): ?WP_Ability {
if ( ! doing_action( 'wp_abilities_api_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_init, 2: string value of the ability name. */
__( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ),
'<code>wp_abilities_api_init</code>',
/* translators: 1: init, 2: string value of the ability name. */
__( 'Abilities cannot be registered before the %1$s action has fired. The ability %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $name ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand DownExpand Up@@ -598,7 +607,12 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* discoverability and management. Ability categories must be registered before abilities
* that reference them.
*
* Ability categories must be registered on the `wp_abilities_api_categories_init` action hook.
* Register ability categories on the `wp_abilities_api_categories_init` action
* hook, the recommended deterministic registration point. Registration after
* the `init` action has fired is also supported. Callers registering afterward
* are responsible for doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability category before `init` will fail and
* trigger a `_doing_it_wrong()` notice.
*
* Example:
*
Expand All@@ -614,6 +628,9 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' );
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Ability_Categories_Registry::register()
* @see wp_register_ability()
Expand All@@ -631,16 +648,16 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* @return WP_Ability_Category|null The registered ability category instance on success, `null` on failure.
*/
function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category {
if ( ! doing_action( 'wp_abilities_api_categories_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_categories_init, 2: ability category slug. */
__( 'Ability categories must be registered on the %1$s action. The ability category %2$s was not registered.' ),
'<code>wp_abilities_api_categories_init</code>',
/* translators: 1: init, 2: ability category slug. */
__( 'Ability categories cannot be registered before the %1$s action has fired. The ability category %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $slug ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand Down
19 changes: 11 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbility.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -129,26 +129,31 @@ public function test_register_ability_invalid_name(): void {
}

/**
* Tests registering an ability when `wp_abilities_api_init` action has not fired.
* Tests registering an ability after the `wp_abilities_api_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability
* @ticket 65583
*/
public function test_register_ability_no_abilities_api_init_action(): void {
public function test_register_ability_after_abilities_api_init_action(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_init' ) );

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability::class, $result );
$this->assertTrue( wp_has_ability( self::$test_ability_name ) );
$this->assertSame( $result, wp_get_ability( self::$test_ability_name ) );
$this->assertArrayHasKey( self::$test_ability_name, wp_get_abilities() );
}

/**
* Tests registering an ability when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Abilities_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability
*/
public function test_register_ability_no_init_action(): void {
global $wp_actions;
Expand All@@ -159,8 +164,6 @@ public function test_register_ability_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_abilities_init_action();

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

// Restore the original action count.
Expand Down
17 changes: 9 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbilityCategory.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,29 +54,32 @@ private function simulate_doing_wp_ability_categories_init_action() {
}

/**
* Test registering ability category before `wp_abilities_api_categories_init` hook.
* Tests registering an ability category after the `wp_abilities_api_categories_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability_category
* @ticket 65583
*/
public function test_register_category_before_init_hook(): void {
public function test_register_category_after_categories_init_hook(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_categories_init' ) );

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
);

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability_Category::class, $result );
$this->assertTrue( wp_has_ability_category( self::$test_ability_category_name ) );
}

/**
* Tests registering an ability category when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Ability_Categories_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability_category
*/
public function test_register_ability_category_no_init_action(): void {
global $wp_actions;
Expand All@@ -87,8 +90,6 @@ public function test_register_ability_category_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_ability_categories_init_action();

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
Expand Down
Loading
, '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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions src/wp-includes/abilities-api.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,9 +19,12 @@
*
* ## Working with Abilities
*
* Abilities must be registered on the `wp_abilities_api_init` action hook.
* Attempting to register an ability outside of this hook will fail and
* trigger a `_doing_it_wrong()` notice.
* Register abilities on the `wp_abilities_api_init` action hook, the recommended
* deterministic registration point. Registration after the `init` action has
* fired is also supported. Callers registering afterward are responsible for
* doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability before the `init` action has fired will
* fail and trigger a `_doing_it_wrong()` notice.

* Example:
*
Expand DownExpand Up@@ -72,7 +75,8 @@
*
* ## Best Practices
*
* - Always register abilities on the `wp_abilities_api_init` hook.
* - Register abilities on the `wp_abilities_api_init` hook, the recommended
* deterministic registration point.
* - Use namespaced ability names to prevent conflicts.
* - Implement robust permission checks in permission callbacks.
* - Provide an `input_schema` to ensure data integrity and document expected inputs.
Expand All@@ -90,7 +94,9 @@
/**
* Registers a new ability using the Abilities API. It requires three steps:
*
* 1. Hook into the `wp_abilities_api_init` action.
* 1. Hook into the `wp_abilities_api_init` action, the recommended deterministic
* registration point. Callers registering after `init` must do so before
* relevant discovery, snapshot, or use.
* 2. Call `wp_register_ability()` with a namespaced name and configuration.
* 3. Provide execute and permission callbacks.
*
Expand DownExpand Up@@ -220,6 +226,9 @@
* This allows abilities to be invoked via HTTP requests to the WordPress REST API.
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Abilities_Registry::register()
* @see wp_register_ability_category()
Expand DownExpand Up@@ -276,16 +285,16 @@
* @return WP_Ability|null The registered ability instance on success, `null` on failure.
*/
function wp_register_ability( string $name, array $args ): ?WP_Ability {
if ( ! doing_action( 'wp_abilities_api_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_init, 2: string value of the ability name. */
__( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ),
'<code>wp_abilities_api_init</code>',
/* translators: 1: init, 2: string value of the ability name. */
__( 'Abilities cannot be registered before the %1$s action has fired. The ability %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $name ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand DownExpand Up@@ -598,7 +607,12 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* discoverability and management. Ability categories must be registered before abilities
* that reference them.
*
* Ability categories must be registered on the `wp_abilities_api_categories_init` action hook.
* Register ability categories on the `wp_abilities_api_categories_init` action
* hook, the recommended deterministic registration point. Registration after
* the `init` action has fired is also supported. Callers registering afterward
* are responsible for doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability category before `init` will fail and
* trigger a `_doing_it_wrong()` notice.
*
* Example:
*
Expand All@@ -614,6 +628,9 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' );
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Ability_Categories_Registry::register()
* @see wp_register_ability()
Expand All@@ -631,16 +648,16 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* @return WP_Ability_Category|null The registered ability category instance on success, `null` on failure.
*/
function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category {
if ( ! doing_action( 'wp_abilities_api_categories_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_categories_init, 2: ability category slug. */
__( 'Ability categories must be registered on the %1$s action. The ability category %2$s was not registered.' ),
'<code>wp_abilities_api_categories_init</code>',
/* translators: 1: init, 2: ability category slug. */
__( 'Ability categories cannot be registered before the %1$s action has fired. The ability category %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $slug ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand Down
19 changes: 11 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbility.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -129,26 +129,31 @@ public function test_register_ability_invalid_name(): void {
}

/**
* Tests registering an ability when `wp_abilities_api_init` action has not fired.
* Tests registering an ability after the `wp_abilities_api_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability
* @ticket 65583
*/
public function test_register_ability_no_abilities_api_init_action(): void {
public function test_register_ability_after_abilities_api_init_action(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_init' ) );

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability::class, $result );
$this->assertTrue( wp_has_ability( self::$test_ability_name ) );
$this->assertSame( $result, wp_get_ability( self::$test_ability_name ) );
$this->assertArrayHasKey( self::$test_ability_name, wp_get_abilities() );
}

/**
* Tests registering an ability when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Abilities_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability
*/
public function test_register_ability_no_init_action(): void {
global $wp_actions;
Expand All@@ -159,8 +164,6 @@ public function test_register_ability_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_abilities_init_action();

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

// Restore the original action count.
Expand Down
17 changes: 9 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbilityCategory.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,29 +54,32 @@ private function simulate_doing_wp_ability_categories_init_action() {
}

/**
* Test registering ability category before `wp_abilities_api_categories_init` hook.
* Tests registering an ability category after the `wp_abilities_api_categories_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability_category
* @ticket 65583
*/
public function test_register_category_before_init_hook(): void {
public function test_register_category_after_categories_init_hook(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_categories_init' ) );

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
);

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability_Category::class, $result );
$this->assertTrue( wp_has_ability_category( self::$test_ability_category_name ) );
}

/**
* Tests registering an ability category when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Ability_Categories_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability_category
*/
public function test_register_ability_category_no_init_action(): void {
global $wp_actions;
Expand All@@ -87,8 +90,6 @@ public function test_register_ability_category_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_ability_categories_init_action();

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
Expand Down
Loading
, '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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions src/wp-includes/abilities-api.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,9 +19,12 @@
*
* ## Working with Abilities
*
* Abilities must be registered on the `wp_abilities_api_init` action hook.
* Attempting to register an ability outside of this hook will fail and
* trigger a `_doing_it_wrong()` notice.
* Register abilities on the `wp_abilities_api_init` action hook, the recommended
* deterministic registration point. Registration after the `init` action has
* fired is also supported. Callers registering afterward are responsible for
* doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability before the `init` action has fired will
* fail and trigger a `_doing_it_wrong()` notice.

* Example:
*
Expand DownExpand Up@@ -72,7 +75,8 @@
*
* ## Best Practices
*
* - Always register abilities on the `wp_abilities_api_init` hook.
* - Register abilities on the `wp_abilities_api_init` hook, the recommended
* deterministic registration point.
* - Use namespaced ability names to prevent conflicts.
* - Implement robust permission checks in permission callbacks.
* - Provide an `input_schema` to ensure data integrity and document expected inputs.
Expand All@@ -90,7 +94,9 @@
/**
* Registers a new ability using the Abilities API. It requires three steps:
*
* 1. Hook into the `wp_abilities_api_init` action.
* 1. Hook into the `wp_abilities_api_init` action, the recommended deterministic
* registration point. Callers registering after `init` must do so before
* relevant discovery, snapshot, or use.
* 2. Call `wp_register_ability()` with a namespaced name and configuration.
* 3. Provide execute and permission callbacks.
*
Expand DownExpand Up@@ -220,6 +226,9 @@
* This allows abilities to be invoked via HTTP requests to the WordPress REST API.
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Abilities_Registry::register()
* @see wp_register_ability_category()
Expand DownExpand Up@@ -276,16 +285,16 @@
* @return WP_Ability|null The registered ability instance on success, `null` on failure.
*/
function wp_register_ability( string $name, array $args ): ?WP_Ability {
if ( ! doing_action( 'wp_abilities_api_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_init, 2: string value of the ability name. */
__( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ),
'<code>wp_abilities_api_init</code>',
/* translators: 1: init, 2: string value of the ability name. */
__( 'Abilities cannot be registered before the %1$s action has fired. The ability %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $name ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand DownExpand Up@@ -598,7 +607,12 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* discoverability and management. Ability categories must be registered before abilities
* that reference them.
*
* Ability categories must be registered on the `wp_abilities_api_categories_init` action hook.
* Register ability categories on the `wp_abilities_api_categories_init` action
* hook, the recommended deterministic registration point. Registration after
* the `init` action has fired is also supported. Callers registering afterward
* are responsible for doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability category before `init` will fail and
* trigger a `_doing_it_wrong()` notice.
*
* Example:
*
Expand All@@ -614,6 +628,9 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' );
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Ability_Categories_Registry::register()
* @see wp_register_ability()
Expand All@@ -631,16 +648,16 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* @return WP_Ability_Category|null The registered ability category instance on success, `null` on failure.
*/
function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category {
if ( ! doing_action( 'wp_abilities_api_categories_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_categories_init, 2: ability category slug. */
__( 'Ability categories must be registered on the %1$s action. The ability category %2$s was not registered.' ),
'<code>wp_abilities_api_categories_init</code>',
/* translators: 1: init, 2: ability category slug. */
__( 'Ability categories cannot be registered before the %1$s action has fired. The ability category %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $slug ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand Down
19 changes: 11 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbility.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -129,26 +129,31 @@ public function test_register_ability_invalid_name(): void {
}

/**
* Tests registering an ability when `wp_abilities_api_init` action has not fired.
* Tests registering an ability after the `wp_abilities_api_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability
* @ticket 65583
*/
public function test_register_ability_no_abilities_api_init_action(): void {
public function test_register_ability_after_abilities_api_init_action(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_init' ) );

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability::class, $result );
$this->assertTrue( wp_has_ability( self::$test_ability_name ) );
$this->assertSame( $result, wp_get_ability( self::$test_ability_name ) );
$this->assertArrayHasKey( self::$test_ability_name, wp_get_abilities() );
}

/**
* Tests registering an ability when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Abilities_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability
*/
public function test_register_ability_no_init_action(): void {
global $wp_actions;
Expand All@@ -159,8 +164,6 @@ public function test_register_ability_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_abilities_init_action();

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

// Restore the original action count.
Expand Down
17 changes: 9 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbilityCategory.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,29 +54,32 @@ private function simulate_doing_wp_ability_categories_init_action() {
}

/**
* Test registering ability category before `wp_abilities_api_categories_init` hook.
* Tests registering an ability category after the `wp_abilities_api_categories_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability_category
* @ticket 65583
*/
public function test_register_category_before_init_hook(): void {
public function test_register_category_after_categories_init_hook(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_categories_init' ) );

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
);

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability_Category::class, $result );
$this->assertTrue( wp_has_ability_category( self::$test_ability_category_name ) );
}

/**
* Tests registering an ability category when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Ability_Categories_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability_category
*/
public function test_register_ability_category_no_init_action(): void {
global $wp_actions;
Expand All@@ -87,8 +90,6 @@ public function test_register_ability_category_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_ability_categories_init_action();

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
Expand Down
Loading
, '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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions src/wp-includes/abilities-api.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,9 +19,12 @@
*
* ## Working with Abilities
*
* Abilities must be registered on the `wp_abilities_api_init` action hook.
* Attempting to register an ability outside of this hook will fail and
* trigger a `_doing_it_wrong()` notice.
* Register abilities on the `wp_abilities_api_init` action hook, the recommended
* deterministic registration point. Registration after the `init` action has
* fired is also supported. Callers registering afterward are responsible for
* doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability before the `init` action has fired will
* fail and trigger a `_doing_it_wrong()` notice.

* Example:
*
Expand DownExpand Up@@ -72,7 +75,8 @@
*
* ## Best Practices
*
* - Always register abilities on the `wp_abilities_api_init` hook.
* - Register abilities on the `wp_abilities_api_init` hook, the recommended
* deterministic registration point.
* - Use namespaced ability names to prevent conflicts.
* - Implement robust permission checks in permission callbacks.
* - Provide an `input_schema` to ensure data integrity and document expected inputs.
Expand All@@ -90,7 +94,9 @@
/**
* Registers a new ability using the Abilities API. It requires three steps:
*
* 1. Hook into the `wp_abilities_api_init` action.
* 1. Hook into the `wp_abilities_api_init` action, the recommended deterministic
* registration point. Callers registering after `init` must do so before
* relevant discovery, snapshot, or use.
* 2. Call `wp_register_ability()` with a namespaced name and configuration.
* 3. Provide execute and permission callbacks.
*
Expand DownExpand Up@@ -220,6 +226,9 @@
* This allows abilities to be invoked via HTTP requests to the WordPress REST API.
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Abilities_Registry::register()
* @see wp_register_ability_category()
Expand DownExpand Up@@ -276,16 +285,16 @@
* @return WP_Ability|null The registered ability instance on success, `null` on failure.
*/
function wp_register_ability( string $name, array $args ): ?WP_Ability {
if ( ! doing_action( 'wp_abilities_api_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_init, 2: string value of the ability name. */
__( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ),
'<code>wp_abilities_api_init</code>',
/* translators: 1: init, 2: string value of the ability name. */
__( 'Abilities cannot be registered before the %1$s action has fired. The ability %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $name ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand DownExpand Up@@ -598,7 +607,12 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* discoverability and management. Ability categories must be registered before abilities
* that reference them.
*
* Ability categories must be registered on the `wp_abilities_api_categories_init` action hook.
* Register ability categories on the `wp_abilities_api_categories_init` action
* hook, the recommended deterministic registration point. Registration after
* the `init` action has fired is also supported. Callers registering afterward
* are responsible for doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability category before `init` will fail and
* trigger a `_doing_it_wrong()` notice.
*
* Example:
*
Expand All@@ -614,6 +628,9 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' );
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Ability_Categories_Registry::register()
* @see wp_register_ability()
Expand All@@ -631,16 +648,16 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* @return WP_Ability_Category|null The registered ability category instance on success, `null` on failure.
*/
function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category {
if ( ! doing_action( 'wp_abilities_api_categories_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_categories_init, 2: ability category slug. */
__( 'Ability categories must be registered on the %1$s action. The ability category %2$s was not registered.' ),
'<code>wp_abilities_api_categories_init</code>',
/* translators: 1: init, 2: ability category slug. */
__( 'Ability categories cannot be registered before the %1$s action has fired. The ability category %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $slug ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand Down
19 changes: 11 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbility.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -129,26 +129,31 @@ public function test_register_ability_invalid_name(): void {
}

/**
* Tests registering an ability when `wp_abilities_api_init` action has not fired.
* Tests registering an ability after the `wp_abilities_api_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability
* @ticket 65583
*/
public function test_register_ability_no_abilities_api_init_action(): void {
public function test_register_ability_after_abilities_api_init_action(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_init' ) );

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability::class, $result );
$this->assertTrue( wp_has_ability( self::$test_ability_name ) );
$this->assertSame( $result, wp_get_ability( self::$test_ability_name ) );
$this->assertArrayHasKey( self::$test_ability_name, wp_get_abilities() );
}

/**
* Tests registering an ability when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Abilities_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability
*/
public function test_register_ability_no_init_action(): void {
global $wp_actions;
Expand All@@ -159,8 +164,6 @@ public function test_register_ability_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_abilities_init_action();

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

// Restore the original action count.
Expand Down
17 changes: 9 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbilityCategory.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,29 +54,32 @@ private function simulate_doing_wp_ability_categories_init_action() {
}

/**
* Test registering ability category before `wp_abilities_api_categories_init` hook.
* Tests registering an ability category after the `wp_abilities_api_categories_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability_category
* @ticket 65583
*/
public function test_register_category_before_init_hook(): void {
public function test_register_category_after_categories_init_hook(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_categories_init' ) );

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
);

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability_Category::class, $result );
$this->assertTrue( wp_has_ability_category( self::$test_ability_category_name ) );
}

/**
* Tests registering an ability category when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Ability_Categories_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability_category
*/
public function test_register_ability_category_no_init_action(): void {
global $wp_actions;
Expand All@@ -87,8 +90,6 @@ public function test_register_ability_category_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_ability_categories_init_action();

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
Expand Down
Loading
, '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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions src/wp-includes/abilities-api.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,9 +19,12 @@
*
* ## Working with Abilities
*
* Abilities must be registered on the `wp_abilities_api_init` action hook.
* Attempting to register an ability outside of this hook will fail and
* trigger a `_doing_it_wrong()` notice.
* Register abilities on the `wp_abilities_api_init` action hook, the recommended
* deterministic registration point. Registration after the `init` action has
* fired is also supported. Callers registering afterward are responsible for
* doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability before the `init` action has fired will
* fail and trigger a `_doing_it_wrong()` notice.

* Example:
*
Expand DownExpand Up@@ -72,7 +75,8 @@
*
* ## Best Practices
*
* - Always register abilities on the `wp_abilities_api_init` hook.
* - Register abilities on the `wp_abilities_api_init` hook, the recommended
* deterministic registration point.
* - Use namespaced ability names to prevent conflicts.
* - Implement robust permission checks in permission callbacks.
* - Provide an `input_schema` to ensure data integrity and document expected inputs.
Expand All@@ -90,7 +94,9 @@
/**
* Registers a new ability using the Abilities API. It requires three steps:
*
* 1. Hook into the `wp_abilities_api_init` action.
* 1. Hook into the `wp_abilities_api_init` action, the recommended deterministic
* registration point. Callers registering after `init` must do so before
* relevant discovery, snapshot, or use.
* 2. Call `wp_register_ability()` with a namespaced name and configuration.
* 3. Provide execute and permission callbacks.
*
Expand DownExpand Up@@ -220,6 +226,9 @@
* This allows abilities to be invoked via HTTP requests to the WordPress REST API.
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Abilities_Registry::register()
* @see wp_register_ability_category()
Expand DownExpand Up@@ -276,16 +285,16 @@
* @return WP_Ability|null The registered ability instance on success, `null` on failure.
*/
function wp_register_ability( string $name, array $args ): ?WP_Ability {
if ( ! doing_action( 'wp_abilities_api_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_init, 2: string value of the ability name. */
__( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ),
'<code>wp_abilities_api_init</code>',
/* translators: 1: init, 2: string value of the ability name. */
__( 'Abilities cannot be registered before the %1$s action has fired. The ability %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $name ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand DownExpand Up@@ -598,7 +607,12 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* discoverability and management. Ability categories must be registered before abilities
* that reference them.
*
* Ability categories must be registered on the `wp_abilities_api_categories_init` action hook.
* Register ability categories on the `wp_abilities_api_categories_init` action
* hook, the recommended deterministic registration point. Registration after
* the `init` action has fired is also supported. Callers registering afterward
* are responsible for doing so before relevant discovery, snapshot, or use.
* Attempting to register an ability category before `init` will fail and
* trigger a `_doing_it_wrong()` notice.
*
* Example:
*
Expand All@@ -614,6 +628,9 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' );
*
* @since 6.9.0
* @since 7.1.0 Registration after the `init` action has fired is supported.
* Callers registering afterward must do so before relevant discovery,
* snapshot, or use.
*
* @see WP_Ability_Categories_Registry::register()
* @see wp_register_ability()
Expand All@@ -631,16 +648,16 @@ function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
* @return WP_Ability_Category|null The registered ability category instance on success, `null` on failure.
*/
function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category {
if ( ! doing_action( 'wp_abilities_api_categories_init' ) ) {
if ( ! did_action( 'init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: wp_abilities_api_categories_init, 2: ability category slug. */
__( 'Ability categories must be registered on the %1$s action. The ability category %2$s was not registered.' ),
'<code>wp_abilities_api_categories_init</code>',
/* translators: 1: init, 2: ability category slug. */
__( 'Ability categories cannot be registered before the %1$s action has fired. The ability category %2$s was not registered.' ),
'<code>init</code>',
'<code>' . esc_html( $slug ) . '</code>'
),
'6.9.0'
'7.1.0'
);
return null;
}
Expand Down
19 changes: 11 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbility.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -129,26 +129,31 @@ public function test_register_ability_invalid_name(): void {
}

/**
* Tests registering an ability when `wp_abilities_api_init` action has not fired.
* Tests registering an ability after the `wp_abilities_api_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability
* @ticket 65583
*/
public function test_register_ability_no_abilities_api_init_action(): void {
public function test_register_ability_after_abilities_api_init_action(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_init' ) );

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability::class, $result );
$this->assertTrue( wp_has_ability( self::$test_ability_name ) );
$this->assertSame( $result, wp_get_ability( self::$test_ability_name ) );
$this->assertArrayHasKey( self::$test_ability_name, wp_get_abilities() );
}

/**
* Tests registering an ability when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Abilities_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability
*/
public function test_register_ability_no_init_action(): void {
global $wp_actions;
Expand All@@ -159,8 +164,6 @@ public function test_register_ability_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_abilities_init_action();

$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );

// Restore the original action count.
Expand Down
17 changes: 9 additions & 8 deletions tests/phpunit/tests/abilities-api/wpRegisterAbilityCategory.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,29 +54,32 @@ private function simulate_doing_wp_ability_categories_init_action() {
}

/**
* Test registering ability category before `wp_abilities_api_categories_init` hook.
* Tests registering an ability category after the `wp_abilities_api_categories_init` action.
*
* @ticket 64098
* Registration after `init` is supported. Callers registering afterward are
* responsible for doing so before relevant discovery, snapshot, or use.
*
* @expectedIncorrectUsage wp_register_ability_category
* @ticket 65583
*/
public function test_register_category_before_init_hook(): void {
public function test_register_category_after_categories_init_hook(): void {
$this->assertFalse( doing_action( 'wp_abilities_api_categories_init' ) );

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
);

$this->assertNull( $result );
$this->assertInstanceOf( WP_Ability_Category::class, $result );
$this->assertTrue( wp_has_ability_category( self::$test_ability_category_name ) );
}

/**
* Tests registering an ability category when `init` action has not fired.
*
* @ticket 64098
* @ticket 65583
*
* @expectedIncorrectUsage WP_Ability_Categories_Registry::get_instance
* @expectedIncorrectUsage wp_register_ability_category
*/
public function test_register_ability_category_no_init_action(): void {
global $wp_actions;
Expand All@@ -87,8 +90,6 @@ public function test_register_ability_category_no_init_action(): void {
// Reset the action count to simulate it not being fired.
unset( $wp_actions['init'] );

$this->simulate_doing_wp_ability_categories_init_action();

$result = wp_register_ability_category(
self::$test_ability_category_name,
self::$test_ability_category_args
Expand Down
Loading