Make WordPress Core

Changeset 65306


Ignore:
Timestamp:
11/06/2026 08:36:45 AM (less than one hour ago)
Author:
gziolo
Message:

Abilities API: Document input preparation for direct permission checks

WP_Ability::check_permissions() does not prepare the input. Callers outside of WP_Ability::execute() must run normalize_input(), validate_input(), and coerce_input() first, or the permission callback may get different input than during execute().

Developed in ​https://github.com/WordPress/wordpress-develop/pull/15514.

Props euvoor, gziolo, zaguiini, jorbin.
See #66482.

Location:
trunk/src/wp-includes
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/abilities-api.php

    r64685 r65306  
    200200 *
    201201 * The execute callback performs the ability's core functionality. It receives
    202  * the normalized, validated input when an input schema is defined, and no arguments
    203  * otherwise. It returns either a result or `WP_Error` on failure. Exceptions thrown
    204  * by execute or permission callbacks are caught and converted to a `WP_Error`.
     202 * the normalized, validated, and coerced input when an input schema is defined,
     203 * and no arguments otherwise. It returns either a result or `WP_Error` on failure.
     204 * Exceptions thrown by execute or permission callbacks are caught and converted
     205 * to a `WP_Error`.
    205206 *
    206207 *     function my_plugin_analyze_text( string $input ): string|WP_Error {
    … …  
    225226 * with no required capabilities.
    226227 *
    227  * The permission callback determines whether the ability can be executed.
    228  * It receives the same input as the execute callback and must return a
    229  * boolean or `WP_Error`. Common use cases include checking user capabilities,
    230  * validating API keys, or verifying system state. In the example above, every
    231  * execution requires `edit_posts`, and the callback only adds the check that
    232  * depends on the input:
     228 * The permission callback determines whether the ability can be executed and
     229 * must return a boolean or `WP_Error`. When called through `WP_Ability::execute()`,
     230 * it receives the same normalized, validated, and coerced input as the execute
     231 * callback. Common use cases include checking user capabilities, validating API
     232 * keys, or verifying system state. In the example above, every execution requires
     233 * `edit_posts`, and the callback only adds the check that depends on the input:
    233234 *
    234235 *     function my_plugin_can_analyze_text( string $input ): bool|WP_Error {
    … …  
    242243 * ### Execution Lifecycle
    243244 *
    244  * Call `WP_Ability::execute()` to normalize and validate input, check permissions,
    245  * invoke the execute callback, and validate output. It returns the result or a
    246  * `WP_Error` on failure. Custom ability classes can override this behavior.
     245 * Call `WP_Ability::execute()` to normalize, validate, and coerce input, check
     246 * permissions, invoke the execute callback, and validate output. It returns the
     247 * result or a `WP_Error` on failure. Custom ability classes can override this
     248 * behavior.
     249 *
     250 * Integrations that call `WP_Ability::check_permissions()` directly must prepare
     251 * the input first. See `WP_Ability::check_permissions()` for the required steps.
    247252 *
    248253 * Hooks run in this order, and processing stops at the first failure:
    … …  
    252257 *  3. `wp_ability_normalize_input`, `wp_ability_validate_input`, and
    253258 *     `wp_ability_permission_result` filters. The input validation filter runs only
    254  *     when a non-empty input schema is defined.
     259 *     when a non-empty input schema is defined. Input coercion has no filter. It runs
     260 *     between validation and the permission check, only when a non-empty input
     261 *     schema is defined.
    255262 *  4. `wp_before_execute_ability` action.
    256263 *  5. `wp_ability_execute_result` and `wp_ability_validate_output` filters.
  • trunk/src/wp-includes/abilities-api/class-wp-ability.php

    r65044 r65306  
    567567         *
    568568         * Also applies schema format sanitization, such as stripping tags for
    569          * `text-field` and sanitizing URLs for `uri`, using rest_sanitize_value_from_schema().
    570          *
    571          * Call validate_input() before this method. Coercing unvalidated input can
     569         * `text-field` and sanitizing URLs for `uri`, using `rest_sanitize_value_from_schema()`.
     570         *
     571         * Call `validate_input()` before this method. Coercing unvalidated input can
    572572         * remove unknown properties or cast invalid values, hiding validation errors.
    573573         * This method does not apply defaults or run input validation filters.
    … …  
    578578         * @since 7.2.0
    579579         *
    580          * @see validate_input()
     580         * @see WP_Ability::validate_input()
    581581         *
    582582         * @param mixed $input Validated ability input.
    583          * @return mixed|WP_Error Coerced input, or WP_Error when it cannot be safely coerced.
     583         * @return mixed|WP_Error Coerced input, or `WP_Error` when it cannot be safely coerced.
    584584         */
    585585        public function coerce_input( $input ) {
    … …  
    752752         * Checks whether the ability has the necessary permissions.
    753753         *
    754          * Please note that input is not automatically validated against the input schema.
    755          * Use `validate_input()` method to validate input before calling this method if needed.
     754         * This method does not prepare the input. Use `execute()` to run the full ability
     755         * lifecycle, which prepares the input before calling this method.
     756         *
     757         * When calling this method directly, prepare the input in the same order as
     758         * `execute()`, and stop if any step returns a `WP_Error`:
     759         *
     760         *  1. `normalize_input()` applies the schema default and returns the input.
     761         *  2. `validate_input()` checks the input against the schema and returns `true`.
     762         *  3. `coerce_input()` converts values to the schema types and returns the input
     763         *     to pass to this method.
     764         *
     765         * Skipping these steps means the permission callback may receive different input
     766         * than it does during `execute()`, such as a missing schema default or a string
     767         * instead of an integer.
    756768         *
    757769         * The checks run in this order:
    … …  
    769781         * @since 7.2.0 Added the `required_capabilities` check.
    770782         *
    771          * @see validate_input()
    772          *
    773          * @param mixed $input Optional. The valid input data for permission checking. Default `null`.
     783         * @see WP_Ability::normalize_input()
     784         * @see WP_Ability::validate_input()
     785         * @see WP_Ability::coerce_input()
     786         * @see WP_Ability::execute()
     787         *
     788         * @param mixed $input Optional. The normalized, validated, and coerced input data for permission checking.
     789         *                     Default `null`.
    774790         * @return bool|WP_Error Whether the ability has the necessary permission.
    775791         */
Note: See TracChangeset for help on using the changeset viewer.