PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.0-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.0-beta
16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / _inc / lib / core-api / class-wpcom-rest-field-controller.php
jetpack / _inc / lib / core-api Last commit date
wpcom-endpoints 2 days ago class-wpcom-rest-field-controller.php 1 month ago class.jetpack-core-api-module-endpoints.php 2 weeks ago class.jetpack-core-api-site-endpoints.php 2 months ago class.jetpack-core-api-widgets-endpoints.php 4 years ago class.jetpack-core-api-xmlrpc-consumer-endpoint.php 4 years ago load-wpcom-endpoints.php 7 months ago
class-wpcom-rest-field-controller.php
443 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * `WP_REST_Controller` is basically a wrapper for `register_rest_route()`
4 * `WPCOM_REST_API_V2_Field_Controller` is a mostly-analogous wrapper for `register_rest_field()`
5 *
6 * @todo - nicer API for array values?
7 *
8 * @package automattic/jetpack
9 */
10
11 /**
12 * Abstract WPCOM_REST_API_V2_Field_Controller class extended for different fields needed in the Jetpack plugin.
13 */
14 abstract class WPCOM_REST_API_V2_Field_Controller {
15 /**
16 * The REST Object Type(s) to which the field should be added.
17 *
18 * @var string|string[]
19 */
20 protected $object_type;
21
22 /**
23 * The name of the REST API field to add.
24 *
25 * @var string
26 */
27 protected $field_name;
28
29 /**
30 * Constructor
31 */
32 public function __construct() {
33 if ( ! $this->object_type ) {
34 _doing_it_wrong(
35 'WPCOM_REST_API_V2_Field_Controller::$object_type',
36 sprintf(
37 /* translators: %s: object_type */
38 esc_html__( "Property '%s' must be overridden.", 'jetpack' ),
39 'object_type'
40 ),
41 'jetpack-6.8'
42 );
43 return;
44 }
45
46 if ( ! $this->field_name ) {
47 _doing_it_wrong(
48 'WPCOM_REST_API_V2_Field_Controller::$field_name',
49 sprintf(
50 /* translators: %s: field_name */
51 esc_html__( "Property '%s' must be overridden.", 'jetpack' ),
52 'field_name'
53 ),
54 'jetpack-6.8'
55 );
56 return;
57 }
58
59 add_action( 'rest_api_init', array( $this, 'register_fields' ) );
60
61 // do this again later to collect any CPTs that get registered later.
62 add_action( 'restapi_theme_init', array( $this, 'register_fields' ), 20 );
63 }
64
65 /**
66 * Registers the field with the appropriate schema and callbacks.
67 */
68 public function register_fields() {
69 foreach ( (array) $this->object_type as $object_type ) {
70 if ( $this->is_registered( $object_type ) ) {
71 continue;
72 }
73 register_rest_field(
74 $object_type,
75 $this->field_name,
76 array(
77 'get_callback' => array( $this, 'get_for_response' ),
78 'update_callback' => array( $this, 'update_from_request' ),
79 'schema' => $this->get_schema(),
80 )
81 );
82 }
83 }
84
85 /**
86 * Checks if the field is already registered for the object_type
87 *
88 * @param string $object_type The name of the object type.
89 * @return boolean Whether the field has been registered for the type.
90 */
91 public function is_registered( $object_type ) {
92 global $wp_rest_additional_fields;
93
94 return ! empty( $wp_rest_additional_fields[ $object_type ][ $this->field_name ] );
95 }
96
97 /**
98 * Ensures the response matches the schema and request context.
99 *
100 * @param mixed $value Value passed in request.
101 * @param WP_REST_Request $request WP API request.
102 *
103 * @return mixed
104 */
105 private function prepare_for_response( $value, $request ) {
106 $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
107 $schema = $this->get_schema();
108
109 $is_valid = rest_validate_value_from_schema( $value, $schema, $this->field_name );
110 if ( is_wp_error( $is_valid ) ) {
111 return $is_valid;
112 }
113
114 return $this->filter_response_by_context( $value, $schema, $context );
115 }
116
117 /**
118 * Returns the schema's default value
119 *
120 * If there is no default, returns the type's falsey value.
121 *
122 * @param array $schema Schema to validate against.
123 *
124 * @return mixed
125 */
126 final public function get_default_value( $schema ) {
127 if ( isset( $schema['default'] ) ) {
128 return $schema['default'];
129 }
130
131 // If you have something more complicated, use $schema['default'].
132 switch ( $schema['type'] ?? 'null' ) {
133 case 'string':
134 return '';
135 case 'integer':
136 case 'number':
137 return 0;
138 case 'object':
139 return (object) array();
140 case 'array':
141 return array();
142 case 'boolean':
143 return false;
144 case 'null':
145 default:
146 return null;
147 }
148 }
149
150 /**
151 * The field's wrapped getter. Does permission checks and output preparation.
152 *
153 * This cannot be extended: implement `->get()` instead.
154 *
155 * @param mixed $object_data Probably an array. Whatever the endpoint returns.
156 * @param string $field_name Should always match `->field_name`.
157 * @param WP_REST_Request $request WP API request.
158 * @param string $object_type Should always match `->object_type`.
159 *
160 * @return mixed
161 */
162 final public function get_for_response( $object_data, $field_name, $request, $object_type ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
163 $permission_check = $this->get_permission_check( $object_data, $request );
164
165 if ( ! $permission_check ) {
166 _doing_it_wrong(
167 'WPCOM_REST_API_V2_Field_Controller::get_permission_check',
168 sprintf(
169 /* translators: %s: get_permission_check() */
170 esc_html__( "Method '%s' must return either true or WP_Error.", 'jetpack' ),
171 'get_permission_check'
172 ),
173 'jetpack-6.8'
174 );
175 return $this->get_default_value( $this->get_schema() );
176 }
177
178 if ( is_wp_error( $permission_check ) ) {
179 return $this->get_default_value( $this->get_schema() );
180 }
181
182 $value = $this->get( $object_data, $request );
183
184 return $this->prepare_for_response( $value, $request );
185 }
186
187 /**
188 * The field's wrapped setter. Does permission checks.
189 *
190 * This cannot be extended: implement `->update()` instead.
191 *
192 * @param mixed $value The new value for the field.
193 * @param mixed $object_data Probably a WordPress object (e.g., WP_Post).
194 * @param string $field_name Should always match `->field_name`.
195 * @param WP_REST_Request $request WP API request.
196 * @param string $object_type Should always match `->object_type`.
197 * @return void|WP_Error
198 */
199 final public function update_from_request( $value, $object_data, $field_name, $request, $object_type ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
200 $permission_check = $this->update_permission_check( $value, $object_data, $request );
201
202 if ( ! $permission_check ) {
203 _doing_it_wrong(
204 'WPCOM_REST_API_V2_Field_Controller::update_permission_check',
205 sprintf(
206 /* translators: %s: update_permission_check() */
207 esc_html__( "Method '%s' must return either true or WP_Error.", 'jetpack' ),
208 'update_permission_check'
209 ),
210 'jetpack-6.8'
211 );
212
213 return new WP_Error(
214 'invalid_user_permission',
215 sprintf(
216 /* translators: %s: the name of an API response field */
217 __( "You are not allowed to access the '%s' field.", 'jetpack' ),
218 $this->field_name
219 )
220 );
221 }
222
223 if ( is_wp_error( $permission_check ) ) {
224 return $permission_check;
225 }
226
227 $updated = $this->update( $value, $object_data, $request );
228
229 if ( is_wp_error( $updated ) ) {
230 return $updated;
231 }
232 }
233
234 /**
235 * Permission Check for the field's getter. Must be implemented in the inheriting class.
236 *
237 * @param mixed $object_data Whatever the endpoint would return for its response.
238 * @param WP_REST_Request $request WP API request.
239 *
240 * @return true|WP_Error
241 */
242 public function get_permission_check( $object_data, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
243 _doing_it_wrong(
244 'WPCOM_REST_API_V2_Field_Controller::get_permission_check',
245 sprintf(
246 /* translators: %s: method name. */
247 esc_html__( "Method '%s' must be overridden.", 'jetpack' ),
248 __METHOD__
249 ),
250 'jetpack-6.8'
251 );
252 return null;
253 }
254
255 /**
256 * The field's "raw" getter. Must be implemented in the inheriting class.
257 *
258 * @param mixed $object_data Whatever the endpoint would return for its response.
259 * @param WP_REST_Request $request WP API request.
260 * @return mixed
261 */
262 public function get( $object_data, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
263 _doing_it_wrong(
264 'WPCOM_REST_API_V2_Field_Controller::get',
265 sprintf(
266 /* translators: %s: method name. */
267 esc_html__( "Method '%s' must be overridden.", 'jetpack' ),
268 __METHOD__
269 ),
270 'jetpack-6.8'
271 );
272 }
273
274 /**
275 * Permission Check for the field's setter. Must be implemented in the inheriting class.
276 *
277 * @param mixed $value The new value for the field.
278 * @param mixed $object_data Probably a WordPress object (e.g., WP_Post).
279 * @param WP_REST_Request $request WP API request.
280 *
281 * @return true|WP_Error
282 */
283 public function update_permission_check( $value, $object_data, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
284 _doing_it_wrong(
285 'WPCOM_REST_API_V2_Field_Controller::update_permission_check',
286 sprintf(
287 /* translators: %s: method name. */
288 esc_html__( "Method '%s' must be overridden.", 'jetpack' ),
289 __METHOD__
290 ),
291 'jetpack-6.8'
292 );
293 return null;
294 }
295
296 /**
297 * The field's "raw" setter. Must be implemented in the inheriting class.
298 *
299 * @param mixed $value The new value for the field.
300 * @param mixed $object_data Probably a WordPress object (e.g., WP_Post).
301 * @param WP_REST_Request $request WP API request.
302 *
303 * @return mixed
304 */
305 public function update( $value, $object_data, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
306 _doing_it_wrong(
307 'WPCOM_REST_API_V2_Field_Controller::update',
308 sprintf(
309 /* translators: %s: method name. */
310 esc_html__( "Method '%s' must be overridden.", 'jetpack' ),
311 __METHOD__
312 ),
313 'jetpack-6.8'
314 );
315 }
316
317 /**
318 * The JSON Schema for the field
319 *
320 * @link https://json-schema.org/understanding-json-schema/
321 * As of WordPress 5.0, Core currently understands:
322 * * type
323 * * string - not minLength, not maxLength, not pattern
324 * * integer - minimum, maximum, exclusiveMinimum, exclusiveMaximum, not multipleOf
325 * * number - minimum, maximum, exclusiveMinimum, exclusiveMaximum, not multipleOf
326 * * boolean
327 * * null
328 * * object - properties, additionalProperties, not propertyNames, not dependencies, not patternProperties, not required
329 * * array: only lists, not tuples - items, not minItems, not maxItems, not uniqueItems, not contains
330 * * enum
331 * * format
332 * * date-time
333 * * email
334 * * ip
335 * * uri
336 * As of WordPress 5.0, Core does not support:
337 * * Multiple type: `type: [ 'string', 'integer' ]`
338 * * $ref, allOf, anyOf, oneOf, not, const
339 *
340 * @return array
341 */
342 public function get_schema() {
343 _doing_it_wrong(
344 'WPCOM_REST_API_V2_Field_Controller::get_schema',
345 sprintf(
346 /* translators: %s: method name. */
347 esc_html__( "Method '%s' must be overridden.", 'jetpack' ),
348 __METHOD__
349 ),
350 'jetpack-6.8'
351 );
352 return null;
353 }
354
355 /**
356 * Ensure that our request matches its expected context.
357 *
358 * @param array $schema Schema to validate against.
359 * @param string $context REST API Request context.
360 * @return bool
361 */
362 private function is_valid_for_context( $schema, $context ) {
363 return empty( $schema['context'] ) || in_array( $context, $schema['context'], true );
364 }
365
366 /**
367 * Removes properties that should not appear in the current
368 * request's context
369 *
370 * $context is a Core REST API Framework request attribute that is
371 * always one of:
372 * * view (what you see on the blog)
373 * * edit (what you see in an editor)
374 * * embed (what you see in, e.g., an oembed)
375 *
376 * Fields (and sub-fields, and sub-sub-...) can be flagged for a
377 * set of specific contexts via the field's schema.
378 *
379 * The Core API will filter out top-level fields with the wrong
380 * context, but will not recurse deeply enough into arrays/objects
381 * to remove all levels of sub-fields with the wrong context.
382 *
383 * This function handles that recursion.
384 *
385 * @param mixed $value Value passed to API request.
386 * @param array $schema Schema to validate against.
387 * @param string $context REST API Request context.
388 *
389 * @return mixed Filtered $value
390 */
391 final public function filter_response_by_context( $value, $schema, $context ) {
392 if ( ! $this->is_valid_for_context( $schema, $context ) ) {
393 // We use this intentionally odd looking WP_Error object
394 // internally only in this recursive function (see below
395 // in the `object` case). It will never be output by the REST API.
396 // If we return this for the top level object, Core
397 // correctly remove the top level object from the response
398 // for us.
399 return new WP_Error( '__wrong-context__' );
400 }
401
402 switch ( $schema['type'] ) {
403 case 'array':
404 if ( ! isset( $schema['items'] ) ) {
405 return $value;
406 }
407
408 // Shortcircuit if we know none of the items are valid for this context.
409 // This would only happen in a strangely written schema.
410 if ( ! $this->is_valid_for_context( $schema['items'], $context ) ) {
411 return array();
412 }
413
414 // Recurse to prune sub-properties of each item.
415 foreach ( $value as $key => $item ) {
416 $value[ $key ] = $this->filter_response_by_context( $item, $schema['items'], $context );
417 }
418
419 return $value;
420 case 'object':
421 if ( ! isset( $schema['properties'] ) ) {
422 return $value;
423 }
424
425 foreach ( $value as $field_name => $field_value ) {
426 if ( isset( $schema['properties'][ $field_name ] ) ) {
427 $field_value = $this->filter_response_by_context( $field_value, $schema['properties'][ $field_name ], $context );
428 if ( is_wp_error( $field_value ) && '__wrong-context__' === $field_value->get_error_code() ) {
429 unset( $value[ $field_name ] );
430 } else {
431 // Respect recursion that pruned sub-properties of each property.
432 $value[ $field_name ] = $field_value;
433 }
434 }
435 }
436
437 return (object) $value;
438 }
439
440 return $value;
441 }
442 }
443