REDROOM
PHP 8.2.31
Path:
Logout
Edit File
Size: 15.34 KB
Close
/home/nshryvcy/radiantskinclinics.org/wp-content/plugins/woocommerce/src/Internal/Abilities/REST/RestAbilityFactory.php
Text
Base64
<?php /** * REST Ability Factory class file. */ declare( strict_types=1 ); namespace Automattic\WooCommerce\Internal\Abilities\REST; use Automattic\WooCommerce\Internal\MCP\Transport\WooCommerceRestTransport; defined( 'ABSPATH' ) || exit; /** * Factory class for creating abilities from REST controllers. * * Handles the conversion of WooCommerce REST API endpoints into WordPress abilities * that can be consumed by MCP or other systems. */ class RestAbilityFactory { /** * Register abilities for a REST controller based on configuration. * * @param array $config Controller configuration containing controller class and abilities array. */ public static function register_controller_abilities( array $config ): void { $controller_class = $config['controller']; if ( ! class_exists( $controller_class ) ) { return; } $controller = new $controller_class(); foreach ( $config['abilities'] as $ability_config ) { self::register_single_ability( $controller, $ability_config, $config['route'] ); } } /** * Register a single ability. * * @param object $controller REST controller instance. * @param array $ability_config Ability configuration array. * @param string $route REST route for this controller. */ private static function register_single_ability( $controller, array $ability_config, string $route ): void { // Only proceed if wp_register_ability function exists. if ( ! function_exists( 'wp_register_ability' ) ) { return; } try { $ability_args = array( 'label' => $ability_config['label'], 'description' => $ability_config['description'], 'category' => 'woocommerce-rest', 'input_schema' => self::get_schema_for_operation( $controller, $ability_config['operation'] ), 'output_schema' => self::get_output_schema( $controller, $ability_config['operation'] ), 'execute_callback' => function ( $input ) use ( $controller, $ability_config, $route ) { return self::execute_operation( $controller, $ability_config['operation'], $input, $route ); }, 'permission_callback' => function () use ( $controller, $ability_config ) { return self::check_permission( $controller, $ability_config['operation'] ); }, 'ability_class' => RestAbility::class, 'meta' => array( 'show_in_rest' => true, ), ); // Add readonly annotation for GET operations (list and get). if ( in_array( $ability_config['operation'], array( 'list', 'get' ), true ) ) { $ability_args['meta']['annotations'] = array( 'readonly' => true, ); } wp_register_ability( $ability_config['id'], $ability_args ); } catch ( \Throwable $e ) { // Log the error for debugging but don't break the registration of other abilities. if ( function_exists( 'wc_get_logger' ) ) { wc_get_logger()->error( "Failed to register ability {$ability_config['id']}: " . $e->getMessage(), array( 'source' => 'woocommerce-rest-abilities' ) ); } } } /** * Get input schema based on operation type. * * @param object $controller REST controller instance. * @param string $operation Operation type (list, get, create, update, delete). * @return array Input schema array. */ private static function get_schema_for_operation( $controller, string $operation ): array { switch ( $operation ) { case 'list': // Use controller's collection parameters. if ( method_exists( $controller, 'get_collection_params' ) ) { return self::sanitize_args_to_schema( $controller->get_collection_params() ); } break; case 'create': // Use controller's creatable schema. if ( method_exists( $controller, 'get_endpoint_args_for_item_schema' ) ) { $args = $controller->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ); return self::sanitize_args_to_schema( $args ); } break; case 'update': // Use controller's editable schema + ID. if ( method_exists( $controller, 'get_endpoint_args_for_item_schema' ) ) { $args = $controller->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ); $schema = self::sanitize_args_to_schema( $args ); // Add ID field for update operations. $schema['properties']['id'] = array( 'type' => 'integer', 'description' => __( 'Unique identifier for the resource', 'woocommerce' ), ); // Ensure ID is required. if ( ! isset( $schema['required'] ) ) { $schema['required'] = array(); } if ( ! in_array( 'id', $schema['required'], true ) ) { $schema['required'][] = 'id'; } return $schema; } break; case 'get': case 'delete': // Only need ID. return array( 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'integer', 'description' => __( 'Unique identifier for the resource', 'woocommerce' ), ), ), 'required' => array( 'id' ), ); } // Fallback. return array( 'type' => 'object' ); } /** * Valid JSON Schema types. * * @var array */ private static $valid_types = array( 'string', 'number', 'integer', 'boolean', 'object', 'array', 'null' ); /** * Sanitize WordPress REST args to valid JSON Schema format. * * Converts WordPress REST API argument arrays to JSON Schema by: * - Removing PHP callbacks (sanitize_callback, validate_callback) * - Converting 'required' from boolean-per-field to array-of-names * - Removing WordPress-specific non-schema fields * - Preserving valid JSON Schema properties * - Converting invalid types (date-time, mixed, action) to valid JSON Schema * - Recursively sanitizing nested properties and items * - Deduplicating enum values * * @param array $args WordPress REST API arguments array. * @return array Valid JSON Schema object. */ private static function sanitize_args_to_schema( array $args ): array { $properties = array(); $required = array(); foreach ( $args as $key => $arg ) { $property = array(); // Copy valid JSON Schema fields, normalizing types. if ( isset( $arg['type'] ) ) { $property = self::normalize_type( $property, $arg['type'] ); } if ( isset( $arg['description'] ) ) { $property['description'] = $arg['description']; } if ( isset( $arg['default'] ) ) { $property['default'] = $arg['default']; } if ( isset( $arg['enum'] ) ) { $property['enum'] = self::dedupe_enum( $arg['enum'] ); } if ( isset( $arg['items'] ) ) { $property['items'] = self::sanitize_schema( $arg['items'] ); } if ( isset( $arg['minimum'] ) ) { $property['minimum'] = $arg['minimum']; } if ( isset( $arg['maximum'] ) ) { $property['maximum'] = $arg['maximum']; } if ( isset( $arg['format'] ) && ! isset( $property['format'] ) ) { $property['format'] = $arg['format']; } if ( isset( $arg['properties'] ) ) { $property['properties'] = self::sanitize_schema_properties( $arg['properties'] ); } // Convert readonly to readOnly (JSON Schema format). if ( isset( $arg['readonly'] ) && $arg['readonly'] ) { $property['readOnly'] = true; } // Collect required fields. if ( isset( $arg['required'] ) && true === $arg['required'] ) { $required[] = $key; } $properties[ $key ] = $property; } $schema = array( 'type' => 'object', 'properties' => $properties, ); if ( ! empty( $required ) ) { $schema['required'] = array_unique( $required ); } return $schema; } /** * Recursively sanitize a JSON Schema node. * * Fixes invalid types, deduplicates enums, and recurses into * nested properties and items. * * @param array $schema A JSON Schema node. * @return array Sanitized schema node. */ private static function sanitize_schema( array $schema ): array { if ( isset( $schema['type'] ) ) { $schema = self::normalize_type( $schema, $schema['type'] ); } if ( isset( $schema['enum'] ) ) { $schema['enum'] = self::dedupe_enum( $schema['enum'] ); } // Remove WordPress-style boolean 'required' — JSON Schema requires an array. if ( isset( $schema['required'] ) && is_bool( $schema['required'] ) ) { unset( $schema['required'] ); } if ( isset( $schema['properties'] ) && is_array( $schema['properties'] ) ) { // Collect required fields from nested boolean 'required' before sanitizing. $required = array(); foreach ( $schema['properties'] as $key => $property ) { if ( is_array( $property ) && isset( $property['required'] ) && true === $property['required'] ) { $required[] = $key; } } if ( ! empty( $required ) ) { $schema['required'] = isset( $schema['required'] ) && is_array( $schema['required'] ) ? array_values( array_unique( array_merge( $schema['required'], $required ) ) ) : $required; } $schema['properties'] = self::sanitize_schema_properties( $schema['properties'] ); } if ( isset( $schema['items'] ) && is_array( $schema['items'] ) ) { $schema['items'] = self::sanitize_schema( $schema['items'] ); } return $schema; } /** * Sanitize a map of JSON Schema properties. * * @param array $properties Map of property name to schema. * @return array Sanitized properties map. */ private static function sanitize_schema_properties( array $properties ): array { foreach ( $properties as $key => $property ) { if ( is_array( $property ) ) { $properties[ $key ] = self::sanitize_schema( $property ); } } return $properties; } /** * Normalize a schema type value. * * Handles both string types ('string', 'date-time', etc.) and * array types (['string', 'null']) used for nullable fields. * * @param array $schema The schema node being built. * @param string|array $type The type value to normalize. * @return array Schema with normalized type (or type removed if all invalid). */ private static function normalize_type( array $schema, $type ): array { if ( is_string( $type ) ) { if ( 'date-time' === $type ) { $schema['type'] = 'string'; if ( ! isset( $schema['format'] ) ) { $schema['format'] = 'date-time'; } } elseif ( 'action' === $type ) { $schema['type'] = 'object'; } elseif ( in_array( $type, self::$valid_types, true ) ) { $schema['type'] = $type; } else { unset( $schema['type'] ); } return $schema; } if ( is_array( $type ) ) { $normalized = array(); foreach ( $type as $single ) { if ( ! is_string( $single ) ) { continue; } if ( 'date-time' === $single ) { $single = 'string'; if ( ! isset( $schema['format'] ) ) { $schema['format'] = 'date-time'; } } elseif ( 'action' === $single ) { $single = 'object'; } elseif ( ! in_array( $single, self::$valid_types, true ) ) { continue; } $normalized[] = $single; } $normalized = array_values( array_unique( $normalized ) ); if ( empty( $normalized ) ) { unset( $schema['type'] ); } elseif ( 1 === count( $normalized ) ) { $schema['type'] = $normalized[0]; } else { $schema['type'] = $normalized; } return $schema; } // Non-string, non-array type — remove it. unset( $schema['type'] ); return $schema; } /** * Remove duplicate enum values while preserving order. * * Uses JSON encoding for fingerprinting to correctly handle * mixed scalar types (1 vs '1'), nulls, and complex values (arrays). * * @param array $values Enum values. * @return array Deduplicated enum values. */ private static function dedupe_enum( array $values ): array { $seen = array(); $unique = array(); foreach ( $values as $value ) { $fingerprint = wp_json_encode( $value ); if ( isset( $seen[ $fingerprint ] ) ) { continue; } $seen[ $fingerprint ] = true; $unique[] = $value; } return $unique; } /** * Get output schema for operation. * * @param object $controller REST controller instance. * @param string $operation Operation type. * @return array Output schema array. */ private static function get_output_schema( $controller, string $operation ): array { if ( method_exists( $controller, 'get_item_schema' ) ) { $schema = self::sanitize_schema( $controller->get_item_schema() ); if ( 'list' === $operation ) { // For list operations, return object wrapping array of items. // This ensures MCP compatibility while maintaining REST structure. return array( 'type' => 'object', 'properties' => array( 'data' => array( 'type' => 'array', 'items' => $schema, ), ), ); } elseif ( 'delete' === $operation ) { // For delete operations, return simple confirmation. return array( 'type' => 'object', 'properties' => array( 'deleted' => array( 'type' => 'boolean' ), 'previous' => $schema, ), ); } // For get, create, update operations. return $schema; } return array( 'type' => 'object' ); } /** * Execute the REST operation. * * @param object $controller REST controller instance. * @param string $operation Operation type. * @param array $input Input parameters. * @param string $route REST route for this controller. * @return mixed Operation result. */ private static function execute_operation( $controller, string $operation, array $input, string $route ) { $method = self::get_http_method_for_operation( $operation ); // Build final route - add ID for single item operations. $request_route = $route; if ( isset( $input['id'] ) && in_array( $operation, array( 'get', 'update', 'delete' ), true ) ) { $request_route .= '/' . intval( $input['id'] ); unset( $input['id'] ); } // Create REST request. $request = new \WP_REST_Request( $method, $request_route ); foreach ( $input as $key => $value ) { $request->set_param( $key, $value ); } // Dispatch through REST API for proper validation and permissions. $response = rest_do_request( $request ); if ( is_wp_error( $response ) ) { return $response; } $data = $response instanceof \WP_REST_Response ? $response->get_data() : $response; // For list operations, wrap in data object to match schema. if ( 'list' === $operation ) { return array( 'data' => $data ); } return $data; } /** * Get HTTP method for a given operation type. * * @param string $operation Operation type (list, get, create, update, delete). * @return string HTTP method (GET, POST, PUT, DELETE). */ private static function get_http_method_for_operation( string $operation ): string { $method_map = array( 'list' => 'GET', 'get' => 'GET', 'create' => 'POST', 'update' => 'PUT', 'delete' => 'DELETE', ); return $method_map[ $operation ] ?? 'GET'; } /** * Check permissions for MCP operations. * * @param object $controller REST controller instance. * @param string $operation Operation type. * @return bool Whether permission is granted. */ private static function check_permission( $controller, string $operation ): bool { // Get HTTP method for the operation. $method = self::get_http_method_for_operation( $operation ); /** * Filter to check REST ability permissions for HTTP method. * * @since 10.3.0 * @param bool $allowed Whether the operation is allowed. Default false. * @param string $method HTTP method (GET, POST, PUT, DELETE). * @param object $controller REST controller instance. */ return apply_filters( 'woocommerce_check_rest_ability_permissions_for_method', false, $method, $controller ); } }
Save
Close
Exit & Reset
Text mode: syntax highlighting auto-detects file type.
Directory Contents
Dirs: 0 × Files: 2
Delete Selected
Select All
Select None
Sort:
Name
Size
Modified
Enable drag-to-move
Name
Size
Perms
Modified
Actions
RestAbility.php
1.16 KB
lrw-r--r--
2025-10-06 17:56:06
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
RestAbilityFactory.php
15.34 KB
lrw-r--r--
2026-03-30 17:12:24
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
Zip Selected
If ZipArchive is unavailable, a
.tar
will be created (no compression).