説明なし

class-wp-rest-widgets-controller.php 24KB

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