home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / rest-api / endpoints / class-wp-rest-terms-controller.php < prev    next >
Encoding:
PHP Script  |  2018-01-23  |  29.8 KB  |  1,013 lines

  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 ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
  380.             return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create new terms.' ), array( 'status' => rest_authorization_required_code() ) );
  381.         }
  382.  
  383.         return true;
  384.     }
  385.  
  386.     /**
  387.      * Creates a single term in a taxonomy.
  388.      *
  389.      * @since 4.7.0
  390.      *
  391.      * @param WP_REST_Request $request Full details about the request.
  392.      * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  393.      */
  394.     public function create_item( $request ) {
  395.         if ( isset( $request['parent'] ) ) {
  396.             if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
  397.                 return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
  398.             }
  399.  
  400.             $parent = get_term( (int) $request['parent'], $this->taxonomy );
  401.  
  402.             if ( ! $parent ) {
  403.                 return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) );
  404.             }
  405.         }
  406.  
  407.         $prepared_term = $this->prepare_item_for_database( $request );
  408.  
  409.         $term = wp_insert_term( wp_slash( $prepared_term->name ), $this->taxonomy, wp_slash( (array) $prepared_term ) );
  410.         if ( is_wp_error( $term ) ) {
  411.             /*
  412.              * If we're going to inform the client that the term already exists,
  413.              * give them the identifier for future use.
  414.              */
  415.             if ( $term_id = $term->get_error_data( 'term_exists' ) ) {
  416.                 $existing_term = get_term( $term_id, $this->taxonomy );
  417.                 $term->add_data( $existing_term->term_id, 'term_exists' );
  418.                 $term->add_data( array( 'status' => 409, 'term_id' => $term_id ) );
  419.             }
  420.  
  421.             return $term;
  422.         }
  423.  
  424.         $term = get_term( $term['term_id'], $this->taxonomy );
  425.  
  426.         /**
  427.          * Fires after a single term is created or updated via the REST API.
  428.          *
  429.          * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
  430.          *
  431.          * @since 4.7.0
  432.          *
  433.          * @param WP_Term         $term     Inserted or updated term object.
  434.          * @param WP_REST_Request $request  Request object.
  435.          * @param bool            $creating True when creating a term, false when updating.
  436.          */
  437.         do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );
  438.  
  439.         $schema = $this->get_item_schema();
  440.         if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
  441.             $meta_update = $this->meta->update_value( $request['meta'], (int) $request['id'] );
  442.  
  443.             if ( is_wp_error( $meta_update ) ) {
  444.                 return $meta_update;
  445.             }
  446.         }
  447.  
  448.         $fields_update = $this->update_additional_fields_for_object( $term, $request );
  449.  
  450.         if ( is_wp_error( $fields_update ) ) {
  451.             return $fields_update;
  452.         }
  453.  
  454.         $request->set_param( 'context', 'view' );
  455.  
  456.         $response = $this->prepare_item_for_response( $term, $request );
  457.         $response = rest_ensure_response( $response );
  458.  
  459.         $response->set_status( 201 );
  460.         $response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) );
  461.  
  462.         return $response;
  463.     }
  464.  
  465.     /**
  466.      * Checks if a request has access to update the specified term.
  467.      *
  468.      * @since 4.7.0
  469.      *
  470.      * @param WP_REST_Request $request Full details about the request.
  471.      * @return bool|WP_Error True if the request has access to update the item, false or WP_Error object otherwise.
  472.      */
  473.     public function update_item_permissions_check( $request ) {
  474.         $term = $this->get_term( $request['id'] );
  475.         if ( is_wp_error( $term ) ) {
  476.             return $term;
  477.         }
  478.  
  479.         if ( ! current_user_can( 'edit_term', $term->term_id ) ) {
  480.             return new WP_Error( 'rest_cannot_update', __( 'Sorry, you are not allowed to edit this term.' ), array( 'status' => rest_authorization_required_code() ) );
  481.         }
  482.  
  483.         return true;
  484.     }
  485.  
  486.     /**
  487.      * Updates a single term from a taxonomy.
  488.      *
  489.      * @since 4.7.0
  490.      *
  491.      * @param WP_REST_Request $request Full details about the request.
  492.      * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  493.      */
  494.     public function update_item( $request ) {
  495.         $term = $this->get_term( $request['id'] );
  496.         if ( is_wp_error( $term ) ) {
  497.             return $term;
  498.         }
  499.  
  500.         if ( isset( $request['parent'] ) ) {
  501.             if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
  502.                 return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
  503.             }
  504.  
  505.             $parent = get_term( (int) $request['parent'], $this->taxonomy );
  506.  
  507.             if ( ! $parent ) {
  508.                 return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) );
  509.             }
  510.         }
  511.  
  512.         $prepared_term = $this->prepare_item_for_database( $request );
  513.  
  514.         // Only update the term if we haz something to update.
  515.         if ( ! empty( $prepared_term ) ) {
  516.             $update = wp_update_term( $term->term_id, $term->taxonomy, wp_slash( (array) $prepared_term ) );
  517.  
  518.             if ( is_wp_error( $update ) ) {
  519.                 return $update;
  520.             }
  521.         }
  522.  
  523.         $term = get_term( $term->term_id, $this->taxonomy );
  524.  
  525.         /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
  526.         do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );
  527.  
  528.         $schema = $this->get_item_schema();
  529.         if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
  530.             $meta_update = $this->meta->update_value( $request['meta'], $term->term_id );
  531.  
  532.             if ( is_wp_error( $meta_update ) ) {
  533.                 return $meta_update;
  534.             }
  535.         }
  536.  
  537.         $fields_update = $this->update_additional_fields_for_object( $term, $request );
  538.  
  539.         if ( is_wp_error( $fields_update ) ) {
  540.             return $fields_update;
  541.         }
  542.  
  543.         $request->set_param( 'context', 'view' );
  544.  
  545.         $response = $this->prepare_item_for_response( $term, $request );
  546.  
  547.         return rest_ensure_response( $response );
  548.     }
  549.  
  550.     /**
  551.      * Checks if a request has access to delete the specified term.
  552.      *
  553.      * @since 4.7.0
  554.      *
  555.      * @param WP_REST_Request $request Full details about the request.
  556.      * @return bool|WP_Error True if the request has access to delete the item, otherwise false or WP_Error object.
  557.      */
  558.     public function delete_item_permissions_check( $request ) {
  559.         $term = $this->get_term( $request['id'] );
  560.         if ( is_wp_error( $term ) ) {
  561.             return $term;
  562.         }
  563.  
  564.         if ( ! current_user_can( 'delete_term', $term->term_id ) ) {
  565.             return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this term.' ), array( 'status' => rest_authorization_required_code() ) );
  566.         }
  567.  
  568.         return true;
  569.     }
  570.  
  571.     /**
  572.      * Deletes a single term from a taxonomy.
  573.      *
  574.      * @since 4.7.0
  575.      *
  576.      * @param WP_REST_Request $request Full details about the request.
  577.      * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  578.      */
  579.     public function delete_item( $request ) {
  580.         $term = $this->get_term( $request['id'] );
  581.         if ( is_wp_error( $term ) ) {
  582.             return $term;
  583.         }
  584.  
  585.         $force = isset( $request['force'] ) ? (bool) $request['force'] : false;
  586.  
  587.         // We don't support trashing for terms.
  588.         if ( ! $force ) {
  589.             /* translators: %s: force=true */
  590.             return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Terms do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) );
  591.         }
  592.  
  593.         $request->set_param( 'context', 'view' );
  594.  
  595.         $previous = $this->prepare_item_for_response( $term, $request );
  596.  
  597.         $retval = wp_delete_term( $term->term_id, $term->taxonomy );
  598.  
  599.         if ( ! $retval ) {
  600.             return new WP_Error( 'rest_cannot_delete', __( 'The term cannot be deleted.' ), array( 'status' => 500 ) );
  601.         }
  602.  
  603.         $response = new WP_REST_Response();
  604.         $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) );
  605.  
  606.         /**
  607.          * Fires after a single term is deleted via the REST API.
  608.          *
  609.          * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
  610.          *
  611.          * @since 4.7.0
  612.          *
  613.          * @param WP_Term          $term     The deleted term.
  614.          * @param WP_REST_Response $response The response data.
  615.          * @param WP_REST_Request  $request  The request sent to the API.
  616.          */
  617.         do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request );
  618.  
  619.         return $response;
  620.     }
  621.  
  622.     /**
  623.      * Prepares a single term for create or update.
  624.      *
  625.      * @since 4.7.0
  626.      *
  627.      * @param WP_REST_Request $request Request object.
  628.      * @return object $prepared_term Term object.
  629.      */
  630.     public function prepare_item_for_database( $request ) {
  631.         $prepared_term = new stdClass;
  632.  
  633.         $schema = $this->get_item_schema();
  634.         if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
  635.             $prepared_term->name = $request['name'];
  636.         }
  637.  
  638.         if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) {
  639.             $prepared_term->slug = $request['slug'];
  640.         }
  641.  
  642.         if ( isset( $request['taxonomy'] ) && ! empty( $schema['properties']['taxonomy'] ) ) {
  643.             $prepared_term->taxonomy = $request['taxonomy'];
  644.         }
  645.  
  646.         if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) {
  647.             $prepared_term->description = $request['description'];
  648.         }
  649.  
  650.         if ( isset( $request['parent'] ) && ! empty( $schema['properties']['parent'] ) ) {
  651.             $parent_term_id = 0;
  652.             $parent_term    = get_term( (int) $request['parent'], $this->taxonomy );
  653.  
  654.             if ( $parent_term ) {
  655.                 $parent_term_id = $parent_term->term_id;
  656.             }
  657.  
  658.             $prepared_term->parent = $parent_term_id;
  659.         }
  660.  
  661.         /**
  662.          * Filters term data before inserting term via the REST API.
  663.          *
  664.          * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
  665.          *
  666.          * @since 4.7.0
  667.          *
  668.          * @param object          $prepared_term Term object.
  669.          * @param WP_REST_Request $request       Request object.
  670.          */
  671.         return apply_filters( "rest_pre_insert_{$this->taxonomy}", $prepared_term, $request );
  672.     }
  673.  
  674.     /**
  675.      * Prepares a single term output for response.
  676.      *
  677.      * @since 4.7.0
  678.      *
  679.      * @param obj             $item    Term object.
  680.      * @param WP_REST_Request $request Request object.
  681.      * @return WP_REST_Response $response Response object.
  682.      */
  683.     public function prepare_item_for_response( $item, $request ) {
  684.  
  685.         $schema = $this->get_item_schema();
  686.         $data   = array();
  687.  
  688.         if ( ! empty( $schema['properties']['id'] ) ) {
  689.             $data['id'] = (int) $item->term_id;
  690.         }
  691.  
  692.         if ( ! empty( $schema['properties']['count'] ) ) {
  693.             $data['count'] = (int) $item->count;
  694.         }
  695.  
  696.         if ( ! empty( $schema['properties']['description'] ) ) {
  697.             $data['description'] = $item->description;
  698.         }
  699.  
  700.         if ( ! empty( $schema['properties']['link'] ) ) {
  701.             $data['link'] = get_term_link( $item );
  702.         }
  703.  
  704.         if ( ! empty( $schema['properties']['name'] ) ) {
  705.             $data['name'] = $item->name;
  706.         }
  707.  
  708.         if ( ! empty( $schema['properties']['slug'] ) ) {
  709.             $data['slug'] = $item->slug;
  710.         }
  711.  
  712.         if ( ! empty( $schema['properties']['taxonomy'] ) ) {
  713.             $data['taxonomy'] = $item->taxonomy;
  714.         }
  715.  
  716.         if ( ! empty( $schema['properties']['parent'] ) ) {
  717.             $data['parent'] = (int) $item->parent;
  718.         }
  719.  
  720.         if ( ! empty( $schema['properties']['meta'] ) ) {
  721.             $data['meta'] = $this->meta->get_value( $item->term_id, $request );
  722.         }
  723.  
  724.         $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
  725.         $data    = $this->add_additional_fields_to_object( $data, $request );
  726.         $data    = $this->filter_response_by_context( $data, $context );
  727.  
  728.         $response = rest_ensure_response( $data );
  729.  
  730.         $response->add_links( $this->prepare_links( $item ) );
  731.  
  732.         /**
  733.          * Filters a term item returned from the API.
  734.          *
  735.          * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
  736.          *
  737.          * Allows modification of the term data right before it is returned.
  738.          *
  739.          * @since 4.7.0
  740.          *
  741.          * @param WP_REST_Response  $response  The response object.
  742.          * @param object            $item      The original term object.
  743.          * @param WP_REST_Request   $request   Request used to generate the response.
  744.          */
  745.         return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $item, $request );
  746.     }
  747.  
  748.     /**
  749.      * Prepares links for the request.
  750.      *
  751.      * @since 4.7.0
  752.      *
  753.      * @param object $term Term object.
  754.      * @return array Links for the given term.
  755.      */
  756.     protected function prepare_links( $term ) {
  757.         $base = $this->namespace . '/' . $this->rest_base;
  758.         $links = array(
  759.             'self'       => array(
  760.                 'href' => rest_url( trailingslashit( $base ) . $term->term_id ),
  761.             ),
  762.             'collection' => array(
  763.                 'href' => rest_url( $base ),
  764.             ),
  765.             'about'      => array(
  766.                 'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $this->taxonomy ) ),
  767.             ),
  768.         );
  769.  
  770.         if ( $term->parent ) {
  771.             $parent_term = get_term( (int) $term->parent, $term->taxonomy );
  772.  
  773.             if ( $parent_term ) {
  774.                 $links['up'] = array(
  775.                     'href'       => rest_url( trailingslashit( $base ) . $parent_term->term_id ),
  776.                     'embeddable' => true,
  777.                 );
  778.             }
  779.         }
  780.  
  781.         $taxonomy_obj = get_taxonomy( $term->taxonomy );
  782.  
  783.         if ( empty( $taxonomy_obj->object_type ) ) {
  784.             return $links;
  785.         }
  786.  
  787.         $post_type_links = array();
  788.  
  789.         foreach ( $taxonomy_obj->object_type as $type ) {
  790.             $post_type_object = get_post_type_object( $type );
  791.  
  792.             if ( empty( $post_type_object->show_in_rest ) ) {
  793.                 continue;
  794.             }
  795.  
  796.             $rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
  797.             $post_type_links[] = array(
  798.                 'href' => add_query_arg( $this->rest_base, $term->term_id, rest_url( sprintf( 'wp/v2/%s', $rest_base ) ) ),
  799.             );
  800.         }
  801.  
  802.         if ( ! empty( $post_type_links ) ) {
  803.             $links['https://api.w.org/post_type'] = $post_type_links;
  804.         }
  805.  
  806.         return $links;
  807.     }
  808.  
  809.     /**
  810.      * Retrieves the term's schema, conforming to JSON Schema.
  811.      *
  812.      * @since 4.7.0
  813.      *
  814.      * @return array Item schema data.
  815.      */
  816.     public function get_item_schema() {
  817.         $schema = array(
  818.             '$schema'    => 'http://json-schema.org/draft-04/schema#',
  819.             'title'      => 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy,
  820.             'type'       => 'object',
  821.             'properties' => array(
  822.                 'id'          => array(
  823.                     'description'  => __( 'Unique identifier for the term.' ),
  824.                     'type'         => 'integer',
  825.                     'context'      => array( 'view', 'embed', 'edit' ),
  826.                     'readonly'     => true,
  827.                 ),
  828.                 'count'       => array(
  829.                     'description'  => __( 'Number of published posts for the term.' ),
  830.                     'type'         => 'integer',
  831.                     'context'      => array( 'view', 'edit' ),
  832.                     'readonly'     => true,
  833.                 ),
  834.                 'description' => array(
  835.                     'description'  => __( 'HTML description of the term.' ),
  836.                     'type'         => 'string',
  837.                     'context'      => array( 'view', 'edit' ),
  838.                 ),
  839.                 'link'        => array(
  840.                     'description'  => __( 'URL of the term.' ),
  841.                     'type'         => 'string',
  842.                     'format'       => 'uri',
  843.                     'context'      => array( 'view', 'embed', 'edit' ),
  844.                     'readonly'     => true,
  845.                 ),
  846.                 'name'        => array(
  847.                     'description'  => __( 'HTML title for the term.' ),
  848.                     'type'         => 'string',
  849.                     'context'      => array( 'view', 'embed', 'edit' ),
  850.                     'arg_options'  => array(
  851.                         'sanitize_callback' => 'sanitize_text_field',
  852.                     ),
  853.                     'required'     => true,
  854.                 ),
  855.                 'slug'        => array(
  856.                     'description'  => __( 'An alphanumeric identifier for the term unique to its type.' ),
  857.                     'type'         => 'string',
  858.                     'context'      => array( 'view', 'embed', 'edit' ),
  859.                     'arg_options'  => array(
  860.                         'sanitize_callback' => array( $this, 'sanitize_slug' ),
  861.                     ),
  862.                 ),
  863.                 'taxonomy'    => array(
  864.                     'description'  => __( 'Type attribution for the term.' ),
  865.                     'type'         => 'string',
  866.                     'enum'         => array_keys( get_taxonomies() ),
  867.                     'context'      => array( 'view', 'embed', 'edit' ),
  868.                     'readonly'     => true,
  869.                 ),
  870.             ),
  871.         );
  872.  
  873.         $taxonomy = get_taxonomy( $this->taxonomy );
  874.  
  875.         if ( $taxonomy->hierarchical ) {
  876.             $schema['properties']['parent'] = array(
  877.                 'description'  => __( 'The parent term ID.' ),
  878.                 'type'         => 'integer',
  879.                 'context'      => array( 'view', 'edit' ),
  880.             );
  881.         }
  882.  
  883.         $schema['properties']['meta'] = $this->meta->get_field_schema();
  884.  
  885.         return $this->add_additional_fields_schema( $schema );
  886.     }
  887.  
  888.     /**
  889.      * Retrieves the query params for collections.
  890.      *
  891.      * @since 4.7.0
  892.      *
  893.      * @return array Collection parameters.
  894.      */
  895.     public function get_collection_params() {
  896.         $query_params = parent::get_collection_params();
  897.         $taxonomy = get_taxonomy( $this->taxonomy );
  898.  
  899.         $query_params['context']['default'] = 'view';
  900.  
  901.         $query_params['exclude'] = array(
  902.             'description'       => __( 'Ensure result set excludes specific IDs.' ),
  903.             'type'              => 'array',
  904.             'items'             => array(
  905.                 'type'          => 'integer',
  906.             ),
  907.             'default'           => array(),
  908.         );
  909.  
  910.         $query_params['include'] = array(
  911.             'description'       => __( 'Limit result set to specific IDs.' ),
  912.             'type'              => 'array',
  913.             'items'             => array(
  914.                 'type'          => 'integer',
  915.             ),
  916.             'default'           => array(),
  917.         );
  918.  
  919.         if ( ! $taxonomy->hierarchical ) {
  920.             $query_params['offset'] = array(
  921.                 'description'       => __( 'Offset the result set by a specific number of items.' ),
  922.                 'type'              => 'integer',
  923.             );
  924.         }
  925.  
  926.         $query_params['order'] = array(
  927.             'description'       => __( 'Order sort attribute ascending or descending.' ),
  928.             'type'              => 'string',
  929.             'default'           => 'asc',
  930.             'enum'              => array(
  931.                 'asc',
  932.                 'desc',
  933.             ),
  934.         );
  935.  
  936.         $query_params['orderby'] = array(
  937.             'description'       => __( 'Sort collection by term attribute.' ),
  938.             'type'              => 'string',
  939.             'default'           => 'name',
  940.             'enum'              => array(
  941.                 'id',
  942.                 'include',
  943.                 'name',
  944.                 'slug',
  945.                 'include_slugs',
  946.                 'term_group',
  947.                 'description',
  948.                 'count',
  949.             ),
  950.         );
  951.  
  952.         $query_params['hide_empty'] = array(
  953.             'description'       => __( 'Whether to hide terms not assigned to any posts.' ),
  954.             'type'              => 'boolean',
  955.             'default'           => false,
  956.         );
  957.  
  958.         if ( $taxonomy->hierarchical ) {
  959.             $query_params['parent'] = array(
  960.                 'description'       => __( 'Limit result set to terms assigned to a specific parent.' ),
  961.                 'type'              => 'integer',
  962.             );
  963.         }
  964.  
  965.         $query_params['post'] = array(
  966.             'description'       => __( 'Limit result set to terms assigned to a specific post.' ),
  967.             'type'              => 'integer',
  968.             'default'           => null,
  969.         );
  970.  
  971.         $query_params['slug'] = array(
  972.             'description'       => __( 'Limit result set to terms with one or more specific slugs.' ),
  973.             'type'              => 'array',
  974.             'items'             => array(
  975.                 'type'          => 'string'
  976.             ),
  977.         );
  978.  
  979.         /**
  980.          * Filter collection parameters for the terms controller.
  981.          *
  982.          * The dynamic part of the filter `$this->taxonomy` refers to the taxonomy
  983.          * slug for the controller.
  984.          *
  985.          * This filter registers the collection parameter, but does not map the
  986.          * collection parameter to an internal WP_Term_Query parameter.  Use the
  987.          * `rest_{$this->taxonomy}_query` filter to set WP_Term_Query parameters.
  988.          *
  989.          * @since 4.7.0
  990.          *
  991.          * @param array       $query_params JSON Schema-formatted collection parameters.
  992.          * @param WP_Taxonomy $taxonomy     Taxonomy object.
  993.          */
  994.         return apply_filters( "rest_{$this->taxonomy}_collection_params", $query_params, $taxonomy );
  995.     }
  996.  
  997.     /**
  998.      * Checks that the taxonomy is valid.
  999.      *
  1000.      * @since 4.7.0
  1001.      *
  1002.      * @param string $taxonomy Taxonomy to check.
  1003.      * @return bool Whether the taxonomy is allowed for REST management.
  1004.      */
  1005.     protected function check_is_taxonomy_allowed( $taxonomy ) {
  1006.         $taxonomy_obj = get_taxonomy( $taxonomy );
  1007.         if ( $taxonomy_obj && ! empty( $taxonomy_obj->show_in_rest ) ) {
  1008.             return true;
  1009.         }
  1010.         return false;
  1011.     }
  1012. }
  1013.