Diff: STRATO-apps/wordpress_03/app/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + /**
3 + * REST API: WP_REST_Menus_Controller class
4 + *
5 + * @package WordPress
6 + * @subpackage REST_API
7 + * @since 5.9.0
8 + */
9 +
10 + /**
11 + * Core class used to managed menu terms associated via the REST API.
12 + *
13 + * @since 5.9.0
14 + *
15 + * @see WP_REST_Controller
16 + */
17 + class WP_REST_Menus_Controller extends WP_REST_Terms_Controller {
18 +
19 + /**
20 + * Checks if a request has access to read menus.
21 + *
22 + * @since 5.9.0
23 + *
24 + * @param WP_REST_Request $request Full details about the request.
25 + * @return bool|WP_Error True if the request has read access, otherwise false or WP_Error object.
26 + */
27 + public function get_items_permissions_check( $request ) {
28 + $has_permission = parent::get_items_permissions_check( $request );
29 +
30 + if ( true !== $has_permission ) {
31 + return $has_permission;
32 + }
33 +
34 + return $this->check_has_read_only_access( $request );
35 + }
36 +
37 + /**
38 + * Checks if a request has access to read or edit the specified menu.
39 + *
40 + * @since 5.9.0
41 + *
42 + * @param WP_REST_Request $request Full details about the request.
43 + * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
44 + */
45 + public function get_item_permissions_check( $request ) {
46 + $has_permission = parent::get_item_permissions_check( $request );
47 +
48 + if ( true !== $has_permission ) {
49 + return $has_permission;
50 + }
51 +
52 + return $this->check_has_read_only_access( $request );
53 + }
54 +
55 + /**
56 + * Gets the term, if the ID is valid.
57 + *
58 + * @since 5.9.0
59 + *
60 + * @param int $id Supplied ID.
61 + * @return WP_Term|WP_Error Term object if ID is valid, WP_Error otherwise.
62 + */
63 + protected function get_term( $id ) {
64 + $term = parent::get_term( $id );
65 +
66 + if ( is_wp_error( $term ) ) {
67 + return $term;
68 + }
69 +
70 + $nav_term = wp_get_nav_menu_object( $term );
71 + $nav_term->auto_add = $this->get_menu_auto_add( $nav_term->term_id );
72 +
73 + return $nav_term;
74 + }
75 +
76 + /**
77 + * Checks whether the current user has read permission for the endpoint.
78 + *
79 + * This allows for any user that can `edit_theme_options` or edit any REST API available post type.
80 + *
81 + * @since 5.9.0
82 + *
83 + * @param WP_REST_Request $request Full details about the request.
84 + * @return true|WP_Error True if the current user has permission, WP_Error object otherwise.
85 + */
86 + protected function check_has_read_only_access( $request ) {
87 + /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */
88 + $read_only_access = apply_filters( 'rest_menu_read_access', false, $request, $this );
89 + if ( $read_only_access ) {
90 + return true;
91 + }
92 +
93 + if ( current_user_can( 'edit_theme_options' ) ) {
94 + return true;
95 + }
96 +
97 + if ( current_user_can( 'edit_posts' ) ) {
98 + return true;
99 + }
100 +
101 + foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
102 + if ( current_user_can( $post_type->cap->edit_posts ) ) {
103 + return true;
104 + }
105 + }
106 +
107 + return new WP_Error(
108 + 'rest_cannot_view',
109 + __( 'Sorry, you are not allowed to view menus.' ),
110 + array( 'status' => rest_authorization_required_code() )
111 + );
112 + }
113 +
114 + /**
115 + * Prepares a single term output for response.
116 + *
117 + * @since 5.9.0
118 + *
119 + * @param WP_Term $term Term object.
120 + * @param WP_REST_Request $request Request object.
121 + * @return WP_REST_Response Response object.
122 + */
123 + public function prepare_item_for_response( $term, $request ) {
124 + $nav_menu = wp_get_nav_menu_object( $term );
125 + $response = parent::prepare_item_for_response( $nav_menu, $request );
126 +
127 + $fields = $this->get_fields_for_response( $request );
128 + $data = $response->get_data();
129 +
130 + if ( rest_is_field_included( 'locations', $fields ) ) {
131 + $data['locations'] = $this->get_menu_locations( $nav_menu->term_id );
132 + }
133 +
134 + if ( rest_is_field_included( 'auto_add', $fields ) ) {
135 + $data['auto_add'] = $this->get_menu_auto_add( $nav_menu->term_id );
136 + }
137 +
138 + $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
139 + $data = $this->add_additional_fields_to_object( $data, $request );
140 + $data = $this->filter_response_by_context( $data, $context );
141 +
142 + $response = rest_ensure_response( $data );
143 +
144 + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
145 + $response->add_links( $this->prepare_links( $term ) );
146 + }
147 +
148 + /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
149 + return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $term, $request );
150 + }
151 +
152 + /**
153 + * Prepares links for the request.
154 + *
155 + * @since 5.9.0
156 + *
157 + * @param WP_Term $term Term object.
158 + * @return array Links for the given term.
159 + */
160 + protected function prepare_links( $term ) {
161 + $links = parent::prepare_links( $term );
162 +
163 + $locations = $this->get_menu_locations( $term->term_id );
164 + foreach ( $locations as $location ) {
165 + $url = rest_url( sprintf( 'wp/v2/menu-locations/%s', $location ) );
166 +
167 + $links['https://api.w.org/menu-location'][] = array(
168 + 'href' => $url,
169 + 'embeddable' => true,
170 + );
171 + }
172 +
173 + return $links;
174 + }
175 +
176 + /**
177 + * Prepares a single term for create or update.
178 + *
179 + * @since 5.9.0
180 + *
181 + * @param WP_REST_Request $request Request object.
182 + * @return object Prepared term data.
183 + */
184 + public function prepare_item_for_database( $request ) {
185 + $prepared_term = parent::prepare_item_for_database( $request );
186 +
187 + $schema = $this->get_item_schema();
188 +
189 + if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
190 + $prepared_term->{'menu-name'} = $request['name'];
191 + }
192 +
193 + return $prepared_term;
194 + }
195 +
196 + /**
197 + * Creates a single term in a taxonomy.
198 + *
199 + * @since 5.9.0
200 + *
201 + * @param WP_REST_Request $request Full details about the request.
202 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
203 + */
204 + public function create_item( $request ) {
205 + if ( isset( $request['parent'] ) ) {
206 + if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
207 + return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
208 + }
209 +
210 + $parent = wp_get_nav_menu_object( (int) $request['parent'] );
211 +
212 + if ( ! $parent ) {
213 + return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) );
214 + }
215 + }
216 +
217 + $prepared_term = $this->prepare_item_for_database( $request );
218 +
219 + $term = wp_update_nav_menu_object( 0, wp_slash( (array) $prepared_term ) );
220 +
221 + if ( is_wp_error( $term ) ) {
222 + /*
223 + * If we're going to inform the client that the term already exists,
224 + * give them the identifier for future use.
225 + */
226 +
227 + if ( in_array( 'menu_exists', $term->get_error_codes(), true ) ) {
228 + $existing_term = get_term_by( 'name', $prepared_term->{'menu-name'}, $this->taxonomy );
229 + $term->add_data( $existing_term->term_id, 'menu_exists' );
230 + $term->add_data(
231 + array(
232 + 'status' => 400,
233 + 'term_id' => $existing_term->term_id,
234 + )
235 + );
236 + } else {
237 + $term->add_data( array( 'status' => 400 ) );
238 + }
239 +
240 + return $term;
241 + }
242 +
243 + $term = $this->get_term( $term );
244 +
245 + /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
246 + do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );
247 +
248 + $schema = $this->get_item_schema();
249 + if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
250 + $meta_update = $this->meta->update_value( $request['meta'], $term->term_id );
251 +
252 + if ( is_wp_error( $meta_update ) ) {
253 + return $meta_update;
254 + }
255 + }
256 +
257 + $locations_update = $this->handle_locations( $term->term_id, $request );
258 +
259 + if ( is_wp_error( $locations_update ) ) {
260 + return $locations_update;
261 + }
262 +
263 + $this->handle_auto_add( $term->term_id, $request );
264 +
265 + $fields_update = $this->update_additional_fields_for_object( $term, $request );
266 +
267 + if ( is_wp_error( $fields_update ) ) {
268 + return $fields_update;
269 + }
270 +
271 + $request->set_param( 'context', 'view' );
272 +
273 + /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
274 + do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, true );
275 +
276 + $response = $this->prepare_item_for_response( $term, $request );
277 + $response = rest_ensure_response( $response );
278 +
279 + $response->set_status( 201 );
280 + $response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) );
281 +
282 + return $response;
283 + }
284 +
285 + /**
286 + * Updates a single term from a taxonomy.
287 + *
288 + * @since 5.9.0
289 + *
290 + * @param WP_REST_Request $request Full details about the request.
291 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
292 + */
293 + public function update_item( $request ) {
294 + $term = $this->get_term( $request['id'] );
295 + if ( is_wp_error( $term ) ) {
296 + return $term;
297 + }
298 +
299 + if ( isset( $request['parent'] ) ) {
300 + if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
301 + return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
302 + }
303 +
304 + $parent = get_term( (int) $request['parent'], $this->taxonomy );
305 +
306 + if ( ! $parent ) {
307 + return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) );
308 + }
309 + }
310 +
311 + $prepared_term = $this->prepare_item_for_database( $request );
312 +
313 + // Only update the term if we have something to update.
314 + if ( ! empty( $prepared_term ) ) {
315 + if ( ! isset( $prepared_term->{'menu-name'} ) ) {
316 + // wp_update_nav_menu_object() requires that the menu-name is always passed.
317 + $prepared_term->{'menu-name'} = $term->name;
318 + }
319 +
320 + $update = wp_update_nav_menu_object( $term->term_id, wp_slash( (array) $prepared_term ) );
321 +
322 + if ( is_wp_error( $update ) ) {
323 + return $update;
324 + }
325 + }
326 +
327 + $term = get_term( $term->term_id, $this->taxonomy );
328 +
329 + /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
330 + do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );
331 +
332 + $schema = $this->get_item_schema();
333 + if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
334 + $meta_update = $this->meta->update_value( $request['meta'], $term->term_id );
335 +
336 + if ( is_wp_error( $meta_update ) ) {
337 + return $meta_update;
338 + }
339 + }
340 +
341 + $locations_update = $this->handle_locations( $term->term_id, $request );
342 +
343 + if ( is_wp_error( $locations_update ) ) {
344 + return $locations_update;
345 + }
346 +
347 + $this->handle_auto_add( $term->term_id, $request );
348 +
349 + $fields_update = $this->update_additional_fields_for_object( $term, $request );
350 +
351 + if ( is_wp_error( $fields_update ) ) {
352 + return $fields_update;
353 + }
354 +
355 + $request->set_param( 'context', 'view' );
356 +
357 + /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
358 + do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, false );
359 +
360 + $response = $this->prepare_item_for_response( $term, $request );
361 +
362 + return rest_ensure_response( $response );
363 + }
364 +
365 + /**
366 + * Deletes a single term from a taxonomy.
367 + *
368 + * @since 5.9.0
369 + *
370 + * @param WP_REST_Request $request Full details about the request.
371 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
372 + */
373 + public function delete_item( $request ) {
374 + $term = $this->get_term( $request['id'] );
375 + if ( is_wp_error( $term ) ) {
376 + return $term;
377 + }
378 +
379 + // We don't support trashing for terms.
380 + if ( ! $request['force'] ) {
381 + /* translators: %s: force=true */
382 + return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Menus do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) );
383 + }
384 +
385 + $request->set_param( 'context', 'view' );
386 +
387 + $previous = $this->prepare_item_for_response( $term, $request );
388 +
389 + $result = wp_delete_nav_menu( $term );
390 +
391 + if ( ! $result || is_wp_error( $result ) ) {
392 + return new WP_Error( 'rest_cannot_delete', __( 'The menu cannot be deleted.' ), array( 'status' => 500 ) );
393 + }
394 +
395 + $response = new WP_REST_Response();
396 + $response->set_data(
397 + array(
398 + 'deleted' => true,
399 + 'previous' => $previous->get_data(),
400 + )
401 + );
402 +
403 + /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
404 + do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request );
405 +
406 + return $response;
407 + }
408 +
409 + /**
410 + * Returns the value of a menu's auto_add setting.
411 + *
412 + * @since 5.9.0
413 + *
414 + * @param int $menu_id The menu id to query.
415 + * @return bool The value of auto_add.
416 + */
417 + protected function get_menu_auto_add( $menu_id ) {
418 + $nav_menu_option = (array) get_option( 'nav_menu_options', array( 'auto_add' => array() ) );
419 +
420 + return in_array( $menu_id, $nav_menu_option['auto_add'], true );
421 + }
422 +
423 + /**
424 + * Updates the menu's auto add from a REST request.
425 + *
426 + * @since 5.9.0
427 + *
428 + * @param int $menu_id The menu id to update.
429 + * @param WP_REST_Request $request Full details about the request.
430 + * @return bool True if the auto add setting was successfully updated.
431 + */
432 + protected function handle_auto_add( $menu_id, $request ) {
433 + if ( ! isset( $request['auto_add'] ) ) {
434 + return true;
435 + }
436 +
437 + $nav_menu_option = (array) get_option( 'nav_menu_options', array( 'auto_add' => array() ) );
438 +
439 + if ( ! isset( $nav_menu_option['auto_add'] ) ) {
440 + $nav_menu_option['auto_add'] = array();
441 + }
442 +
443 + $auto_add = $request['auto_add'];
444 +
445 + $i = array_search( $menu_id, $nav_menu_option['auto_add'], true );
446 +
447 + if ( $auto_add && false === $i ) {
448 + $nav_menu_option['auto_add'][] = $menu_id;
449 + } elseif ( ! $auto_add && false !== $i ) {
450 + array_splice( $nav_menu_option['auto_add'], $i, 1 );
451 + }
452 +
453 + $update = update_option( 'nav_menu_options', $nav_menu_option );
454 +
455 + /** This action is documented in wp-includes/nav-menu.php */
456 + do_action( 'wp_update_nav_menu', $menu_id );
457 +
458 + return $update;
459 + }
460 +
461 + /**
462 + * Returns the names of the locations assigned to the menu.
463 + *
464 + * @since 5.9.0
465 + *
466 + * @param int $menu_id The menu id.
467 + * @return string[] The locations assigned to the menu.
468 + */
469 + protected function get_menu_locations( $menu_id ) {
470 + $locations = get_nav_menu_locations();
471 + $menu_locations = array();
472 +
473 + foreach ( $locations as $location => $assigned_menu_id ) {
474 + if ( $menu_id === $assigned_menu_id ) {
475 + $menu_locations[] = $location;
476 + }
477 + }
478 +
479 + return $menu_locations;
480 + }
481 +
482 + /**
483 + * Updates the menu's locations from a REST request.
484 + *
485 + * @since 5.9.0
486 + *
487 + * @param int $menu_id The menu id to update.
488 + * @param WP_REST_Request $request Full details about the request.
489 + * @return true|WP_Error True on success, a WP_Error on an error updating any of the locations.
490 + */
491 + protected function handle_locations( $menu_id, $request ) {
492 + if ( ! isset( $request['locations'] ) ) {
493 + return true;
494 + }
495 +
496 + $menu_locations = get_registered_nav_menus();
497 + $menu_locations = array_keys( $menu_locations );
498 + $new_locations = array();
499 + foreach ( $request['locations'] as $location ) {
500 + if ( ! in_array( $location, $menu_locations, true ) ) {
501 + return new WP_Error(
502 + 'rest_invalid_menu_location',
503 + __( 'Invalid menu location.' ),
504 + array(
505 + 'status' => 400,
506 + 'location' => $location,
507 + )
508 + );
509 + }
510 + $new_locations[ $location ] = $menu_id;
511 + }
512 + $assigned_menu = get_nav_menu_locations();
513 + foreach ( $assigned_menu as $location => $term_id ) {
514 + if ( $term_id === $menu_id ) {
515 + unset( $assigned_menu[ $location ] );
516 + }
517 + }
518 + $new_assignments = array_merge( $assigned_menu, $new_locations );
519 + set_theme_mod( 'nav_menu_locations', $new_assignments );
520 +
521 + return true;
522 + }
523 +
524 + /**
525 + * Retrieves the term's schema, conforming to JSON Schema.
526 + *
527 + * @since 5.9.0
528 + *
529 + * @return array Item schema data.
530 + */
531 + public function get_item_schema() {
532 + if ( $this->schema ) {
533 + return $this->add_additional_fields_schema( $this->schema );
534 + }
535 +
536 + $schema = parent::get_item_schema();
537 + unset( $schema['properties']['count'], $schema['properties']['link'], $schema['properties']['taxonomy'] );
538 +
539 + $schema['properties']['locations'] = array(
540 + 'description' => __( 'The locations assigned to the menu.' ),
541 + 'type' => 'array',
542 + 'items' => array(
543 + 'type' => 'string',
544 + ),
545 + 'context' => array( 'view', 'edit' ),
546 + 'arg_options' => array(
547 + 'validate_callback' => static function ( $locations, $request, $param ) {
548 + $valid = rest_validate_request_arg( $locations, $request, $param );
549 +
550 + if ( true !== $valid ) {
551 + return $valid;
552 + }
553 +
554 + $locations = rest_sanitize_request_arg( $locations, $request, $param );
555 +
556 + foreach ( $locations as $location ) {
557 + if ( ! array_key_exists( $location, get_registered_nav_menus() ) ) {
558 + return new WP_Error(
559 + 'rest_invalid_menu_location',
560 + __( 'Invalid menu location.' ),
561 + array(
562 + 'location' => $location,
563 + )
564 + );
565 + }
566 + }
567 +
568 + return true;
569 + },
570 + ),
571 + );
572 +
573 + $schema['properties']['auto_add'] = array(
574 + 'description' => __( 'Whether to automatically add top level pages to this menu.' ),
575 + 'context' => array( 'view', 'edit' ),
576 + 'type' => 'boolean',
577 + );
578 +
579 + $this->schema = $schema;
580 +
581 + return $this->add_additional_fields_schema( $this->schema );
582 + }
583 + }
584 +