PluginProbe
Gutenberg / 12.1.0
Gutenberg v12.1.0
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / class-wp-rest-widgets-controller.php

class-wp-rest-widgets-controller.php in Gutenberg 12.1.0, at lib/class-wp-rest-widgets-controller.php

725 lines 21.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST API: WP_REST_Widgets_Controller class
4 *
5 * @package WordPress
6 * @subpackage REST_API
7 * @since 5.6.0
8 */
9
10 /**
11 * Core class to access widgets via the REST API.
12 *
13 * @since 5.6.0
14 *
15 * @see WP_REST_Controller
16 */
17 class WP_REST_Widgets_Controller extends WP_REST_Controller {
18
19 /**
20 * Widgets controller constructor.
21 *
22 * @since 5.6.0
23 */
24 public function __construct() {
25 $this->namespace = 'wp/v2';
26 $this->rest_base = 'widgets';
27 }
28
29 /**
30 * Registers the widget routes for the controller.
31 *
32 * @since 5.6.0
33 */
34 public function register_routes() {
35 register_rest_route(
36 $this->namespace,
37 $this->rest_base,
38 array(
39 array(
40 'methods' => WP_REST_Server::READABLE,
41 'callback' => array( $this, 'get_items' ),
42 'permission_callback' => array( $this, 'get_items_permissions_check' ),
43 'args' => $this->get_collection_params(),
44 ),
45 array(
46 'methods' => WP_REST_Server::CREATABLE,
47 'callback' => array( $this, 'create_item' ),
48 'permission_callback' => array( $this, 'create_item_permissions_check' ),
49 'args' => $this->get_endpoint_args_for_item_schema(),
50 ),
51 'allow_batch' => array( 'v1' => true ),
52 'schema' => array( $this, 'get_public_item_schema' ),
53 )
54 );
55
56 register_rest_route(
57 $this->namespace,
58 $this->rest_base . '/(?P<id>[\w\-]+)',
59 array(
60 array(
61 'methods' => WP_REST_Server::READABLE,
62 'callback' => array( $this, 'get_item' ),
63 'permission_callback' => array( $this, 'get_item_permissions_check' ),
64 'args' => array(
65 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
66 ),
67 ),
68 array(
69 'methods' => WP_REST_Server::EDITABLE,
70 'callback' => array( $this, 'update_item' ),
71 'permission_callback' => array( $this, 'update_item_permissions_check' ),
72 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
73 ),
74 array(
75 'methods' => WP_REST_Server::DELETABLE,
76 'callback' => array( $this, 'delete_item' ),
77 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
78 'args' => array(
79 'force' => array(
80 'description' => __( 'Whether to force removal of the widget, or move it to the inactive sidebar.', 'gutenberg' ),
81 'type' => 'boolean',
82 ),
83 ),
84 ),
85 'allow_batch' => array( 'v1' => true ),
86 'schema' => array( $this, 'get_public_item_schema' ),
87 )
88 );
89 }
90
91 /**
92 * Checks if a given request has access to get widgets.
93 *
94 * @since 5.6.0
95 *
96 * @param WP_REST_Request $request Full details about the request.
97 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
98 */
99 public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
100 return $this->permissions_check();
101 }
102
103 /**
104 * Retrieves a collection of widgets.
105 *
106 * @since 5.6.0
107 *
108 * @param WP_REST_Request $request Full details about the request.
109 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
110 */
111 public function get_items( $request ) {
112 $prepared = array();
113
114 foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) {
115 if ( isset( $request['sidebar'] ) && $sidebar_id !== $request['sidebar'] ) {
116 continue;
117 }
118
119 foreach ( $widget_ids as $widget_id ) {
120 $response = $this->prepare_item_for_response( compact( 'sidebar_id', 'widget_id' ), $request );
121
122 if ( ! is_wp_error( $response ) ) {
123 $prepared[] = $this->prepare_response_for_collection( $response );
124 }
125 }
126 }
127
128 return new WP_REST_Response( $prepared );
129 }
130
131 /**
132 * Checks if a given request has access to get a widget.
133 *
134 * @since 5.6.0
135 *
136 * @param WP_REST_Request $request Full details about the request.
137 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
138 */
139 public function get_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
140 return $this->permissions_check();
141 }
142
143 /**
144 * Gets an individual widget.
145 *
146 * @since 5.6.0
147 *
148 * @param WP_REST_Request $request Full details about the request.
149 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
150 */
151 public function get_item( $request ) {
152 $widget_id = $request['id'];
153 $sidebar_id = wp_find_widgets_sidebar( $widget_id );
154
155 if ( is_null( $sidebar_id ) ) {
156 return new WP_Error(
157 'rest_widget_not_found',
158 __( 'No widget was found with that id.', 'gutenberg' ),
159 array( 'status' => 404 )
160 );
161 }
162
163 return $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
164 }
165
166 /**
167 * Checks if a given request has access to create widgets.
168 *
169 * @since 5.6.0
170 *
171 * @param WP_REST_Request $request Full details about the request.
172 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
173 */
174 public function create_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
175 return $this->permissions_check();
176 }
177
178 /**
179 * Creates a widget.
180 *
181 * @since 5.6.0
182 *
183 * @param WP_REST_Request $request Full details about the request.
184 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
185 */
186 public function create_item( $request ) {
187 $sidebar_id = $request['sidebar'];
188
189 $widget_id = $this->save_widget( $request );
190
191 if ( is_wp_error( $widget_id ) ) {
192 return $widget_id;
193 }
194
195 wp_assign_widget_to_sidebar( $widget_id, $sidebar_id );
196
197 $request['context'] = 'edit';
198
199 $response = $this->prepare_item_for_response( compact( 'sidebar_id', 'widget_id' ), $request );
200
201 if ( is_wp_error( $response ) ) {
202 return $response;
203 }
204
205 $response->set_status( 201 );
206
207 return $response;
208 }
209
210 /**
211 * Checks if a given request has access to update widgets.
212 *
213 * @since 5.6.0
214 *
215 * @param WP_REST_Request $request Full details about the request.
216 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
217 */
218 public function update_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
219 return $this->permissions_check();
220 }
221
222 /**
223 * Updates an existing widget.
224 *
225 * @since 5.6.0
226 *
227 * @param WP_REST_Request $request Full details about the request.
228 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
229 */
230 public function update_item( $request ) {
231 $widget_id = $request['id'];
232 $sidebar_id = wp_find_widgets_sidebar( $widget_id );
233
234 // Allow sidebar to be unset or missing when widget is not a WP_Widget.
235 $parsed_id = wp_parse_widget_id( $widget_id );
236 $widget_object = gutenberg_get_widget_object( $parsed_id['id_base'] );
237 if ( is_null( $sidebar_id ) && $widget_object ) {
238 return new WP_Error(
239 'rest_widget_not_found',
240 __( 'No widget was found with that id.', 'gutenberg' ),
241 array( 'status' => 404 )
242 );
243 }
244
245 if (
246 $request->has_param( 'instance' ) ||
247 $request->has_param( 'form_data' )
248 ) {
249 $maybe_error = $this->save_widget( $request );
250 if ( is_wp_error( $maybe_error ) ) {
251 return $maybe_error;
252 }
253 }
254
255 if ( $request->has_param( 'sidebar' ) ) {
256 if ( $sidebar_id !== $request['sidebar'] ) {
257 $sidebar_id = $request['sidebar'];
258 wp_assign_widget_to_sidebar( $widget_id, $sidebar_id );
259 }
260 }
261
262 $request['context'] = 'edit';
263
264 return $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
265 }
266
267 /**
268 * Checks if a given request has access to delete widgets.
269 *
270 * @since 5.6.0
271 *
272 * @param WP_REST_Request $request Full details about the request.
273 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
274 */
275 public function delete_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
276 return $this->permissions_check();
277 }
278
279 /**
280 * Deletes a widget.
281 *
282 * @since 5.6.0
283 *
284 * @global array $wp_registered_widget_updates The registered widget update functions.
285 *
286 * @param WP_REST_Request $request Full details about the request.
287 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
288 */
289 public function delete_item( $request ) {
290 global $wp_registered_widget_updates;
291
292 $widget_id = $request['id'];
293 $sidebar_id = wp_find_widgets_sidebar( $widget_id );
294
295 if ( is_null( $sidebar_id ) ) {
296 return new WP_Error(
297 'rest_widget_not_found',
298 __( 'No widget was found with that id.', 'gutenberg' ),
299 array( 'status' => 404 )
300 );
301 }
302
303 $request['context'] = 'edit';
304
305 if ( $request['force'] ) {
306 $response = $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
307
308 $parsed_id = wp_parse_widget_id( $widget_id );
309 $id_base = $parsed_id['id_base'];
310
311 $original_post = $_POST;
312 $original_request = $_REQUEST;
313
314 $_POST = array(
315 'sidebar' => $sidebar_id,
316 "widget-$id_base" => array(),
317 'the-widget-id' => $widget_id,
318 'delete_widget' => '1',
319 );
320 $_REQUEST = $_POST;
321
322 $callback = $wp_registered_widget_updates[ $id_base ]['callback'];
323 $params = $wp_registered_widget_updates[ $id_base ]['params'];
324
325 if ( is_callable( $callback ) ) {
326 ob_start();
327 call_user_func_array( $callback, $params );
328 ob_end_clean();
329 }
330
331 $_POST = $original_post;
332 $_REQUEST = $original_request;
333
334 wp_assign_widget_to_sidebar( $widget_id, '' );
335
336 $response->set_data(
337 array(
338 'deleted' => true,
339 'previous' => $response->get_data(),
340 )
341 );
342 } else {
343 wp_assign_widget_to_sidebar( $widget_id, 'wp_inactive_widgets' );
344
345 $response = $this->prepare_item_for_response(
346 array(
347 'sidebar_id' => 'wp_inactive_widgets',
348 'widget_id' => $widget_id,
349 ),
350 $request
351 );
352 }
353
354 return $response;
355 }
356
357 /**
358 * Performs a permissions check for managing widgets.
359 *
360 * @since 5.6.0
361 *
362 * @return true|WP_Error
363 */
364 protected function permissions_check() {
365 if ( ! current_user_can( 'edit_theme_options' ) ) {
366 return new WP_Error(
367 'rest_cannot_manage_widgets',
368 __( 'Sorry, you are not allowed to manage widgets on this site.', 'gutenberg' ),
369 array(
370 'status' => rest_authorization_required_code(),
371 )
372 );
373 }
374
375 return true;
376 }
377
378 /**
379 * Saves the widget in the request object.
380 *
381 * @since 5.6.0
382 *
383 * @param WP_REST_Request $request Full details about the request.
384 *
385 * @return string The saved widget ID.
386 */
387 protected function save_widget( $request ) {
388 global $wp_registered_widget_updates;
389
390 require_once ABSPATH . 'wp-admin/includes/widgets.php'; // For next_widget_id_number().
391
392 if ( isset( $request['id'] ) ) {
393 // Saving an existing widget.
394 $id = $request['id'];
395 $parsed_id = wp_parse_widget_id( $id );
396 $id_base = $parsed_id['id_base'];
397 $number = isset( $parsed_id['number'] ) ? $parsed_id['number'] : null;
398 $widget_object = gutenberg_get_widget_object( $id_base );
399 } elseif ( $request['id_base'] ) {
400 // Saving a new widget.
401 $id_base = $request['id_base'];
402 $widget_object = gutenberg_get_widget_object( $id_base );
403 $number = $widget_object ? next_widget_id_number( $id_base ) : null;
404 $id = $widget_object ? $id_base . '-' . $number : $id_base;
405 } else {
406 return new WP_Error(
407 'rest_invalid_widget',
408 __( 'Widget type (id_base) is required.', 'gutenberg' ),
409 array( 'status' => 400 )
410 );
411 }
412
413 if ( ! isset( $wp_registered_widget_updates[ $id_base ] ) ) {
414 return new WP_Error(
415 'rest_invalid_widget',
416 __( 'The provided widget type (id_base) cannot be updated.', 'gutenberg' ),
417 array( 'status' => 400 )
418 );
419 }
420
421 if ( isset( $request['instance'] ) ) {
422 if ( ! $widget_object ) {
423 return new WP_Error(
424 'rest_invalid_widget',
425 __( 'Cannot set instance on a widget that does not extend WP_Widget.', 'gutenberg' ),
426 array( 'status' => 400 )
427 );
428 }
429
430 if ( isset( $request['instance']['raw'] ) ) {
431 if ( empty( $widget_object->show_instance_in_rest ) ) {
432 return new WP_Error(
433 'rest_invalid_widget',
434 __( 'Widget type does not support raw instances.', 'gutenberg' ),
435 array( 'status' => 400 )
436 );
437 }
438 $instance = $request['instance']['raw'];
439 } elseif ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) {
440 $serialized_instance = base64_decode( $request['instance']['encoded'] );
441 if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) {
442 return new WP_Error(
443 'rest_invalid_widget',
444 __( 'The provided instance is malformed.', 'gutenberg' ),
445 array( 'status' => 400 )
446 );
447 }
448 $instance = unserialize( $serialized_instance );
449 } else {
450 return new WP_Error(
451 'rest_invalid_widget',
452 __( 'The provided instance is invalid. Must contain raw OR encoded and hash.', 'gutenberg' ),
453 array( 'status' => 400 )
454 );
455 }
456
457 $form_data = array(
458 "widget-$id_base" => array(
459 $number => $instance,
460 ),
461 );
462 } elseif ( isset( $request['form_data'] ) ) {
463 $form_data = $request['form_data'];
464 } else {
465 $form_data = array();
466 }
467
468 $original_post = $_POST;
469 $original_request = $_REQUEST;
470
471 foreach ( $form_data as $key => $value ) {
472 $slashed_value = wp_slash( $value );
473 $_POST[ $key ] = $slashed_value;
474 $_REQUEST[ $key ] = $slashed_value;
475 }
476
477 $callback = $wp_registered_widget_updates[ $id_base ]['callback'];
478 $params = $wp_registered_widget_updates[ $id_base ]['params'];
479
480 if ( is_callable( $callback ) ) {
481 ob_start();
482 call_user_func_array( $callback, $params );
483 ob_end_clean();
484 }
485
486 $_POST = $original_post;
487 $_REQUEST = $original_request;
488
489 if ( $widget_object ) {
490 // Register any multi-widget that the update callback just created.
491 $widget_object->_set( $number );
492 $widget_object->_register_one( $number );
493
494 // WP_Widget sets updated = true after an update to prevent more
495 // than one widget from being saved per request. This isn't what we
496 // want in the REST API, though, as we support batch requests.
497 $widget_object->updated = false;
498 }
499
500 return $id;
501 }
502
503 /**
504 * Prepares the widget for the REST response.
505 *
506 * @since 5.6.0
507 *
508 * @global array $wp_registered_sidebars The registered sidebars.
509 * @global array $wp_registered_widgets The registered widgets.
510 * @global array $wp_registered_widget_controls The registered widget controls.
511 *
512 * @param array $item An array containing a widget_id and sidebar_id.
513 * @param WP_REST_Request $request Request object.
514 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
515 */
516 public function prepare_item_for_response( $item, $request ) {
517 global $wp_registered_widgets;
518
519 $widget_id = $item['widget_id'];
520 $sidebar_id = $item['sidebar_id'];
521
522 if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
523 return new WP_Error(
524 'rest_invalid_widget',
525 __( 'The requested widget is invalid.', 'gutenberg' ),
526 array( 'status' => 500 )
527 );
528 }
529
530 $widget = $wp_registered_widgets[ $widget_id ];
531 $parsed_id = wp_parse_widget_id( $widget_id );
532 $fields = $this->get_fields_for_response( $request );
533
534 $prepared = array(
535 'id' => $widget_id,
536 'id_base' => $parsed_id['id_base'],
537 'sidebar' => $sidebar_id,
538 'rendered' => '',
539 'rendered_form' => null,
540 'instance' => null,
541 );
542
543 if (
544 rest_is_field_included( 'rendered', $fields ) &&
545 'wp_inactive_widgets' !== $sidebar_id
546 ) {
547 $prepared['rendered'] = trim( wp_render_widget( $widget_id, $sidebar_id ) );
548 }
549
550 if ( rest_is_field_included( 'rendered_form', $fields ) ) {
551 $rendered_form = wp_render_widget_control( $widget_id );
552 if ( ! is_null( $rendered_form ) ) {
553 $prepared['rendered_form'] = trim( $rendered_form );
554 }
555 }
556
557 if ( rest_is_field_included( 'instance', $fields ) ) {
558 $widget_object = gutenberg_get_widget_object( $parsed_id['id_base'] );
559 $instance = gutenberg_get_widget_instance( $widget_id );
560
561 if ( ! is_null( $instance ) ) {
562 $serialized_instance = serialize( $instance );
563 $prepared['instance']['encoded'] = base64_encode( $serialized_instance );
564 $prepared['instance']['hash'] = wp_hash( $serialized_instance );
565
566 if ( ! empty( $widget_object->show_instance_in_rest ) ) {
567 // Use new stdClass so that JSON result is {} and not [].
568 $prepared['instance']['raw'] = empty( $instance ) ? new stdClass : $instance;
569 }
570 }
571 }
572
573 $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
574 $prepared = $this->add_additional_fields_to_object( $prepared, $request );
575 $prepared = $this->filter_response_by_context( $prepared, $context );
576
577 $response = rest_ensure_response( $prepared );
578
579 $response->add_links( $this->prepare_links( $prepared ) );
580
581 /**
582 * Filters the REST API response for a widget.
583 *
584 * @since 5.6.0
585 *
586 * @param WP_REST_Response $response The response object.
587 * @param array $widget The registered widget data.
588 * @param WP_REST_Request $request Request used to generate the response.
589 */
590 return apply_filters( 'rest_prepare_widget', $response, $widget, $request );
591 }
592
593 /**
594 * Prepares links for the widget.
595 *
596 * @since 5.6.0
597 *
598 * @param array $prepared Widget.
599 * @return array Links for the given widget.
600 */
601 protected function prepare_links( $prepared ) {
602 $id_base = ! empty( $prepared['id_base'] ) ? $prepared['id_base'] : $prepared['id'];
603
604 return array(
605 'self' => array(
606 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $prepared['id'] ) ),
607 ),
608 'collection' => array(
609 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
610 ),
611 'about' => array(
612 'href' => rest_url( sprintf( 'wp/v2/widget-types/%s', $id_base ) ),
613 'embeddable' => true,
614 ),
615 'https://api.w.org/sidebar' => array(
616 'href' => rest_url( sprintf( 'wp/v2/sidebars/%s/', $prepared['sidebar'] ) ),
617 ),
618 );
619 }
620
621 /**
622 * Gets the list of collection params.
623 *
624 * @since 5.6.0
625 *
626 * @return array[]
627 */
628 public function get_collection_params() {
629 return array(
630 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
631 'sidebar' => array(
632 'description' => __( 'The sidebar to return widgets for.', 'gutenberg' ),
633 'type' => 'string',
634 ),
635 );
636 }
637
638 /**
639 * Retrieves the widget's schema, conforming to JSON Schema.
640 *
641 * @since 5.6.0
642 *
643 * @return array Item schema data.
644 */
645 public function get_item_schema() {
646 if ( $this->schema ) {
647 return $this->add_additional_fields_schema( $this->schema );
648 }
649
650 $this->schema = array(
651 '$schema' => 'http://json-schema.org/draft-04/schema#',
652 'title' => 'widget',
653 'type' => 'object',
654 'properties' => array(
655 'id' => array(
656 'description' => __( 'Unique identifier for the widget.', 'gutenberg' ),
657 'type' => 'string',
658 'context' => array( 'view', 'edit', 'embed' ),
659 ),
660 'id_base' => array(
661 'description' => __( 'The type of the widget. Corresponds to ID in widget-types endpoint.', 'gutenberg' ),
662 'type' => 'string',
663 'context' => array( 'view', 'edit', 'embed' ),
664 ),
665 'sidebar' => array(
666 'description' => __( 'The sidebar the widget belongs to.', 'gutenberg' ),
667 'type' => 'string',
668 'default' => 'wp_inactive_widgets',
669 'required' => true,
670 'context' => array( 'view', 'edit', 'embed' ),
671 ),
672 'rendered' => array(
673 'description' => __( 'HTML representation of the widget.', 'gutenberg' ),
674 'type' => 'string',
675 'context' => array( 'view', 'edit', 'embed' ),
676 'readonly' => true,
677 ),
678 'rendered_form' => array(
679 'description' => __( 'HTML representation of the widget admin form.', 'gutenberg' ),
680 'type' => 'string',
681 'context' => array( 'edit' ),
682 'readonly' => true,
683 ),
684 'instance' => array(
685 'description' => __( 'Instance settings of the widget, if supported.', 'gutenberg' ),
686 'type' => 'object',
687 'context' => array( 'view', 'edit', 'embed' ),
688 'default' => null,
689 'properties' => array(
690 'encoded' => array(
691 'description' => __( 'Base64 encoded representation of the instance settings.', 'gutenberg' ),
692 'type' => 'string',
693 'context' => array( 'view', 'edit', 'embed' ),
694 ),
695 'hash' => array(
696 'description' => __( 'Cryptographic hash of the instance settings.', 'gutenberg' ),
697 'type' => 'string',
698 'context' => array( 'view', 'edit', 'embed' ),
699 ),
700 'raw' => array(
701 'description' => __( 'Unencoded instance settings, if supported.', 'gutenberg' ),
702 'type' => 'object',
703 'context' => array( 'view', 'edit', 'embed' ),
704 ),
705 ),
706 ),
707 'form_data' => array(
708 'description' => __( 'URL-encoded form data from the widget admin form. Used to update a widget that does not support instance. Write only.', 'gutenberg' ),
709 'type' => 'string',
710 'context' => array(),
711 'arg_options' => array(
712 'sanitize_callback' => function( $string ) {
713 $array = array();
714 wp_parse_str( $string, $array );
715 return $array;
716 },
717 ),
718 ),
719 ),
720 );
721
722 return $this->add_additional_fields_schema( $this->schema );
723 }
724 }
725