From 75e8caddc794fc00aa64312491f83178133aa67f Mon Sep 17 00:00:00 2001
From: Vedanshmini26 <97348795+Vedanshmini26@users.noreply.github.com>
Date: Thu, 9 Apr 2026 12:48:44 +0530
Subject: [PATCH 1/4] Fix #8592: Include private pages in parent dropdown for
users with read_private_posts capability
---
src/wp-admin/includes/class-wp-posts-list-table.php | 4 ++++
src/wp-admin/includes/meta-boxes.php | 5 +++++
2 files changed, 9 insertions(+)
diff --git a/src/wp-admin/includes/class-wp-posts-list-table.php b/src/wp-admin/includes/class-wp-posts-list-table.php
index c7d10fca217ef..cf1ff8bd476d2 100644
--- a/src/wp-admin/includes/class-wp-posts-list-table.php
+++ b/src/wp-admin/includes/class-wp-posts-list-table.php
@@ -1885,6 +1885,10 @@ public function inline_edit() {
'sort_column' => 'menu_order, post_title',
);
+ if ( current_user_can( $post_type_object->cap->read_private_posts ) ) {
+ $dropdown_args['post_status'] = array( 'publish', 'private' );
+ }
+
if ( $bulk ) {
$dropdown_args['show_option_no_change'] = __( '— No Change —' );
$dropdown_args['id'] = 'bulk_edit_post_parent';
diff --git a/src/wp-admin/includes/meta-boxes.php b/src/wp-admin/includes/meta-boxes.php
index 0884c110b65bd..e5425a1cd6f41 100644
--- a/src/wp-admin/includes/meta-boxes.php
+++ b/src/wp-admin/includes/meta-boxes.php
@@ -1012,6 +1012,11 @@ function page_attributes_meta_box( $post ) {
'echo' => 0,
);
+ $post_type_object = get_post_type_object( $post->post_type );
+ if ( current_user_can( $post_type_object->cap->read_private_posts ) ) {
+ $dropdown_args['post_status'] = array( 'publish', 'private' );
+ }
+
/**
* Filters the arguments used to generate a Pages drop-down element.
*
From 3919716e70779c87bc1accb59f515a73e1b7b667 Mon Sep 17 00:00:00 2001
From: Vedanshmini26 <97348795+Vedanshmini26@users.noreply.github.com>
Date: Thu, 9 Apr 2026 13:13:40 +0530
Subject: [PATCH 2/4] Feature #37522: Add wp_lostpassword_form() function for
embedding the lost password form anywhere
---
src/wp-includes/general-template.php | 101 +++++++++++++++++++++++++++
src/wp-login.php | 32 ++-------
2 files changed, 107 insertions(+), 26 deletions(-)
diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php
index 47e2aeb2ebb05..e2de8969d65f3 100644
--- a/src/wp-includes/general-template.php
+++ b/src/wp-includes/general-template.php
@@ -651,6 +651,107 @@ function wp_login_form( $args = array() ) {
}
}
+/**
+ * Outputs or returns the lost password form for use anywhere on a WordPress site.
+ *
+ * @since 7.1.0
+ *
+ * @param array $args {
+ * Optional. Array of arguments to control the form output. Default empty array.
+ *
+ * @type bool $echo Whether to display the form or return the output. Default true.
+ * @type string $redirect URL to redirect to after submitting the form. Default empty string.
+ * @type string $form_id ID attribute for the form element. Default 'lostpasswordform'.
+ * @type string $id_username ID attribute for the username input. Default 'user_login'.
+ * @type string $id_submit ID attribute for the submit button. Default 'wp-submit'.
+ * @type string $label_username Label for the username input. Default 'Username or Email Address'.
+ * @type string $label_submit Label for the submit button. Default 'Get New Password'.
+ * }
+ * @return void|string Void if 'echo' argument is true, lost password form HTML if 'echo' is false.
+ */
+function wp_lostpassword_form( $args = array() ) {
+ $defaults = array(
+ 'echo' => true,
+ 'redirect' => '',
+ 'form_id' => 'lostpasswordform',
+ 'id_username' => 'user_login',
+ 'id_submit' => 'wp-submit',
+ 'label_username' => __( 'Username or Email Address' ),
+ 'label_submit' => __( 'Get New Password' ),
+ );
+
+ /**
+ * Filters the default lost password form arguments.
+ *
+ * @since 7.1.0
+ *
+ * @see wp_lostpassword_form()
+ *
+ * @param array $defaults An array of default lost password form arguments.
+ */
+ $args = wp_parse_args( $args, apply_filters( 'lostpassword_form_defaults', $defaults ) );
+
+ $user_login = '';
+ if ( isset( $_POST['user_login'] ) && is_string( $_POST['user_login'] ) ) {
+ $user_login = wp_unslash( $_POST['user_login'] );
+ }
+
+ /**
+ * Filters content to display at the top of the lost password form.
+ *
+ * The filter evaluates just following the opening form tag element.
+ *
+ * @since 7.1.0
+ *
+ * @param string $content Content to display. Default empty.
+ * @param array $args Array of lost password form arguments.
+ */
+ $lostpassword_form_top = apply_filters( 'lostpassword_form_top', '', $args );
+
+ /**
+ * Filters content to display at the bottom of the lost password form.
+ *
+ * The filter evaluates just preceding the closing form tag element.
+ *
+ * @since 7.1.0
+ *
+ * @param string $content Content to display. Default empty.
+ * @param array $args Array of lost password form arguments.
+ */
+ $lostpassword_form_bottom = apply_filters( 'lostpassword_form_bottom', '', $args );
+
+ ob_start();
+ ?>
+
+ true,
+ 'redirect' => $redirect_to,
+ )
+ );
?>
-
-
Date: Thu, 9 Apr 2026 15:41:40 +0530
Subject: [PATCH 3/4] Trigger CI
From e870f28f61a64cc23434cc09bcad22173f0353cf Mon Sep 17 00:00:00 2001
From: Vedanshmini26 <97348795+Vedanshmini26@users.noreply.github.com>
Date: Thu, 9 Apr 2026 16:04:21 +0530
Subject: [PATCH 4/4] Feature #64990: Add filtering support to
wp_get_abilities() via \$args array
---
src/wp-includes/abilities-api.php | 192 ++++++++++++++++--
...s-wp-rest-abilities-v1-list-controller.php | 34 ++--
2 files changed, 197 insertions(+), 29 deletions(-)
diff --git a/src/wp-includes/abilities-api.php b/src/wp-includes/abilities-api.php
index 73ba658f3f10d..76deefb1320ec 100644
--- a/src/wp-includes/abilities-api.php
+++ b/src/wp-includes/abilities-api.php
@@ -396,33 +396,201 @@ function wp_get_ability( string $name ): ?WP_Ability {
}
/**
- * Retrieves all registered abilities.
+ * Retrieves registered abilities, optionally filtered by the given arguments.
*
- * Returns an array of all ability instances currently registered in the system.
- * Use this for discovery, debugging, or building administrative interfaces.
+ * When called without arguments, returns all registered abilities. When called
+ * with an $args array, returns only abilities that match every specified condition.
*
- * Example:
+ * Filtering pipeline (executed in order):
+ *
+ * 1. Declarative filters (`category`, `namespace`, `meta`) — per-item, AND logic between
+ * arg types, OR logic within multi-value `category` arrays.
+ * 2. `match_callback` — per-item, caller-scoped. Return true to include, false to exclude.
+ * 3. `wp_get_abilities_match` filter — per-item, ecosystem-scoped. Plugins can enforce
+ * universal inclusion rules regardless of what the caller passed.
+ * 4. `result_callback` — on the full matched array, caller-scoped. Sort, slice, or reshape.
+ * 5. `wp_get_abilities_result` filter — on the full array, ecosystem-scoped.
+ *
+ * Steps 1–3 run inside a single loop over the registry — no extra iteration.
+ *
+ * Examples:
*
- * // Prints information about all available abilities.
+ * // All abilities (unchanged behaviour).
* $abilities = wp_get_abilities();
- * foreach ( $abilities as $ability ) {
- * echo $ability->get_label() . ': ' . $ability->get_description() . "\n";
- * }
+ *
+ * // Filter by category.
+ * $abilities = wp_get_abilities( array( 'category' => 'content' ) );
+ *
+ * // Filter by multiple categories (OR logic).
+ * $abilities = wp_get_abilities( array( 'category' => array( 'content', 'settings' ) ) );
+ *
+ * // Filter by namespace.
+ * $abilities = wp_get_abilities( array( 'namespace' => 'woocommerce' ) );
+ *
+ * // Filter by meta.
+ * $abilities = wp_get_abilities( array( 'meta' => array( 'show_in_rest' => true ) ) );
+ *
+ * // Combine filters (AND logic between arg types).
+ * $abilities = wp_get_abilities( array(
+ * 'category' => 'content',
+ * 'namespace' => 'core',
+ * 'meta' => array( 'show_in_rest' => true ),
+ * ) );
+ *
+ * // Caller-scoped per-item callback.
+ * $abilities = wp_get_abilities( array(
+ * 'match_callback' => function ( WP_Ability $ability ) {
+ * return current_user_can( 'manage_options' );
+ * },
+ * ) );
+ *
+ * // Caller-scoped result callback (sort + paginate).
+ * $abilities = wp_get_abilities( array(
+ * 'result_callback' => function ( array $abilities ) {
+ * usort( $abilities, fn( $a, $b ) => strcasecmp( $a->get_label(), $b->get_label() ) );
+ * return array_slice( $abilities, 0, 10 );
+ * },
+ * ) );
*
* @since 6.9.0
+ * @since 7.1.0 Added the `$args` parameter for filtering support.
*
* @see WP_Abilities_Registry::get_all_registered()
*
- * @return WP_Ability[] An array of registered WP_Ability instances. Returns an empty
- * array if no abilities are registered or if the registry is unavailable.
+ * @param array $args {
+ * Optional. Arguments to filter the returned abilities. Default empty array (returns all).
+ *
+ * @type string|string[] $category Filter by category slug. A single string or an array of
+ * slugs — abilities matching any of the given slugs are
+ * included (OR logic within this arg type).
+ * @type string $namespace Filter by ability namespace prefix. Pass the namespace
+ * without a trailing slash, e.g. `'woocommerce'` matches
+ * `'woocommerce/create-order'`.
+ * @type array $meta Filter by meta key/value pairs. All conditions must
+ * match (AND logic). Supports nested arrays for structured
+ * meta, e.g. `array( 'mcp' => array( 'public' => true ) )`.
+ * @type callable $match_callback Optional. A callback invoked per ability after declarative
+ * filters. Receives a WP_Ability instance, returns bool.
+ * Return true to include, false to exclude.
+ * @type callable $result_callback Optional. A callback invoked once on the full matched
+ * array. Receives WP_Ability[], must return WP_Ability[].
+ * Use for sorting, slicing, or reshaping the result.
+ * }
+ * @return WP_Ability[] An array of registered WP_Ability instances matching the given args.
+ * Returns an empty array if no abilities are registered, the registry is
+ * unavailable, or no abilities match the given args.
*/
-function wp_get_abilities(): array {
+function wp_get_abilities( array $args = array() ): array {
$registry = WP_Abilities_Registry::get_instance();
if ( null === $registry ) {
return array();
}
- return $registry->get_all_registered();
+ $abilities = $registry->get_all_registered();
+
+ // Bail early when no filtering is requested.
+ if ( empty( $args ) ) {
+ return $abilities;
+ }
+
+ $category = isset( $args['category'] ) ? (array) $args['category'] : array();
+ $namespace = isset( $args['namespace'] ) && is_string( $args['namespace'] ) ? rtrim( $args['namespace'], '/' ) . '/' : '';
+ $meta = isset( $args['meta'] ) && is_array( $args['meta'] ) ? $args['meta'] : array();
+ $match_callback = isset( $args['match_callback'] ) && is_callable( $args['match_callback'] ) ? $args['match_callback'] : null;
+ $result_callback = isset( $args['result_callback'] ) && is_callable( $args['result_callback'] ) ? $args['result_callback'] : null;
+
+ $matched = array();
+
+ foreach ( $abilities as $ability ) {
+ // Step 1a: Filter by category (OR logic within the arg).
+ if ( ! empty( $category ) && ! in_array( $ability->get_category(), $category, true ) ) {
+ continue;
+ }
+
+ // Step 1b: Filter by namespace prefix.
+ if ( '' !== $namespace && ! str_starts_with( $ability->get_name(), $namespace ) ) {
+ continue;
+ }
+
+ // Step 1c: Filter by meta key/value pairs (AND logic, supports nested arrays).
+ if ( ! empty( $meta ) && ! wp_get_abilities_match_meta( $ability->get_meta(), $meta ) ) {
+ continue;
+ }
+
+ // Step 2: Caller-scoped per-item callback.
+ $include = true;
+ if ( null !== $match_callback ) {
+ $include = (bool) call_user_func( $match_callback, $ability );
+ }
+
+ /**
+ * Filters whether an individual ability should be included in the result set.
+ *
+ * Fires after the declarative filters and the caller-scoped match_callback.
+ * Plugins can use this to enforce universal inclusion rules regardless of
+ * what the caller passed in $args.
+ *
+ * @since 7.1.0
+ *
+ * @param bool $include Whether to include the ability. Default true (after declarative filters pass).
+ * @param WP_Ability $ability The ability instance being evaluated.
+ * @param array $args The full $args array passed to wp_get_abilities().
+ */
+ $include = (bool) apply_filters( 'wp_get_abilities_match', $include, $ability, $args );
+
+ if ( $include ) {
+ $matched[] = $ability;
+ }
+ }
+
+ // Step 4: Caller-scoped result callback.
+ if ( null !== $result_callback ) {
+ $matched = (array) call_user_func( $result_callback, $matched );
+ }
+
+ /**
+ * Filters the full list of matched abilities after all per-item filtering is complete.
+ *
+ * Fires after the caller-scoped result_callback. Plugins can use this to sort,
+ * paginate, or reshape the final result set universally.
+ *
+ * @since 7.1.0
+ *
+ * @param WP_Ability[] $matched The matched abilities after all filtering.
+ * @param array $args The full $args array passed to wp_get_abilities().
+ */
+ return (array) apply_filters( 'wp_get_abilities_result', $matched, $args );
+}
+
+/**
+ * Checks whether an ability's meta array matches a set of required key/value conditions.
+ *
+ * All conditions must match (AND logic). Supports nested arrays for structured meta,
+ * e.g. `array( 'mcp' => array( 'public' => true ) )`.
+ *
+ * @since 7.1.0
+ * @access private
+ *
+ * @param array $meta The ability's meta array.
+ * @param array $conditions The required key/value conditions to match against.
+ * @return bool True if all conditions match, false otherwise.
+ */
+function wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
+ foreach ( $conditions as $key => $value ) {
+ if ( ! array_key_exists( $key, $meta ) ) {
+ return false;
+ }
+
+ if ( is_array( $value ) ) {
+ if ( ! is_array( $meta[ $key ] ) || ! wp_get_abilities_match_meta( $meta[ $key ], $value ) ) {
+ return false;
+ }
+ } elseif ( $meta[ $key ] !== $value ) {
+ return false;
+ }
+ }
+
+ return true;
}
/**
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php
index e3ce0c4f2e03e..080d6c52ede34 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php
@@ -86,26 +86,20 @@ public function register_routes(): void {
* @return WP_REST_Response Response object on success.
*/
public function get_items( $request ) {
- $abilities = array_filter(
- wp_get_abilities(),
- static function ( $ability ) {
- return $ability->get_meta_item( 'show_in_rest' );
- }
+ $query_args = array(
+ 'meta' => array( 'show_in_rest' => true ),
);
- // Filter by ability category if specified.
- $category = $request['category'];
- if ( ! empty( $category ) ) {
- $abilities = array_filter(
- $abilities,
- static function ( $ability ) use ( $category ) {
- return $ability->get_category() === $category;
- }
- );
- // Reset array keys after filtering.
- $abilities = array_values( $abilities );
+ if ( ! empty( $request['category'] ) ) {
+ $query_args['category'] = $request['category'];
+ }
+
+ if ( ! empty( $request['namespace'] ) ) {
+ $query_args['namespace'] = $request['namespace'];
}
+ $abilities = wp_get_abilities( $query_args );
+
$page = $request['page'];
$per_page = $request['per_page'];
$offset = ( $page - 1 ) * $per_page;
@@ -432,12 +426,18 @@ public function get_collection_params(): array {
'minimum' => 1,
'maximum' => 100,
),
- 'category' => array(
+ 'category' => array(
'description' => __( 'Limit results to abilities in specific ability category.' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
),
+ 'namespace' => array(
+ 'description' => __( 'Limit results to abilities in a specific namespace.' ),
+ 'type' => 'string',
+ 'sanitize_callback' => 'sanitize_key',
+ 'validate_callback' => 'rest_validate_request_arg',
+ ),
);
}
}