PluginProbe
WindPress – Tailwind CSS integration for WordPress / 3.2.90
WindPress – Tailwind CSS integration for WordPress v3.2.90
3.2.90 3.2.89 3.2.88 3.2.87 3.2.86 3.2.85 3.2.84 3.2.83 3.2.82 3.2.81 trunk 3.0.0 3.0.1 3.0.10 3.0.11 3.0.12 3.0.13 3.0.14 3.0.15 3.0.16 3.0.17 3.0.2 3.0.3 3.0.4 3.0.5 All 144 releases
windpress / vendor / wordpress / abilities-api / includes / rest-api / endpoints / class-wp-rest-abilities-v1-run-controller.php

class-wp-rest-abilities-v1-run-controller.php in WindPress – Tailwind CSS integration for WordPress 3.2.90, at vendor/wordpress/abilities-api/includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php

245 lines 6.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST API run controller for Abilities API.
4 *
5 * @package WordPress
6 * @subpackage Abilities_API
7 * @since 6.9.0
8 */
9
10 declare( strict_types = 1 );
11
12 /**
13 * Core controller used to execute abilities via the REST API.
14 *
15 * @since 6.9.0
16 *
17 * @see WP_REST_Controller
18 */
19 class WP_REST_Abilities_V1_Run_Controller extends WP_REST_Controller {
20
21 /**
22 * REST API namespace.
23 *
24 * @since 6.9.0
25 * @var string
26 */
27 protected $namespace = 'wp-abilities/v1';
28
29 /**
30 * REST API base route.
31 *
32 * @since 6.9.0
33 * @var string
34 */
35 protected $rest_base = 'abilities';
36
37 /**
38 * Registers the routes for ability execution.
39 *
40 * @since 6.9.0
41 *
42 * @see register_rest_route()
43 */
44 public function register_routes(): void {
45 register_rest_route(
46 $this->namespace,
47 '/' . $this->rest_base . '/(?P<name>[a-zA-Z0-9\-\/]+?)/run',
48 array(
49 'args' => array(
50 'name' => array(
51 'description' => __( 'Unique identifier for the ability.' ),
52 'type' => 'string',
53 'pattern' => '^[a-zA-Z0-9\-\/]+$',
54 ),
55 ),
56
57 // TODO: We register ALLMETHODS because at route registration time, we don't know which abilities
58 // exist or their annotations (`destructive`, `idempotent`, `readonly`). This is due to WordPress
59 // load order - routes are registered early, before plugins have registered their abilities.
60 // This approach works but could be improved with lazy route registration or a different
61 // architecture that allows type-specific routes after abilities are registered.
62 // This was the same issue that we ended up seeing with the Feature API.
63 array(
64 'methods' => WP_REST_Server::ALLMETHODS,
65 'callback' => array( $this, 'execute_ability' ),
66 'permission_callback' => array( $this, 'check_ability_permissions' ),
67 'args' => $this->get_run_args(),
68 ),
69 'schema' => array( $this, 'get_run_schema' ),
70 )
71 );
72 }
73
74 /**
75 * Executes an ability.
76 *
77 * @since 6.9.0
78 *
79 * @param WP_REST_Request $request Full details about the request.
80 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
81 */
82 public function execute_ability( $request ) {
83 $ability = wp_get_ability( $request['name'] );
84 if ( ! $ability ) {
85 return new WP_Error(
86 'rest_ability_not_found',
87 __( 'Ability not found.' ),
88 array( 'status' => 404 )
89 );
90 }
91
92 $input = $this->get_input_from_request( $request );
93 $result = $ability->execute( $input );
94 if ( is_wp_error( $result ) ) {
95 return $result;
96 }
97
98 return rest_ensure_response( $result );
99 }
100
101 /**
102 * Validates if the HTTP method matches the expected method for the ability based on its annotations.
103 *
104 * @since 6.9.0
105 *
106 * @param string $request_method The HTTP method of the request.
107 * @param array<string, (bool|null)> $annotations The ability annotations.
108 * @return true|WP_Error True on success, or WP_Error object on failure.
109 */
110 public function validate_request_method( string $request_method, array $annotations ) {
111 $expected_method = 'POST';
112 if ( ! empty( $annotations['readonly'] ) ) {
113 $expected_method = 'GET';
114 } elseif ( ! empty( $annotations['destructive'] ) && ! empty( $annotations['idempotent'] ) ) {
115 $expected_method = 'DELETE';
116 }
117
118 if ( $expected_method === $request_method ) {
119 return true;
120 }
121
122 $error_message = __( 'Abilities that perform updates require POST method.' );
123 if ( 'GET' === $expected_method ) {
124 $error_message = __( 'Read-only abilities require GET method.' );
125 } elseif ( 'DELETE' === $expected_method ) {
126 $error_message = __( 'Abilities that perform destructive actions require DELETE method.' );
127 }
128 return new WP_Error(
129 'rest_ability_invalid_method',
130 $error_message,
131 array( 'status' => 405 )
132 );
133 }
134
135 /**
136 * Checks if a given request has permission to execute a specific ability.
137 *
138 * @since 6.9.0
139 *
140 * @param WP_REST_Request $request Full details about the request.
141 * @return true|WP_Error True if the request has execution permission, WP_Error object otherwise.
142 */
143 public function check_ability_permissions( $request ) {
144 $ability = wp_get_ability( $request['name'] );
145 if ( ! $ability || ! $ability->get_meta_item( 'show_in_rest' ) ) {
146 return new WP_Error(
147 'rest_ability_not_found',
148 __( 'Ability not found.' ),
149 array( 'status' => 404 )
150 );
151 }
152
153 $is_valid = $this->validate_request_method(
154 $request->get_method(),
155 $ability->get_meta_item( 'annotations' )
156 );
157 if ( is_wp_error( $is_valid ) ) {
158 return $is_valid;
159 }
160
161 $input = $this->get_input_from_request( $request );
162 $input = $ability->normalize_input( $input );
163 $is_valid = $ability->validate_input( $input );
164 if ( is_wp_error( $is_valid ) ) {
165 $is_valid->add_data( array( 'status' => 400 ) );
166 return $is_valid;
167 }
168
169 $result = $ability->check_permissions( $input );
170 if ( is_wp_error( $result ) ) {
171 $result->add_data( array( 'status' => rest_authorization_required_code() ) );
172 return $result;
173 }
174 if ( ! $result ) {
175 return new WP_Error(
176 'rest_ability_cannot_execute',
177 __( 'Sorry, you are not allowed to execute this ability.' ),
178 array( 'status' => rest_authorization_required_code() )
179 );
180 }
181
182 return true;
183 }
184
185 /**
186 * Extracts input parameters from the request.
187 *
188 * @since 6.9.0
189 *
190 * @param WP_REST_Request $request The request object.
191 * @return mixed|null The input parameters.
192 */
193 private function get_input_from_request( $request ) {
194 if ( in_array( $request->get_method(), array( 'GET', 'DELETE' ), true ) ) {
195 // For GET and DELETE requests, look for 'input' query parameter.
196 $query_params = $request->get_query_params();
197 return $query_params['input'] ?? null;
198 }
199
200 // For POST requests, look for 'input' in JSON body.
201 $json_params = $request->get_json_params();
202 return $json_params['input'] ?? null;
203 }
204
205 /**
206 * Retrieves the arguments for ability execution endpoint.
207 *
208 * @since 6.9.0
209 *
210 * @return array<string, mixed> Arguments for the run endpoint.
211 */
212 public function get_run_args(): array {
213 return array(
214 'input' => array(
215 'description' => __( 'Input parameters for the ability execution.' ),
216 'type' => array( 'integer', 'number', 'boolean', 'string', 'array', 'object', 'null' ),
217 'default' => null,
218 ),
219 );
220 }
221
222 /**
223 * Retrieves the schema for ability execution endpoint.
224 *
225 * @since 6.9.0
226 *
227 * @return array<string, mixed> Schema for the run endpoint.
228 */
229 public function get_run_schema(): array {
230 return array(
231 '$schema' => 'http://json-schema.org/draft-04/schema#',
232 'title' => 'ability-execution',
233 'type' => 'object',
234 'properties' => array(
235 'result' => array(
236 'description' => __( 'The result of the ability execution.' ),
237 'type' => array( 'integer', 'number', 'boolean', 'string', 'array', 'object', 'null' ),
238 'context' => array( 'view', 'edit' ),
239 'readonly' => true,
240 ),
241 ),
242 );
243 }
244 }
245