wp/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php
changeset 7 cf61fcea0001
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
       
     1 <?php
       
     2 /**
       
     3  * REST API: WP_REST_Terms_Controller class
       
     4  *
       
     5  * @package WordPress
       
     6  * @subpackage REST_API
       
     7  * @since 4.7.0
       
     8  */
       
     9 
       
    10 /**
       
    11  * Core class used to managed terms associated with a taxonomy via the REST API.
       
    12  *
       
    13  * @since 4.7.0
       
    14  *
       
    15  * @see WP_REST_Controller
       
    16  */
       
    17 class WP_REST_Terms_Controller extends WP_REST_Controller {
       
    18 
       
    19 	/**
       
    20 	 * Taxonomy key.
       
    21 	 *
       
    22 	 * @since 4.7.0
       
    23 	 * @var string
       
    24 	 */
       
    25 	protected $taxonomy;
       
    26 
       
    27 	/**
       
    28 	 * Instance of a term meta fields object.
       
    29 	 *
       
    30 	 * @since 4.7.0
       
    31 	 * @var WP_REST_Term_Meta_Fields
       
    32 	 */
       
    33 	protected $meta;
       
    34 
       
    35 	/**
       
    36 	 * Column to have the terms be sorted by.
       
    37 	 *
       
    38 	 * @since 4.7.0
       
    39 	 * @var string
       
    40 	 */
       
    41 	protected $sort_column;
       
    42 
       
    43 	/**
       
    44 	 * Number of terms that were found.
       
    45 	 *
       
    46 	 * @since 4.7.0
       
    47 	 * @var int
       
    48 	 */
       
    49 	protected $total_terms;
       
    50 
       
    51 	/**
       
    52 	 * Constructor.
       
    53 	 *
       
    54 	 * @since 4.7.0
       
    55 	 *
       
    56 	 * @param string $taxonomy Taxonomy key.
       
    57 	 */
       
    58 	public function __construct( $taxonomy ) {
       
    59 		$this->taxonomy = $taxonomy;
       
    60 		$this->namespace = 'wp/v2';
       
    61 		$tax_obj = get_taxonomy( $taxonomy );
       
    62 		$this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name;
       
    63 
       
    64 		$this->meta = new WP_REST_Term_Meta_Fields( $taxonomy );
       
    65 	}
       
    66 
       
    67 	/**
       
    68 	 * Registers the routes for the objects of the controller.
       
    69 	 *
       
    70 	 * @since 4.7.0
       
    71 	 *
       
    72 	 * @see register_rest_route()
       
    73 	 */
       
    74 	public function register_routes() {
       
    75 
       
    76 		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
       
    77 			array(
       
    78 				'methods'             => WP_REST_Server::READABLE,
       
    79 				'callback'            => array( $this, 'get_items' ),
       
    80 				'permission_callback' => array( $this, 'get_items_permissions_check' ),
       
    81 				'args'                => $this->get_collection_params(),
       
    82 			),
       
    83 			array(
       
    84 				'methods'             => WP_REST_Server::CREATABLE,
       
    85 				'callback'            => array( $this, 'create_item' ),
       
    86 				'permission_callback' => array( $this, 'create_item_permissions_check' ),
       
    87 				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
       
    88 			),
       
    89 			'schema' => array( $this, 'get_public_item_schema' ),
       
    90 		) );
       
    91 
       
    92 		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
       
    93 			'args' => array(
       
    94 				'id' => array(
       
    95 					'description' => __( 'Unique identifier for the term.' ),
       
    96 					'type'        => 'integer',
       
    97 				),
       
    98 			),
       
    99 			array(
       
   100 				'methods'             => WP_REST_Server::READABLE,
       
   101 				'callback'            => array( $this, 'get_item' ),
       
   102 				'permission_callback' => array( $this, 'get_item_permissions_check' ),
       
   103 				'args'                => array(
       
   104 					'context' => $this->get_context_param( array( 'default' => 'view' ) ),
       
   105 				),
       
   106 			),
       
   107 			array(
       
   108 				'methods'             => WP_REST_Server::EDITABLE,
       
   109 				'callback'            => array( $this, 'update_item' ),
       
   110 				'permission_callback' => array( $this, 'update_item_permissions_check' ),
       
   111 				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
       
   112 			),
       
   113 			array(
       
   114 				'methods'             => WP_REST_Server::DELETABLE,
       
   115 				'callback'            => array( $this, 'delete_item' ),
       
   116 				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
       
   117 				'args'                => array(
       
   118 					'force' => array(
       
   119 						'type'        => 'boolean',
       
   120 						'default'     => false,
       
   121 						'description' => __( 'Required to be true, as terms do not support trashing.' ),
       
   122 					),
       
   123 				),
       
   124 			),
       
   125 			'schema' => array( $this, 'get_public_item_schema' ),
       
   126 		) );
       
   127 	}
       
   128 
       
   129 	/**
       
   130 	 * Checks if a request has access to read terms in the specified taxonomy.
       
   131 	 *
       
   132 	 * @since 4.7.0
       
   133 	 *
       
   134 	 * @param WP_REST_Request $request Full details about the request.
       
   135 	 * @return bool|WP_Error True if the request has read access, otherwise false or WP_Error object.
       
   136 	 */
       
   137 	public function get_items_permissions_check( $request ) {
       
   138 		$tax_obj = get_taxonomy( $this->taxonomy );
       
   139 		if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
       
   140 			return false;
       
   141 		}
       
   142 		if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) {
       
   143 			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit terms in this taxonomy.' ), array( 'status' => rest_authorization_required_code() ) );
       
   144 		}
       
   145 		return true;
       
   146 	}
       
   147 
       
   148 	/**
       
   149 	 * Retrieves terms associated with a taxonomy.
       
   150 	 *
       
   151 	 * @since 4.7.0
       
   152 	 *
       
   153 	 * @param WP_REST_Request $request Full details about the request.
       
   154 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
       
   155 	 */
       
   156 	public function get_items( $request ) {
       
   157 
       
   158 		// Retrieve the list of registered collection query parameters.
       
   159 		$registered = $this->get_collection_params();
       
   160 
       
   161 		/*
       
   162 		 * This array defines mappings between public API query parameters whose
       
   163 		 * values are accepted as-passed, and their internal WP_Query parameter
       
   164 		 * name equivalents (some are the same). Only values which are also
       
   165 		 * present in $registered will be set.
       
   166 		 */
       
   167 		$parameter_mappings = array(
       
   168 			'exclude'    => 'exclude',
       
   169 			'include'    => 'include',
       
   170 			'order'      => 'order',
       
   171 			'orderby'    => 'orderby',
       
   172 			'post'       => 'post',
       
   173 			'hide_empty' => 'hide_empty',
       
   174 			'per_page'   => 'number',
       
   175 			'search'     => 'search',
       
   176 			'slug'       => 'slug',
       
   177 		);
       
   178 
       
   179 		$prepared_args = array();
       
   180 
       
   181 		/*
       
   182 		 * For each known parameter which is both registered and present in the request,
       
   183 		 * set the parameter's value on the query $prepared_args.
       
   184 		 */
       
   185 		foreach ( $parameter_mappings as $api_param => $wp_param ) {
       
   186 			if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
       
   187 				$prepared_args[ $wp_param ] = $request[ $api_param ];
       
   188 			}
       
   189 		}
       
   190 
       
   191 		if ( isset( $prepared_args['orderby'] ) && isset( $request['orderby'] ) ) {
       
   192 			$orderby_mappings = array(
       
   193 				'include_slugs' => 'slug__in',
       
   194 			);
       
   195 
       
   196 			if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
       
   197 				$prepared_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
       
   198 			}
       
   199 		}
       
   200 
       
   201 		if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) {
       
   202 			$prepared_args['offset'] = $request['offset'];
       
   203 		} else {
       
   204 			$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
       
   205 		}
       
   206 
       
   207 		$taxonomy_obj = get_taxonomy( $this->taxonomy );
       
   208 
       
   209 		if ( $taxonomy_obj->hierarchical && isset( $registered['parent'], $request['parent'] ) ) {
       
   210 			if ( 0 === $request['parent'] ) {
       
   211 				// Only query top-level terms.
       
   212 				$prepared_args['parent'] = 0;
       
   213 			} else {
       
   214 				if ( $request['parent'] ) {
       
   215 					$prepared_args['parent'] = $request['parent'];
       
   216 				}
       
   217 			}
       
   218 		}
       
   219 
       
   220 		/**
       
   221 		 * Filters the query arguments before passing them to get_terms().
       
   222 		 *
       
   223 		 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
       
   224 		 *
       
   225 		 * Enables adding extra arguments or setting defaults for a terms
       
   226 		 * collection request.
       
   227 		 *
       
   228 		 * @since 4.7.0
       
   229 		 *
       
   230 		 * @link https://developer.wordpress.org/reference/functions/get_terms/
       
   231 		 *
       
   232 		 * @param array           $prepared_args Array of arguments to be
       
   233 		 *                                       passed to get_terms().
       
   234 		 * @param WP_REST_Request $request       The current request.
       
   235 		 */
       
   236 		$prepared_args = apply_filters( "rest_{$this->taxonomy}_query", $prepared_args, $request );
       
   237 
       
   238 		if ( ! empty( $prepared_args['post'] )  ) {
       
   239 			$query_result = wp_get_object_terms( $prepared_args['post'], $this->taxonomy, $prepared_args );
       
   240 
       
   241 			// Used when calling wp_count_terms() below.
       
   242 			$prepared_args['object_ids'] = $prepared_args['post'];
       
   243 		} else {
       
   244 			$query_result = get_terms( $this->taxonomy, $prepared_args );
       
   245 		}
       
   246 
       
   247 		$count_args = $prepared_args;
       
   248 
       
   249 		unset( $count_args['number'], $count_args['offset'] );
       
   250 
       
   251 		$total_terms = wp_count_terms( $this->taxonomy, $count_args );
       
   252 
       
   253 		// wp_count_terms can return a falsy value when the term has no children.
       
   254 		if ( ! $total_terms ) {
       
   255 			$total_terms = 0;
       
   256 		}
       
   257 
       
   258 		$response = array();
       
   259 
       
   260 		foreach ( $query_result as $term ) {
       
   261 			$data = $this->prepare_item_for_response( $term, $request );
       
   262 			$response[] = $this->prepare_response_for_collection( $data );
       
   263 		}
       
   264 
       
   265 		$response = rest_ensure_response( $response );
       
   266 
       
   267 		// Store pagination values for headers.
       
   268 		$per_page = (int) $prepared_args['number'];
       
   269 		$page     = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
       
   270 
       
   271 		$response->header( 'X-WP-Total', (int) $total_terms );
       
   272 
       
   273 		$max_pages = ceil( $total_terms / $per_page );
       
   274 
       
   275 		$response->header( 'X-WP-TotalPages', (int) $max_pages );
       
   276 
       
   277 		$base = add_query_arg( $request->get_query_params(), rest_url( $this->namespace . '/' . $this->rest_base ) );
       
   278 		if ( $page > 1 ) {
       
   279 			$prev_page = $page - 1;
       
   280 
       
   281 			if ( $prev_page > $max_pages ) {
       
   282 				$prev_page = $max_pages;
       
   283 			}
       
   284 
       
   285 			$prev_link = add_query_arg( 'page', $prev_page, $base );
       
   286 			$response->link_header( 'prev', $prev_link );
       
   287 		}
       
   288 		if ( $max_pages > $page ) {
       
   289 			$next_page = $page + 1;
       
   290 			$next_link = add_query_arg( 'page', $next_page, $base );
       
   291 
       
   292 			$response->link_header( 'next', $next_link );
       
   293 		}
       
   294 
       
   295 		return $response;
       
   296 	}
       
   297 
       
   298 	/**
       
   299 	 * Get the term, if the ID is valid.
       
   300 	 *
       
   301 	 * @since 4.7.2
       
   302 	 *
       
   303 	 * @param int $id Supplied ID.
       
   304 	 * @return WP_Term|WP_Error Term object if ID is valid, WP_Error otherwise.
       
   305 	 */
       
   306 	protected function get_term( $id ) {
       
   307 		$error = new WP_Error( 'rest_term_invalid', __( 'Term does not exist.' ), array( 'status' => 404 ) );
       
   308 
       
   309 		if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
       
   310 			return $error;
       
   311 		}
       
   312 
       
   313 		if ( (int) $id <= 0 ) {
       
   314 			return $error;
       
   315 		}
       
   316 
       
   317 		$term = get_term( (int) $id, $this->taxonomy );
       
   318 		if ( empty( $term ) || $term->taxonomy !== $this->taxonomy ) {
       
   319 			return $error;
       
   320 		}
       
   321 
       
   322 		return $term;
       
   323 	}
       
   324 
       
   325 	/**
       
   326 	 * Checks if a request has access to read or edit the specified term.
       
   327 	 *
       
   328 	 * @since 4.7.0
       
   329 	 *
       
   330 	 * @param WP_REST_Request $request Full details about the request.
       
   331 	 * @return bool|WP_Error True if the request has read access for the item, otherwise false or WP_Error object.
       
   332 	 */
       
   333 	public function get_item_permissions_check( $request ) {
       
   334 		$term = $this->get_term( $request['id'] );
       
   335 		if ( is_wp_error( $term ) ) {
       
   336 			return $term;
       
   337 		}
       
   338 
       
   339 		if ( 'edit' === $request['context'] && ! current_user_can( 'edit_term', $term->term_id ) ) {
       
   340 			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this term.' ), array( 'status' => rest_authorization_required_code() ) );
       
   341 		}
       
   342 		return true;
       
   343 	}
       
   344 
       
   345 	/**
       
   346 	 * Gets a single term from a taxonomy.
       
   347 	 *
       
   348 	 * @since 4.7.0
       
   349 	 *
       
   350 	 * @param WP_REST_Request $request Full details about the request.
       
   351 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
       
   352 	 */
       
   353 	public function get_item( $request ) {
       
   354 		$term = $this->get_term( $request['id'] );
       
   355 		if ( is_wp_error( $term ) ) {
       
   356 			return $term;
       
   357 		}
       
   358 
       
   359 		$response = $this->prepare_item_for_response( $term, $request );
       
   360 
       
   361 		return rest_ensure_response( $response );
       
   362 	}
       
   363 
       
   364 	/**
       
   365 	 * Checks if a request has access to create a term.
       
   366 	 *
       
   367 	 * @since 4.7.0
       
   368 	 *
       
   369 	 * @param WP_REST_Request $request Full details about the request.
       
   370 	 * @return bool|WP_Error True if the request has access to create items, false or WP_Error object otherwise.
       
   371 	 */
       
   372 	public function create_item_permissions_check( $request ) {
       
   373 
       
   374 		if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
       
   375 			return false;
       
   376 		}
       
   377 
       
   378 		$taxonomy_obj = get_taxonomy( $this->taxonomy );
       
   379 		if ( ( is_taxonomy_hierarchical( $this->taxonomy )
       
   380 				&& ! current_user_can( $taxonomy_obj->cap->edit_terms ) )
       
   381 			|| ( ! is_taxonomy_hierarchical( $this->taxonomy )
       
   382 				&& ! current_user_can( $taxonomy_obj->cap->assign_terms ) ) ) {
       
   383 			return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create new terms.' ), array( 'status' => rest_authorization_required_code() ) );
       
   384 		}
       
   385 
       
   386 		return true;
       
   387 	}
       
   388 
       
   389 	/**
       
   390 	 * Creates a single term in a taxonomy.
       
   391 	 *
       
   392 	 * @since 4.7.0
       
   393 	 *
       
   394 	 * @param WP_REST_Request $request Full details about the request.
       
   395 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
       
   396 	 */
       
   397 	public function create_item( $request ) {
       
   398 		if ( isset( $request['parent'] ) ) {
       
   399 			if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
       
   400 				return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
       
   401 			}
       
   402 
       
   403 			$parent = get_term( (int) $request['parent'], $this->taxonomy );
       
   404 
       
   405 			if ( ! $parent ) {
       
   406 				return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) );
       
   407 			}
       
   408 		}
       
   409 
       
   410 		$prepared_term = $this->prepare_item_for_database( $request );
       
   411 
       
   412 		$term = wp_insert_term( wp_slash( $prepared_term->name ), $this->taxonomy, wp_slash( (array) $prepared_term ) );
       
   413 		if ( is_wp_error( $term ) ) {
       
   414 			/*
       
   415 			 * If we're going to inform the client that the term already exists,
       
   416 			 * give them the identifier for future use.
       
   417 			 */
       
   418 			if ( $term_id = $term->get_error_data( 'term_exists' ) ) {
       
   419 				$existing_term = get_term( $term_id, $this->taxonomy );
       
   420 				$term->add_data( $existing_term->term_id, 'term_exists' );
       
   421 				$term->add_data( array( 'status' => 409, 'term_id' => $term_id ) );
       
   422 			}
       
   423 
       
   424 			return $term;
       
   425 		}
       
   426 
       
   427 		$term = get_term( $term['term_id'], $this->taxonomy );
       
   428 
       
   429 		/**
       
   430 		 * Fires after a single term is created or updated via the REST API.
       
   431 		 *
       
   432 		 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
       
   433 		 *
       
   434 		 * @since 4.7.0
       
   435 		 *
       
   436 		 * @param WP_Term         $term     Inserted or updated term object.
       
   437 		 * @param WP_REST_Request $request  Request object.
       
   438 		 * @param bool            $creating True when creating a term, false when updating.
       
   439 		 */
       
   440 		do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );
       
   441 
       
   442 		$schema = $this->get_item_schema();
       
   443 		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
       
   444 			$meta_update = $this->meta->update_value( $request['meta'], (int) $request['id'] );
       
   445 
       
   446 			if ( is_wp_error( $meta_update ) ) {
       
   447 				return $meta_update;
       
   448 			}
       
   449 		}
       
   450 
       
   451 		$fields_update = $this->update_additional_fields_for_object( $term, $request );
       
   452 
       
   453 		if ( is_wp_error( $fields_update ) ) {
       
   454 			return $fields_update;
       
   455 		}
       
   456 
       
   457 		$request->set_param( 'context', 'view' );
       
   458 
       
   459 		$response = $this->prepare_item_for_response( $term, $request );
       
   460 		$response = rest_ensure_response( $response );
       
   461 
       
   462 		$response->set_status( 201 );
       
   463 		$response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) );
       
   464 
       
   465 		return $response;
       
   466 	}
       
   467 
       
   468 	/**
       
   469 	 * Checks if a request has access to update the specified term.
       
   470 	 *
       
   471 	 * @since 4.7.0
       
   472 	 *
       
   473 	 * @param WP_REST_Request $request Full details about the request.
       
   474 	 * @return bool|WP_Error True if the request has access to update the item, false or WP_Error object otherwise.
       
   475 	 */
       
   476 	public function update_item_permissions_check( $request ) {
       
   477 		$term = $this->get_term( $request['id'] );
       
   478 		if ( is_wp_error( $term ) ) {
       
   479 			return $term;
       
   480 		}
       
   481 
       
   482 		if ( ! current_user_can( 'edit_term', $term->term_id ) ) {
       
   483 			return new WP_Error( 'rest_cannot_update', __( 'Sorry, you are not allowed to edit this term.' ), array( 'status' => rest_authorization_required_code() ) );
       
   484 		}
       
   485 
       
   486 		return true;
       
   487 	}
       
   488 
       
   489 	/**
       
   490 	 * Updates a single term from a taxonomy.
       
   491 	 *
       
   492 	 * @since 4.7.0
       
   493 	 *
       
   494 	 * @param WP_REST_Request $request Full details about the request.
       
   495 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
       
   496 	 */
       
   497 	public function update_item( $request ) {
       
   498 		$term = $this->get_term( $request['id'] );
       
   499 		if ( is_wp_error( $term ) ) {
       
   500 			return $term;
       
   501 		}
       
   502 
       
   503 		if ( isset( $request['parent'] ) ) {
       
   504 			if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
       
   505 				return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
       
   506 			}
       
   507 
       
   508 			$parent = get_term( (int) $request['parent'], $this->taxonomy );
       
   509 
       
   510 			if ( ! $parent ) {
       
   511 				return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) );
       
   512 			}
       
   513 		}
       
   514 
       
   515 		$prepared_term = $this->prepare_item_for_database( $request );
       
   516 
       
   517 		// Only update the term if we haz something to update.
       
   518 		if ( ! empty( $prepared_term ) ) {
       
   519 			$update = wp_update_term( $term->term_id, $term->taxonomy, wp_slash( (array) $prepared_term ) );
       
   520 
       
   521 			if ( is_wp_error( $update ) ) {
       
   522 				return $update;
       
   523 			}
       
   524 		}
       
   525 
       
   526 		$term = get_term( $term->term_id, $this->taxonomy );
       
   527 
       
   528 		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
       
   529 		do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );
       
   530 
       
   531 		$schema = $this->get_item_schema();
       
   532 		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
       
   533 			$meta_update = $this->meta->update_value( $request['meta'], $term->term_id );
       
   534 
       
   535 			if ( is_wp_error( $meta_update ) ) {
       
   536 				return $meta_update;
       
   537 			}
       
   538 		}
       
   539 
       
   540 		$fields_update = $this->update_additional_fields_for_object( $term, $request );
       
   541 
       
   542 		if ( is_wp_error( $fields_update ) ) {
       
   543 			return $fields_update;
       
   544 		}
       
   545 
       
   546 		$request->set_param( 'context', 'view' );
       
   547 
       
   548 		$response = $this->prepare_item_for_response( $term, $request );
       
   549 
       
   550 		return rest_ensure_response( $response );
       
   551 	}
       
   552 
       
   553 	/**
       
   554 	 * Checks if a request has access to delete the specified term.
       
   555 	 *
       
   556 	 * @since 4.7.0
       
   557 	 *
       
   558 	 * @param WP_REST_Request $request Full details about the request.
       
   559 	 * @return bool|WP_Error True if the request has access to delete the item, otherwise false or WP_Error object.
       
   560 	 */
       
   561 	public function delete_item_permissions_check( $request ) {
       
   562 		$term = $this->get_term( $request['id'] );
       
   563 		if ( is_wp_error( $term ) ) {
       
   564 			return $term;
       
   565 		}
       
   566 
       
   567 		if ( ! current_user_can( 'delete_term', $term->term_id ) ) {
       
   568 			return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this term.' ), array( 'status' => rest_authorization_required_code() ) );
       
   569 		}
       
   570 
       
   571 		return true;
       
   572 	}
       
   573 
       
   574 	/**
       
   575 	 * Deletes a single term from a taxonomy.
       
   576 	 *
       
   577 	 * @since 4.7.0
       
   578 	 *
       
   579 	 * @param WP_REST_Request $request Full details about the request.
       
   580 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
       
   581 	 */
       
   582 	public function delete_item( $request ) {
       
   583 		$term = $this->get_term( $request['id'] );
       
   584 		if ( is_wp_error( $term ) ) {
       
   585 			return $term;
       
   586 		}
       
   587 
       
   588 		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
       
   589 
       
   590 		// We don't support trashing for terms.
       
   591 		if ( ! $force ) {
       
   592 			/* translators: %s: force=true */
       
   593 			return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Terms do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) );
       
   594 		}
       
   595 
       
   596 		$request->set_param( 'context', 'view' );
       
   597 
       
   598 		$previous = $this->prepare_item_for_response( $term, $request );
       
   599 
       
   600 		$retval = wp_delete_term( $term->term_id, $term->taxonomy );
       
   601 
       
   602 		if ( ! $retval ) {
       
   603 			return new WP_Error( 'rest_cannot_delete', __( 'The term cannot be deleted.' ), array( 'status' => 500 ) );
       
   604 		}
       
   605 
       
   606 		$response = new WP_REST_Response();
       
   607 		$response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) );
       
   608 
       
   609 		/**
       
   610 		 * Fires after a single term is deleted via the REST API.
       
   611 		 *
       
   612 		 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
       
   613 		 *
       
   614 		 * @since 4.7.0
       
   615 		 *
       
   616 		 * @param WP_Term          $term     The deleted term.
       
   617 		 * @param WP_REST_Response $response The response data.
       
   618 		 * @param WP_REST_Request  $request  The request sent to the API.
       
   619 		 */
       
   620 		do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request );
       
   621 
       
   622 		return $response;
       
   623 	}
       
   624 
       
   625 	/**
       
   626 	 * Prepares a single term for create or update.
       
   627 	 *
       
   628 	 * @since 4.7.0
       
   629 	 *
       
   630 	 * @param WP_REST_Request $request Request object.
       
   631 	 * @return object $prepared_term Term object.
       
   632 	 */
       
   633 	public function prepare_item_for_database( $request ) {
       
   634 		$prepared_term = new stdClass;
       
   635 
       
   636 		$schema = $this->get_item_schema();
       
   637 		if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
       
   638 			$prepared_term->name = $request['name'];
       
   639 		}
       
   640 
       
   641 		if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) {
       
   642 			$prepared_term->slug = $request['slug'];
       
   643 		}
       
   644 
       
   645 		if ( isset( $request['taxonomy'] ) && ! empty( $schema['properties']['taxonomy'] ) ) {
       
   646 			$prepared_term->taxonomy = $request['taxonomy'];
       
   647 		}
       
   648 
       
   649 		if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) {
       
   650 			$prepared_term->description = $request['description'];
       
   651 		}
       
   652 
       
   653 		if ( isset( $request['parent'] ) && ! empty( $schema['properties']['parent'] ) ) {
       
   654 			$parent_term_id = 0;
       
   655 			$parent_term    = get_term( (int) $request['parent'], $this->taxonomy );
       
   656 
       
   657 			if ( $parent_term ) {
       
   658 				$parent_term_id = $parent_term->term_id;
       
   659 			}
       
   660 
       
   661 			$prepared_term->parent = $parent_term_id;
       
   662 		}
       
   663 
       
   664 		/**
       
   665 		 * Filters term data before inserting term via the REST API.
       
   666 		 *
       
   667 		 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
       
   668 		 *
       
   669 		 * @since 4.7.0
       
   670 		 *
       
   671 		 * @param object          $prepared_term Term object.
       
   672 		 * @param WP_REST_Request $request       Request object.
       
   673 		 */
       
   674 		return apply_filters( "rest_pre_insert_{$this->taxonomy}", $prepared_term, $request );
       
   675 	}
       
   676 
       
   677 	/**
       
   678 	 * Prepares a single term output for response.
       
   679 	 *
       
   680 	 * @since 4.7.0
       
   681 	 *
       
   682 	 * @param obj             $item    Term object.
       
   683 	 * @param WP_REST_Request $request Request object.
       
   684 	 * @return WP_REST_Response $response Response object.
       
   685 	 */
       
   686 	public function prepare_item_for_response( $item, $request ) {
       
   687 
       
   688 		$fields = $this->get_fields_for_response( $request );
       
   689 		$data   = array();
       
   690 
       
   691 		if ( in_array( 'id', $fields, true ) ) {
       
   692 			$data['id'] = (int) $item->term_id;
       
   693 		}
       
   694 
       
   695 		if ( in_array( 'count', $fields, true ) ) {
       
   696 			$data['count'] = (int) $item->count;
       
   697 		}
       
   698 
       
   699 		if ( in_array( 'description', $fields, true ) ) {
       
   700 			$data['description'] = $item->description;
       
   701 		}
       
   702 
       
   703 		if ( in_array( 'link', $fields, true ) ) {
       
   704 			$data['link'] = get_term_link( $item );
       
   705 		}
       
   706 
       
   707 		if ( in_array( 'name', $fields, true ) ) {
       
   708 			$data['name'] = $item->name;
       
   709 		}
       
   710 
       
   711 		if ( in_array( 'slug', $fields, true ) ) {
       
   712 			$data['slug'] = $item->slug;
       
   713 		}
       
   714 
       
   715 		if ( in_array( 'taxonomy', $fields, true ) ) {
       
   716 			$data['taxonomy'] = $item->taxonomy;
       
   717 		}
       
   718 
       
   719 		if ( in_array( 'parent', $fields, true ) ) {
       
   720 			$data['parent'] = (int) $item->parent;
       
   721 		}
       
   722 
       
   723 		if ( in_array( 'meta', $fields, true ) ) {
       
   724 			$data['meta'] = $this->meta->get_value( $item->term_id, $request );
       
   725 		}
       
   726 
       
   727 		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
       
   728 		$data    = $this->add_additional_fields_to_object( $data, $request );
       
   729 		$data    = $this->filter_response_by_context( $data, $context );
       
   730 
       
   731 		$response = rest_ensure_response( $data );
       
   732 
       
   733 		$response->add_links( $this->prepare_links( $item ) );
       
   734 
       
   735 		/**
       
   736 		 * Filters a term item returned from the API.
       
   737 		 *
       
   738 		 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
       
   739 		 *
       
   740 		 * Allows modification of the term data right before it is returned.
       
   741 		 *
       
   742 		 * @since 4.7.0
       
   743 		 *
       
   744 		 * @param WP_REST_Response  $response  The response object.
       
   745 		 * @param object            $item      The original term object.
       
   746 		 * @param WP_REST_Request   $request   Request used to generate the response.
       
   747 		 */
       
   748 		return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $item, $request );
       
   749 	}
       
   750 
       
   751 	/**
       
   752 	 * Prepares links for the request.
       
   753 	 *
       
   754 	 * @since 4.7.0
       
   755 	 *
       
   756 	 * @param object $term Term object.
       
   757 	 * @return array Links for the given term.
       
   758 	 */
       
   759 	protected function prepare_links( $term ) {
       
   760 		$base = $this->namespace . '/' . $this->rest_base;
       
   761 		$links = array(
       
   762 			'self'       => array(
       
   763 				'href' => rest_url( trailingslashit( $base ) . $term->term_id ),
       
   764 			),
       
   765 			'collection' => array(
       
   766 				'href' => rest_url( $base ),
       
   767 			),
       
   768 			'about'      => array(
       
   769 				'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $this->taxonomy ) ),
       
   770 			),
       
   771 		);
       
   772 
       
   773 		if ( $term->parent ) {
       
   774 			$parent_term = get_term( (int) $term->parent, $term->taxonomy );
       
   775 
       
   776 			if ( $parent_term ) {
       
   777 				$links['up'] = array(
       
   778 					'href'       => rest_url( trailingslashit( $base ) . $parent_term->term_id ),
       
   779 					'embeddable' => true,
       
   780 				);
       
   781 			}
       
   782 		}
       
   783 
       
   784 		$taxonomy_obj = get_taxonomy( $term->taxonomy );
       
   785 
       
   786 		if ( empty( $taxonomy_obj->object_type ) ) {
       
   787 			return $links;
       
   788 		}
       
   789 
       
   790 		$post_type_links = array();
       
   791 
       
   792 		foreach ( $taxonomy_obj->object_type as $type ) {
       
   793 			$post_type_object = get_post_type_object( $type );
       
   794 
       
   795 			if ( empty( $post_type_object->show_in_rest ) ) {
       
   796 				continue;
       
   797 			}
       
   798 
       
   799 			$rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
       
   800 			$post_type_links[] = array(
       
   801 				'href' => add_query_arg( $this->rest_base, $term->term_id, rest_url( sprintf( 'wp/v2/%s', $rest_base ) ) ),
       
   802 			);
       
   803 		}
       
   804 
       
   805 		if ( ! empty( $post_type_links ) ) {
       
   806 			$links['https://api.w.org/post_type'] = $post_type_links;
       
   807 		}
       
   808 
       
   809 		return $links;
       
   810 	}
       
   811 
       
   812 	/**
       
   813 	 * Retrieves the term's schema, conforming to JSON Schema.
       
   814 	 *
       
   815 	 * @since 4.7.0
       
   816 	 *
       
   817 	 * @return array Item schema data.
       
   818 	 */
       
   819 	public function get_item_schema() {
       
   820 		$schema = array(
       
   821 			'$schema'    => 'http://json-schema.org/draft-04/schema#',
       
   822 			'title'      => 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy,
       
   823 			'type'       => 'object',
       
   824 			'properties' => array(
       
   825 				'id'          => array(
       
   826 					'description'  => __( 'Unique identifier for the term.' ),
       
   827 					'type'         => 'integer',
       
   828 					'context'      => array( 'view', 'embed', 'edit' ),
       
   829 					'readonly'     => true,
       
   830 				),
       
   831 				'count'       => array(
       
   832 					'description'  => __( 'Number of published posts for the term.' ),
       
   833 					'type'         => 'integer',
       
   834 					'context'      => array( 'view', 'edit' ),
       
   835 					'readonly'     => true,
       
   836 				),
       
   837 				'description' => array(
       
   838 					'description'  => __( 'HTML description of the term.' ),
       
   839 					'type'         => 'string',
       
   840 					'context'      => array( 'view', 'edit' ),
       
   841 				),
       
   842 				'link'        => array(
       
   843 					'description'  => __( 'URL of the term.' ),
       
   844 					'type'         => 'string',
       
   845 					'format'       => 'uri',
       
   846 					'context'      => array( 'view', 'embed', 'edit' ),
       
   847 					'readonly'     => true,
       
   848 				),
       
   849 				'name'        => array(
       
   850 					'description'  => __( 'HTML title for the term.' ),
       
   851 					'type'         => 'string',
       
   852 					'context'      => array( 'view', 'embed', 'edit' ),
       
   853 					'arg_options'  => array(
       
   854 						'sanitize_callback' => 'sanitize_text_field',
       
   855 					),
       
   856 					'required'     => true,
       
   857 				),
       
   858 				'slug'        => array(
       
   859 					'description'  => __( 'An alphanumeric identifier for the term unique to its type.' ),
       
   860 					'type'         => 'string',
       
   861 					'context'      => array( 'view', 'embed', 'edit' ),
       
   862 					'arg_options'  => array(
       
   863 						'sanitize_callback' => array( $this, 'sanitize_slug' ),
       
   864 					),
       
   865 				),
       
   866 				'taxonomy'    => array(
       
   867 					'description'  => __( 'Type attribution for the term.' ),
       
   868 					'type'         => 'string',
       
   869 					'enum'         => array_keys( get_taxonomies() ),
       
   870 					'context'      => array( 'view', 'embed', 'edit' ),
       
   871 					'readonly'     => true,
       
   872 				),
       
   873 			),
       
   874 		);
       
   875 
       
   876 		$taxonomy = get_taxonomy( $this->taxonomy );
       
   877 
       
   878 		if ( $taxonomy->hierarchical ) {
       
   879 			$schema['properties']['parent'] = array(
       
   880 				'description'  => __( 'The parent term ID.' ),
       
   881 				'type'         => 'integer',
       
   882 				'context'      => array( 'view', 'edit' ),
       
   883 			);
       
   884 		}
       
   885 
       
   886 		$schema['properties']['meta'] = $this->meta->get_field_schema();
       
   887 
       
   888 		return $this->add_additional_fields_schema( $schema );
       
   889 	}
       
   890 
       
   891 	/**
       
   892 	 * Retrieves the query params for collections.
       
   893 	 *
       
   894 	 * @since 4.7.0
       
   895 	 *
       
   896 	 * @return array Collection parameters.
       
   897 	 */
       
   898 	public function get_collection_params() {
       
   899 		$query_params = parent::get_collection_params();
       
   900 		$taxonomy = get_taxonomy( $this->taxonomy );
       
   901 
       
   902 		$query_params['context']['default'] = 'view';
       
   903 
       
   904 		$query_params['exclude'] = array(
       
   905 			'description'       => __( 'Ensure result set excludes specific IDs.' ),
       
   906 			'type'              => 'array',
       
   907 			'items'             => array(
       
   908 				'type'          => 'integer',
       
   909 			),
       
   910 			'default'           => array(),
       
   911 		);
       
   912 
       
   913 		$query_params['include'] = array(
       
   914 			'description'       => __( 'Limit result set to specific IDs.' ),
       
   915 			'type'              => 'array',
       
   916 			'items'             => array(
       
   917 				'type'          => 'integer',
       
   918 			),
       
   919 			'default'           => array(),
       
   920 		);
       
   921 
       
   922 		if ( ! $taxonomy->hierarchical ) {
       
   923 			$query_params['offset'] = array(
       
   924 				'description'       => __( 'Offset the result set by a specific number of items.' ),
       
   925 				'type'              => 'integer',
       
   926 			);
       
   927 		}
       
   928 
       
   929 		$query_params['order'] = array(
       
   930 			'description'       => __( 'Order sort attribute ascending or descending.' ),
       
   931 			'type'              => 'string',
       
   932 			'default'           => 'asc',
       
   933 			'enum'              => array(
       
   934 				'asc',
       
   935 				'desc',
       
   936 			),
       
   937 		);
       
   938 
       
   939 		$query_params['orderby'] = array(
       
   940 			'description'       => __( 'Sort collection by term attribute.' ),
       
   941 			'type'              => 'string',
       
   942 			'default'           => 'name',
       
   943 			'enum'              => array(
       
   944 				'id',
       
   945 				'include',
       
   946 				'name',
       
   947 				'slug',
       
   948 				'include_slugs',
       
   949 				'term_group',
       
   950 				'description',
       
   951 				'count',
       
   952 			),
       
   953 		);
       
   954 
       
   955 		$query_params['hide_empty'] = array(
       
   956 			'description'       => __( 'Whether to hide terms not assigned to any posts.' ),
       
   957 			'type'              => 'boolean',
       
   958 			'default'           => false,
       
   959 		);
       
   960 
       
   961 		if ( $taxonomy->hierarchical ) {
       
   962 			$query_params['parent'] = array(
       
   963 				'description'       => __( 'Limit result set to terms assigned to a specific parent.' ),
       
   964 				'type'              => 'integer',
       
   965 			);
       
   966 		}
       
   967 
       
   968 		$query_params['post'] = array(
       
   969 			'description'       => __( 'Limit result set to terms assigned to a specific post.' ),
       
   970 			'type'              => 'integer',
       
   971 			'default'           => null,
       
   972 		);
       
   973 
       
   974 		$query_params['slug'] = array(
       
   975 			'description'       => __( 'Limit result set to terms with one or more specific slugs.' ),
       
   976 			'type'              => 'array',
       
   977 			'items'             => array(
       
   978 				'type'          => 'string'
       
   979 			),
       
   980 		);
       
   981 
       
   982 		/**
       
   983 		 * Filter collection parameters for the terms controller.
       
   984 		 *
       
   985 		 * The dynamic part of the filter `$this->taxonomy` refers to the taxonomy
       
   986 		 * slug for the controller.
       
   987 		 *
       
   988 		 * This filter registers the collection parameter, but does not map the
       
   989 		 * collection parameter to an internal WP_Term_Query parameter.  Use the
       
   990 		 * `rest_{$this->taxonomy}_query` filter to set WP_Term_Query parameters.
       
   991 		 *
       
   992 		 * @since 4.7.0
       
   993 		 *
       
   994 		 * @param array       $query_params JSON Schema-formatted collection parameters.
       
   995 		 * @param WP_Taxonomy $taxonomy     Taxonomy object.
       
   996 		 */
       
   997 		return apply_filters( "rest_{$this->taxonomy}_collection_params", $query_params, $taxonomy );
       
   998 	}
       
   999 
       
  1000 	/**
       
  1001 	 * Checks that the taxonomy is valid.
       
  1002 	 *
       
  1003 	 * @since 4.7.0
       
  1004 	 *
       
  1005 	 * @param string $taxonomy Taxonomy to check.
       
  1006 	 * @return bool Whether the taxonomy is allowed for REST management.
       
  1007 	 */
       
  1008 	protected function check_is_taxonomy_allowed( $taxonomy ) {
       
  1009 		$taxonomy_obj = get_taxonomy( $taxonomy );
       
  1010 		if ( $taxonomy_obj && ! empty( $taxonomy_obj->show_in_rest ) ) {
       
  1011 			return true;
       
  1012 		}
       
  1013 		return false;
       
  1014 	}
       
  1015 }